Я использую Rails 6.0.0.rc1 и у меня возникают проблемы с работой тройной вложенной формы. У меня products есть options с option_values. Таким образом, продукт может иметь опцию «Цвет» и значение опции «Красный». Я хотел бы создать все это в классической вложенной форме в форме продукта.

Форма работает, и я могу сохранить продукт с опцией, но не значение опции при отправке. Я не уверен, почему это не работает, когда я пытаюсь встроить fields_for значения параметров в параметры fields_for.

Что я здесь не так делаю? Я чувствую, что упускаю что-то очевидное, но не могу понять это. (Вероятно, не имеет значения, но учтите, что мне нужно охватить каждый объект account_id и моим пользователем has_one :account, что является причиной скрытого поля.)

Вот моя модель продукта:

class Product < ApplicationRecord
  belongs_to :account

  has_many :options, dependent: :destroy
  accepts_nested_attributes_for :options, allow_destroy: true

  validates :account_id,  presence: true
  validates :name,        presence: true
end

Вариант модели:

class Option < ApplicationRecord
  belongs_to :account
  belongs_to :product

  has_many :option_values, dependent: :destroy
  accepts_nested_attributes_for :option_values, allow_destroy: true

  validates :account_id,  presence: true
  validates :name,        presence: true
end

Модель OptionValue:

class OptionValue < ApplicationRecord
  belongs_to :account
  belongs_to :option

  validates :account_id,  presence: true
  validates :name,        presence: true
end

Вот форма продукта:

<%= form_with(model: product, local: true) do |f| %>
  <%= f.fields_for :options do |options_form| %>
    <fieldset class='form-group'>
      <%= options_form.hidden_field :account_id, value: current_user.account.id %>
      <%= options_form.label :name, 'Option' %>
      <%= options_form.text_field :name, class: 'form-control' %>
    </fieldset>

    <%= f.fields_for :option_values do |values_form| %>
      <fieldset class='form-group'>
        <%= values_form.label :name, 'Value' %>
        <%= values_form.text_field :name, class: 'form-control' %>
      </fieldset>
    <% end %>
  <% end %>
<% end %>

ProductsController :

class ProductsController < ApplicationController
  def new
    @product = Product.new
    @product.options.build
  end

  def create
    @account = current_user.account
    @product = @account.products.build(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  private 
    def product_params
      params.require(:product).permit(
        :account_id, :name,
        options_attributes: [
          :id, :account_id, :name, :_destroy,
          option_values_attributes:[:id, :account_id, :name, :_destroy ]
        ]
      )
    end
end
-2
Lee McAlilly 30 Май 2019 в 20:48

2 ответа

Лучший ответ

Сначала вам нужно изменить форму на гнездо option_values внутри option и добавить поле account_id в значения параметров:

<%= form_with(model: product, local: true) do |f| %>
  <%= f.fields_for :options do |options_form| %>
    <fieldset class='form-group'>
      <%= options_form.hidden_field :account_id, value: current_user.account.id %>
      <%= options_form.label :name, 'Option' %>
      <%= options_form.text_field :name, class: 'form-control' %>
    </fieldset>

    <%= options_form.fields_for :option_values do |values_form| %>
      <fieldset class='form-group'>
        <%= values_form.hidden_field :account_id, value: current_user.account.id %>
        <%= values_form.label :name, 'Value' %>
        <%= values_form.text_field :name, class: 'form-control' %>
      </fieldset>
    <% end %>
  <% end %>
<% end %>

Также вам нужно строить вложенные записи в контроллере. Другой вариант - создать их динамически с помощью javascript (см., Например, cocoon gem). Чтобы построить 3 варианта с 3 значениями каждый:

def new 
  @account = current_user.account 
  # it is better to create associated product 
  @product = @account.products.new 
  3.times do 
    option = @product.options.build 
    3.times { option.option_values.build } 
  end 
end
1
Vasilisa 31 Май 2019 в 04:17

Обновление:

Потому что я пытался следовать этим Railscast для вложенных форм, самым большим проблема заключалась в том, что я не осознавал, что версия, с которой работает Райан Бейтс, была «Редактировать», а не «Новая», поэтому я добавил «Продукты», «Опции» и «Значения» через консоль и получил форму, работающую с этим кодом:

_form.html.erb

  <%= f.fields_for :options do |builder| %>
    <%= render 'option_fields', f: builder %>
  <% end %>

_option_fields.html.erb

<fieldset class='form-group'>
  <%= f.hidden_field :account_id, value: current_user.account.id %>

  <%= f.label :name, 'Option' %>
  <%= f.text_field :name, class: 'form-control' %>
  <br>
  <%= f.check_box :_destroy, class: 'form-check-input' %>
  <%= f.label :_destroy, 'Remove Option' %>

  <small id="optionHelp" class="form-text text-muted">
    (e.g. "Size" or "Color")
  </small>

  <%= f.fields_for :option_values do |builder| %>
    <%= render 'option_value_fields', f: builder %>
  <% end %>

</fieldset>

_option_value_fields.html.erb

<fieldset class='form-group'>
  <%= f.hidden_field :account_id, value: current_user.account.id %>
  <%= f.label :name, 'Value' %>
  <%= f.text_field :name, class: 'form-control' %>
  <br>
  <%= f.check_box :_destroy, class: 'form-check-input' %>
  <%= f.label :_destroy, 'Remove Value' %>

  <small id="optionValueHelp" class="form-text text-muted">
    (e.g. "Small, Medium, Large" or "Red, Green, Blue")
  </small>
</fieldset>

Кроме того, единственное отличие от Railscast заключается в использовании сильных параметров в контроллере, так что вам просто нужно вложить их так:

ProductsController

def product_params
      params.require(:product).permit(:account_id, :name, options_attributes [:id, :account_id, :name, :_destroy, option_values_attributes: [:id, :account_id, :name, :_destroy]])
end

OptionsController

def option_params
     params.require(:option).permit(:account_id, :name, option_values_attributes [:id, :account_id, :name, :_destroy])
end

OptionValuesController

def option_value_params
     params.require(:option_value).permit(:account_id, :option_id, :name)
end

Вместо того чтобы создавать вложенные объекты в контроллере, я собираюсь сделать это с помощью Javascript, как в эпизоде с Railscast, или с Жемчужина кокона, как Василиса предложила в своем ответе.

Просто хотел поделиться кодом, который действительно работал, на случай, если кто-то столкнется с подобными проблемами. Я думаю, что Railscast, хотя и старый, по-прежнему является отличным введением во вложенные формы в Rails, но вы просто должны знать об изменениях, необходимых для использования form_with и сильных параметров. Большое спасибо Василисе за помощь в этом.

Основные «ошибки», на которые нужно обратить внимание при следовании за Rails Nested Form Railscast это:

  • form_with имеет некоторый другой синтаксис, чем у старых rails form_tag
  • Убедитесь, что у вас нет проблем с опечатками или именами при создании блоков формы, потому что они вложены дважды
  • То же самое с параметрами вложенности в ваших контроллерах, просто помните о вашем синтаксисе и опечатках
  • Имейте в виду, что Райан Бейтс демонстрирует данные, которые не были добавлены через форму, которую он создает, поэтому, если вы хотите следовать, вам нужно будет создать некоторые данные в консоли
  • С сильными параметрами вам придется явно перечислить :_destroy в качестве параметра, чтобы его флажки «Удалить» работали
0
Lee McAlilly 31 Май 2019 в 16:58