Тройная вложенная форма с Rails form_with и сильными параметрами - PullRequest
0 голосов
/ 30 мая 2019

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

Форма работает, и я могу сохранить Продукт с Опцией, но не Значение параметра при отправке.Я не уверен, почему он не работает, когда я пытаюсь встроить 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 ]

1 голос
/ 31 мая 2019

Сначала вам нужно изменить форму на гнездо 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
0 голосов
/ 31 мая 2019

Обновление:

Поскольку я пытался проследить за этой вложенной формой 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, или с Cocoon gem , как Vasilisa , предложенным в ее ответе.

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

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

  • form_with имеет некоторый синтаксис, отличный от старых rails form_tag
  • Убедитесь, что у вас нет проблем с опечатками или именами при создании блоков формы, потому что они вложены дважды
  • То же самое с параметрами вложенности в ваших контроллерах, просто помните ваш синтаксис и опечатки
  • Имейте в виду, что Райан Бейтс демонстрирует данные, которые не были добавлены через форму, которую он строит, поэтому, если вы хотите следовать, вам нужно будет создать некоторые данные в консоли
  • С сильными параметрами вам придется явно указать :_destroy в качестве параметра, чтобы его флажки "Удалить" работали
...