rails 5个带茧嵌套形式的记录一次性创建

rails 5 with cocoon nested form records creation all at once

我想知道是否可能,给出以下场景,使用 has_many 在连接 table 中创建记录:通过 Rails 5 中的关系与 cocoon 和 simple_form 使用尚未创建的记录的 :id。我问这个,因为我发现在 "join-table" 中创建这些记录的唯一方法是编辑产品然后添加输入,但为了用户体验,我认为一次完成所有操作会更好,输入新产品参数,添加输入并在提交表单后,使用其 ID 创建产品,链接输入的 join-table 记录等:

场景:

Given i open a new form to create a "product"
And i fill the "product" parameters in the form
And i add with cocoon a dynamic field to link the product with an "input" that exists in order to create a new record in the join table "product_inputs"
And i click the "submit" button
Then the ID of the product created is submited in the new "product_inputs" table record, linked with the "input" id

我设置的模型如下:

class Product < ApplicationRecord
belongs_to :user, inverse_of: :products
  has_many :product_inputs
  has_many :inputs, through: :product_inputs, :class_name => 'Input'
  accepts_nested_attributes_for :inputs
  accepts_nested_attributes_for :product_inputs, :reject_if => :all_blank, :allow_destroy => true

  validates :name, :presence => true
  validates :description, :presence => true
  validates_presence_of :user_id
end


class Input < ApplicationRecord
belongs_to :user, inverse_of: :inputs
  has_many :input_providers
  has_many :providers, through: :input_providers
  has_many :product_inputs
  has_many :products, through: :product_inputs
  accepts_nested_attributes_for :products
  accepts_nested_attributes_for :product_inputs, :reject_if => :all_blank
  accepts_nested_attributes_for :providers
  accepts_nested_attributes_for :input_providers, :reject_if => :all_blank, :allow_destroy => true

  validates :name, :presence => true
  validates :description, :presence => true
  validates_presence_of :user_id
end


class ProductInput < ApplicationRecord
belongs_to :product
  belongs_to :input
  accepts_nested_attributes_for :input, :reject_if => :all_blank
  accepts_nested_attributes_for :product, :reject_if => :all_blank

  validates_presence_of :input
  validates_presence_of :product
  validates :quantity, numericality: { only_integer: true }, :allow_nil => true
end

产品控制器所需参数:

    def product_params
      parameters = params.require(:product).permit(:id, :name, :description, :stock, :price, :user_id,
                                        product_inputs_attributes: [:id, :input_id, :product_id, :quantity, :_destroy,
                                        input_attributes: [:id, :name, :description, :_destroy]])
      parameters[:user_id] = current_user.id
      parameters
    end

产品_形式:

<%= simple_form_for @product, :html => { class: "smart-form product_validations" } do |f| %>
    <fieldset>
      <div class="row">
        <section class="col col-4">
          <%= f.input :name, placeholder: 'Product Name', label: false %>
        </section>
        <section class="col col-4">
          <%= f.input :description, placeholder: 'Enter Description', label: false %>
        </section>
        <section class="col col-4">
          <%= f.input :stock, placeholder: 'Stock', label: false %>
        </section>
      </div>
      <% if params[:id] %>
        <div class="row">
          <section class="col col-6">
            <%= link_to_add_association 'add an input', f, :product_inputs, class: 'btn btn-success btn-sm' %>
          </section>
        </div>
        <div class="row smart-form">
          <section id="inputs" class="col col-12">
            <%= f.simple_fields_for :product_inputs do |product_input| %>
                <%= render 'product_input_fields', :f => product_input %>
            <% end %>
          </section>
        </div>
      <% end %>
    </fieldset>
    <footer>
      <%= link_to 'Cancel', products_path, class: 'btn btn-default' %>
      <%= f.button :submit, :class => 'btn btn-primary' %>
    </footer>
<% end %>

_product_input_fields.html.erb

<div class="nested-fields product_input-fields">
  <fieldset>
    <div class="row">
      <section class="input_from_list col col-4">
        <%= f.association :input, :required => true,
                          collection: Input.order(:name),
                          prompt: 'Choose an existing input', label: false %>
      </section>
      <section class="nested-fields col col-4">
        <%= f.input :quantity, :required => true, :placeholder => 'Enter the input quantity', :label => false %>
      </section>
      <section class="col col-4">
        <%= link_to_remove_association f, class: 'remove-tag btn btn-danger btn-xs' do %>
            <div class="glyphicon glyphicon-remove"></div>
        <% end %>
      </section>
      </div>
    <div class="row">
      <section class="col col-4">
        <%= link_to_add_association 'or create a new input', f, :input, class: 'btn btn-default btn-xs add-input' %>
      </section>
    </div>
  </fieldset>
</div>

_input_fields.html.erb

<div class="nested-fields">
  <section class="col col-6">
    <div class="new_input">
      <%= f.input :name, :placeholder => 'Create this new input', :label => false %>
    </div>
  </section>
  <section class="col col-6">
    <div class="new_input">
      <%= f.input :description, :placeholder => 'Enter a Description', :label => false %>
    </div>
  </section>
  <%= link_to 'Cancel', class: 'btn btn-default' %>
</div>

Product.js

$('#inputs').on('cocoon:after-insert',
    function (e, input) {
        console.log('inserting new input ...');
        $(".product-input-fields a.add-tag").data("association-insertion-position", 'after').data("association-insertion-node", 'this');
        $(this).find('.product-input-fields').bind('cocoon:after-insert',
            function () {
                console.log('insert new input ...');
                console.log($(this));
                $(this).find(".input_from_list").remove();
                $(this).find("a.add_fields").hide();
            });
    });

$('.product-input-fields').bind('cocoon:after-insert',
    function (e) {
        console.log('replace OLD input ...');
        e.stopPropagation();
        console.log($(this));
        $(this).find(".input_from_list").remove();
        $(this).find("a.add_fields").hide();
    });

根据您的代码,您确实检查了 example project and I can verify that that works for rails 5. However in your code I see you are missing the inverse_of: specifiers of the has_many associations. See recent commits

Rails 5 已更改,因此默认需要 belongs_to 关系。保存新项目时,id 仅在保存后设置,因此验证将失败。但是,正确指定 inverse_of rails 可以正确验证您的新项目。