2013-10-24 5 views
2

Я пытаюсь построить приложение рецепт-хранитель с тремя основными моделями:Как ссылаться на существующий экземпляр модели во вложенной форме Rails?

Рецепт - рецепт для конкретного блюда
Ингредиент - список ингредиентов, проверенными на уникальность
Количество - Соединительный стол между ингредиентом и рецептом, который также отражает количество конкретного ингредиента, необходимого для конкретного рецепта.

Я использую вложенную форму (см ниже), который я построил, используя удивительный Railscast на вложенных форм (Part 1, Part 2) для вдохновения. (Моя форма в некотором смысле более сложна, чем руководство, из-за потребностей этой конкретной схемы, но я смог заставить ее работать аналогичным образом.)

Однако, когда моя форма отправлена, все и вся перечисленные ингредиенты создаются заново, и если ингредиент уже существует в БД, он не подтверждает валидность уникальности и препятствует созданию рецепта. Общее сопротивление.

Так что мой вопрос: Есть ли способ, чтобы представить эту форму, так что если ингредиент существует, имя которого совпадает с одним из моих полей, имя ингредиент, он ссылается на существующий компонент вместо того, чтобы пытаться создать новый с одно и то же имя?

специфика кода ниже ...


Recipe.rb В:

class Recipe < ActiveRecord::Base 
    attr_accessible :name, :description, :directions, :quantities_attributes, 
        :ingredient_attributes 

    has_many :quantities, dependent: :destroy 
    has_many :ingredients, through: :quantities 
    accepts_nested_attributes_for :quantities, allow_destroy: true 

В Quantity.rb:

class Quantity < ActiveRecord::Base 
    attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes 

    belongs_to :recipe 
    belongs_to :ingredient 
    accepts_nested_attributes_for :ingredient 

И в Ingredient.rb:

class Ingredient < ActiveRecord::Base 
    attr_accessible :name 
    validates :name, :uniqueness => { :case_sensitive => false } 

    has_many :quantities 
    has_many :recipes, through: :quantities 

Вот моя вложенная форма, которая отображается на Recipe#new:

<%= form_for @recipe do |f| %> 
    <%= render 'recipe_form_errors' %> 

    <%= f.label :name %><br> 
    <%= f.text_field :name %><br> 
    <h3>Ingredients</h3> 

    <div id='ingredients'> 
    <%= f.fields_for :quantities do |ff| %> 
     <div class='ingredient_fields'> 
     <%= ff.fields_for :ingredient_attributes do |fff| %> 
      <%= fff.label :name %> 
      <%= fff.text_field :name %> 
     <% end %> 
     <%= ff.label :amount %> 
     <%= ff.text_field :amount, size: "10" %> 
     <%= ff.hidden_field :_destroy %> 
     <%= link_to_function "remove", "remove_fields(this)" %><br> 
     </div> 
    <% end %> 
    <%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %> 
    </div><br> 

    <%= f.label :description %><br> 
    <%= f.text_area :description, rows: 4, columns: 100 %><br> 
    <%= f.label :directions %><br> 
    <%= f.text_area :directions, rows: 4, columns: 100 %><br> 
    <%= f.submit %> 
<% end %> 

link_to и link_to_function там, чтобы разрешить добавление и удаление пар количество/ингредиента на лету, и были адаптированы из Railscast упоминалось ранее. Они могут использовать некоторые рефакторинги, но работают более или менее так, как должны.


Обновление: запрос Per Леже, здесь соответствующий код из recipes_controller.rb. В маршруте Recipes#new3.times { @recipe.quantities.build } устанавливает три пустые пары количества/ингредиентов для любого данного рецепта; их можно удалить или добавить на лету, используя ссылки «Добавить ингредиент» и «удалить», упомянутые выше.

class RecipesController < ApplicationController 

    def new 
    @recipe = Recipe.new 
    3.times { @recipe.quantities.build } 
    @quantity = Quantity.new 
    end 

    def create 
    @recipe = Recipe.new(params[:recipe]) 

    if @recipe.save 
     redirect_to @recipe 
    else 
     render :action => 'new' 
    end 
    end 

ответ

2

Вы не должны ставить логику матча ингредиентов в поле зрения - это обязанность Recipe#create создавать собственные объекты перед передачей «эм к модели.Pls разделяет соответствующий код для контроллера

несколько нот до прихода к коду:

  1. Я использую [email protected], но попытался написать Rails3-совместимый код.
  2. attr_acessible устарел в Rails 4, поэтому вместо этого используются сильные параметры. Если вы когда-нибудь подумали, что обновите приложение, просто начните с сильных параметров с самого начала.
  3. Рекомендует сделать Ingredient низким обсаженным, чтобы обеспечить однородный вид сверху прецедентного нечувствительности

ОК, здесь мы идем:

Удалить attr_accessible строки в Recipe.rb, Quantity.rb и Ingredient.rb.

без учета регистра, низкий обсаженные Ingredient.rb:

class Ingredient < ActiveRecord::Base 
    before_save { self.name.downcase! } # to simplify search and unified view 
    validates :name, :uniqueness => { :case_sensitive => false } 

    has_many :quantities 
    has_many :recipes, through: :quantities 
end 


<div id='ingredients'> части скорректированной формы для создания/обновления Рецепта:

<%= f.fields_for :quantities do |ff| %> 
    <div class='ingredient_fields'> 
    <%= ff.fields_for :ingredient do |fff| %> 
     <%= fff.label :name %> 
     <%= fff.text_field :name, size: "10" %> 
    <% end %> 
    ... 
    </div> 
<% end %> 
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %> 

Мы должны использовать :ingredient от Quantity nested_attributes и Rails добавит _attributes -part при создании params -hash для дальнейшего массового присвоения. Он позволяет использовать ту же форму как в новых, так и в действиях обновления. Для этой части работы правильная ассоциация должна быть определена заранее. См. Отрегулированный Recipe#new ниже.

и, наконец, recipes_controller.rb:

def new 
    @recipe = Recipe.new 
    3.times do 
    @recipe.quantities.build #initialize recipe -> quantities association 
    @recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association 
    end 
end 

def create 
    @recipe = Recipe.new(recipe_params)  
    prepare_recipe 

    if @recipe.save ... #now all saved in proper way 
end 

def update 
    @recipe = Recipe.find(params[:id]) 
    @recipe.attributes = recipe_params 
    prepare_recipe  

    if @recipe.save ... #now all saved in proper way 
end 

private 
def prepare_recipe 
    @recipe.quantities.each do |quantity| 
    # do case-insensitive search via 'where' and building SQL-request 
    if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first 
     quantity.ingredient_id = quantity.ingredient.id = ingredient.id 
    end 
    end 
end 

def recipe_params 
    params.require(:recipe).permit(
    :name, 
    :description, 
    :directions, 
    :quantities_attributes => [ 
     :id, 
     :amount, 
     :_destroy, 
     :ingredient_attributes => [ 
     #:id commented bc we pick 'id' for existing ingredients manually and for new we create it 
     :name 
    ]]) 
end 

В prepare_recipe мы делаем следующие вещи:

  1. Найти ID ингредиента с данным именем
  2. Набор foreign_key quantity.ingredient_id для ID
  3. Установите quantity.ingredient.id в ID (подумайте, что произойдет, если вы этого не сделаете и измените имя ингредиента в Рецепте)

Наслаждайтесь!

+0

Спасибо за ответ. Я добавил соответствующий код контроллера в конец моего исходного сообщения. Нужно отметить, что в отношении логики в представлении, но я не уверен, какие строки являются оскорбительными. Я хотел убедиться, что пользователь может добавлять ингредиенты и их количества при создании рецепта, и я пытался ограничить код только тем, что требовалось для формы. –

+0

Как заполнить поля ингредиентов при обновлении рецепта? Фрагмент сверху не работает. (Rails 4.1.4) – Scotty

+0

Также, как я запрашиваю количество? Есть ли способ сопоставить его с ингредиентом? т. е. @ recipe.ingredient [1] .Чтобы? – Scotty

Смежные вопросы