front end work
This commit is contained in:
parent
47014118c8
commit
2fd83a5d3d
@ -95,11 +95,6 @@ class FoodsController < ApplicationController
|
||||
render :show
|
||||
end
|
||||
|
||||
def prefetch
|
||||
@foods = Food.all.order(:name)
|
||||
render :search
|
||||
end
|
||||
|
||||
def search
|
||||
@foods = Food.search(params[:query]).order(:name)
|
||||
end
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" v-if="updateAvailable" href="#" @click.prevent="updateApp">UPDATE AVAILABLE!</a>
|
||||
<router-link to="/" class="navbar-item">Recipes</router-link>
|
||||
<router-link to="/ingredients" class="navbar-item">Ingredients</router-link>
|
||||
<router-link to="/foods" class="navbar-item">Ingredients</router-link>
|
||||
<router-link to="/calculator" class="navbar-item">Calculator</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/logs" class="navbar-item">Log</router-link>
|
||||
<router-link v-if="isLoggedIn" to="/notes" class="navbar-item">Notes</router-link>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="title">{{action}} {{ingredient.name || "[Unnamed Ingredient]"}}</h1>
|
||||
<h1 class="title">{{action}} {{food.name || "[Unnamed Food]"}}</h1>
|
||||
|
||||
<app-validation-errors :errors="validationErrors"></app-validation-errors>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Name</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input is-small-mobile" v-model="ingredient.name">
|
||||
<input type="text" class="input is-small-mobile" v-model="food.name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,13 +15,13 @@
|
||||
<label class="label is-small-mobile">Nutrient Databank Number</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<button type="button" class="button" :class="{'is-primary': hasNdbn}"><app-icon :icon="hasNdbn ? 'link-intact' : 'link-broken'" size="sm"></app-icon><span>{{ingredient.ndbn}}</span></button>
|
||||
<button type="button" class="button" :class="{'is-primary': hasNdbn}"><app-icon :icon="hasNdbn ? 'link-intact' : 'link-broken'" size="sm"></app-icon><span>{{food.ndbn}}</span></button>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<app-autocomplete
|
||||
:inputClass="'is-small-mobile'"
|
||||
ref="autocomplete"
|
||||
v-model="ingredient.usda_food_name"
|
||||
v-model="food.usda_food_name"
|
||||
:minLength="2"
|
||||
valueAttribute="name"
|
||||
labelAttribute="description"
|
||||
@ -39,14 +39,14 @@
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Density</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input is-small-mobile" v-model="ingredient.density">
|
||||
<input type="text" class="input is-small-mobile" v-model="food.density">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label is-small-mobile">Notes</label>
|
||||
<div class="control">
|
||||
<textarea type="text" class="textarea is-small-mobile" v-model="ingredient.notes"></textarea>
|
||||
<textarea type="text" class="textarea is-small-mobile" v-model="food.notes"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
<th>Grams</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr v-for="unit in visibleIngredientUnits" :key="unit.id">
|
||||
<tr v-for="unit in visibleFoodUnits" :key="unit.id">
|
||||
<td>
|
||||
<div class="control">
|
||||
<input type="text" class="input is-small-mobile" v-model="unit.name">
|
||||
@ -99,7 +99,7 @@
|
||||
<th>Name</th>
|
||||
<th>Grams</th>
|
||||
</tr>
|
||||
<tr v-for="unit in ingredient.ndbn_units">
|
||||
<tr v-for="unit in food.ndbn_units">
|
||||
<td>{{unit.description}}</td>
|
||||
<td>{{unit.gram_weight}}</td>
|
||||
</tr>
|
||||
@ -121,7 +121,7 @@
|
||||
<label class="label is-small-mobile">{{nutrient.label}}</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input type="text" class="input is-small-mobile" :disabled="hasNdbn" v-model="ingredient[name]">
|
||||
<input type="text" class="input is-small-mobile" :disabled="hasNdbn" v-model="food[name]">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" tabindex="-1" class="unit-label button is-static is-small-mobile">{{nutrient.unit}}</button>
|
||||
@ -141,7 +141,7 @@
|
||||
|
||||
export default {
|
||||
props: {
|
||||
ingredient: {
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
@ -191,18 +191,18 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
visibleIngredientUnits() {
|
||||
return this.ingredient.ingredient_units.filter(iu => iu._destroy !== true);
|
||||
visibleFoodUnits() {
|
||||
return this.food.food_units.filter(iu => iu._destroy !== true);
|
||||
},
|
||||
|
||||
hasNdbn() {
|
||||
return this.ingredient.ndbn !== null;
|
||||
return this.food.ndbn !== null;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addUnit() {
|
||||
this.ingredient.ingredient_units.push({
|
||||
this.food.food_units.push({
|
||||
id: null,
|
||||
name: null,
|
||||
gram_weight: null
|
||||
@ -213,15 +213,15 @@
|
||||
if (unit.id) {
|
||||
unit._destroy = true;
|
||||
} else {
|
||||
const idx = this.ingredient.ingredient_units.findIndex(i => i === unit);
|
||||
this.ingredient.ingredient_units.splice(idx, 1);
|
||||
const idx = this.food.food_units.findIndex(i => i === unit);
|
||||
this.food.food_units.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
|
||||
removeNdbn() {
|
||||
this.ingredient.ndbn = null;
|
||||
this.ingredient.usda_food_name = null;
|
||||
this.ingredient.ndbn_units = [];
|
||||
this.food.ndbn = null;
|
||||
this.food.usda_food_name = null;
|
||||
this.food.ndbn_units = [];
|
||||
},
|
||||
|
||||
updateSearchItems(text) {
|
||||
@ -236,13 +236,13 @@
|
||||
},
|
||||
|
||||
searchItemSelected(food) {
|
||||
this.ingredient.ndbn = food.ndbn;
|
||||
this.ingredient.usda_food_name = food.name;
|
||||
this.ingredient.ndbn_units = [];
|
||||
this.food.ndbn = food.ndbn;
|
||||
this.food.usda_food_name = food.name;
|
||||
this.food.ndbn_units = [];
|
||||
|
||||
this.loadResource(
|
||||
api.postIngredientSelectNdbn(this.ingredient)
|
||||
.then(i => Object.assign(this.ingredient, i))
|
||||
api.postIngredientSelectNdbn(this.food)
|
||||
.then(i => Object.assign(this.food, i))
|
||||
);
|
||||
|
||||
},
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ingredient.name}}
|
||||
{{food.name}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
export default {
|
||||
props: {
|
||||
ingredient: {
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
</app-modal>
|
||||
|
||||
<div>
|
||||
<recipe-edit-ingredient-item v-for="(i, idx) in visibleIngredients" :key="i.id" :ingredient="i" :show-labels="idx === 0 || isMobile" @deleteIngredient="deleteIngredient"></recipe-edit-ingredient-item>
|
||||
<recipe-edit-ingredient-item v-for="(i, idx) in visibleIngredients" :key="i.id" :ingredient="i" :show-labels="idx === 0 || isMobile" @deleteFood="deleteFood"></recipe-edit-ingredient-item>
|
||||
</div>
|
||||
|
||||
<button type="button" class="button is-primary" @click="addIngredient">Add Ingredient</button>
|
||||
@ -117,11 +117,11 @@
|
||||
this.ingredients.push(this.createIngredient());
|
||||
},
|
||||
|
||||
deleteIngredient(ingredient) {
|
||||
if (ingredient.id) {
|
||||
ingredient._destroy = true;
|
||||
deleteFood(food) {
|
||||
if (food.id) {
|
||||
food._destroy = true;
|
||||
} else {
|
||||
const idx = this.ingredients.findIndex(i => i === ingredient);
|
||||
const idx = this.ingredients.findIndex(i => i === food);
|
||||
this.ingredients.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<span class="label is-small-mobile" v-if="showLabels"> </span>
|
||||
<button type="button" class="button is-danger is-small" @click="deleteIngredient(ingredient)">
|
||||
<button type="button" class="button is-danger is-small" @click="deleteFood(ingredient)">
|
||||
<app-icon icon="x" size="md"></app-icon>
|
||||
</button>
|
||||
</div>
|
||||
@ -62,8 +62,8 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
deleteIngredient(ingredient) {
|
||||
this.$emit("deleteIngredient", ingredient);
|
||||
deleteFood(ingredient) {
|
||||
this.$emit("deleteFood", ingredient);
|
||||
},
|
||||
|
||||
updateSearchItems(text) {
|
||||
|
@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="ingredient === null">
|
||||
<div v-if="food === null">
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else>
|
||||
<ingredient-show :ingredient="ingredient"></ingredient-show>
|
||||
<food-show :food="food"></food-show>
|
||||
</div>
|
||||
|
||||
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: ingredientId }}">Edit</router-link>
|
||||
<router-link class="button" to="/ingredients">Back</router-link>
|
||||
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_food', params: { id: foodId }}">Edit</router-link>
|
||||
<router-link class="button" to="/foods">Back</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import IngredientShow from "./IngredientShow";
|
||||
import FoodShow from "./FoodShow";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
ingredient: null
|
||||
food: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
ingredientId: state => state.route.params.id,
|
||||
foodId: state => state.route.params.id,
|
||||
})
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadResource(
|
||||
api.getIngredient(this.ingredientId)
|
||||
.then(data => { this.ingredient = data; return data; })
|
||||
api.getFood(this.foodId)
|
||||
.then(data => { this.food = data; return data; })
|
||||
);
|
||||
},
|
||||
|
||||
components: {
|
||||
IngredientShow
|
||||
FoodShow
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<ingredient-edit :ingredient="ingredient" :validation-errors="validationErrors" action="Creating"></ingredient-edit>
|
||||
<food-edit :food="food" :validation-errors="validationErrors" action="Creating"></food-edit>
|
||||
|
||||
<button type="button" class="button is-primary" @click="save">Save</button>
|
||||
<router-link class="button is-secondary" to="/ingredients">Cancel</router-link>
|
||||
<router-link class="button is-secondary" to="/food">Cancel</router-link>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import IngredientEdit from "./IngredientEdit";
|
||||
import FoodEdit from "./FoodEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
@ -19,7 +19,7 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ingredient: {
|
||||
food: {
|
||||
name: null,
|
||||
notes: null,
|
||||
ndbn: null,
|
||||
@ -49,7 +49,7 @@
|
||||
vit_k: null,
|
||||
cholesterol: null,
|
||||
lipids: null,
|
||||
ingredient_units: []
|
||||
food_units: []
|
||||
},
|
||||
validationErrors: {}
|
||||
}
|
||||
@ -59,15 +59,15 @@
|
||||
save() {
|
||||
this.validationErrors = {}
|
||||
this.loadResource(
|
||||
api.postIngredient(this.ingredient)
|
||||
.then(() => this.$router.push('/ingredients'))
|
||||
api.postFood(this.food)
|
||||
.then(() => this.$router.push('/foods'))
|
||||
.catch(Errors.onlyFor(Errors.ApiValidationError, err => this.validationErrors = err.validationErrors()))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
IngredientEdit
|
||||
FoodEdit
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<div v-if="ingredient === null">
|
||||
<div v-if="food === null">
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else>
|
||||
<ingredient-edit :ingredient="ingredient" :validation-errors="validationErrors"></ingredient-edit>
|
||||
<food-edit :food="food" :validation-errors="validationErrors"></food-edit>
|
||||
</div>
|
||||
|
||||
<button type="button" class="button is-primary" @click="save">Save</button>
|
||||
<router-link class="button is-secondary" to="/ingredients">Cancel</router-link>
|
||||
<router-link class="button is-secondary" to="/foods">Cancel</router-link>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import IngredientEdit from "./IngredientEdit";
|
||||
import FoodEdit from "./FoodEdit";
|
||||
import { mapState } from "vuex";
|
||||
import api from "../lib/Api";
|
||||
import * as Errors from '../lib/Errors';
|
||||
@ -24,14 +24,14 @@
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
ingredient: null,
|
||||
food: null,
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
ingredientId: state => state.route.params.id,
|
||||
foodId: state => state.route.params.id,
|
||||
})
|
||||
},
|
||||
|
||||
@ -39,8 +39,8 @@
|
||||
save() {
|
||||
this.validationErrors = {};
|
||||
this.loadResource(
|
||||
api.patchIngredient(this.ingredient)
|
||||
.then(() => this.$router.push({name: 'ingredient', params: {id: this.ingredientId }}))
|
||||
api.patchFood(this.food)
|
||||
.then(() => this.$router.push({name: 'food', params: {id: this.foodId }}))
|
||||
.catch(Errors.onlyFor(Errors.ApiValidationError, err => this.validationErrors = err.validationErrors()))
|
||||
);
|
||||
}
|
||||
@ -48,13 +48,13 @@
|
||||
|
||||
created() {
|
||||
this.loadResource(
|
||||
api.getIngredient(this.ingredientId)
|
||||
.then(data => { this.ingredient = data; return data; })
|
||||
api.getFood(this.foodId)
|
||||
.then(data => { this.food = data; return data; })
|
||||
);
|
||||
},
|
||||
|
||||
components: {
|
||||
IngredientEdit
|
||||
FoodEdit
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
<h1 class="title">Ingredients</h1>
|
||||
|
||||
<div class="buttons">
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'new_food'}" class="button is-primary">Create Ingredient</router-link>
|
||||
</div>
|
||||
|
||||
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="ingredient" @changePage="changePage"></app-pager>
|
||||
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="food" @changePage="changePage"></app-pager>
|
||||
|
||||
<table class="table is-fullwidth is-narrow">
|
||||
<thead>
|
||||
@ -29,16 +29,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="i in ingredients" :key="i.id">
|
||||
<td><router-link :to="{name: 'ingredient', params: { id: i.id } }">{{i.name}}</router-link></td>
|
||||
<tr v-for="i in foods" :key="i.id">
|
||||
<td><router-link :to="{name: 'food', params: { id: i.id } }">{{i.name}}</router-link></td>
|
||||
<td><app-icon v-if="i.usda" icon="check"></app-icon></td>
|
||||
<td>{{i.kcal}}</td>
|
||||
<td>{{i.density}}</td>
|
||||
<td>
|
||||
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: i.id } }">
|
||||
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_food', params: { id: i.id } }">
|
||||
<app-icon icon="pencil"></app-icon>
|
||||
</router-link>
|
||||
<button v-if="isLoggedIn" type="button" class="button is-danger" @click="deleteIngredient(i)">
|
||||
<button v-if="isLoggedIn" type="button" class="button is-danger" @click="deleteFood(i)">
|
||||
<app-icon icon="x"></app-icon>
|
||||
</button>
|
||||
</td>
|
||||
@ -46,13 +46,13 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="ingredient" @changePage="changePage"></app-pager>
|
||||
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="food" @changePage="changePage"></app-pager>
|
||||
|
||||
<div class="buttons">
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
|
||||
<router-link v-if="isLoggedIn" :to="{name: 'new_food'}" class="button is-primary">Create Ingredient</router-link>
|
||||
</div>
|
||||
|
||||
<app-confirm :open="showConfirmIngredientDelete" :message="confirmIngredientDeleteMessage" :cancel="ingredientDeleteCancel" :confirm="ingredientDeleteConfirm"></app-confirm>
|
||||
<app-confirm :open="showConfirmFoodDelete" :message="confirmFoodDeleteMessage" :cancel="foodDeleteCancel" :confirm="foodDeleteConfirm"></app-confirm>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -65,8 +65,8 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ingredientData: null,
|
||||
ingredientForDeletion: null,
|
||||
foodData: null,
|
||||
foodForDeletion: null,
|
||||
search: {
|
||||
page: 1,
|
||||
per: 25,
|
||||
@ -76,35 +76,35 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
ingredients() {
|
||||
if (this.ingredientData) {
|
||||
return this.ingredientData.ingredients;
|
||||
foods() {
|
||||
if (this.foodData) {
|
||||
return this.foodData.foods;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
totalPages() {
|
||||
if (this.ingredientData) {
|
||||
return this.ingredientData.total_pages
|
||||
if (this.foodData) {
|
||||
return this.foodData.total_pages
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
currentPage() {
|
||||
if (this.ingredientData) {
|
||||
return this.ingredientData.current_page
|
||||
if (this.foodData) {
|
||||
return this.foodData.current_page
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
showConfirmIngredientDelete() {
|
||||
return this.ingredientForDeletion !== null;
|
||||
showConfirmFoodDelete() {
|
||||
return this.foodForDeletion !== null;
|
||||
},
|
||||
|
||||
confirmIngredientDeleteMessage() {
|
||||
if (this.ingredientForDeletion !== null) {
|
||||
return `Are you sure you want to delete ${this.ingredientForDeletion.name}?`;
|
||||
confirmFoodDeleteMessage() {
|
||||
if (this.foodForDeletion !== null) {
|
||||
return `Are you sure you want to delete ${this.foodForDeletion.name}?`;
|
||||
} else {
|
||||
return "??";
|
||||
}
|
||||
@ -118,30 +118,30 @@
|
||||
|
||||
getList: debounce(function() {
|
||||
return this.loadResource(
|
||||
api.getIngredientList(this.search.page, this.search.per, this.search.name)
|
||||
.then(data => this.ingredientData = data)
|
||||
api.getFoodList(this.search.page, this.search.per, this.search.name)
|
||||
.then(data => this.foodData = data)
|
||||
);
|
||||
}, 500, {leading: true, trailing: true}),
|
||||
|
||||
deleteIngredient(ingredient) {
|
||||
this.ingredientForDeletion = ingredient;
|
||||
deleteFood(food) {
|
||||
this.foodForDeletion = food;
|
||||
},
|
||||
|
||||
ingredientDeleteCancel() {
|
||||
this.ingredientForDeletion = null;
|
||||
foodDeleteCancel() {
|
||||
this.foodForDeletion = null;
|
||||
},
|
||||
|
||||
ingredientDeleteConfirm() {
|
||||
if (this.ingredientForDeletion !== null) {
|
||||
foodDeleteConfirm() {
|
||||
if (this.foodForDeletion !== null) {
|
||||
this.loadResource(
|
||||
api.deleteIngredient(this.ingredientForDeletion.id).then(res => {
|
||||
this.ingredientForDeletion = null;
|
||||
api.deleteFood(this.foodForDeletion.id).then(res => {
|
||||
this.foodForDeletion = null;
|
||||
return this.getList();
|
||||
})
|
||||
);
|
||||
|
||||
console.log("This is where the thing happens!!");
|
||||
this.ingredientForDeletion = null;
|
||||
this.foodForDeletion = null;
|
||||
}
|
||||
}
|
||||
},
|
@ -213,66 +213,66 @@ class Api {
|
||||
return this.get("/calculator/calculate", params);
|
||||
}
|
||||
|
||||
getIngredientList(page, per, name) {
|
||||
getFoodList(page, per, name) {
|
||||
const params = {
|
||||
page,
|
||||
per,
|
||||
name
|
||||
};
|
||||
|
||||
return this.get("/ingredients/", params);
|
||||
return this.get("/foods/", params);
|
||||
}
|
||||
|
||||
getIngredient(id) {
|
||||
return this.get("/ingredients/" + id);
|
||||
getFood(id) {
|
||||
return this.get("/foods/" + id);
|
||||
}
|
||||
|
||||
buildIngredientParams(ingredient) {
|
||||
buildFoodParams(food) {
|
||||
return {
|
||||
ingredient: {
|
||||
name: ingredient.name,
|
||||
notes: ingredient.notes,
|
||||
ndbn: ingredient.ndbn,
|
||||
density: ingredient.density,
|
||||
food: {
|
||||
name: food.name,
|
||||
notes: food.notes,
|
||||
ndbn: food.ndbn,
|
||||
density: food.density,
|
||||
|
||||
water: ingredient.water,
|
||||
ash: ingredient.ash,
|
||||
protein: ingredient.protein,
|
||||
kcal: ingredient.kcal,
|
||||
fiber: ingredient.fiber,
|
||||
sugar: ingredient.sugar,
|
||||
carbohydrates: ingredient.carbohydrates,
|
||||
calcium: ingredient.calcium,
|
||||
iron: ingredient.iron,
|
||||
magnesium: ingredient.magnesium,
|
||||
phosphorus: ingredient.phosphorus,
|
||||
potassium: ingredient.potassium,
|
||||
sodium: ingredient.sodium,
|
||||
zinc: ingredient.zinc,
|
||||
copper: ingredient.copper,
|
||||
manganese: ingredient.manganese,
|
||||
vit_c: ingredient.vit_c,
|
||||
vit_b6: ingredient.vit_b6,
|
||||
vit_b12: ingredient.vit_b12,
|
||||
vit_a: ingredient.vit_a,
|
||||
vit_e: ingredient.vit_e,
|
||||
vit_d: ingredient.vit_d,
|
||||
vit_k: ingredient.vit_k,
|
||||
cholesterol: ingredient.cholesterol,
|
||||
lipids: ingredient.lipids,
|
||||
water: food.water,
|
||||
ash: food.ash,
|
||||
protein: food.protein,
|
||||
kcal: food.kcal,
|
||||
fiber: food.fiber,
|
||||
sugar: food.sugar,
|
||||
carbohydrates: food.carbohydrates,
|
||||
calcium: food.calcium,
|
||||
iron: food.iron,
|
||||
magnesium: food.magnesium,
|
||||
phosphorus: food.phosphorus,
|
||||
potassium: food.potassium,
|
||||
sodium: food.sodium,
|
||||
zinc: food.zinc,
|
||||
copper: food.copper,
|
||||
manganese: food.manganese,
|
||||
vit_c: food.vit_c,
|
||||
vit_b6: food.vit_b6,
|
||||
vit_b12: food.vit_b12,
|
||||
vit_a: food.vit_a,
|
||||
vit_e: food.vit_e,
|
||||
vit_d: food.vit_d,
|
||||
vit_k: food.vit_k,
|
||||
cholesterol: food.cholesterol,
|
||||
lipids: food.lipids,
|
||||
|
||||
|
||||
ingredient_units_attributes: ingredient.ingredient_units.map(iu => {
|
||||
if (iu._destroy) {
|
||||
food_units_attributes: food.food_units.map(fu => {
|
||||
if (fu._destroy) {
|
||||
return {
|
||||
id: iu.id,
|
||||
id: fu.id,
|
||||
_destroy: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: iu.id,
|
||||
name: iu.name,
|
||||
gram_weight: iu.gram_weight
|
||||
id: fu.id,
|
||||
name: fu.name,
|
||||
gram_weight: fu.gram_weight
|
||||
};
|
||||
}
|
||||
})
|
||||
@ -280,25 +280,25 @@ class Api {
|
||||
}
|
||||
}
|
||||
|
||||
postIngredient(ingredient) {
|
||||
return this.post("/ingredients/", this.buildIngredientParams(ingredient));
|
||||
postFood(food) {
|
||||
return this.post("/foods/", this.buildFoodParams(food));
|
||||
}
|
||||
|
||||
patchIngredient(ingredient) {
|
||||
return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient));
|
||||
patchFood(food) {
|
||||
return this.patch("/foods/" + food.id, this.buildFoodParams(food));
|
||||
}
|
||||
|
||||
deleteIngredient(id) {
|
||||
return this.del("/ingredients/" + id);
|
||||
deleteFood(id) {
|
||||
return this.del("/foods/" + id);
|
||||
}
|
||||
|
||||
postIngredientSelectNdbn(ingredient) {
|
||||
const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn";
|
||||
return this.post(url, this.buildIngredientParams(ingredient));
|
||||
const url = ingredient.id ? "/foods/" + ingredient.id + "/select_ndbn" : "/foods/select_ndbn";
|
||||
return this.post(url, this.buildFoodParams(ingredient));
|
||||
}
|
||||
|
||||
getUsdaFoodSearch(query) {
|
||||
return this.get("/ingredients/usda_food_search", {query: query});
|
||||
return this.get("/foods/usda_food_search", {query: query});
|
||||
}
|
||||
|
||||
getNoteList() {
|
||||
|
@ -10,10 +10,10 @@ import TheLogList from './components/TheLogList';
|
||||
import TheLogCreator from './components/TheLogCreator';
|
||||
import TheLogEditor from './components/TheLogEditor';
|
||||
|
||||
import TheIngredientList from './components/TheIngredientList';
|
||||
import TheIngredient from "./components/TheIngredient";
|
||||
import TheIngredientEditor from "./components/TheIngredientEditor";
|
||||
import TheIngredientCreator from "./components/TheIngredientCreator";
|
||||
import TheFoodList from './components/TheFoodList';
|
||||
import TheFood from "./components/TheFood";
|
||||
import TheFoodEditor from "./components/TheFoodEditor";
|
||||
import TheFoodCreator from "./components/TheFoodCreator";
|
||||
import TheNotesList from './components/TheNotesList';
|
||||
import TheRecipe from './components/TheRecipe';
|
||||
import TheRecipeEditor from './components/TheRecipeEditor';
|
||||
@ -71,24 +71,24 @@ router.addRoutes(
|
||||
component: TheCalculator
|
||||
},
|
||||
{
|
||||
path: "/ingredients",
|
||||
name: "ingredients",
|
||||
component: TheIngredientList
|
||||
path: "/foods",
|
||||
name: "foods",
|
||||
component: TheFoodList
|
||||
},
|
||||
{
|
||||
path: "/ingredients/new",
|
||||
name: "new_ingredient",
|
||||
component: TheIngredientCreator
|
||||
path: "/foods/new",
|
||||
name: "new_food",
|
||||
component: TheFoodCreator
|
||||
},
|
||||
{
|
||||
path: "/ingredients/:id/edit",
|
||||
name: "edit_ingredient",
|
||||
component: TheIngredientEditor
|
||||
path: "/foods/:id/edit",
|
||||
name: "edit_food",
|
||||
component: TheFoodEditor
|
||||
},
|
||||
{
|
||||
path: "/ingredients/:id",
|
||||
name: "ingredient",
|
||||
component: TheIngredient
|
||||
path: "/foods/:id",
|
||||
name: "food",
|
||||
component: TheFood
|
||||
},
|
||||
{
|
||||
path: "/logs",
|
||||
|
@ -30,14 +30,18 @@ class Food < ApplicationRecord
|
||||
units
|
||||
end
|
||||
|
||||
def nutrition_per_100g
|
||||
def nutrition_data
|
||||
self
|
||||
end
|
||||
|
||||
def nutrition_per_100g_errors
|
||||
def nutrition_errors
|
||||
[]
|
||||
end
|
||||
|
||||
def nutrition_unit
|
||||
UnitConversion.parse('100 grams')
|
||||
end
|
||||
|
||||
def ndbn=(value)
|
||||
@usda_food = nil
|
||||
super
|
||||
|
@ -1,25 +0,0 @@
|
||||
class IngredientProxy
|
||||
|
||||
attr_reader :ingredient
|
||||
|
||||
def initialize(food)
|
||||
@food = food
|
||||
end
|
||||
|
||||
def name
|
||||
@food.name
|
||||
end
|
||||
|
||||
def density
|
||||
@food.density
|
||||
end
|
||||
|
||||
def density?
|
||||
@food.density.present?
|
||||
end
|
||||
|
||||
def get_custom_unit_equivalent(custom_unit_name)
|
||||
@food.custom_unit_weight(custom_unit_name)
|
||||
end
|
||||
|
||||
end
|
@ -36,28 +36,31 @@ class NutritionData
|
||||
self.instance_variable_set("@#{n}".to_sym, 0.0)
|
||||
end
|
||||
|
||||
valid_ingredients = []
|
||||
|
||||
recipe_ingredients.each do |i|
|
||||
if i.ingredient_id.nil?
|
||||
if i.ingredient.nil? || i.ingredient.nutrition_data.nil?
|
||||
@errors << "#{i.name} has no nutrition data"
|
||||
elsif !i.can_convert_to_grams?
|
||||
@errors << "#{i.name} can't be converted to grams"
|
||||
else
|
||||
valid_ingredients << i
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
nutrient_scale = i.calculate_nutrition_ratio
|
||||
|
||||
if nutrient_scale.nil?
|
||||
@errors << "#{i.name} has an unknown quantity or unit"
|
||||
next
|
||||
end
|
||||
|
||||
unless i.ingredient.nutrition_errors.empty?
|
||||
@errors << "#{i.name} has errors: #{i.ingredient.nutrition_errors.join(", ")}"
|
||||
end
|
||||
|
||||
valid_ingredients.each do |i|
|
||||
grams = i.to_grams
|
||||
missing = []
|
||||
|
||||
NUTRIENTS.each do |k, n|
|
||||
value = i.food.send(k)
|
||||
value = i.ingredient.nutrition_data.send(k)
|
||||
if value.present?
|
||||
value = value.to_f
|
||||
running_total = self.instance_variable_get("@#{k}".to_sym)
|
||||
delta = (grams / 100.0) * value
|
||||
delta = value * nutrient_scale
|
||||
self.instance_variable_set("@#{k}".to_sym, running_total + delta)
|
||||
else
|
||||
missing << k
|
||||
|
@ -76,7 +76,7 @@ class Recipe < ApplicationRecord
|
||||
end
|
||||
|
||||
def yields_list
|
||||
@yields_list ||= self.yields.to_s.split(',').concat(['1 recipe']).map { |y| y.strip }.select { |y| y.present? }.map do |y|
|
||||
@yields_list ||= self.yields.to_s.split(',').concat(['1 each']).map { |y| y.strip }.select { |y| y.present? }.map do |y|
|
||||
begin
|
||||
UnitConversion::parse(y)
|
||||
rescue UntConversion::UnparseableUnitError
|
||||
@ -106,6 +106,10 @@ class Recipe < ApplicationRecord
|
||||
@nutrition_data
|
||||
end
|
||||
|
||||
def nutrition_errors
|
||||
nutrition_data.errors
|
||||
end
|
||||
|
||||
def update_rating!
|
||||
self.rating = Log.for_recipe(self).for_user(self.user_id).where('rating IS NOT NULL').average(:rating)
|
||||
save(validate: false)
|
||||
@ -142,23 +146,30 @@ class Recipe < ApplicationRecord
|
||||
end
|
||||
|
||||
def custom_units
|
||||
arbitrary = self.yields_list.select { |y| !y.mass? && !y.volume }
|
||||
arbitrary = self.yields_list.select { |y| !y.mass? && !y.volume? }
|
||||
mass = self.yields_list.select { |y| y.mass? }
|
||||
volume = self.yields_list.select { |y| y.volume? }
|
||||
|
||||
primary_unit = mass.first || volume.first
|
||||
|
||||
Hash[arbitrary.map { |y| [y.unit.unit, primary_unit] }]
|
||||
|
||||
Hash[yields_list.select { |y| !y.mass? && !y.volume? && y.unit }.map { [y.unit.unit, y] }]
|
||||
if primary_unit
|
||||
cus = {}
|
||||
arbitrary.each do |y|
|
||||
ratio = 1
|
||||
if y.value.value != 1
|
||||
ratio = 1.0 / y.value.value
|
||||
end
|
||||
|
||||
def nutrition_per_100g
|
||||
|
||||
cus[y.unit.unit] = primary_unit.scale(ratio)
|
||||
end
|
||||
cus
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def nutrition_per_100g_errors
|
||||
|
||||
def nutrition_unit
|
||||
UnitConversion.parse('1 each')
|
||||
end
|
||||
|
||||
def self.for_criteria(criteria)
|
||||
|
@ -100,7 +100,7 @@ class RecipeIngredient < ApplicationRecord
|
||||
density = UnitConversion.parse(ingredient.density)
|
||||
if density.density?
|
||||
value_unit = UnitConversion.parse(self.quantity, self.units)
|
||||
value_unit = value_unit.to_volume(density)
|
||||
value_unit = value_unit.to_volume(density).auto_unit
|
||||
|
||||
self.quantity = value_unit.pretty_value
|
||||
self.units = value_unit.unit.to_s
|
||||
@ -111,65 +111,36 @@ class RecipeIngredient < ApplicationRecord
|
||||
def to_mass
|
||||
return unless self.quantity.present?
|
||||
if ingredient && ingredient.density?
|
||||
UnitConversion::with_custom_units(self.ingredient.custom_units) do
|
||||
density = UnitConversion.parse(ingredient.density)
|
||||
if density.density?
|
||||
value_unit = UnitConversion.parse(self.quantity, self.units)
|
||||
value_unit = value_unit.to_mass(density)
|
||||
value_unit = value_unit.to_mass(density).auto_unit
|
||||
|
||||
self.quantity = value_unit.pretty_value
|
||||
self.units = value_unit.unit.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_custom_unit_equivalent
|
||||
if self.ingredient
|
||||
unit = self.units.present? ? self.units.downcase : ''
|
||||
pair = self.ingredient.custom_units.detect do |u, e|
|
||||
if unit.empty?
|
||||
['each', 'ech', 'item', 'per', 'recipe'].include?(u.downcase)
|
||||
else
|
||||
[u.downcase, u.downcase.singularize, u.downcase.pluralize].any? { |uv| [unit, unit.singularize, unit.pluralize].include?(uv) }
|
||||
end
|
||||
end
|
||||
pair ? pair[1] : nil
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def can_convert_to_grams?
|
||||
vu = as_value_unit
|
||||
vu.present? && (vu.mass? || (vu.volume? && self.ingredient && self.ingredient.density?))
|
||||
end
|
||||
|
||||
def to_grams
|
||||
value_unit = as_value_unit
|
||||
gram_unit = value_unit.convert('g', self.ingredient ? self.ingredient.density : nil)
|
||||
gram_unit.raw_value
|
||||
end
|
||||
|
||||
# Based on current quantity and units, return the value with with to multiply each nutrient to get the total amount
|
||||
# supplied by this ingredient
|
||||
def calculate_nutrition_ratio
|
||||
|
||||
if self.ingredient.blank? || self.quantity.blank?
|
||||
return nil
|
||||
end
|
||||
|
||||
def as_value_unit
|
||||
UnitConversion::with_custom_units(self.ingredient.custom_units) do
|
||||
unit_is_each = self.units.blank? || %w(each ech item items per recipe recipes).include?(self.units.downcase)
|
||||
unit = UnitConversion::parse(self.quantity, unit_is_each ? 'each' : self.units)
|
||||
nutrition_unit = self.ingredient.nutrition_unit
|
||||
|
||||
custom_unit = self.get_custom_unit_equivalent
|
||||
|
||||
case
|
||||
when self.quantity.blank?
|
||||
nil
|
||||
when custom_unit.present?
|
||||
vu = UnitConversion.parse(custom_unit)
|
||||
vu.scale(self.quantity)
|
||||
when self.units.present?
|
||||
UnitConversion.parse(self.quantity, self.units)
|
||||
else
|
||||
nil
|
||||
converted_unit = unit.convert(nutrition_unit.unit.unit, self.ingredient.density)
|
||||
converted_unit.scale(1.0 / nutrition_unit.value.value).raw_value
|
||||
end
|
||||
rescue UnitConversion::UnparseableUnitError
|
||||
nil
|
||||
end
|
||||
|
||||
def log_copy
|
||||
|
@ -1,71 +0,0 @@
|
||||
class RecipeProxy
|
||||
|
||||
attr_reader :recipe
|
||||
|
||||
def initialize(recipe)
|
||||
@recipe = recipe
|
||||
parse_yields
|
||||
end
|
||||
|
||||
def name
|
||||
@recipe.name
|
||||
end
|
||||
|
||||
def density
|
||||
@density
|
||||
end
|
||||
|
||||
def density?
|
||||
!self.density.nil?
|
||||
end
|
||||
|
||||
def get_custom_unit_equivalent(custom_unit_name)
|
||||
unit = @custom_yields.detect { |vu| vu.unit.unit == custom_unit_name }
|
||||
known_unit = @unit_yields.first
|
||||
if unit && known_unit
|
||||
# ex:
|
||||
# custom_unit_name: "rolls"
|
||||
# yields: "3 rolls, 500 g"
|
||||
# desired return: 166.6 g
|
||||
|
||||
ValueUnit.for(known_unit.value.value / unit.value.value, known_unit.unit)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_yields
|
||||
@custom_yields = []
|
||||
@unit_yields = []
|
||||
@density = nil
|
||||
|
||||
@recipe.yields_list.each do |y|
|
||||
begin
|
||||
vu = UnitConversion::parse(yield_string)
|
||||
rescue UnparseableUnitError
|
||||
vu = nil
|
||||
end
|
||||
|
||||
if vu
|
||||
if vu.unit.nil?
|
||||
@custom_yields << ValueUnit.for(vu.value, 'servings')
|
||||
elsif vu.mass? || vu.volume?
|
||||
@unit_yields << vu
|
||||
else
|
||||
@custom_yields << vu
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
vol = @unit_yields.detect { |u| u.volume? }
|
||||
mas = @unit_yields.detect { |u| u.mass? }
|
||||
# ex
|
||||
# vol: 2 cups
|
||||
# mas: 7 oz
|
||||
if vol && mas
|
||||
@density = "#{mas.value.value / vol.value.value} #{mas.unit.original_unit}/#{vol.unit.original_unit}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -40,7 +40,7 @@ else
|
||||
json.ndbn_units []
|
||||
end
|
||||
|
||||
json.ingredient_units @food.ingredient_units do |iu|
|
||||
json.food_units @food.food_units do |iu|
|
||||
json.extract! iu, :id, :name, :gram_weight
|
||||
json._destroy false
|
||||
end
|
@ -4,6 +4,7 @@ require 'unit_conversion/formatters'
|
||||
require 'unit_conversion/parsed_number'
|
||||
require 'unit_conversion/parsed_unit'
|
||||
require 'unit_conversion/conversions'
|
||||
require 'unit_conversion/unitwise_patch'
|
||||
require 'unit_conversion/value_unit'
|
||||
|
||||
module UnitConversion
|
||||
@ -32,5 +33,28 @@ module UnitConversion
|
||||
[unit.pretty_value, unit.unit.to_s]
|
||||
end
|
||||
|
||||
def with_custom_units(unit_map, &block)
|
||||
|
||||
atom_configs = []
|
||||
|
||||
unit_map.each do |custom_unit, value_unit|
|
||||
raise(UnknownUnitError, "#{value_unit.unit} is not valid") unless (value_unit.mass? || value_unit.volume?)
|
||||
base_name = custom_unit.to_s.strip.downcase
|
||||
|
||||
atom_configs << {
|
||||
names: [base_name, base_name.pluralize, base_name.singularize].uniq,
|
||||
primary_code: "[prlsy_#{base_name[0]}]",
|
||||
scale: {
|
||||
value: value_unit.value.value,
|
||||
unit_code: value_unit.unitwise.unit.to_s
|
||||
},
|
||||
property: value_unit.mass? ? 'mass' : 'volume'
|
||||
}
|
||||
end
|
||||
|
||||
Unitwise::with_custom_units(atom_configs, &block)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -52,10 +52,14 @@ module UnitConversion
|
||||
|
||||
def convert(value_unit)
|
||||
|
||||
unless known_auto_unit?(value_unit.unit)
|
||||
if value_unit.unit.nil? || (!value_unit.mass? && !value_unit.volume?)
|
||||
return value_unit
|
||||
end
|
||||
|
||||
unless known_auto_unit?(value_unit.unit)
|
||||
value_unit = value_unit.mass? ? value_unit.convert("g") : value_unit.convert("ml")
|
||||
end
|
||||
|
||||
value = value_unit.raw_value
|
||||
unit = value_unit.unit.unit
|
||||
new_unit = unit
|
||||
|
43
lib/unit_conversion/unitwise_patch.rb
Normal file
43
lib/unit_conversion/unitwise_patch.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module Unitwise
|
||||
module Expression
|
||||
class Decomposer
|
||||
class << self
|
||||
def reset(n = {})
|
||||
old = {
|
||||
parsers: @parsers,
|
||||
transformer: @transformer,
|
||||
cache: @cache
|
||||
}
|
||||
@parsers = n[:parsers]
|
||||
@transformer = n[:transformer]
|
||||
@cache = n[:cache]
|
||||
old
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.with_custom_units(unit_list, &block)
|
||||
|
||||
atoms = []
|
||||
|
||||
unit_list.each do |u|
|
||||
atom = Unitwise::Atom.new(u)
|
||||
atom.validate!
|
||||
Unitwise::Atom.all.push(atom)
|
||||
atoms.push(atom)
|
||||
end
|
||||
rem = Unitwise::Expression::Decomposer.send(:reset)
|
||||
|
||||
ret_val = block.call
|
||||
|
||||
atoms.each do |a|
|
||||
idx = Unitwise::Atom.all.index { |b| b.equal?(a) }
|
||||
Unitwise::Atom.all.delete_at(idx)
|
||||
# Unitwise::Atom.all.pop
|
||||
end
|
||||
Unitwise::Expression::Decomposer.send(:reset, rem)
|
||||
|
||||
ret_val
|
||||
end
|
||||
end
|
@ -2,6 +2,30 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe UnitConversion do
|
||||
|
||||
describe '.with_custom_units' do
|
||||
it 'can convert' do
|
||||
UnitConversion.with_custom_units({
|
||||
'recipe': UnitConversion.parse('4 cups')
|
||||
}) do
|
||||
vu = UnitConversion.parse('2 1/2 recipes')
|
||||
expect(vu.volume?).to be_truthy
|
||||
lu = vu.convert("liters")
|
||||
expect(lu.raw_value).to be_between(2.3, 2.4)
|
||||
expect(lu.unit.to_s).to eq 'liter'
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the value of the block' do
|
||||
val = UnitConversion.with_custom_units({
|
||||
'recipe': UnitConversion.parse('4 cups')
|
||||
}) do
|
||||
42
|
||||
end
|
||||
|
||||
expect(val).to eq 42
|
||||
end
|
||||
end
|
||||
|
||||
describe '.auto_unit' do
|
||||
it 'leaves units alone if reasonable' do
|
||||
expect(UnitConversion.auto_unit('1/2', 'tbsp')).to eq ['1/2', 'tablespoon']
|
||||
|
@ -28,7 +28,7 @@ RSpec.describe NutritionData, type: :model do
|
||||
let(:rec2_ingredients) do
|
||||
[
|
||||
RecipeIngredient.new({
|
||||
quantity: '100',
|
||||
quantity: '250',
|
||||
units: 'g',
|
||||
sort_order: 1,
|
||||
recipe_as_ingredient: recipe1
|
||||
@ -48,9 +48,12 @@ RSpec.describe NutritionData, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
it 'runs' do
|
||||
it 'runs and calculates properly' do
|
||||
n = recipe2.nutrition_data
|
||||
|
||||
expect(n.kcal).to eq 20
|
||||
expect(n.protein).to eq 1
|
||||
expect(n.lipids).to eq 3
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -6,9 +6,8 @@ RSpec.describe RecipeIngredient, type: :model do
|
||||
|
||||
it 'converts volume ingredients with density' do
|
||||
ri = RecipeIngredient.new(quantity: 2, units: 'tbsp', food: create(:food_with_density))
|
||||
expect(ri.as_value_unit.mass?).to be_falsey
|
||||
ri.to_mass
|
||||
expect(ri.as_value_unit.mass?).to be_truthy
|
||||
expect(ri.units).to eq 'ounce'
|
||||
end
|
||||
|
||||
it 'converts ingredients with custom units' do
|
||||
@ -16,66 +15,68 @@ RSpec.describe RecipeIngredient, type: :model do
|
||||
i.food_units << FoodUnit.new(name: 'pat', gram_weight: 25)
|
||||
ri = RecipeIngredient.new(quantity: 2, units: 'pat', food: i)
|
||||
ri.to_mass
|
||||
vu = ri.as_value_unit
|
||||
expect(vu.raw_value).to eq 50
|
||||
expect(vu.unit.to_s).to eq 'gram'
|
||||
expect(ri.quantity).to eq '50'
|
||||
expect(ri.units).to eq 'gram'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#can_convert_to_grams' do
|
||||
describe 'with no ingredient detail' do
|
||||
it 'returns false if no quantity or unit' do
|
||||
ri = RecipeIngredient.new
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
describe '#calculate_nutrition_ratio' do
|
||||
it 'returns nil if no ratio can be calculated' do
|
||||
ri = create(:recipe_ingredient)
|
||||
expect(ri.calculate_nutrition_ratio).to be_nil
|
||||
|
||||
it 'returns false if no quantity' do
|
||||
ri = RecipeIngredient.new(units: 'lbs')
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
ri.quantity = 50
|
||||
expect(ri.calculate_nutrition_ratio).to be_nil
|
||||
|
||||
it 'returns false if no units' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2')
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false if weird units' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'dogs')
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true if unit is mass' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'lbs')
|
||||
expect(ri.can_convert_to_grams?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if unit is volume' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups')
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with ingredient' do
|
||||
it 'returns false if unit is volume and ingredient has no density' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups', food: create(:food))
|
||||
expect(ri.can_convert_to_grams?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true if unit is volume and ingredient has density' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups', food: create(:food_with_density))
|
||||
expect(ri.can_convert_to_grams?).to be_truthy
|
||||
end
|
||||
ri.units = 'cats'
|
||||
expect(ri.calculate_nutrition_ratio).to be_nil
|
||||
end
|
||||
|
||||
describe 'with recipe_as_ingredient' do
|
||||
it 'return true if unit is volume and recipe has density' do
|
||||
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups', recipe_as_ingredient: create(:recipe, yields: '4 cups, 13 oz'))
|
||||
expect(ri.can_convert_to_grams?).to be_truthy
|
||||
let(:recipe) { create(:recipe, yields: '250 g, 0.5 l, 2 rolls') }
|
||||
let(:recipe_ingredient) { create(:recipe_ingredient, food: nil, recipe_as_ingredient: recipe) }
|
||||
|
||||
it 'returns nil with no quantity' do
|
||||
ri = recipe_ingredient
|
||||
expect(ri.calculate_nutrition_ratio).to be_nil
|
||||
end
|
||||
|
||||
it 'returns returns a proper scale with a unitless quantity' do
|
||||
ri = recipe_ingredient
|
||||
ri.quantity = 2
|
||||
expect(ri.calculate_nutrition_ratio).to eq 2
|
||||
|
||||
ri.quantity = 4
|
||||
expect(ri.calculate_nutrition_ratio).to eq 4
|
||||
end
|
||||
|
||||
it 'returns a proper scale with a counted unit' do
|
||||
ri = recipe_ingredient
|
||||
ri.quantity = 3
|
||||
ri.units = "rolls"
|
||||
expect(ri.calculate_nutrition_ratio).to eq 1.5
|
||||
end
|
||||
|
||||
it 'returns a proper scale with a mass unit' do
|
||||
ri = recipe_ingredient
|
||||
ri.quantity = 500
|
||||
ri.units = 'g'
|
||||
expect(ri.calculate_nutrition_ratio).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with food' do
|
||||
let(:food) { create(:food) }
|
||||
let(:recipe_ingredient) { create(:recipe_ingredient, food: food) }
|
||||
|
||||
it 'returns a proper scale with a mass unit' do
|
||||
ri = recipe_ingredient
|
||||
ri.quantity = 500
|
||||
ri.units = 'g'
|
||||
expect(ri.calculate_nutrition_ratio).to eq 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -2,13 +2,13 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe Recipe, type: :model do
|
||||
describe '#yields_list' do
|
||||
it 'always has "1 recipe" as a yield' do
|
||||
it 'always has "1 each" as a yield' do
|
||||
r = create(:recipe, yields: '')
|
||||
l = r.yields_list
|
||||
expect(l.length).to eq 1
|
||||
expect(l.first).to be_a UnitConversion::ValueUnit
|
||||
expect(l.first.value.value).to eq 1
|
||||
expect(l.first.unit.unit).to eq 'recipe'
|
||||
expect(l.first.unit.unit).to eq 'each'
|
||||
end
|
||||
|
||||
it 'caches the list and resets it when yield is changed' do
|
||||
@ -24,6 +24,35 @@ RSpec.describe Recipe, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#custom_units' do
|
||||
it 'returns nothing if no conversion possible' do
|
||||
r = create(:recipe, yields: '')
|
||||
expect(r.custom_units).to be_empty
|
||||
|
||||
r = create(:recipe, yields: '3 rolls, 2 buttercups')
|
||||
expect(r.custom_units).to be_empty
|
||||
|
||||
r = create(:recipe, yields: '1 dealybob')
|
||||
expect(r.custom_units).to be_empty
|
||||
end
|
||||
|
||||
it 'returns a each mapping for a convertable yields' do
|
||||
r = create(:recipe, yields: '2 1/2 cups')
|
||||
expect(r.custom_units.length).to eq 1
|
||||
expect(r.custom_units['each']).to be_a UnitConversion::ValueUnit
|
||||
expect(r.custom_units['each'].value.value).to eq 2.5
|
||||
end
|
||||
|
||||
it 'creates properly scaled arbitrary units' do
|
||||
r = create(:recipe, yields: '6 Tbsp, 3 glarps')
|
||||
expect(r.custom_units.length).to eq 2
|
||||
expect(r.custom_units['each']).to be_a UnitConversion::ValueUnit
|
||||
expect(r.custom_units['glarps']).to be_a UnitConversion::ValueUnit
|
||||
expect(r.custom_units['glarps'].value.value).to eq 2
|
||||
expect(r.custom_units['glarps'].unit.unit).to eq '[tbs_us]'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_rating!' do
|
||||
|
||||
it 'should set rating to nil with no ratings' do
|
||||
|
Loading…
Reference in New Issue
Block a user