Rails has_many 通过表单在连接模型中带有复选框和额外字段

Posted

技术标签:

【中文标题】Rails has_many 通过表单在连接模型中带有复选框和额外字段【英文标题】:Rails has_many through form with checkboxes and extra field in the join model 【发布时间】:2012-02-28 18:40:24 【问题描述】:

我正在尝试解决一个非常常见的(如我所想的)任务。

共有三种型号:

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

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

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

我在复杂的表单、habtm 复选框等上看到了那些漂亮的 Railscast。我几乎没有搜索 ***。但我没有成功。

我找到了一个post,它描述的问题与我的几乎完全相同。最后一个答案对我来说很有意义(看起来这是正确的方法)。但它实际上并不能很好地工作(即如果验证失败)。我希望类别始终以相同的顺序显示(在新/编辑表单中;在验证之前/之后),并且如果验证失败等,复选框保持在原来的位置。

感谢任何想法。 我是 Rails 新手(从 Cakephp 切换),所以请耐心等待并尽可能详细地编写。请以正确的方式指出我!

谢谢。 :)

【问题讨论】:

【参考方案1】:

使用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

【讨论】:

谢谢!但是您的解决方案似乎与验证和其他内容不兼容。复选框总是被选中,即使我取消选中它们也会被忽略(在验证失败或验证通过关系后它们会返回)。此外,我想始终以相同的顺序列出类别(在您的示例中,您在已与产品关联的类别之后附加未分配的类别)。有什么想法吗? 您可以在收货时订购@product.categorizations 你能粘贴通过的参数吗? 好吧.. 现在我已经尝试了你的整个代码(复制粘贴它)。而且我不得不说它根本不起作用。:) 你在发帖之前自己尝试过吗?即使我通过修复它的一些错误使它工作,仍然存在一些大问题(即在数据库中创建具有 NULL category_id 的分类等)。您能否尝试使其正常工作(如我在最初的问题中所述)?哦,顺便说一句,我在 Rails 3.2 上。 好吧。上面的代码是给你正确的方向..不要指望一切都会像你预期的那样顺利..你必须迎合代码没有执行的事情。【参考方案2】:

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

我的模型:

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| %>
  ...
  <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 %>

还有控制器:

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

乍一看,它工作得很好。我希望它不会以某种方式破裂.. :)

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

【讨论】:

做得很好。不过,我觉得可能有更简单的方法。 非常感谢分享,我还必须补充***.com/a/15920542/148421,因为我的值没有被保存并且我错过了如何允许嵌套属性 使用强参数需要添加 categorizations_attributes: [:id, :category_id, :description, :_destroy]【参考方案3】:

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

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

【讨论】:

这不允许操作要求的额外连接模型字段

以上是关于Rails has_many 通过表单在连接模型中带有复选框和额外字段的主要内容,如果未能解决你的问题,请参考以下文章

Rails has_many :通过连接模型中的额外属性查找

使用带有 Rails 的改革 gem,我如何填充 has_many :通过嵌套模型

如何在has_many中获取模型的属性:通过Rails 5中的关联

ruby on rails has_many 关系表单验证孩子

Rails 4,通过连接表在has_many中指定自定义外键?

带有has_many的模型中的属性总和:通过关联在Rails 5中不起作用