This commit is contained in:
parent
128bfcd535
commit
ffed63e0b3
@ -2,7 +2,7 @@ class IngredientsController < ApplicationController
|
||||
|
||||
def search
|
||||
@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 }
|
||||
end
|
||||
|
||||
|
@ -86,7 +86,7 @@ class RecipesController < ApplicationController
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
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
|
||||
|
||||
def criteria_params
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="recipe-edit">
|
||||
<template v-if="!forLogging">
|
||||
|
||||
<div class="columns">
|
||||
@ -10,6 +10,10 @@
|
||||
<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>
|
||||
@ -35,8 +39,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="title is-4">Ingredients</h3>
|
||||
|
||||
<recipe-edit-ingredient-editor :ingredients="recipe.ingredients"></recipe-edit-ingredient-editor>
|
||||
|
||||
|
||||
@ -53,6 +55,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="recipe.is_ingredient" />
|
||||
Is Ingredient
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -121,6 +129,10 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.recipe-edit {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.directions-input {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
||||
<h3 class="title is-4">
|
||||
Ingredients
|
||||
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
||||
</h3>
|
||||
|
||||
<app-modal wide :open="isBulkEditing" title="Edit Ingredients" @dismiss="cancelBulkEditing">
|
||||
<div class="columns">
|
||||
<div class="column is-half bulk-input">
|
||||
@ -102,6 +106,10 @@
|
||||
|
||||
methods: {
|
||||
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 {
|
||||
id: null,
|
||||
quantity: null,
|
||||
@ -109,7 +117,7 @@
|
||||
name: null,
|
||||
preparation: 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 v-for="nutrient in recipe.nutrition_data.nutrients" :key="nutrient.name">
|
||||
<td>{{nutrient.label}}</td>
|
||||
<td>{{nutrient.value}}</td>
|
||||
<td>{{ roundValue(nutrient.value) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -227,6 +227,10 @@
|
||||
this.$router.push({name: 'recipe', query: { scale: this.scaleValue, system: this.systemConvertValue, unit: this.unitConvertValue }});
|
||||
},
|
||||
|
||||
roundValue(v) {
|
||||
return parseFloat(v).toFixed(2);
|
||||
},
|
||||
|
||||
formatMinutes(min) {
|
||||
if (min) {
|
||||
const partUnits = [
|
||||
|
@ -152,6 +152,7 @@ class Api {
|
||||
description: recipe.description,
|
||||
source: recipe.source,
|
||||
yields: recipe.yields,
|
||||
is_ingredient: recipe.is_ingredient,
|
||||
total_time: recipe.total_time,
|
||||
active_time: recipe.active_time,
|
||||
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
|
||||
include Ingredient
|
||||
include TokenizedLike
|
||||
|
||||
belongs_to :user
|
||||
@ -42,6 +41,10 @@ class Food < ApplicationRecord
|
||||
UnitConversion.parse('100 grams')
|
||||
end
|
||||
|
||||
def density?
|
||||
!density.nil?
|
||||
end
|
||||
|
||||
def ndbn=(value)
|
||||
@usda_food = nil
|
||||
super
|
||||
|
@ -1,5 +1,5 @@
|
||||
class Recipe < ApplicationRecord
|
||||
include Ingredient
|
||||
include DefaultValues
|
||||
include TokenizedLike
|
||||
|
||||
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 :not_log, -> { where('is_log <> ? OR is_log IS NULL', true) }
|
||||
scope :active, -> { undeleted.not_log }
|
||||
scope :is_ingredient, -> { where(is_ingredient: true) }
|
||||
|
||||
accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true
|
||||
|
||||
default_values is_ingredient: false
|
||||
|
||||
validates :name, presence: true
|
||||
validates :total_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
|
||||
|
||||
@ -30,6 +34,14 @@ class Recipe < ApplicationRecord
|
||||
].join('/')
|
||||
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)
|
||||
self.converted_scale = factor
|
||||
recipe_ingredients.each do |ri|
|
||||
@ -145,6 +157,10 @@ 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? }
|
||||
@ -173,7 +189,7 @@ class Recipe < ApplicationRecord
|
||||
end
|
||||
|
||||
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?
|
||||
query = query.matches_tokens(:name, criteria.name.split(' '))
|
||||
@ -191,9 +207,9 @@ class Recipe < ApplicationRecord
|
||||
tokens = query.to_s.split(' ')
|
||||
|
||||
if tokens.empty?
|
||||
Recipe.none
|
||||
self.none
|
||||
else
|
||||
Recipe.matches_tokens(:name, tokens)
|
||||
self.matches_tokens(:name, tokens)
|
||||
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)
|
||||
|
||||
|
@ -13,6 +13,8 @@ require "action_view/railtie"
|
||||
# require "sprockets/railtie"
|
||||
require "rails/test_unit/railtie"
|
||||
|
||||
require "active_support/core_ext/string/inquiry"
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
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.
|
||||
|
||||
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|
|
||||
t.integer "food_id", null: false
|
||||
@ -110,6 +110,7 @@ ActiveRecord::Schema.define(version: 2018_09_10_235229) do
|
||||
t.boolean "is_log"
|
||||
t.float "rating"
|
||||
t.text "step_text"
|
||||
t.boolean "is_ingredient"
|
||||
end
|
||||
|
||||
create_table "recipes_tags", id: false, force: :cascade do |t|
|
||||
|
@ -1,6 +1,30 @@
|
||||
require 'rails_helper'
|
||||
|
||||
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
|
||||
it 'always has "1 each" as a yield' do
|
||||
r = create(:recipe, yields: '')
|
||||
|
Loading…
Reference in New Issue
Block a user