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

我正试图解决一个非常常见的(我认为)任务。

有三种型号:

class Product  :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 

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

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

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

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

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

谢谢。 🙂

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

我的模特:

 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 

表格:

 <%= form_for(@product) do |f| %> ... 
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :categorizations, @product.initialized_categorizations do |builder| %> <% category = builder.object.category %> <%= builder.hidden_field :category_id %>
<%= builder.label :enable, category.name %> <%= builder.check_box :enable %>
<%= builder.label :description %>
<%= builder.text_field :description %>
<% end %>
<%= f.submit %>
<% end %>

和控制器:

 class ProductsController < ApplicationController 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 

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

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

使用accepts_nested_attributes_for插入intermediate tablecategorizations视图表单看起来像 –

 # make sure to build product categorizations at controller level if not already class ProductsController < ApplicationController before_filter :build_product, :only => [:new] before_filter :load_product, :only => [:edit] before_filter :build_or_load_categorization, :only => [:new, :edit] def create @product.attributes = params[:product] if @product.save flash[:success] = I18n.t('product.create.success') redirect_to :action => :index else render_with_categorization(:new) end end def update @product.attributes = params[:product] if @product.save flash[:success] = I18n.t('product.update.success') redirect_to :action => :index else render_with_categorization(:edit) end end private def build_product @product = Product.new end def load_product @product = Product.find_by_id(params[:id]) @product || invalid_url end def build_or_load_categorization Category.where('id not in (?)', @product.categories).each do |c| @product.categorizations.new(:category => c) end end def render_with_categorization(template) build_or_load_categorization render :action => template end end 

内部视图

 = form_for @product do |f| = f.fields_for :categorizations do |c| %label= c.object.category.name = c.check_box :category_id, {}, c.object.category_id, nil %label Description = c.text_field :description 

我只是做了以下事情。 它对我有用..

 <%= f.label :category, "Category" %> <%= f.select :category_ids, Category.order('name ASC').all.collect {|c| [c.name, c.id]}, {} %>