This commit is contained in:
Dan Elbert 2018-09-12 17:17:15 -05:00
parent ffed63e0b3
commit 0c4c5b899b
17 changed files with 153 additions and 111 deletions

View File

@ -7,20 +7,27 @@ class CalculatorController < ApplicationController
def calculate def calculate
input = params[:input] input = params[:input]
output_unit = params[:output_unit] output_unit = params[:output_unit]
ingredient_id = params[:ingredient_id]
ingredient = nil
density = params[:density] density = params[:density]
density = nil unless density.present? density = nil unless density.present?
if ingredient_id.present?
ingredient = Ingredient.find_by_ingredient_id(ingredient_id)
end
data = {errors: [], output: ''} data = {errors: [], output: ''}
begin begin
unit = UnitConversion.parse(input) UnitConversion::with_custom_units(ingredient ? ingredient.custom_units : []) do
if output_unit.present? unit = UnitConversion.parse(input)
unit = unit.convert(output_unit, density) if output_unit.present?
data[:output] = unit.to_s unit = unit.convert(output_unit, density)
else data[:output] = unit.to_s
data[:output] = unit.auto_unit.to_s else
data[:output] = unit.auto_unit.to_s
end
end end
rescue UnitConversion::UnparseableUnitError => e rescue UnitConversion::UnparseableUnitError => e
data[:errors] << e.message data[:errors] << e.message
end end

View File

@ -61,12 +61,12 @@ class LogsController < ApplicationController
private private
def set_log def set_log
@log = Log.includes({recipe: {recipe_ingredients: {ingredient: :ingredient_units} }}).find(params[:id]) @log = Log.includes({recipe: {recipe_ingredients: {food: :food_units} }}).find(params[:id])
end end
def set_recipe def set_recipe
if params[:recipe_id].present? if params[:recipe_id].present?
@recipe = Recipe.includes([{recipe_ingredients: [:ingredient]}]).find(params[:recipe_id]) @recipe = Recipe.includes([{recipe_ingredients: [:food]}]).find(params[:recipe_id])
end end
end end

View File

@ -81,7 +81,7 @@ class RecipesController < ApplicationController
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_recipe def set_recipe
@recipe = Recipe.includes(recipe_ingredients: {food: :food_units }).find(params[:id]) @recipe = Recipe.includes(recipe_ingredients: [{food: :food_units }, {recipe_as_ingredient: {recipe_ingredients: {food: :food_units }}}]).find(params[:id])
end end
# 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.

View File

@ -1,10 +1,10 @@
<template> <template>
<span ref="wrapper" class="rating" @click="handleClick" @mousemove="handleMousemove" @mouseleave="handleMouseleave"> <span ref="wrapper" class="rating" @click="handleClick" @mousemove="handleMousemove" @mouseleave="handleMouseleave">
<span class="set empty-set"> <span class="set empty-set">
<app-icon v-for="i in starCount" :key="i" icon="star-empty" padding="0"></app-icon> <app-iconic-icon v-for="i in starCount" :key="i" icon="star-empty" size="md"></app-iconic-icon>
</span> </span>
<span class="set filled-set" :style="filledStyle"> <span class="set filled-set" :style="filledStyle">
<app-icon v-for="i in starCount" :key="i" icon="star" padding="0"></app-icon> <app-iconic-icon v-for="i in starCount" :key="i" icon="star" size="md"></app-iconic-icon>
</span> </span>
</span> </span>
</template> </template>
@ -113,6 +113,11 @@
.set { .set {
white-space: nowrap; white-space: nowrap;
svg.iconic {
width: 1.5em;
height: 1.5em;
}
} }
.empty-set { .empty-set {

View File

@ -10,10 +10,6 @@
<div class="column"> <div class="column">
<app-text-field label="Source" v-model="recipe.source"></app-text-field> <app-text-field label="Source" v-model="recipe.source"></app-text-field>
</div> </div>
<div class="column">
</div>
</div> </div>
<app-text-field label="Description" type="textarea" v-model="recipe.description"></app-text-field> <app-text-field label="Description" type="textarea" v-model="recipe.description"></app-text-field>

View File

@ -16,19 +16,22 @@
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody>
<tr v-for="i in taskItems" :key="i.id" @click="toggleItem(i)"> <transition-group tag="tbody" name="list-item-move">
<td> <tr v-for="i in taskItems" :key="i.id" @click="toggleItem(i)">
<div class="check"> <td>
<app-icon v-if="i.completed" icon="check"></app-icon> <div class="check">
<span class="icon" v-else></span> <app-icon v-if="i.completed" icon="check"></app-icon>
</div> <span class="icon" v-else></span>
</td> </div>
<td>{{ i.name }}</td> </td>
<td>{{ i.quantity }}</td> <td>{{ i.name }}</td>
<td></td> <td>{{ i.quantity }}</td>
</tr> <td></td>
<tr v-if="taskItems.length === 0"> </tr>
</transition-group>
<tbody v-if="taskItems.length === 0">
<tr>
<td colspan="4"> <td colspan="4">
No Items No Items
</td> </td>
@ -73,20 +76,16 @@
}, },
computed: { computed: {
completedTaskItems() {
return (this.taskList ? this.taskList.task_items : []).filter(i => i.completed);
},
uncompletedTaskItems() {
return (this.taskList ? this.taskList.task_items : []).filter(i => !i.completed);
},
taskItems() { taskItems() {
const top = []; return this.uncompletedTaskItems.concat(this.completedTaskItems);
const bottom = [];
const list = (this.taskList ? this.taskList.task_items : []);
for (let i of list) {
if (!i.completed) {
top.push(i);
} else {
bottom.push(i);
}
}
return top.concat(bottom);
} }
}, },
@ -148,8 +147,9 @@
margin-bottom: 0; margin-bottom: 0;
} }
div.check .icon { div.check {
border: 2px solid $link; border: 2px solid $link;
display: flex;
} }
</style> </style>

View File

@ -44,7 +44,7 @@
<div class="field column"> <div class="field column">
<label class="label">Density</label> <label class="label">Density</label>
<div class="control"> <div class="control">
<input class="input" type="text" placeholder="8.345 lb/gallon" v-model="density"> <input class="input" type="text" placeholder="8.345 lb/gallon" v-model="density" :disabled="ingredient !== null">
</div> </div>
</div> </div>
</div> </div>
@ -82,13 +82,13 @@
}, },
searchItemSelected(ingredient) { searchItemSelected(ingredient) {
this.ingredient = ingredient; this.ingredient = ingredient || null;
this.ingredient_name = ingredient.name; this.ingredient_name = ingredient.name || null;
this.density = ingredient.density; this.density = ingredient.density || null;
}, },
updateOutput: debounce(function() { updateOutput: debounce(function() {
this.loadResource(api.getCalculate(this.input, this.outputUnit, this.density) this.loadResource(api.getCalculate(this.input, this.outputUnit, this.ingredient ? this.ingredient.ingredient_id : null, this.density)
.then(data => { .then(data => {
this.output = data.output; this.output = data.output;
this.errors = data.errors; this.errors = data.errors;
@ -108,7 +108,7 @@
created() { created() {
this.$watch( this.$watch(
function() { function() {
return this.input + this.outputUnit + this.density; return [this.input, this.outputUnit, this.density, this.ingredient];
}, },
function() { function() {
this.updateOutput(); this.updateOutput();

View File

@ -16,9 +16,9 @@
<div v-if="currentTaskList !== null"> <div v-if="currentTaskList !== null">
<div class="box"> <div class="box">
<button class="button" @click="deleteCompletedItems">Clear Completed</button> <button class="button" @click="deleteCompletedItems" v-if="completedItemCount > 0">Clear Completed</button>
<button class="button" @click="completeAllItems">Check All</button> <button class="button" @click="completeAllItems" v-if="uncompletedItemCount > 0">Check All</button>
<button class="button" @click="unCompleteAllItems">Uncheck All</button> <button class="button" @click="unCompleteAllItems" v-if="completedItemCount > 0">Uncheck All</button>
</div> </div>
<div class="box"> <div class="box">
@ -66,6 +66,14 @@
} else { } else {
return this.currentTaskList.name; return this.currentTaskList.name;
} }
},
completedItemCount() {
return this.currentTaskList === null ? 0 : this.currentTaskList.task_items.filter(i => i.completed).length;
},
uncompletedItemCount() {
return this.currentTaskList === null ? 0 : this.currentTaskList.task_items.filter(i => !i.completed).length;
} }
}, },

View File

@ -205,10 +205,11 @@ class Api {
return this.get("/ingredients/search", params); return this.get("/ingredients/search", params);
} }
getCalculate(input, output_unit, density) { getCalculate(input, output_unit, ingredient_id, density) {
const params = { const params = {
input, input,
output_unit, output_unit,
ingredient_id,
density density
}; };
return this.get("/calculator/calculate", params); return this.get("/calculator/calculate", params);

View File

@ -15,7 +15,8 @@
overflow: hidden; overflow: hidden;
} }
//.expand-enter,
//.expand-leave-to {
// height: 0; .list-item-move-move {
//} transition: transform 0.25s;
}

View File

@ -1,4 +1,4 @@
class Food < ApplicationRecord class Food < Ingredient
include TokenizedLike include TokenizedLike
belongs_to :user belongs_to :user
@ -33,18 +33,10 @@ class Food < ApplicationRecord
self self
end end
def nutrition_errors
[]
end
def nutrition_unit def nutrition_unit
UnitConversion.parse('100 grams') UnitConversion.parse('100 grams')
end end
def density?
!density.nil?
end
def ndbn=(value) def ndbn=(value)
@usda_food = nil @usda_food = nil
super super

41
app/models/ingredient.rb Normal file
View File

@ -0,0 +1,41 @@
class Ingredient < ApplicationRecord
self.abstract_class = true
class << self
def find_by_ingredient_id(ingredient_id)
puts "looking up |#{ingredient_id}|"
case ingredient_id
when /^R(\d+)$/
puts 'rec'
Recipe.find($1)
when /^F(\d+)$/
puts 'food'
Food.find($1)
else
raise ActiveRecord::RecordNotFound
end
end
end
def ingredient_id
case self
when Recipe
"R#{id}"
when Food
"F#{id}"
else
raise 'Unknown ingredient'
end
end
def nutrition_errors
[]
end
def density?
!self.density.nil?
end
end

View File

@ -50,7 +50,7 @@ class NutritionData
end end
unless i.ingredient.nutrition_errors.empty? unless i.ingredient.nutrition_errors.empty?
@errors << "#{i.name} has errors: #{i.ingredient.nutrition_errors.join(", ")}" @errors << "#{i.name}: #{i.ingredient.nutrition_errors.join(", ")}"
end end
missing = [] missing = []

View File

@ -1,4 +1,4 @@
class Recipe < ApplicationRecord class Recipe < Ingredient
include DefaultValues include DefaultValues
include TokenizedLike include TokenizedLike
@ -157,10 +157,6 @@ class Recipe < ApplicationRecord
end end
end end
def density?
!density.nil?
end
def custom_units def custom_units
arbitrary = self.yields_list.select { |y| !y.mass? && !y.volume? } arbitrary = self.yields_list.select { |y| !y.mass? && !y.volume? }
mass = self.yields_list.select { |y| y.mass? } mass = self.yields_list.select { |y| y.mass? }

View File

@ -1,15 +1,7 @@
json.array! @ingredients do |i| json.array! @ingredients do |i|
json.extract! i, :name json.extract! i, :ingredient_id, :name, :density
json.id i.ingredient_id
case i
when Recipe
json.id "R#{i.id}"
when Food
json.id "F#{i.id}"
else
json.id nil
end
end end

View File

@ -12,7 +12,7 @@ puts "Seeding..."
dan = User.create!({username: 'dan', full_name: 'Dan', email: 'dan.elbert@gmail.com', password: 'qwerty', password_confirmation: 'qwerty'}) dan = User.create!({username: 'dan', full_name: 'Dan', email: 'dan.elbert@gmail.com', password: 'qwerty', password_confirmation: 'qwerty'})
ingredients = { foods = {
water: {name: 'Water', density: '1 g/ml'}, water: {name: 'Water', density: '1 g/ml'},
butter: {name: 'Butter, Salted', ndbn: '01001'}, butter: {name: 'Butter, Salted', ndbn: '01001'},
butter_sal: {name: 'Butter, Unsalted', density: '226 gram/cup'}, butter_sal: {name: 'Butter, Unsalted', density: '226 gram/cup'},
@ -43,8 +43,8 @@ ingredients = {
} }
ingredients.each do |k, v| foods.each do |k, v|
ingredients[k] = Food.create!({user_id: dan.id}.merge(v)) foods[k] = Food.create!({user_id: dan.id}.merge(v))
end end
g = Recipe.create!({ g = Recipe.create!({
@ -69,19 +69,19 @@ bb = Recipe.create!({
bb.tag_names = ['beef', 'dinner', 'stirfry'] 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', food: ingredients[:flank]}, {quantity: '1', units: 'pound', preparation: 'flank steak, skirt steak, hanger steak, or flap meat, cut into 1/4-inch thick strips', food: foods[:flank]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: ingredients[:soy_sauce]}, {quantity: '1/4', units: 'cup', preparation: 'divided', food: foods[:soy_sauce]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: ingredients[:shaoxing]}, {quantity: '1/4', units: 'cup', preparation: 'divided', food: foods[:shaoxing]},
{quantity: '2', units: 'teaspoons', preparation: '', food: ingredients[:cornstarch]}, {quantity: '2', units: 'teaspoons', preparation: '', food: foods[:cornstarch]},
{quantity: '1/3', units: 'cup', preparation: '', food: ingredients[:stock]}, {quantity: '1/3', units: 'cup', preparation: '', food: foods[:stock]},
{quantity: '1/4', units: 'cup', preparation: '', food: ingredients[:oyster_sauce]}, {quantity: '1/4', units: 'cup', preparation: '', food: foods[:oyster_sauce]},
{quantity: '1', units: 'tablespoon', preparation: '', food: ingredients[:sugar]}, {quantity: '1', units: 'tablespoon', preparation: '', food: foods[:sugar]},
{quantity: '1', units: 'teaspoon', preparation: '', food: ingredients[:seasame_oil]}, {quantity: '1', units: 'teaspoon', preparation: '', food: foods[:seasame_oil]},
{quantity: '2', units: 'medium cloves', preparation: 'finely minced', food: ingredients[:garlic]}, {quantity: '2', units: 'medium cloves', preparation: 'finely minced', food: foods[:garlic]},
{quantity: '2', units: 'teaspoons', preparation: 'finely minced', name: 'ginger root'}, {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: '3', units: '', preparation: 'whites finely sliced, greens cut into 1/2-inch segments, reserved separately', name: 'Scallions'},
{quantity: '4', units: 'tablespoons', preparation: '', food: ingredients[:peanut_oil]}, {quantity: '4', units: 'tablespoons', preparation: '', food: foods[:peanut_oil]},
{quantity: '1', units: 'pound', preparation: '', food: ingredients[:broccoli]}, {quantity: '1', units: 'pound', preparation: '', food: foods[:broccoli]},
].each_with_index do |ri, i| ].each_with_index do |ri, i|
RecipeIngredient.create!({recipe: bb, sort_order: i}.merge(ri)) RecipeIngredient.create!({recipe: bb, sort_order: i}.merge(ri))
end end

View File

@ -20,23 +20,26 @@ module Unitwise
def self.with_custom_units(unit_list, &block) def self.with_custom_units(unit_list, &block)
atoms = [] atoms = []
ret_val = nil
unit_list.each do |u| begin
atom = Unitwise::Atom.new(u) unit_list.each do |u|
atom.validate! atom = Unitwise::Atom.new(u)
Unitwise::Atom.all.push(atom) atom.validate!
atoms.push(atom) Unitwise::Atom.all.push(atom)
atoms.push(atom)
end
rem = Unitwise::Expression::Decomposer.send(:reset)
ret_val = block.call
ensure
atoms.each do |a|
idx = Unitwise::Atom.all.index { |b| b.equal?(a) }
Unitwise::Atom.all.delete_at(idx)
# Unitwise::Atom.all.pop
end
Unitwise::Expression::Decomposer.send(:reset, rem)
end end
rem = Unitwise::Expression::Decomposer.send(:reset)
ret_val = block.call
atoms.each do |a|
idx = Unitwise::Atom.all.index { |b| b.equal?(a) }
Unitwise::Atom.all.delete_at(idx)
# Unitwise::Atom.all.pop
end
Unitwise::Expression::Decomposer.send(:reset, rem)
ret_val ret_val
end end