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 table
即categorizations
视图表单看起来像 -
# 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 关系表单验证孩子