Rails has_many通过表单与连接模型中的复选框和额外字段

ok3*_*k32 16 forms ruby-on-rails nested-forms has-many-through ruby-on-rails-3

我正在尝试解决一个非常常见的(我认为)任务.

有三种型号:

class Product < ActiveRecord::Base  
  validates :name, presence: true

  has_many :categorizations
  has_many :categories, :through => :categorizations

  accepts_nested_attributes_for :categorizations
end

class Categorization < ActiveRecord::Base
  belongs_to :product
  belongs_to :category

  validates :description, presence: true # note the additional field here
end

class Category < ActiveRecord::Base
  validates :name, presence: true
end
Run Code Online (Sandbox Code Playgroud)

我的问题始于产品新/编辑表格.

在创建产品时,我需要检查它所属的类别(通过复选框).我知道可以通过创建名称为'product [category_ids] []'的复选框来完成.但是我还需要输入每个已检查关系的描述,这些关系将存储在连接模型(Categorization)中.

我在复杂的表格,habtm复选框等上看到了那些漂亮的Railscasts.我一直在寻找StackOverflow.但我没有成功.

我发现一篇文章描述了与我几乎完全相同的问题.最后一个答案对我来说有点意义(看起来这是正确的方法).但它实际上并不是很好(即如果验证失败).我希望类别始终以相同的顺序显示(在新的/编辑表单中;在验证之前/之后)和复选框,以便在验证失败时保持原样等等.

任何人都赞赏.我是Rails的新手(从CakePHP转换)所以请耐心等待并尽可能详细地写.请以正确的方式指出我!

谢谢.:)

ok3*_*k32 30

看起来我想通了!这是我得到的:

我的模特:

class Product < ActiveRecord::Base
  has_many :categorizations, dependent: :destroy
  has_many :categories, through: :categorizations

  accepts_nested_attributes_for :categorizations, allow_destroy: true

  validates :name, presence: true

  def initialized_categorizations # this is the key method
    [].tap do |o|
      Category.all.each do |category|
        if c = categorizations.find { |c| c.category_id == category.id }
          o << c.tap { |c| c.enable ||= true }
        else
          o << Categorization.new(category: category)
        end
      end
    end
  end

end

class Category < ActiveRecord::Base
  has_many :categorizations, dependent: :destroy
  has_many :products, through: :categorizations

  validates :name, presence: true
end

class Categorization < ActiveRecord::Base
  belongs_to :product
  belongs_to :category

  validates :description, presence: true

  attr_accessor :enable # nice little thingy here
end
Run Code Online (Sandbox Code Playgroud)

表格:

<%= form_for(@product) do |f| %>
  ...
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <%= f.fields_for :categorizations, @product.initialized_categorizations do |builder| %>
    <% category = builder.object.category %>
    <%= builder.hidden_field :category_id %>

    <div class="field">
      <%= builder.label :enable, category.name %>
      <%= builder.check_box :enable %>
    </div>

    <div class="field">
      <%= builder.label :description %><br />
      <%= builder.text_field :description %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
Run Code Online (Sandbox Code Playgroud)

和控制器:

class ProductsController < ApplicationController
  # use `before_action` instead of `before_filter` if you are using rails 5+ and above, because `before_filter` has been deprecated/removed in those versions of rails.
  before_filter :process_categorizations_attrs, only: [:create, :update]

  def process_categorizations_attrs
    params[:product][:categorizations_attributes].values.each do |cat_attr|
      cat_attr[:_destroy] = true if cat_attr[:enable] != '1'
    end
  end

  ...

  # all the rest is a standard scaffolded code

end
Run Code Online (Sandbox Code Playgroud)

从第一眼看它工作得很好.我希望它不会以某种方式破坏.. :)

谢谢大家.特别感谢Sandip Ransing参与讨论.我希望它对像我这样的人有用.

  • 做得很好。我觉得可能有一种更简单的方法。 (2认同)