Added ingredient functionality
This commit is contained in:
parent
2ad4a22184
commit
16bc8b562b
@ -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 {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
$brand-primary: darken(#93C54B, 10%);
|
$brand-primary: darken(#93C54B, 10%);
|
||||||
|
$brand-success: $brand-primary;
|
||||||
$text-color: #3E3F3A;
|
$text-color: #3E3F3A;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
17
app/models/ingredient_unit.rb
Normal file
17
app/models/ingredient_unit.rb
Normal 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
|
@ -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
|
||||||
|
@ -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' %>
|
||||||
|
22
app/views/ingredients/_ingredient_unit_fields.html.erb
Normal file
22
app/views/ingredients/_ingredient_unit_fields.html.erb
Normal 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>
|
@ -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
|
9
db/migrate/20160705181132_add_ingredient_units.rb
Normal file
9
db/migrate/20160705181132_add_ingredient_units.rb
Normal 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
|
10
db/schema.rb
10
db/schema.rb
@ -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"
|
||||||
|
8
spec/factories/ingredient_unit.rb
Normal file
8
spec/factories/ingredient_unit.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FactoryGirl.define do
|
||||||
|
factory :ingredient_unit do
|
||||||
|
ingredient
|
||||||
|
name 'Each'
|
||||||
|
gram_weight 10.5
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -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
|
||||||
|
31
spec/models/ingredient_unit_spec.rb
Normal file
31
spec/models/ingredient_unit_spec.rb
Normal 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
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user