diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 5fbeb62..abbdaba 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -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 {
diff --git a/app/assets/stylesheets/journal_custom_colors.scss b/app/assets/stylesheets/journal_custom_colors.scss
index ed9e16d..4196c72 100644
--- a/app/assets/stylesheets/journal_custom_colors.scss
+++ b/app/assets/stylesheets/journal_custom_colors.scss
@@ -1,4 +1,5 @@
$brand-primary: darken(#93C54B, 10%);
+$brand-success: $brand-primary;
$text-color: #3E3F3A;
diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb
index b646bd0..dde57e2 100644
--- a/app/controllers/ingredients_controller.rb
+++ b/app/controllers/ingredients_controller.rb
@@ -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
diff --git a/app/models/ingredient.rb b/app/models/ingredient.rb
index ffd5f85..217ec9c 100644
--- a/app/models/ingredient.rb
+++ b/app/models/ingredient.rb
@@ -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
diff --git a/app/models/ingredient_unit.rb b/app/models/ingredient_unit.rb
new file mode 100644
index 0000000..0a92278
--- /dev/null
+++ b/app/models/ingredient_unit.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/recipe_ingredient.rb b/app/models/recipe_ingredient.rb
index 2c9b7dc..0be47b2 100644
--- a/app/models/recipe_ingredient.rb
+++ b/app/models/recipe_ingredient.rb
@@ -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
diff --git a/app/views/ingredients/_form.html.erb b/app/views/ingredients/_form.html.erb
index cf84fca..af4f632 100644
--- a/app/views/ingredients/_form.html.erb
+++ b/app/views/ingredients/_form.html.erb
@@ -33,6 +33,47 @@
<%= f.text_field :density, class: 'form-control', disabled: has_ndbn %>
+
+
+
Ingredient Units
+
+
+
+ <%= 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' %>
+
+
+
+
+ <% if has_ndbn %>
+
+
+
+
NDBN Unit Weights
+
+
+
+
+
+ Name |
+ Grams |
+
+
+ <% @ingredient.usda_food.usda_food_weights.each do |w| %>
+
+ <%= "#{w.amount} #{w.description}" %> |
+ <%= w.gram_weight %> |
+
+ <% end %>
+
+
+
+
+ <% end %>
+
<%= f.label :notes, class: 'control-label' %>
<%= f.text_area :notes, class: 'form-control' %>
diff --git a/app/views/ingredients/_ingredient_unit_fields.html.erb b/app/views/ingredients/_ingredient_unit_fields.html.erb
new file mode 100644
index 0000000..804bbf8
--- /dev/null
+++ b/app/views/ingredients/_ingredient_unit_fields.html.erb
@@ -0,0 +1,22 @@
+
+
+
+ <%= f.label :name, class: 'control-label' %>
+ <%= f.text_field :name, class: 'form-control' %>
+
+
+
+ <%= f.label :gram_weight, 'Grams', class: 'control-label' %>
+ <%= f.text_field :gram_weight, class: 'form-control' %>
+
+
+
+
+
\ No newline at end of file
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index ac033bf..1950a5b 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -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
\ No newline at end of file
diff --git a/db/migrate/20160705181132_add_ingredient_units.rb b/db/migrate/20160705181132_add_ingredient_units.rb
new file mode 100644
index 0000000..43a66b6
--- /dev/null
+++ b/db/migrate/20160705181132_add_ingredient_units.rb
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index d309b08..45691c5 100644
--- a/db/schema.rb
+++ b/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"
diff --git a/spec/factories/ingredient_unit.rb b/spec/factories/ingredient_unit.rb
new file mode 100644
index 0000000..74fc05b
--- /dev/null
+++ b/spec/factories/ingredient_unit.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :ingredient_unit do
+ ingredient
+ name 'Each'
+ gram_weight 10.5
+ end
+
+end
diff --git a/spec/models/ingredient_spec.rb b/spec/models/ingredient_spec.rb
index e79a252..682a401 100644
--- a/spec/models/ingredient_spec.rb
+++ b/spec/models/ingredient_spec.rb
@@ -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
diff --git a/spec/models/ingredient_unit_spec.rb b/spec/models/ingredient_unit_spec.rb
new file mode 100644
index 0000000..c2b189c
--- /dev/null
+++ b/spec/models/ingredient_unit_spec.rb
@@ -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
\ No newline at end of file
diff --git a/spec/models/recipe_ingredient_spec.rb b/spec/models/recipe_ingredient_spec.rb
index a0c61cf..091d8d8 100644
--- a/spec/models/recipe_ingredient_spec.rb
+++ b/spec/models/recipe_ingredient_spec.rb
@@ -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