recipe as ingredient work

This commit is contained in:
Dan Elbert 2018-09-11 10:38:07 -05:00
parent a6cf7c1b16
commit 56fe5aae35
33 changed files with 407 additions and 273 deletions

View File

@ -29,7 +29,7 @@ class CalculatorController < ApplicationController
end
def ingredient_search
@ingredients = Ingredient.has_density.search(params[:query]).order(:name)
@foods = Food.has_density.search(params[:query]).order(:name)
end
end

View File

@ -0,0 +1,141 @@
class FoodsController < ApplicationController
before_action :set_food, only: [:show, :edit, :update, :destroy]
before_action :ensure_valid_user, except: [:index, :show]
# GET /foods
# GET /foods.json
def index
@foods = Food.all.order(:name).page(params[:page]).per(params[:per])
if params[:name].present?
@foods = @foods.matches_tokens(:name, params[:name].split.take(4))
end
end
def show
end
# GET /foods/new
def new
@food = Food.new
end
# GET /foods/1/edit
def edit
ensure_owner @food
end
# POST /foods
# POST /foods.json
def create
@food = Food.new(food_params)
@food.user = current_user
if @food.ndbn.present?
@food.set_usda_food(UsdaFood.find_by_ndbn(@food.ndbn))
end
respond_to do |format|
if @food.save
format.html { redirect_to foods_path, notice: 'Ingredient was successfully created.' }
format.json { render json: { success: true }, status: :created, location: @food }
else
format.html { render :new }
format.json { render json: @food.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /foods/1
def update
ensure_owner @food do
@food.assign_attributes(food_params)
if @food.ndbn.present?
@food.set_usda_food(UsdaFood.find_by_ndbn(@food.ndbn))
end
respond_to do |format|
if @food.save
format.html { redirect_to foods_path, notice: 'Ingredient was successfully updated.' }
format.json { render json: { success: true }, status: :ok, location: @food }
else
format.html { render :edit }
format.json { render json: @food.errors, status: :unprocessable_entity }
end
end
end
end
# DELETE /foods/1
# DELETE /foods/1.json
def destroy
ensure_owner @food do
@food.destroy
respond_to do |format|
format.html { redirect_to foods_url, notice: 'Ingredient was successfully destroyed.' }
format.json { head :no_content }
end
end
end
def select_ndbn
if params[:id].present?
@food = Food.find(params[:id])
else
@food = Food.new
end
@food.assign_attributes(food_params)
if @food.ndbn.present?
@food.set_usda_food(UsdaFood.find_by_ndbn(@food.ndbn))
end
render :show
end
def prefetch
@foods = Food.all.order(:name)
render :search
end
def search
@foods = Food.search(params[:query]).order(:name)
end
def convert
@conversion = Conversion.new(conversion_params)
if @conversion.valid?
@output_quantity = @conversion.output_quantity
@conversion = Conversion.new
else
@output_quantity = ''
end
end
def usda_food_search
@foods = UsdaFood.search(params[:query]).limit(250).order(:long_description)
respond_to do |format|
format.html { render :layout => false }
format.json { }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_food
@food = Food.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def food_params
params.require(:food).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ash, :iron, :magnesium, :phosphorus, :potassium, :zinc, :copper, :manganese, :vit_c, :vit_b6, :vit_b12, :vit_a, :vit_e, :vit_d, :cholesterol, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
end
def conversion_params
params.require(:conversion).permit(:input_quantity, :input_units, :scale, :output_units, :ingredient_id)
end
end

View File

@ -1,141 +0,0 @@
class IngredientsController < ApplicationController
before_action :set_ingredient, only: [:show, :edit, :update, :destroy]
before_action :ensure_valid_user, except: [:index, :show]
# GET /ingredients
# GET /ingredients.json
def index
@ingredients = Ingredient.all.order(:name).page(params[:page]).per(params[:per])
if params[:name].present?
@ingredients = @ingredients.matches_tokens(:name, params[:name].split.take(4))
end
end
def show
end
# GET /ingredients/new
def new
@ingredient = Ingredient.new
end
# GET /ingredients/1/edit
def edit
ensure_owner @ingredient
end
# POST /ingredients
# POST /ingredients.json
def create
@ingredient = Ingredient.new(ingredient_params)
@ingredient.user = current_user
if @ingredient.ndbn.present?
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully created.' }
format.json { render json: { success: true }, status: :created, location: @ingredient }
else
format.html { render :new }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /ingredients/1
def update
ensure_owner @ingredient do
@ingredient.assign_attributes(ingredient_params)
if @ingredient.ndbn.present?
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
respond_to do |format|
if @ingredient.save
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render json: { success: true }, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
end
end
end
end
# DELETE /ingredients/1
# DELETE /ingredients/1.json
def destroy
ensure_owner @ingredient do
@ingredient.destroy
respond_to do |format|
format.html { redirect_to ingredients_url, notice: 'Ingredient was successfully destroyed.' }
format.json { head :no_content }
end
end
end
def select_ndbn
if params[:id].present?
@ingredient = Ingredient.find(params[:id])
else
@ingredient = Ingredient.new
end
@ingredient.assign_attributes(ingredient_params)
if @ingredient.ndbn.present?
@ingredient.set_usda_food(UsdaFood.find_by_ndbn(@ingredient.ndbn))
end
render :show
end
def prefetch
@ingredients = Ingredient.all.order(:name)
render :search
end
def search
@ingredients = Ingredient.search(params[:query]).order(:name)
end
def convert
@conversion = Conversion.new(conversion_params)
if @conversion.valid?
@output_quantity = @conversion.output_quantity
@conversion = Conversion.new
else
@output_quantity = ''
end
end
def usda_food_search
@foods = UsdaFood.search(params[:query]).limit(250).order(:long_description)
respond_to do |format|
format.html { render :layout => false }
format.json { }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_ingredient
@ingredient = Ingredient.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def ingredient_params
params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ash, :iron, :magnesium, :phosphorus, :potassium, :zinc, :copper, :manganese, :vit_c, :vit_b6, :vit_b12, :vit_a, :vit_e, :vit_d, :cholesterol, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
end
def conversion_params
params.require(:conversion).permit(:input_quantity, :input_units, :scale, :output_units, :ingredient_id)
end
end

View File

@ -0,0 +1,4 @@
module FoodsHelper
end

View File

@ -1,4 +0,0 @@
module IngredientsHelper
end

View File

@ -0,0 +1,24 @@
# Mixin for Ingredient-like things.
# Consumer is expected to have the following methods:
# #name => name of ingredient
# #density => valid density string or nil
# #custom_units => map of unit names to a weight (eg { 'clove' => '25 g', 'bulb' => '175 g' })
# #nutrition_per_100g => Object that responds to all nutrition keys in NutritionData
# nutrition_per_100g_errors => list of errors
module Ingredient
extend ActiveSupport::Concern
included do
end
# Shared helper methods
def density?
!density.nil?
end
module ClassMethods
end
end

View File

@ -21,7 +21,7 @@ class Conversion
def check_conversion
begin
ingredient = ingredient_id.blank? ? nil : Ingredient.find(ingredient_id)
ingredient = ingredient_id.blank? ? nil : Food.find(ingredient_id)
scale = self.scale.blank? ? '1' : self.scale
density = ingredient.nil? ? nil : ingredient.density
@output_quantity = UnitConversion.convert(input_quantity, scale, input_units, output_units, density)

View File

@ -1,10 +1,11 @@
class Ingredient < ApplicationRecord
class Food < ApplicationRecord
include Ingredient
include TokenizedLike
belongs_to :user
has_many :ingredient_units, inverse_of: :ingredient, dependent: :destroy
has_many :food_units, inverse_of: :food, dependent: :destroy
accepts_nested_attributes_for :ingredient_units, allow_destroy: true
accepts_nested_attributes_for :food_units, allow_destroy: true
validates :name, presence: true
validates :density, density: true, allow_blank: true
@ -15,20 +16,26 @@ class Ingredient < ApplicationRecord
tokens = query.to_s.split(' ')
if tokens.empty?
Ingredient.none
Food.none
else
Ingredient.matches_tokens(:name, tokens)
Food.matches_tokens(:name, tokens)
end
end
def custom_unit_weight(unit)
ingredient_units.each do |iu|
if iu.matches?(unit)
return UnitConversion::parse(iu.gram_weight, 'grams')
def custom_units
units = {}
food_units.each do |fu|
units[fu.name] = UnitConversion::parse(fu.gram_weight, "grams")
end
units
end
nil
def nutrition_per_100g
self
end
def nutrition_per_100g_errors
[]
end
def ndbn=(value)

View File

@ -1,5 +1,5 @@
class IngredientUnit < ApplicationRecord
belongs_to :ingredient, inverse_of: :ingredient_units
class FoodUnit < ApplicationRecord
belongs_to :food, inverse_of: :food_units
validates :name, presence: true
validates :gram_weight, presence: true

View File

@ -2,24 +2,24 @@ class IngredientProxy
attr_reader :ingredient
def initialize(ingredient)
@ingredient = ingredient
def initialize(food)
@food = food
end
def name
@ingredient.name
@food.name
end
def density
@ingredient.density
@food.density
end
def density?
@ingredient.density.present?
@food.density.present?
end
def get_custom_unit_equivalent(custom_unit_name)
@ingredient.custom_unit_weight(custom_unit_name)
@food.custom_unit_weight(custom_unit_name)
end
end

View File

@ -53,7 +53,7 @@ class NutritionData
missing = []
NUTRIENTS.each do |k, n|
value = i.ingredient.send(k)
value = i.food.send(k)
if value.present?
value = value.to_f
running_total = self.instance_variable_get("@#{k}".to_sym)
@ -70,11 +70,6 @@ class NutritionData
end
end
NUTRIENTS.each do |k, n|
v = self.instance_variable_get("@#{k}".to_sym)
self.instance_variable_set("@#{k}".to_sym, v.round(2))
end
end
end

View File

@ -1,4 +1,5 @@
class Recipe < ApplicationRecord
include Ingredient
include TokenizedLike
has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
@ -71,7 +72,13 @@ class Recipe < ApplicationRecord
def yields_list
if self.yields.present?
self.yields.split(',').map { |y| y.strip }.select { |y| y.present? }
self.yields.split(',').map { |y| y.strip }.select { |y| y.present? }.map do |y|
begin
UnitConversion::parse(y)
rescue UntConversion::UnparseableUnitError
nil
end
end.compact
else
[]
end
@ -123,6 +130,28 @@ class Recipe < ApplicationRecord
copy
end
def density
mas = yields_list.detect { |y| y.mass? }
vol = yields_list.detect { |y| y.volume? }
if mas && vol
"#{mas.value.value / vol.value.value} #{mas.unit.original_unit}/#{vol.unit.original_unit}"
else
nil
end
end
def custom_units
Hash[yields_list.select { |y| !y.mass? && !y.volume? && y.unit }.map { [y.unit.unit, y] }]
end
def nutrition_per_100g
end
def nutrition_per_100g_errors
end
def self.for_criteria(criteria)
query = active.order(criteria.sort_column => criteria.sort_direction)

View File

@ -1,14 +1,14 @@
class RecipeIngredient < ApplicationRecord
belongs_to :ingredient, optional: true
belongs_to :food, optional: true
belongs_to :recipe_as_ingredient, class_name: 'Recipe', optional: true
belongs_to :recipe, inverse_of: :recipe_ingredients, touch: true
validates :sort_order, presence: true
def name
if self.ingredient_detail.present?
self.ingredient_detail.name
if self.ingredient.present?
self.ingredient.name
else
super
end
@ -20,40 +20,42 @@ class RecipeIngredient < ApplicationRecord
str
end
def ingredient_detail_id
def ingredient_id
case
when recipe_as_ingredient_id
"R#{recipe_as_ingredient_id}"
when ingredient_id
"I#{ingredient_id}"
when food_id
"F#{food_id}"
else
nil
end
end
def ingredient_detail_id=(val)
@recipe_detail = nil
def ingredient_id=(val)
return val if self.ingredient_id == val
@ingredient = nil
case val
when -> (v) { v.blank? }
self.recipe_as_ingredient_id = nil
self.ingredient_id = nil
self.food_id = nil
when /^R(\d+)$/
self.ingredient_id = nil
self.food_id = nil
self.recipe_as_ingredient_id = $1.to_i
when /^I(\d+)$/
when /^F(\d+)$/
self.recipe_as_ingredient_id = nil
self.ingredient_id = $1.to_i
self.food_id = $1.to_i
else
raise "Invalid ingredient_detail_id: #{val}"
raise "Invalid ingredient_id: #{val}"
end
end
def ingredient_detail
@recipe_detail ||= case
def ingredient
@ingredient ||= case
when self.recipe_as_ingredient_id
RecipeProxy.new(self.recipe_as_ingredient)
when self.ingredient_id
IngredientProxy.new(self.ingredient)
self.recipe_as_ingredient
when self.food_id
self.food
else
nil
end
@ -94,7 +96,7 @@ class RecipeIngredient < ApplicationRecord
def to_volume
return unless self.quantity.present?
if ingredient_detail && ingredient_detail.density?
if ingredient && ingredient.density?
density = UnitConversion.parse(ingredient.density)
if density.density?
value_unit = UnitConversion.parse(self.quantity, self.units)
@ -107,44 +109,56 @@ class RecipeIngredient < ApplicationRecord
end
def to_mass
if ingredient_detail
value_unit = as_value_unit
density = self.ingredient_detail.density? ? UnitConversion.parse(ingredient_detail.density) : nil
case
when value_unit.nil?
when value_unit.mass?
self.quantity = value_unit.pretty_value
self.units = value_unit.unit.to_s
when value_unit.volume? && density && density.density?
return unless self.quantity.present?
if ingredient && ingredient.density?
density = UnitConversion.parse(ingredient.density)
if density.density?
value_unit = UnitConversion.parse(self.quantity, self.units)
value_unit = value_unit.to_mass(density)
self.quantity = value_unit.pretty_value
self.units = value_unit.unit.to_s
end
end
end
def custom_unit?
self.ingredient_detail && self.ingredient_detail.get_custom_unit_equivalent(self.units).present?
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'].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_detail && self.ingredient_detail.density.present?))
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_detail ? self.ingredient_detail.density : nil)
gram_unit = value_unit.convert('g', self.ingredient ? self.ingredient.density : nil)
gram_unit.raw_value
end
def as_value_unit
custom_unit = self.get_custom_unit_equivalent
case
when self.quantity.blank?
nil
when self.custom_unit?
self.ingredient_detail.get_custom_unit_equivalent(self.units).scale(self.quantity)
when custom_unit.present?
vu = UnitConversion.parse(custom_unit)
vu.scale(self.quantity)
when self.units.present?
UnitConversion.parse(self.quantity, self.units)
else
@ -154,7 +168,7 @@ class RecipeIngredient < ApplicationRecord
def log_copy
copy = RecipeIngredient.new
copy.ingredient = self.ingredient
copy.food = self.food
copy.recipe_as_ingredient = self.recipe_as_ingredient
copy.name = self.name
copy.sort_order = self.sort_order

View File

@ -1,7 +1,7 @@
class User < ApplicationRecord
has_many :recipes, dependent: :nullify
has_many :ingredients, dependent: :nullify
has_many :foods, dependent: :nullify
has_many :task_lists, dependent: :destroy
has_secure_password

View File

@ -1,5 +1,5 @@
json.array! @ingredients do |i|
json.array! @foods do |i|
json.extract! i, :id, :name, :density

View File

@ -1,10 +1,10 @@
json.cache_root! [Ingredient.all, @ingredients] do
json.cache_root! [Food.all, @foods] do
json.extract! @ingredients, :total_count, :total_pages, :current_page
json.page_size @ingredients.limit_value
json.extract! @foods, :total_count, :total_pages, :current_page
json.page_size @foods.limit_value
json.ingredients @ingredients do |i|
json.foods @foods do |i|
json.extract! i, :id, :name, :ndbn, :kcal
json.usda i.ndbn.present?

View File

@ -1,5 +1,5 @@
json.array! @ingredients do |i|
json.array! @foods do |i|
json.extract! i, :id, :name, :density, :notes

View File

@ -1,5 +1,5 @@
json.extract! @ingredient,
json.extract! @food,
:id,
:name,
:ndbn,
@ -32,15 +32,15 @@ json.extract! @ingredient,
:cholesterol,
:lipids
if @ingredient.ndbn.present?
json.ndbn_units @ingredient.usda_food.usda_food_weights do |fw|
if @food.ndbn.present?
json.ndbn_units @food.usda_food.usda_food_weights do |fw|
json.extract! fw, :amount, :description, :gram_weight
end
else
json.ndbn_units []
end
json.ingredient_units @ingredient.ingredient_units do |iu|
json.ingredient_units @food.ingredient_units do |iu|
json.extract! iu, :id, :name, :gram_weight
json._destroy false
end

View File

@ -10,10 +10,10 @@ json.ingredients recipe.recipe_ingredients do |ri|
json.extract! ri, :id, :ingredient_detail_id, :display_name, :name, :quantity, :units, :preparation, :sort_order
json.ingredient_detail do
if ri.ingredient.nil? && ri.ingredient_as_recipe.nil?
if ri.food.nil? && ri.ingredient_as_recipe.nil?
json.null!
elsif ri.ingredient
json.extract! ri.ingredient, :name, :density, :notes
elsif ri.food
json.extract! ri.food, :name, :density, :notes
else
json.extract! ri.recipe_as_ingredient, :name
end

View File

@ -10,7 +10,7 @@ Rails.application.routes.draw do
resources :logs, except: [:new, :create]
resources :ingredients, except: [] do
resources :foods, except: [] do
collection do
get :usda_food_search

View File

@ -0,0 +1,9 @@
class RenameIngredient < ActiveRecord::Migration[5.2]
def change
rename_table :ingredients, :foods
rename_table :ingredient_units, :food_units
rename_column :food_units, :ingredient_id, :food_id
rename_column :recipe_ingredients, :ingredient_id, :food_id
end
end

View File

@ -10,16 +10,16 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2018_09_06_191333) do
ActiveRecord::Schema.define(version: 2018_09_10_235229) do
create_table "ingredient_units", force: :cascade do |t|
t.integer "ingredient_id", null: false
create_table "food_units", force: :cascade do |t|
t.integer "food_id", null: false
t.string "name", null: false
t.decimal "gram_weight", precision: 10, scale: 2, null: false
t.index ["ingredient_id"], name: "index_ingredient_units_on_ingredient_id"
t.index ["food_id"], name: "index_food_units_on_food_id"
end
create_table "ingredients", force: :cascade do |t|
create_table "foods", force: :cascade do |t|
t.string "name"
t.string "density"
t.text "notes"
@ -74,7 +74,7 @@ ActiveRecord::Schema.define(version: 2018_09_06_191333) do
end
create_table "recipe_ingredients", force: :cascade do |t|
t.integer "ingredient_id"
t.integer "food_id"
t.integer "recipe_id"
t.string "name"
t.integer "sort_order"

View File

@ -44,7 +44,7 @@ ingredients = {
}
ingredients.each do |k, v|
ingredients[k] = Ingredient.create!({user_id: dan.id}.merge(v))
ingredients[k] = Food.create!({user_id: dan.id}.merge(v))
end
g = Recipe.create!({
@ -69,19 +69,19 @@ bb = Recipe.create!({
bb.tag_names = ['beef', 'dinner', 'stirfry']
[
{quantity: '1', units: 'pound', preparation: 'flank steak, skirt steak, hanger steak, or flap meat, cut into 1/4-inch thick strips', ingredient: ingredients[:flank]},
{quantity: '1/4', units: 'cup', preparation: 'divided', ingredient: ingredients[:soy_sauce]},
{quantity: '1/4', units: 'cup', preparation: 'divided', ingredient: ingredients[:shaoxing]},
{quantity: '2', units: 'teaspoons', preparation: '', ingredient: ingredients[:cornstarch]},
{quantity: '1/3', units: 'cup', preparation: '', ingredient: ingredients[:stock]},
{quantity: '1/4', units: 'cup', preparation: '', ingredient: ingredients[:oyster_sauce]},
{quantity: '1', units: 'tablespoon', preparation: '', ingredient: ingredients[:sugar]},
{quantity: '1', units: 'teaspoon', preparation: '', ingredient: ingredients[:seasame_oil]},
{quantity: '2', units: 'medium cloves', preparation: 'finely minced', ingredient: ingredients[:garlic]},
{quantity: '1', units: 'pound', preparation: 'flank steak, skirt steak, hanger steak, or flap meat, cut into 1/4-inch thick strips', food: ingredients[:flank]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: ingredients[:soy_sauce]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: ingredients[:shaoxing]},
{quantity: '2', units: 'teaspoons', preparation: '', food: ingredients[:cornstarch]},
{quantity: '1/3', units: 'cup', preparation: '', food: ingredients[:stock]},
{quantity: '1/4', units: 'cup', preparation: '', food: ingredients[:oyster_sauce]},
{quantity: '1', units: 'tablespoon', preparation: '', food: ingredients[:sugar]},
{quantity: '1', units: 'teaspoon', preparation: '', food: ingredients[:seasame_oil]},
{quantity: '2', units: 'medium cloves', preparation: 'finely minced', food: ingredients[:garlic]},
{quantity: '2', units: 'teaspoons', preparation: 'finely minced', name: 'ginger root'},
{quantity: '3', units: '', preparation: 'whites finely sliced, greens cut into 1/2-inch segments, reserved separately', name: 'Scallions'},
{quantity: '4', units: 'tablespoons', preparation: '', ingredient: ingredients[:peanut_oil]},
{quantity: '1', units: 'pound', preparation: '', ingredient: ingredients[:broccoli]},
{quantity: '4', units: 'tablespoons', preparation: '', food: ingredients[:peanut_oil]},
{quantity: '1', units: 'pound', preparation: '', food: ingredients[:broccoli]},
].each_with_index do |ri, i|
RecipeIngredient.create!({recipe: bb, sort_order: i}.merge(ri))
end

View File

@ -207,7 +207,7 @@ class UsdaImporter
sorted_files.each { |k, v| `rm #{v}` }
end
Ingredient.where('ndbn != ?', '').where('ndbn IS NOT NULL').each do |i|
Food.where('ndbn != ?', '').where('ndbn IS NOT NULL').each do |i|
i.set_usda_food(i.usda_food)
i.save!
end

View File

@ -1,6 +1,6 @@
FactoryBot.define do
factory :ingredient_unit do
ingredient
factory :food_unit do
food
name 'Each'
gram_weight 10.5
end

View File

@ -1,12 +1,12 @@
FactoryBot.define do
factory :ingredient do
name 'Ingredient'
factory :food do
name 'Food'
density nil
notes 'note note note'
user
end
factory :ingredient_with_density, parent: :ingredient do
factory :food_with_density, parent: :food do
name 'Butter'
density '8 oz/cup'
end

View File

@ -2,7 +2,7 @@ FactoryBot.define do
factory :recipe_ingredient do
sort_order 1
recipe
ingredient
food
end
end

View File

@ -1,11 +1,11 @@
require 'rails_helper'
RSpec.describe Ingredient, type: :model do
RSpec.describe Food, type: :model do
describe 'validation' do
it 'validates density' do
i = build(:ingredient)
i = build(:food)
expect(i).to be_valid
i.density = '5'
@ -25,7 +25,7 @@ RSpec.describe Ingredient, type: :model do
describe 'set_usda_food' do
it 'sets the density' do
i = build(:ingredient)
i = build(:food)
f = create(:salted_butter)
create(:usda_food_weight, usda_food: f)
@ -35,15 +35,15 @@ RSpec.describe Ingredient, type: :model do
end
end
describe '#custom_unit_weight' do
it 'returns a ValueUnit for valid custom weights' do
i = build(:ingredient)
i.ingredient_units << IngredientUnit.new(name: 'clove', gram_weight: 20.0)
describe '#custom_units' do
it 'returns a hash based on food_units' do
i = build(:food)
i.food_units << FoodUnit.new(name: 'clove', gram_weight: 20.0)
vu = i.custom_unit_weight('clove')
expect(vu).not_to be_nil
expect(vu.raw_value).to eq 20.0
expect(vu.unit.to_s).to eq 'gram'
units = i.custom_units
expect(units['clove']).to be_a UnitConversion::ValueUnit
expect(units['clove'].value.value).to eq 20.0
expect(units['clove'].unit.unit).to eq 'g'
end
end

View File

@ -1,6 +1,6 @@
require 'rails_helper'
RSpec.describe IngredientUnit, type: :model do
RSpec.describe FoodUnit, type: :model do
describe 'matches?' do
it 'matches empty units to each-like terms' do
@ -15,16 +15,16 @@ RSpec.describe IngredientUnit, type: :model do
]
each_terms.each do |t|
expect(create(:ingredient_unit, name: t).matches?('')).to eq(true), "expected #{t} to match"
expect(create(:food_unit, name: t).matches?('')).to eq(true), "expected #{t} to match"
end
end
it 'matches unit names' do
expect(create(:ingredient_unit, name: 'Clove').matches?('clove')).to eq true
expect(create(:food_unit, name: 'Clove').matches?('clove')).to eq true
end
it 'matches pluralized unit names' do
expect(create(:ingredient_unit, name: 'Clove').matches?('cloves')).to eq true
expect(create(:food_unit, name: 'Clove').matches?('cloves')).to eq true
end
end

View File

@ -0,0 +1,56 @@
require 'rails_helper'
RSpec.describe NutritionData, type: :model do
let(:rec1_ingredients) do
[
RecipeIngredient.new({
quantity: '100',
units: 'g',
sort_order: 1,
food: create(:food, kcal: 10, protein: 2)
}),
RecipeIngredient.new({
quantity: '100',
units: 'g',
sort_order: 2,
food: create(:food, kcal: 10, lipids: 2)
})
]
end
let(:recipe1) do
create(:recipe, yields: '500 g').tap do |r|
r.recipe_ingredients = rec1_ingredients
end
end
let(:rec2_ingredients) do
[
RecipeIngredient.new({
quantity: '100',
units: 'g',
sort_order: 1,
recipe_as_ingredient: recipe1
}),
RecipeIngredient.new({
quantity: '100',
units: 'g',
sort_order: 2,
food: create(:food, kcal: 10, lipids: 2)
})
]
end
let(:recipe2) do
create(:recipe, yields: '500 g').tap do |r|
r.recipe_ingredients = rec2_ingredients
end
end
it 'runs' do
n = recipe2.nutrition_data
end
end

View File

@ -5,16 +5,16 @@ RSpec.describe RecipeIngredient, type: :model do
describe 'to_mass' do
it 'converts volume ingredients with density' do
ri = RecipeIngredient.new(quantity: 2, units: 'tbsp', ingredient: create(:ingredient_with_density))
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
end
it 'converts ingredients with custom units' do
i = create(:ingredient_with_density)
i.ingredient_units << IngredientUnit.new(name: 'pat', gram_weight: 25)
ri = RecipeIngredient.new(quantity: 2, units: 'pat', ingredient: i)
i = create(:food_with_density)
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
@ -58,19 +58,19 @@ RSpec.describe RecipeIngredient, type: :model do
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', ingredient: create(:ingredient))
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', ingredient: create(:ingredient_with_density))
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups', food: create(:food_with_density))
expect(ri.can_convert_to_grams?).to be_truthy
end
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))
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
end
end