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
input = params[:input]
output_unit = params[:output_unit]
ingredient_id = params[:ingredient_id]
ingredient = nil
density = params[:density]
density = nil unless density.present?
if ingredient_id.present?
ingredient = Ingredient.find_by_ingredient_id(ingredient_id)
end
data = {errors: [], output: ''}
begin
unit = UnitConversion.parse(input)
if output_unit.present?
unit = unit.convert(output_unit, density)
data[:output] = unit.to_s
else
data[:output] = unit.auto_unit.to_s
UnitConversion::with_custom_units(ingredient ? ingredient.custom_units : []) do
unit = UnitConversion.parse(input)
if output_unit.present?
unit = unit.convert(output_unit, density)
data[:output] = unit.to_s
else
data[:output] = unit.auto_unit.to_s
end
end
rescue UnitConversion::UnparseableUnitError => e
data[:errors] << e.message
end

View File

@ -61,12 +61,12 @@ class LogsController < ApplicationController
private
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
def set_recipe
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

View File

@ -81,7 +81,7 @@ class RecipesController < ApplicationController
private
# Use callbacks to share common setup or constraints between actions.
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
# Never trust parameters from the scary internet, only allow the white list through.

View File

@ -1,10 +1,10 @@
<template>
<span ref="wrapper" class="rating" @click="handleClick" @mousemove="handleMousemove" @mouseleave="handleMouseleave">
<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 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>
</template>
@ -113,6 +113,11 @@
.set {
white-space: nowrap;
svg.iconic {
width: 1.5em;
height: 1.5em;
}
}
.empty-set {

View File

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

View File

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

View File

@ -44,7 +44,7 @@
<div class="field column">
<label class="label">Density</label>
<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>
@ -82,13 +82,13 @@
},
searchItemSelected(ingredient) {
this.ingredient = ingredient;
this.ingredient_name = ingredient.name;
this.density = ingredient.density;
this.ingredient = ingredient || null;
this.ingredient_name = ingredient.name || null;
this.density = ingredient.density || null;
},
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 => {
this.output = data.output;
this.errors = data.errors;
@ -108,7 +108,7 @@
created() {
this.$watch(
function() {
return this.input + this.outputUnit + this.density;
return [this.input, this.outputUnit, this.density, this.ingredient];
},
function() {
this.updateOutput();

View File

@ -16,9 +16,9 @@
<div v-if="currentTaskList !== null">
<div class="box">
<button class="button" @click="deleteCompletedItems">Clear Completed</button>
<button class="button" @click="completeAllItems">Check All</button>
<button class="button" @click="unCompleteAllItems">Uncheck All</button>
<button class="button" @click="deleteCompletedItems" v-if="completedItemCount > 0">Clear Completed</button>
<button class="button" @click="completeAllItems" v-if="uncompletedItemCount > 0">Check All</button>
<button class="button" @click="unCompleteAllItems" v-if="completedItemCount > 0">Uncheck All</button>
</div>
<div class="box">
@ -66,6 +66,14 @@
} else {
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);
}
getCalculate(input, output_unit, density) {
getCalculate(input, output_unit, ingredient_id, density) {
const params = {
input,
output_unit,
ingredient_id,
density
};
return this.get("/calculator/calculate", params);

View File

@ -15,7 +15,8 @@
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
belongs_to :user
@ -33,18 +33,10 @@ class Food < ApplicationRecord
self
end
def nutrition_errors
[]
end
def nutrition_unit
UnitConversion.parse('100 grams')
end
def density?
!density.nil?
end
def ndbn=(value)
@usda_food = nil
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
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
missing = []

View File

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

View File

@ -1,15 +1,7 @@
json.array! @ingredients do |i|
json.extract! i, :name
case i
when Recipe
json.id "R#{i.id}"
when Food
json.id "F#{i.id}"
else
json.id nil
end
json.extract! i, :ingredient_id, :name, :density
json.id i.ingredient_id
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'})
ingredients = {
foods = {
water: {name: 'Water', density: '1 g/ml'},
butter: {name: 'Butter, Salted', ndbn: '01001'},
butter_sal: {name: 'Butter, Unsalted', density: '226 gram/cup'},
@ -43,8 +43,8 @@ ingredients = {
}
ingredients.each do |k, v|
ingredients[k] = Food.create!({user_id: dan.id}.merge(v))
foods.each do |k, v|
foods[k] = Food.create!({user_id: dan.id}.merge(v))
end
g = Recipe.create!({
@ -69,19 +69,19 @@ bb = Recipe.create!({
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/4', units: 'cup', preparation: 'divided', food: ingredients[:soy_sauce]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: ingredients[:shaoxing]},
{quantity: '2', units: 'teaspoons', preparation: '', food: ingredients[:cornstarch]},
{quantity: '1/3', units: 'cup', preparation: '', food: ingredients[:stock]},
{quantity: '1/4', units: 'cup', preparation: '', food: ingredients[:oyster_sauce]},
{quantity: '1', units: 'tablespoon', preparation: '', food: ingredients[:sugar]},
{quantity: '1', units: 'teaspoon', preparation: '', food: ingredients[:seasame_oil]},
{quantity: '2', units: 'medium cloves', preparation: 'finely minced', food: ingredients[:garlic]},
{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: foods[:soy_sauce]},
{quantity: '1/4', units: 'cup', preparation: 'divided', food: foods[:shaoxing]},
{quantity: '2', units: 'teaspoons', preparation: '', food: foods[:cornstarch]},
{quantity: '1/3', units: 'cup', preparation: '', food: foods[:stock]},
{quantity: '1/4', units: 'cup', preparation: '', food: foods[:oyster_sauce]},
{quantity: '1', units: 'tablespoon', preparation: '', food: foods[:sugar]},
{quantity: '1', units: 'teaspoon', preparation: '', food: foods[:seasame_oil]},
{quantity: '2', units: 'medium cloves', preparation: 'finely minced', food: foods[:garlic]},
{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: '4', units: 'tablespoons', preparation: '', food: ingredients[:peanut_oil]},
{quantity: '1', units: 'pound', preparation: '', food: ingredients[:broccoli]},
{quantity: '4', units: 'tablespoons', preparation: '', food: foods[:peanut_oil]},
{quantity: '1', units: 'pound', preparation: '', food: foods[:broccoli]},
].each_with_index do |ri, i|
RecipeIngredient.create!({recipe: bb, sort_order: i}.merge(ri))
end

View File

@ -20,23 +20,26 @@ module Unitwise
def self.with_custom_units(unit_list, &block)
atoms = []
ret_val = nil
unit_list.each do |u|
atom = Unitwise::Atom.new(u)
atom.validate!
Unitwise::Atom.all.push(atom)
atoms.push(atom)
begin
unit_list.each do |u|
atom = Unitwise::Atom.new(u)
atom.validate!
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
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
end