This commit is contained in:
Dan Elbert 2018-08-27 16:44:45 -05:00
parent a2f2a05679
commit 021c066cf4
8 changed files with 212 additions and 53 deletions

View File

@ -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

View File

@ -69,6 +69,14 @@ class Recipe < ApplicationRecord
self self
end 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 def tag_names
self.tags.map { |t| t.name } self.tags.map { |t| t.name }
end end
@ -90,13 +98,6 @@ class Recipe < ApplicationRecord
@nutrition_data @nutrition_data
end end
def parsed_yield
if @parsed_yield.nil? && self.yields.present?
@parsed_yield = YieldParser.parse(self.yields)
end
@parsed_yield
end
def update_rating! def update_rating!
self.rating = Log.for_recipe(self).for_user(self.user_id).where('rating IS NOT NULL').average(:rating) self.rating = Log.for_recipe(self).for_user(self.user_id).where('rating IS NOT NULL').average(:rating)
save(validate: false) save(validate: false)

View File

@ -1,13 +1,14 @@
class RecipeIngredient < ApplicationRecord class RecipeIngredient < ApplicationRecord
belongs_to :ingredient, optional: true belongs_to :ingredient, optional: true
belongs_to :recipe_as_ingredient, class_name: 'Recipe', optional: true
belongs_to :recipe, inverse_of: :recipe_ingredients, touch: true belongs_to :recipe, inverse_of: :recipe_ingredients, touch: true
validates :sort_order, presence: true validates :sort_order, presence: true
def name def name
if self.ingredient_id.present? if self.ingredient_detail.present?
self.ingredient.name self.ingredient_detail.name
else else
super super
end end
@ -19,6 +20,45 @@ class RecipeIngredient < ApplicationRecord
str str
end 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) def scale(factor, auto_unit = false)
if factor.present? && self.quantity.present? && factor != '1' if factor.present? && self.quantity.present? && factor != '1'
@ -54,7 +94,7 @@ class RecipeIngredient < ApplicationRecord
def to_volume def to_volume
return unless self.quantity.present? return unless self.quantity.present?
if ingredient && ingredient.density if ingredient_detail && ingredient_detail.density?
density = UnitConversion.parse(ingredient.density) density = UnitConversion.parse(ingredient.density)
if density.density? if density.density?
value_unit = UnitConversion.parse(self.quantity, self.units) value_unit = UnitConversion.parse(self.quantity, self.units)
@ -67,9 +107,9 @@ class RecipeIngredient < ApplicationRecord
end end
def to_mass def to_mass
if ingredient if ingredient_detail
value_unit = as_value_unit 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 case
when value_unit.nil? when value_unit.nil?
@ -85,17 +125,17 @@ class RecipeIngredient < ApplicationRecord
end end
def custom_unit? 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 end
def can_convert_to_grams? def can_convert_to_grams?
vu = as_value_unit 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 end
def to_grams def to_grams
value_unit = as_value_unit 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 gram_unit.raw_value
end end
@ -104,7 +144,7 @@ class RecipeIngredient < ApplicationRecord
when self.quantity.blank? when self.quantity.blank?
nil nil
when self.custom_unit? 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? when self.units.present?
UnitConversion.parse(self.quantity, self.units) UnitConversion.parse(self.quantity, self.units)
else else
@ -115,6 +155,7 @@ class RecipeIngredient < ApplicationRecord
def log_copy def log_copy
copy = RecipeIngredient.new copy = RecipeIngredient.new
copy.ingredient = self.ingredient copy.ingredient = self.ingredient
copy.recipe_as_ingredient = self.recipe_as_ingredient
copy.name = self.name copy.name = self.name
copy.sort_order = self.sort_order copy.sort_order = self.sort_order
copy.quantity = self.quantity copy.quantity = self.quantity

View File

@ -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

View File

@ -7,13 +7,15 @@ json.rendered_steps MarkdownProcessor.render(recipe.step_text)
json.tags recipe.tag_names json.tags recipe.tag_names
json.ingredients recipe.recipe_ingredients do |ri| 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 json.ingredient_detail do
if ri.ingredient.nil? if ri.ingredient.nil? && ri.ingredient_as_recipe.nil?
json.null! json.null!
elsif ri.ingredient
json.extract! ri.ingredient, :name, :density, :notes
else else
json.extract! ri.ingredient, :id, :name, :density, :notes json.extract! ri.recipe_as_ingredient, :name
end end
end end

View File

@ -0,0 +1,5 @@
class AddRecipeAsIngredientId < ActiveRecord::Migration[5.2]
def change
add_column :recipe_ingredients, :recipe_as_ingredient_id, :integer
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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| create_table "ingredient_units", force: :cascade do |t|
t.integer "ingredient_id", null: false 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 "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.text "preparation" t.text "preparation"
t.integer "recipe_as_ingredient_id"
t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id" t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id"
end end

View File

@ -24,6 +24,7 @@ RSpec.describe RecipeIngredient, type: :model do
end end
describe '#can_convert_to_grams' do describe '#can_convert_to_grams' do
describe 'with no ingredient detail' do
it 'returns false if no quantity or unit' do it 'returns false if no quantity or unit' do
ri = RecipeIngredient.new ri = RecipeIngredient.new
expect(ri.can_convert_to_grams?).to be_falsey expect(ri.can_convert_to_grams?).to be_falsey
@ -49,20 +50,32 @@ RSpec.describe RecipeIngredient, type: :model do
expect(ri.can_convert_to_grams?).to be_truthy expect(ri.can_convert_to_grams?).to be_truthy
end end
it 'returns false if unit is volume and there is no ingredient' do it 'returns false if unit is volume' do
ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups') ri = RecipeIngredient.new(quantity: '5 1/2', units: 'cups')
expect(ri.can_convert_to_grams?).to be_falsey expect(ri.can_convert_to_grams?).to be_falsey
end end
end
describe 'with ingredient' do
it 'returns false if unit is volume and ingredient has no density' 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', ingredient: create(:ingredient))
expect(ri.can_convert_to_grams?).to be_falsey expect(ri.can_convert_to_grams?).to be_falsey
end end
it 'returns false if unit is volume and ingredient has density' do 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', ingredient: create(:ingredient_with_density))
expect(ri.can_convert_to_grams?).to be_truthy expect(ri.can_convert_to_grams?).to be_truthy
end end
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))
expect(ri.can_convert_to_grams?).to be_truthy
end
end
end
end end