From 021c066cf44fec2ece81ef9650464c48eb00b25f Mon Sep 17 00:00:00 2001 From: Dan Elbert Date: Mon, 27 Aug 2018 16:44:45 -0500 Subject: [PATCH] rai --- app/models/ingredient_proxy.rb | 25 ++++++ app/models/recipe.rb | 15 ++-- app/models/recipe_ingredient.rb | 59 +++++++++++--- app/models/recipe_proxy.rb | 71 +++++++++++++++++ app/views/recipes/_recipe.json.jbuilder | 10 ++- ...80823174505_add_recipe_as_ingredient_id.rb | 5 ++ db/schema.rb | 3 +- spec/models/recipe_ingredient_spec.rb | 77 +++++++++++-------- 8 files changed, 212 insertions(+), 53 deletions(-) create mode 100644 app/models/ingredient_proxy.rb create mode 100644 app/models/recipe_proxy.rb create mode 100644 db/migrate/20180823174505_add_recipe_as_ingredient_id.rb diff --git a/app/models/ingredient_proxy.rb b/app/models/ingredient_proxy.rb new file mode 100644 index 0000000..94c9c60 --- /dev/null +++ b/app/models/ingredient_proxy.rb @@ -0,0 +1,25 @@ +class IngredientProxy + + attr_reader :ingredient + + def initialize(ingredient) + @ingredient = ingredient + end + + def name + @ingredient.name + end + + def density + @ingredient.density + end + + def density? + @ingredient.density.present? + end + + def get_custom_unit_equivalent(custom_unit_name) + @ingredient.custom_unit_weight(custom_unit_name) + end + +end \ No newline at end of file diff --git a/app/models/recipe.rb b/app/models/recipe.rb index b93bb5f..22db7f0 100644 --- a/app/models/recipe.rb +++ b/app/models/recipe.rb @@ -69,6 +69,14 @@ class Recipe < ApplicationRecord self end + def yields_list + if self.yields.present? + self.yields.split(',').map { |y| y.strip }.select { |y| y.present? } + else + [] + end + end + def tag_names self.tags.map { |t| t.name } end @@ -90,13 +98,6 @@ class Recipe < ApplicationRecord @nutrition_data end - def parsed_yield - if @parsed_yield.nil? && self.yields.present? - @parsed_yield = YieldParser.parse(self.yields) - end - @parsed_yield - 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) diff --git a/app/models/recipe_ingredient.rb b/app/models/recipe_ingredient.rb index d5bb4dc..d0d34ec 100644 --- a/app/models/recipe_ingredient.rb +++ b/app/models/recipe_ingredient.rb @@ -1,13 +1,14 @@ class RecipeIngredient < ApplicationRecord belongs_to :ingredient, 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_id.present? - self.ingredient.name + if self.ingredient_detail.present? + self.ingredient_detail.name else super end @@ -19,6 +20,45 @@ class RecipeIngredient < ApplicationRecord str end + def ingredient_detail_id + case + when recipe_as_ingredient_id + "R#{recipe_as_ingredient_id}" + when ingredient_id + "I#{ingredient_id}" + else + nil + end + end + + def ingredient_detail_id=(val) + @recipe_detail = nil + case val + when -> (v) { v.blank? } + self.recipe_as_ingredient_id = nil + self.ingredient_id = nil + when /^R(\d+)$/ + self.ingredient_id = nil + self.recipe_as_ingredient_id = $1.to_i + when /^I(\d+)$/ + self.recipe_as_ingredient_id = nil + self.ingredient_id = $1.to_i + else + raise "Invalid ingredient_detail_id: #{val}" + end + end + + def ingredient_detail + @recipe_detail ||= case + when self.recipe_as_ingredient_id + RecipeProxy.new(self.recipe_as_ingredient) + when self.ingredient_id + IngredientProxy.new(self.ingredient) + else + nil + end + end + def scale(factor, auto_unit = false) if factor.present? && self.quantity.present? && factor != '1' @@ -54,7 +94,7 @@ class RecipeIngredient < ApplicationRecord def to_volume return unless self.quantity.present? - if ingredient && ingredient.density + if ingredient_detail && ingredient_detail.density? density = UnitConversion.parse(ingredient.density) if density.density? value_unit = UnitConversion.parse(self.quantity, self.units) @@ -67,9 +107,9 @@ class RecipeIngredient < ApplicationRecord end def to_mass - if ingredient + if ingredient_detail value_unit = as_value_unit - density = self.ingredient.density? ? UnitConversion.parse(ingredient.density) : nil + density = self.ingredient_detail.density? ? UnitConversion.parse(ingredient_detail.density) : nil case when value_unit.nil? @@ -85,17 +125,17 @@ class RecipeIngredient < ApplicationRecord end def custom_unit? - self.ingredient && self.ingredient.custom_unit_weight(self.units).present? + self.ingredient_detail && self.ingredient_detail.get_custom_unit_equivalent(self.units).present? end def can_convert_to_grams? vu = as_value_unit - vu.present? && (vu.mass? || (vu.volume? && self.ingredient && self.ingredient.density.present?)) + vu.present? && (vu.mass? || (vu.volume? && self.ingredient_detail && self.ingredient_detail.density.present?)) end def to_grams value_unit = as_value_unit - gram_unit = value_unit.convert('g', self.ingredient ? self.ingredient.density : nil) + gram_unit = value_unit.convert('g', self.ingredient_detail ? self.ingredient_detail.density : nil) gram_unit.raw_value end @@ -104,7 +144,7 @@ class RecipeIngredient < ApplicationRecord when self.quantity.blank? nil when self.custom_unit? - self.ingredient.custom_unit_weight(self.units).scale(self.quantity) + self.ingredient_detail.get_custom_unit_equivalent(self.units).scale(self.quantity) when self.units.present? UnitConversion.parse(self.quantity, self.units) else @@ -115,6 +155,7 @@ class RecipeIngredient < ApplicationRecord def log_copy copy = RecipeIngredient.new copy.ingredient = self.ingredient + copy.recipe_as_ingredient = self.recipe_as_ingredient copy.name = self.name copy.sort_order = self.sort_order copy.quantity = self.quantity diff --git a/app/models/recipe_proxy.rb b/app/models/recipe_proxy.rb new file mode 100644 index 0000000..72db01d --- /dev/null +++ b/app/models/recipe_proxy.rb @@ -0,0 +1,71 @@ +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 \ No newline at end of file diff --git a/app/views/recipes/_recipe.json.jbuilder b/app/views/recipes/_recipe.json.jbuilder index 0db38e0..4a4351e 100644 --- a/app/views/recipes/_recipe.json.jbuilder +++ b/app/views/recipes/_recipe.json.jbuilder @@ -7,13 +7,15 @@ json.rendered_steps MarkdownProcessor.render(recipe.step_text) json.tags recipe.tag_names json.ingredients recipe.recipe_ingredients do |ri| - json.extract! ri, :id, :ingredient_id, :display_name, :name, :quantity, :units, :preparation, :sort_order + json.extract! ri, :id, :ingredient_detail_id, :display_name, :name, :quantity, :units, :preparation, :sort_order - json.ingredient do - if ri.ingredient.nil? + json.ingredient_detail do + if ri.ingredient.nil? && ri.ingredient_as_recipe.nil? json.null! + elsif ri.ingredient + json.extract! ri.ingredient, :name, :density, :notes else - json.extract! ri.ingredient, :id, :name, :density, :notes + json.extract! ri.recipe_as_ingredient, :name end end diff --git a/db/migrate/20180823174505_add_recipe_as_ingredient_id.rb b/db/migrate/20180823174505_add_recipe_as_ingredient_id.rb new file mode 100644 index 0000000..cecab79 --- /dev/null +++ b/db/migrate/20180823174505_add_recipe_as_ingredient_id.rb @@ -0,0 +1,5 @@ +class AddRecipeAsIngredientId < ActiveRecord::Migration[5.2] + def change + add_column :recipe_ingredients, :recipe_as_ingredient_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 84ae75c..d0985c8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2017_04_14_233856) do +ActiveRecord::Schema.define(version: 2018_08_23_174505) do create_table "ingredient_units", force: :cascade do |t| t.integer "ingredient_id", null: false @@ -83,6 +83,7 @@ ActiveRecord::Schema.define(version: 2017_04_14_233856) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "preparation" + t.integer "recipe_as_ingredient_id" t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id" end diff --git a/spec/models/recipe_ingredient_spec.rb b/spec/models/recipe_ingredient_spec.rb index 091d8d8..9c2d599 100644 --- a/spec/models/recipe_ingredient_spec.rb +++ b/spec/models/recipe_ingredient_spec.rb @@ -24,45 +24,58 @@ RSpec.describe RecipeIngredient, type: :model do end describe '#can_convert_to_grams' do - it 'returns false if no quantity or unit' do - ri = RecipeIngredient.new - expect(ri.can_convert_to_grams?).to be_falsey + 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 + + it 'returns false if no quantity' do + ri = RecipeIngredient.new(units: 'lbs') + expect(ri.can_convert_to_grams?).to be_falsey + end + + 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 - it 'returns false if no quantity' do - ri = RecipeIngredient.new(units: 'lbs') - expect(ri.can_convert_to_grams?).to be_falsey + 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)) + 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)) + expect(ri.can_convert_to_grams?).to be_truthy + end end - it 'returns false if no units' do - ri = RecipeIngredient.new(quantity: '5 1/2') - expect(ri.can_convert_to_grams?).to be_falsey + 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)) + expect(ri.can_convert_to_grams?).to be_truthy + end 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 and there is no ingredient' do - ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups') - expect(ri.can_convert_to_grams?).to be_falsey - end - - 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)) - expect(ri.can_convert_to_grams?).to be_falsey - end - - it 'returns false if unit is volume and ingredient has density' do - ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups', ingredient: create(:ingredient_with_density)) - expect(ri.can_convert_to_grams?).to be_truthy - end end end