This commit is contained in:
parent
128bfcd535
commit
ffed63e0b3
@ -2,7 +2,7 @@ class IngredientsController < ApplicationController
|
|||||||
|
|
||||||
def search
|
def search
|
||||||
@ingredients = Food.search(params[:query]).order(:name).to_a
|
@ingredients = Food.search(params[:query]).order(:name).to_a
|
||||||
@ingredients.concat(Recipe.search_by_name(params[:query]).order(:name).to_a)
|
@ingredients.concat(Recipe.is_ingredient.search_by_name(params[:query]).order(:name).to_a)
|
||||||
@ingredients.sort { |a, b| a.name <=> b.name }
|
@ingredients.sort { |a, b| a.name <=> b.name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class RecipesController < 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 recipe_params
|
def recipe_params
|
||||||
params.require(:recipe).permit(:name, :description, :source, :yields, :total_time, :active_time, :step_text, tag_names: [], recipe_ingredients_attributes: [:name, :ingredient_id, :quantity, :units, :preparation, :sort_order, :id, :_destroy])
|
params.require(:recipe).permit(:name, :description, :source, :yields, :total_time, :active_time, :step_text, :is_ingredient, tag_names: [], recipe_ingredients_attributes: [:name, :ingredient_id, :quantity, :units, :preparation, :sort_order, :id, :_destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
def criteria_params
|
def criteria_params
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="recipe-edit">
|
||||||
<template v-if="!forLogging">
|
<template v-if="!forLogging">
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
@ -10,6 +10,10 @@
|
|||||||
<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>
|
||||||
@ -35,8 +39,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="title is-4">Ingredients</h3>
|
|
||||||
|
|
||||||
<recipe-edit-ingredient-editor :ingredients="recipe.ingredients"></recipe-edit-ingredient-editor>
|
<recipe-edit-ingredient-editor :ingredients="recipe.ingredients"></recipe-edit-ingredient-editor>
|
||||||
|
|
||||||
|
|
||||||
@ -53,6 +55,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" v-model="recipe.is_ingredient" />
|
||||||
|
Is Ingredient
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -121,6 +129,10 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.recipe-edit {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.directions-input {
|
.directions-input {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<h3 class="title is-4">
|
||||||
|
Ingredients
|
||||||
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
||||||
|
</h3>
|
||||||
|
|
||||||
<app-modal wide :open="isBulkEditing" title="Edit Ingredients" @dismiss="cancelBulkEditing">
|
<app-modal wide :open="isBulkEditing" title="Edit Ingredients" @dismiss="cancelBulkEditing">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half bulk-input">
|
<div class="column is-half bulk-input">
|
||||||
@ -102,6 +106,10 @@
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
createIngredient() {
|
createIngredient() {
|
||||||
|
const sort_orders = this.ingredients.map(i => i.sort_order);
|
||||||
|
sort_orders.push(0);
|
||||||
|
const next_sort_order = Math.max(...sort_orders) + 5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: null,
|
id: null,
|
||||||
quantity: null,
|
quantity: null,
|
||||||
@ -109,7 +117,7 @@
|
|||||||
name: null,
|
name: null,
|
||||||
preparation: null,
|
preparation: null,
|
||||||
ingredient_id: null,
|
ingredient_id: null,
|
||||||
sort_order: Math.max([0].concat(this.ingredients.map(i => i.sort_order))) + 5
|
sort_order: next_sort_order
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr v-for="nutrient in recipe.nutrition_data.nutrients" :key="nutrient.name">
|
<tr v-for="nutrient in recipe.nutrition_data.nutrients" :key="nutrient.name">
|
||||||
<td>{{nutrient.label}}</td>
|
<td>{{nutrient.label}}</td>
|
||||||
<td>{{nutrient.value}}</td>
|
<td>{{ roundValue(nutrient.value) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@ -227,6 +227,10 @@
|
|||||||
this.$router.push({name: 'recipe', query: { scale: this.scaleValue, system: this.systemConvertValue, unit: this.unitConvertValue }});
|
this.$router.push({name: 'recipe', query: { scale: this.scaleValue, system: this.systemConvertValue, unit: this.unitConvertValue }});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
roundValue(v) {
|
||||||
|
return parseFloat(v).toFixed(2);
|
||||||
|
},
|
||||||
|
|
||||||
formatMinutes(min) {
|
formatMinutes(min) {
|
||||||
if (min) {
|
if (min) {
|
||||||
const partUnits = [
|
const partUnits = [
|
||||||
|
|||||||
@ -152,6 +152,7 @@ class Api {
|
|||||||
description: recipe.description,
|
description: recipe.description,
|
||||||
source: recipe.source,
|
source: recipe.source,
|
||||||
yields: recipe.yields,
|
yields: recipe.yields,
|
||||||
|
is_ingredient: recipe.is_ingredient,
|
||||||
total_time: recipe.total_time,
|
total_time: recipe.total_time,
|
||||||
active_time: recipe.active_time,
|
active_time: recipe.active_time,
|
||||||
step_text: recipe.step_text,
|
step_text: recipe.step_text,
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
# Mixin for Ingredient-like things.
|
|
||||||
# Consumer is expected to have the following methods:
|
|
||||||
# #name => name of ingredient
|
|
||||||
# #density => valid density string or nil
|
|
||||||
# #custom_units => map of unit names to a weight (eg { 'clove' => '25 g', 'bulb' => '175 g' })
|
|
||||||
# #nutrition_per_100g => Object that responds to all nutrition keys in NutritionData
|
|
||||||
# nutrition_per_100g_errors => list of errors
|
|
||||||
module Ingredient
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
end
|
|
||||||
|
|
||||||
# Shared helper methods
|
|
||||||
def density?
|
|
||||||
!density.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
class Food < ApplicationRecord
|
class Food < ApplicationRecord
|
||||||
include Ingredient
|
|
||||||
include TokenizedLike
|
include TokenizedLike
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
@ -42,6 +41,10 @@ class Food < ApplicationRecord
|
|||||||
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
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
class Recipe < ApplicationRecord
|
class Recipe < ApplicationRecord
|
||||||
include Ingredient
|
include DefaultValues
|
||||||
include TokenizedLike
|
include TokenizedLike
|
||||||
|
|
||||||
has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
|
has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
|
||||||
@ -10,12 +10,16 @@ class Recipe < ApplicationRecord
|
|||||||
scope :undeleted, -> { where('deleted <> ? OR deleted IS NULL', true) }
|
scope :undeleted, -> { where('deleted <> ? OR deleted IS NULL', true) }
|
||||||
scope :not_log, -> { where('is_log <> ? OR is_log IS NULL', true) }
|
scope :not_log, -> { where('is_log <> ? OR is_log IS NULL', true) }
|
||||||
scope :active, -> { undeleted.not_log }
|
scope :active, -> { undeleted.not_log }
|
||||||
|
scope :is_ingredient, -> { where(is_ingredient: true) }
|
||||||
|
|
||||||
accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true
|
accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true
|
||||||
|
|
||||||
|
default_values is_ingredient: false
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :total_time, numericality: true, allow_blank: true
|
validates :total_time, numericality: true, allow_blank: true
|
||||||
validates :active_time, numericality: true, allow_blank: true
|
validates :active_time, numericality: true, allow_blank: true
|
||||||
|
validate :validate_is_ingredient
|
||||||
|
|
||||||
attr_accessor :converted_scale, :converted_system, :converted_unit
|
attr_accessor :converted_scale, :converted_system, :converted_unit
|
||||||
|
|
||||||
@ -30,6 +34,14 @@ class Recipe < ApplicationRecord
|
|||||||
].join('/')
|
].join('/')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_is_ingredient
|
||||||
|
if self.is_ingredient
|
||||||
|
if self.recipe_ingredients.any? { |ri| !ri.recipe_as_ingredient_id.nil? }
|
||||||
|
errors[:base] << 'A recipe marked as an ingredient cannot contain recipe ingredients'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def scale(factor, auto_unit = false)
|
def scale(factor, auto_unit = false)
|
||||||
self.converted_scale = factor
|
self.converted_scale = factor
|
||||||
recipe_ingredients.each do |ri|
|
recipe_ingredients.each do |ri|
|
||||||
@ -145,6 +157,10 @@ 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? }
|
||||||
@ -173,7 +189,7 @@ class Recipe < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.for_criteria(criteria)
|
def self.for_criteria(criteria)
|
||||||
query = active.order(criteria.sort_column => criteria.sort_direction)
|
query = self.active.order(criteria.sort_column => criteria.sort_direction)
|
||||||
|
|
||||||
if criteria.name.present?
|
if criteria.name.present?
|
||||||
query = query.matches_tokens(:name, criteria.name.split(' '))
|
query = query.matches_tokens(:name, criteria.name.split(' '))
|
||||||
@ -191,9 +207,9 @@ class Recipe < ApplicationRecord
|
|||||||
tokens = query.to_s.split(' ')
|
tokens = query.to_s.split(' ')
|
||||||
|
|
||||||
if tokens.empty?
|
if tokens.empty?
|
||||||
Recipe.none
|
self.none
|
||||||
else
|
else
|
||||||
Recipe.matches_tokens(:name, tokens)
|
self.matches_tokens(:name, tokens)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
json.extract! recipe, :id, :name, :rating, :yields, :total_time, :active_time, :source, :created_at, :updated_at, :step_text, :converted_scale, :converted_system, :converted_unit
|
json.extract! recipe, :id, :name, :rating, :yields, :total_time, :active_time, :source, :is_ingredient, :created_at, :updated_at, :step_text, :converted_scale, :converted_system, :converted_unit
|
||||||
|
|
||||||
json.rendered_steps MarkdownProcessor.render(recipe.step_text)
|
json.rendered_steps MarkdownProcessor.render(recipe.step_text)
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,8 @@ require "action_view/railtie"
|
|||||||
# require "sprockets/railtie"
|
# require "sprockets/railtie"
|
||||||
require "rails/test_unit/railtie"
|
require "rails/test_unit/railtie"
|
||||||
|
|
||||||
|
require "active_support/core_ext/string/inquiry"
|
||||||
|
|
||||||
# Require the gems listed in Gemfile, including any gems
|
# Require the gems listed in Gemfile, including any gems
|
||||||
# you've limited to :test, :development, or :production.
|
# you've limited to :test, :development, or :production.
|
||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
|
|||||||
14
db/migrate/20180912172758_add_is_ingredient_to_recipe.rb
Normal file
14
db/migrate/20180912172758_add_is_ingredient_to_recipe.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class AddIsIngredientToRecipe < ActiveRecord::Migration[5.2]
|
||||||
|
|
||||||
|
class RecipeMigrator < ActiveRecord::Base
|
||||||
|
self.table_name = 'recipes'
|
||||||
|
end
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :recipes, :is_ingredient, :boolean, index: true
|
||||||
|
|
||||||
|
RecipeMigrator.reset_column_information
|
||||||
|
RecipeMigrator.update_all(is_ingredient: false)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# 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: 2018_09_10_235229) do
|
ActiveRecord::Schema.define(version: 2018_09_12_172758) do
|
||||||
|
|
||||||
create_table "food_units", force: :cascade do |t|
|
create_table "food_units", force: :cascade do |t|
|
||||||
t.integer "food_id", null: false
|
t.integer "food_id", null: false
|
||||||
@ -110,6 +110,7 @@ ActiveRecord::Schema.define(version: 2018_09_10_235229) do
|
|||||||
t.boolean "is_log"
|
t.boolean "is_log"
|
||||||
t.float "rating"
|
t.float "rating"
|
||||||
t.text "step_text"
|
t.text "step_text"
|
||||||
|
t.boolean "is_ingredient"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "recipes_tags", id: false, force: :cascade do |t|
|
create_table "recipes_tags", id: false, force: :cascade do |t|
|
||||||
|
|||||||
@ -1,6 +1,30 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Recipe, type: :model do
|
RSpec.describe Recipe, type: :model do
|
||||||
|
describe '.for_criteria' do
|
||||||
|
|
||||||
|
it 'finds by name' do
|
||||||
|
r1 = create(:recipe, name: 'chicken soup')
|
||||||
|
r2 = create(:recipe, name: 'turkey soup')
|
||||||
|
r3 = create(:recipe, name: 'chicken curry')
|
||||||
|
|
||||||
|
c = ViewModels::RecipeCriteria.new({page: 1, per: 200})
|
||||||
|
c.name = 'chicken'
|
||||||
|
r = Recipe.for_criteria(c)
|
||||||
|
expect(r).to contain_exactly(r1, r3)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.search_by_name' do
|
||||||
|
it 'can be chained' do
|
||||||
|
r1 = create(:recipe, name: 'chicken soup', is_ingredient: true)
|
||||||
|
r2 = create(:recipe, name: 'turkey soup')
|
||||||
|
|
||||||
|
expect(Recipe.is_ingredient.search_by_name('soup')).to contain_exactly(r1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#yields_list' do
|
describe '#yields_list' do
|
||||||
it 'always has "1 each" as a yield' do
|
it 'always has "1 each" as a yield' do
|
||||||
r = create(:recipe, yields: '')
|
r = create(:recipe, yields: '')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user