diff --git a/app/controllers/calculator_controller.rb b/app/controllers/calculator_controller.rb index 9c2cad8..e0fe0b4 100644 --- a/app/controllers/calculator_controller.rb +++ b/app/controllers/calculator_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/foods_controller.rb b/app/controllers/foods_controller.rb new file mode 100644 index 0000000..0cbce39 --- /dev/null +++ b/app/controllers/foods_controller.rb @@ -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 diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb deleted file mode 100644 index e98aeaf..0000000 --- a/app/controllers/ingredients_controller.rb +++ /dev/null @@ -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 diff --git a/app/helpers/foods_helper.rb b/app/helpers/foods_helper.rb new file mode 100644 index 0000000..17e47d8 --- /dev/null +++ b/app/helpers/foods_helper.rb @@ -0,0 +1,4 @@ +module FoodsHelper + + +end diff --git a/app/helpers/ingredients_helper.rb b/app/helpers/ingredients_helper.rb deleted file mode 100644 index 58448ab..0000000 --- a/app/helpers/ingredients_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -module IngredientsHelper - - -end diff --git a/app/models/concerns/ingredient.rb b/app/models/concerns/ingredient.rb new file mode 100644 index 0000000..b226451 --- /dev/null +++ b/app/models/concerns/ingredient.rb @@ -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 \ No newline at end of file diff --git a/app/models/conversion.rb b/app/models/conversion.rb index 29c1cd1..2fa4482 100644 --- a/app/models/conversion.rb +++ b/app/models/conversion.rb @@ -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) diff --git a/app/models/ingredient.rb b/app/models/food.rb similarity index 72% rename from app/models/ingredient.rb rename to app/models/food.rb index e22b520..b0dfb4f 100644 --- a/app/models/ingredient.rb +++ b/app/models/food.rb @@ -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') - end + 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) diff --git a/app/models/ingredient_unit.rb b/app/models/food_unit.rb similarity index 76% rename from app/models/ingredient_unit.rb rename to app/models/food_unit.rb index 6691667..b7a6a16 100644 --- a/app/models/ingredient_unit.rb +++ b/app/models/food_unit.rb @@ -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 diff --git a/app/models/ingredient_proxy.rb b/app/models/ingredient_proxy.rb index 94c9c60..f5a1dcd 100644 --- a/app/models/ingredient_proxy.rb +++ b/app/models/ingredient_proxy.rb @@ -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 \ No newline at end of file diff --git a/app/models/nutrition_data.rb b/app/models/nutrition_data.rb index 3de8f5b..237fbc5 100644 --- a/app/models/nutrition_data.rb +++ b/app/models/nutrition_data.rb @@ -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 \ No newline at end of file diff --git a/app/models/recipe.rb b/app/models/recipe.rb index 651aeaf..eea7eb6 100644 --- a/app/models/recipe.rb +++ b/app/models/recipe.rb @@ -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) diff --git a/app/models/recipe_ingredient.rb b/app/models/recipe_ingredient.rb index d0d34ec..e991016 100644 --- a/app/models/recipe_ingredient.rb +++ b/app/models/recipe_ingredient.rb @@ -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 + 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) - 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? - value_unit = value_unit.to_mass(density) - self.quantity = value_unit.pretty_value - self.units = value_unit.unit.to_s + 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 diff --git a/app/models/user.rb b/app/models/user.rb index 4838cd2..ca60665 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/views/calculator/ingredient_search.json.jbuilder b/app/views/calculator/ingredient_search.json.jbuilder index d2b083f..f3f5124 100644 --- a/app/views/calculator/ingredient_search.json.jbuilder +++ b/app/views/calculator/ingredient_search.json.jbuilder @@ -1,5 +1,5 @@ -json.array! @ingredients do |i| +json.array! @foods do |i| json.extract! i, :id, :name, :density diff --git a/app/views/ingredients/convert.json.jbuilder b/app/views/foods/convert.json.jbuilder similarity index 100% rename from app/views/ingredients/convert.json.jbuilder rename to app/views/foods/convert.json.jbuilder diff --git a/app/views/ingredients/index.json.jbuilder b/app/views/foods/index.json.jbuilder similarity index 60% rename from app/views/ingredients/index.json.jbuilder rename to app/views/foods/index.json.jbuilder index 266b0b5..c5beccb 100644 --- a/app/views/ingredients/index.json.jbuilder +++ b/app/views/foods/index.json.jbuilder @@ -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? diff --git a/app/views/ingredients/search.json.jbuilder b/app/views/foods/search.json.jbuilder similarity index 62% rename from app/views/ingredients/search.json.jbuilder rename to app/views/foods/search.json.jbuilder index 9a42810..5eb7fb8 100644 --- a/app/views/ingredients/search.json.jbuilder +++ b/app/views/foods/search.json.jbuilder @@ -1,5 +1,5 @@ -json.array! @ingredients do |i| +json.array! @foods do |i| json.extract! i, :id, :name, :density, :notes diff --git a/app/views/ingredients/show.json.jbuilder b/app/views/foods/show.json.jbuilder similarity index 83% rename from app/views/ingredients/show.json.jbuilder rename to app/views/foods/show.json.jbuilder index 6dcaf6f..6667552 100644 --- a/app/views/ingredients/show.json.jbuilder +++ b/app/views/foods/show.json.jbuilder @@ -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 \ No newline at end of file diff --git a/app/views/ingredients/usda_food_search.json.jbuilder b/app/views/foods/usda_food_search.json.jbuilder similarity index 100% rename from app/views/ingredients/usda_food_search.json.jbuilder rename to app/views/foods/usda_food_search.json.jbuilder diff --git a/app/views/recipes/_recipe.json.jbuilder b/app/views/recipes/_recipe.json.jbuilder index 4a4351e..be04ac8 100644 --- a/app/views/recipes/_recipe.json.jbuilder +++ b/app/views/recipes/_recipe.json.jbuilder @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 773d14b..57e0365 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20180910235229_rename_ingredient.rb b/db/migrate/20180910235229_rename_ingredient.rb new file mode 100644 index 0000000..3b3aaac --- /dev/null +++ b/db/migrate/20180910235229_rename_ingredient.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 98cb2e9..d4a2d4f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/db/seeds.rb b/db/seeds.rb index cb0d9d1..ef03f4d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -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 diff --git a/lib/usda_importer.rb b/lib/usda_importer.rb index eb76e77..2a01cbb 100644 --- a/lib/usda_importer.rb +++ b/lib/usda_importer.rb @@ -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 diff --git a/spec/factories/ingredient_units.rb b/spec/factories/food_units.rb similarity index 60% rename from spec/factories/ingredient_units.rb rename to spec/factories/food_units.rb index c8ba875..aa703a7 100644 --- a/spec/factories/ingredient_units.rb +++ b/spec/factories/food_units.rb @@ -1,6 +1,6 @@ FactoryBot.define do - factory :ingredient_unit do - ingredient + factory :food_unit do + food name 'Each' gram_weight 10.5 end diff --git a/spec/factories/ingredients.rb b/spec/factories/foods.rb similarity index 55% rename from spec/factories/ingredients.rb rename to spec/factories/foods.rb index 4b6dc2a..1928daf 100644 --- a/spec/factories/ingredients.rb +++ b/spec/factories/foods.rb @@ -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 diff --git a/spec/factories/recipe_ingredients.rb b/spec/factories/recipe_ingredients.rb index c1fa213..e03e230 100644 --- a/spec/factories/recipe_ingredients.rb +++ b/spec/factories/recipe_ingredients.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :recipe_ingredient do sort_order 1 recipe - ingredient + food end end diff --git a/spec/models/ingredient_spec.rb b/spec/models/food_spec.rb similarity index 57% rename from spec/models/ingredient_spec.rb rename to spec/models/food_spec.rb index 682a401..90f938c 100644 --- a/spec/models/ingredient_spec.rb +++ b/spec/models/food_spec.rb @@ -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 diff --git a/spec/models/ingredient_unit_spec.rb b/spec/models/food_unit_spec.rb similarity index 55% rename from spec/models/ingredient_unit_spec.rb rename to spec/models/food_unit_spec.rb index c2b189c..13e40e3 100644 --- a/spec/models/ingredient_unit_spec.rb +++ b/spec/models/food_unit_spec.rb @@ -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 diff --git a/spec/models/nutrition_data_spec.rb b/spec/models/nutrition_data_spec.rb new file mode 100644 index 0000000..74f4b38 --- /dev/null +++ b/spec/models/nutrition_data_spec.rb @@ -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 diff --git a/spec/models/recipe_ingredient_spec.rb b/spec/models/recipe_ingredient_spec.rb index 9c2d599..d5f5e52 100644 --- a/spec/models/recipe_ingredient_spec.rb +++ b/spec/models/recipe_ingredient_spec.rb @@ -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