Added ingredient functionality

This commit is contained in:
Dan Elbert 2016-07-05 16:31:36 -05:00
parent 2ad4a22184
commit 16bc8b562b
15 changed files with 241 additions and 16 deletions

View File

@ -24,6 +24,28 @@
@import "typeahead-bootstrap"; @import "typeahead-bootstrap";
@import "recipes"; @import "recipes";
// Skin overrides
.has-error {
.help-block,
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline,
&.radio label,
&.checkbox label,
&.radio-inline label,
&.checkbox-inline label,
.form-control-feedback {
color: $state-danger-text;
}
.form-control,
.form-control:focus {
border-color: $state-danger-border;
}
}
$footer_height: 40px; $footer_height: 40px;
html { html {

View File

@ -1,4 +1,5 @@
$brand-primary: darken(#93C54B, 10%); $brand-primary: darken(#93C54B, 10%);
$brand-success: $brand-primary;
$text-color: #3E3F3A; $text-color: #3E3F3A;

View File

@ -127,7 +127,7 @@ class IngredientsController < ApplicationController
# Never trust parameters from the scary internet, only allow the white list through. # Never trust parameters from the scary internet, only allow the white list through.
def ingredient_params def ingredient_params
params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar) params.require(:ingredient).permit(:name, :notes, :ndbn, :density, :water, :protein, :lipids, :carbohydrates, :kcal, :fiber, :sugar, :calcium, :sodium, :vit_k, :ingredient_units_attributes => [:name, :gram_weight, :id, :_destroy])
end end
def conversion_params def conversion_params

View File

@ -2,6 +2,9 @@ class Ingredient < ActiveRecord::Base
include TokenizedLike include TokenizedLike
belongs_to :user belongs_to :user
has_many :ingredient_units, inverse_of: :ingredient, dependent: :destroy
accepts_nested_attributes_for :ingredient_units, allow_destroy: true
validates :name, presence: true validates :name, presence: true
validates :density, density: true, allow_blank: true validates :density, density: true, allow_blank: true
@ -18,6 +21,16 @@ class Ingredient < ActiveRecord::Base
end end
end end
def custom_unit_weight(unit)
ingredient_units.each do |iu|
if iu.matches?(unit)
return UnitConversion::parse(iu.gram_weight, 'grams')
end
end
nil
end
def ndbn=(value) def ndbn=(value)
@usda_food = nil @usda_food = nil
super super

View File

@ -0,0 +1,17 @@
class IngredientUnit < ActiveRecord::Base
belongs_to :ingredient, inverse_of: :ingredient_units
validates :name, presence: true
validates :gram_weight, presence: true
def matches?(unit)
case
when unit.empty? && ['each', 'ech', 'item', 'per'].include?(self.name.downcase)
true
when unit.downcase.singularize == self.name.downcase
true
else
false
end
end
end

View File

@ -67,32 +67,47 @@ class RecipeIngredient < ActiveRecord::Base
end end
def to_mass def to_mass
return unless self.quantity.present? value_unit = as_value_unit
if ingredient && ingredient.density density = self.ingredient.density? ? UnitConversion.parse(ingredient.density) : nil
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.quantity = value_unit.pretty_value
self.units = value_unit.unit.to_s self.units = value_unit.unit.to_s
end end
end end
def custom_unit?
self.ingredient && self.ingredient.custom_unit_weight(self.units).present?
end end
def can_convert_to_grams? def can_convert_to_grams?
if self.quantity.present? && self.units.present? vu = as_value_unit
value_unit = UnitConversion.parse(self.quantity, self.units) vu.present? && (vu.mass? || (vu.volume? && self.ingredient && self.ingredient.density.present?))
value_unit.mass? || (value_unit.volume? && self.ingredient && self.ingredient.density.present?)
else
false
end
end end
def to_grams def to_grams
value_unit = UnitConversion.parse(self.quantity, self.units) 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 ? self.ingredient.density : nil)
gram_unit.raw_value gram_unit.raw_value
end end
def as_value_unit
case
when self.quantity.blank?
nil
when self.custom_unit?
self.ingredient.custom_unit_weight(self.units).scale(self.quantity)
when self.units.present?
UnitConversion.parse(self.quantity, self.units)
else
nil
end
end
end end

View File

@ -33,6 +33,47 @@
<%= f.text_field :density, class: 'form-control', disabled: has_ndbn %> <%= f.text_field :density, class: 'form-control', disabled: has_ndbn %>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Ingredient Units</h3>
</div>
<div class="panel-body">
<div class="ingredient-unit-list">
<%= f.fields_for :ingredient_units do |iu_form| %>
<%= render partial: 'ingredients/ingredient_unit_fields', locals: {f: iu_form } %>
<% end %>
<%= link_to_add_association 'add unit', f, :ingredient_units, class: 'btn btn-primary' %>
</div>
</div>
</div>
<% if has_ndbn %>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">NDBN Unit Weights</h3>
</div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Grams</th>
</tr>
</thead>
<% @ingredient.usda_food.usda_food_weights.each do |w| %>
<tr>
<td><%= "#{w.amount} #{w.description}" %></td>
<td><%= w.gram_weight %></td>
</tr>
<% end %>
</table>
</div>
</div>
<% end %>
<div class="form-group"> <div class="form-group">
<%= f.label :notes, class: 'control-label' %> <%= f.label :notes, class: 'control-label' %>
<%= f.text_area :notes, class: 'form-control' %> <%= f.text_area :notes, class: 'form-control' %>

View File

@ -0,0 +1,22 @@
<div class="nested-fields row">
<div class="form-group col-xs-5 col-sm-6">
<%= f.label :name, class: 'control-label' %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group col-xs-4 col-sm-5">
<%= f.label :gram_weight, 'Grams', class: 'control-label' %>
<%= f.text_field :gram_weight, class: 'form-control' %>
</div>
<div class="form-group col-xs-3 col-sm-1">
<label class="control-label"></label>
<p class="form-control-static">
<%= link_to_remove_association f, class: 'btn btn-danger btn-sm' do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
</p>
</div>
</div>

View File

@ -14,3 +14,7 @@
# ActiveSupport::Inflector.inflections(:en) do |inflect| # ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful' # inflect.acronym 'RESTful'
# end # end
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.irregular 'clove', 'cloves'
end

View File

@ -0,0 +1,9 @@
class AddIngredientUnits < ActiveRecord::Migration
def change
create_table :ingredient_units do |t|
t.integer :ingredient_id, null: false, index: true
t.string :name, null: false
t.decimal :gram_weight, precision: 10, scale: 2, null: false
end
end
end

View File

@ -11,7 +11,15 @@
# #
# 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: 20160622180838) do ActiveRecord::Schema.define(version: 20160705181132) do
create_table "ingredient_units", force: :cascade do |t|
t.integer "ingredient_id", null: false
t.string "name", null: false
t.decimal "gram_weight", precision: 10, scale: 2, null: false
end
add_index "ingredient_units", ["ingredient_id"], name: "index_ingredient_units_on_ingredient_id"
create_table "ingredients", force: :cascade do |t| create_table "ingredients", force: :cascade do |t|
t.string "name" t.string "name"

View File

@ -0,0 +1,8 @@
FactoryGirl.define do
factory :ingredient_unit do
ingredient
name 'Each'
gram_weight 10.5
end
end

View File

@ -27,6 +27,7 @@ RSpec.describe Ingredient, type: :model do
it 'sets the density' do it 'sets the density' do
i = build(:ingredient) i = build(:ingredient)
f = create(:salted_butter) f = create(:salted_butter)
create(:usda_food_weight, usda_food: f)
i.set_usda_food(f) i.set_usda_food(f)
@ -34,4 +35,16 @@ RSpec.describe Ingredient, type: :model do
end end
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)
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'
end
end
end end

View File

@ -0,0 +1,31 @@
require 'rails_helper'
RSpec.describe IngredientUnit, type: :model do
describe 'matches?' do
it 'matches empty units to each-like terms' do
each_terms = [
'Each',
'each',
'ech',
'item',
'per',
'PER',
'Per'
]
each_terms.each do |t|
expect(create(:ingredient_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
end
it 'matches pluralized unit names' do
expect(create(:ingredient_unit, name: 'Clove').matches?('cloves')).to eq true
end
end
end

View File

@ -2,6 +2,27 @@ require 'rails_helper'
RSpec.describe RecipeIngredient, type: :model do 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))
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)
ri.to_mass
vu = ri.as_value_unit
expect(vu.raw_value).to eq 50
expect(vu.unit.to_s).to eq 'gram'
end
end
describe '#can_convert_to_grams' do describe '#can_convert_to_grams' 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