Added ingredient functionality
This commit is contained in:
parent
2ad4a22184
commit
16bc8b562b
@ -24,6 +24,28 @@
|
||||
@import "typeahead-bootstrap";
|
||||
@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;
|
||||
|
||||
html {
|
||||
|
@ -1,4 +1,5 @@
|
||||
|
||||
$brand-primary: darken(#93C54B, 10%);
|
||||
$brand-success: $brand-primary;
|
||||
$text-color: #3E3F3A;
|
||||
|
||||
|
@ -127,7 +127,7 @@ class IngredientsController < ApplicationController
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
def conversion_params
|
||||
|
@ -2,6 +2,9 @@ class Ingredient < ActiveRecord::Base
|
||||
include TokenizedLike
|
||||
|
||||
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 :density, density: true, allow_blank: true
|
||||
@ -18,6 +21,16 @@ class Ingredient < ActiveRecord::Base
|
||||
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)
|
||||
@usda_food = nil
|
||||
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
|
||||
|
||||
def to_mass
|
||||
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)
|
||||
value_unit = as_value_unit
|
||||
density = self.ingredient.density? ? UnitConversion.parse(ingredient.density) : nil
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def custom_unit?
|
||||
self.ingredient && self.ingredient.custom_unit_weight(self.units).present?
|
||||
end
|
||||
|
||||
def can_convert_to_grams?
|
||||
if self.quantity.present? && self.units.present?
|
||||
value_unit = UnitConversion.parse(self.quantity, self.units)
|
||||
value_unit.mass? || (value_unit.volume? && self.ingredient && self.ingredient.density.present?)
|
||||
else
|
||||
false
|
||||
end
|
||||
vu = as_value_unit
|
||||
vu.present? && (vu.mass? || (vu.volume? && self.ingredient && self.ingredient.density.present?))
|
||||
end
|
||||
|
||||
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.raw_value
|
||||
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
|
||||
|
@ -33,6 +33,47 @@
|
||||
<%= f.text_field :density, class: 'form-control', disabled: has_ndbn %>
|
||||
</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">
|
||||
<%= f.label :notes, class: 'control-label' %>
|
||||
<%= 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|
|
||||
# inflect.acronym 'RESTful'
|
||||
# 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.
|
||||
|
||||
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|
|
||||
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
|
||||
i = build(:ingredient)
|
||||
f = create(:salted_butter)
|
||||
create(:usda_food_weight, usda_food: f)
|
||||
|
||||
i.set_usda_food(f)
|
||||
|
||||
@ -34,4 +35,16 @@ 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)
|
||||
|
||||
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
|
||||
|
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
|
||||
|
||||
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
|
||||
it 'returns false if no quantity or unit' do
|
||||
ri = RecipeIngredient.new
|
||||
|
Loading…
Reference in New Issue
Block a user