Rails has_many通过表单和其他属性

我正在尝试创建一个表单,允许用户向广告系列添加/编辑/删除位置。 我目前找到的所有示例都是针对HABTM表单(不允许编辑has_many through配置中存在的其他属性)或仅列出现有关系。

下面的图片显示了我想要完成的任务。

要添加到广告系列的地点列表

该列表将显示每个可用位置。 将检查通过campaign_locations模型建立关系的位置,并使其campaign_location特定属性可编辑。 应该能够检查未经检查的位置,输入的campaign_location特定数据以及在提交时创建的新关系。

以下是我目前实施的代码。 我尝试过使用collection_check_boxes ,这非常接近我的需要,除了它不允许我编辑campaign_location属性。

我已经能够成功编辑/删除现有的campaign_locations,但我无法弄清楚如何将其合并以显示所有可用的位置(如附图)。


楷模

campaign.rb

 class Campaign < ActiveRecord::Base has_many :campaign_locations has_many :campaign_products has_many :products, through: :campaign_products has_many :locations, through: :campaign_locations accepts_nested_attributes_for :campaign_locations, allow_destroy: true end 

campaign_location.rb

 class CampaignLocation < ActiveRecord::Base belongs_to :campaign belongs_to :location end 

location.rb

 class Location < ActiveRecord::Base has_many :campaign_locations has_many :campaigns, through: :campaign_locations end 

视图

运动/ _form.html.haml

 = form_for @campaign do |campaign_form| # this properly shows existing campaign_locations, and properly allows me # to edit the campaign_location attributes as well as destroy the relationship = campaign_form.fields_for :campaign_locations do |cl_f| = cl_f.check_box :_destroy, {:checked => cl_f.object.persisted?}, false, true = cl_f.label cl_f.object.location.title = cl_f.datetime_field :pickup_time_start = cl_f.datetime_field :pickup_time_end = cl_f.text_field :pickup_timezone # this properly lists all available locations as well as checks the ones # which have a current relationship to the campaign via campaign_locations = campaign_form.collection_check_boxes :location_ids, Location.all, :id, :title 

表单HTML的一部分

                          

您遇到的问题是空白位置尚未实例化,因此您的视图无法构建表单元素。 要解决此问题,您需要在控制器的new操作和edit操作中构建空白位置。

 class CampaignController < ApplicationController def new empty_locations = Location.where.not(id: @campaign.locations.pluck(:id)) empty_locations.each { |l| @campaign.campaign_locations.build(location: l) } end def edit # do same thing as new end end 

然后,在editupdate操作中,您需要删除用户提交表单时从params哈希中留空的所有位置。

 class CampaignController < ApplicationController def create params[:campaign][:campaign_locations].reject! do |cl| cl[:pickup_time_start].blank? && cl[:pickup_time_end].blank? && cl[:pickup_timezone].blank? end end def update # do same thing as create end end 

另外,我认为你需要为location_id一个隐藏字段。

您应该在模型和表单中添加非模型属性复选框,表示是保存还是删除关系。 向表单添加一个带有关系id的隐藏字段,最后根据复选框覆盖accepts_nested_attributes_for以保存或销毁,并调用super。

 class CampaignLocation < ActiveRecord::Base belongs_to :campaign belongs_to :location # Returns true if a saved record, used by form def option_included new_record? ? false : true end end class Campaign < ActiveRecord::Base ... accepts_nested_attributes_for :campaign_locations, allow_destroy: true def campaign_locations_attributes=(attributes) attributes.values.each do |attribute| attribute[:_destroy] = true if attribute[:option_included] != '1' attribute.delete(:option_included) end super end end 

表格:

 = form_for @campaign do |campaign_form| - locations.each do |location| = campaign_form.fields_for, :campaign_locations, @campaign.campaign_locations.find_or_initialize_by(location_id: location.id) do |cf| = cf.check_box :option_included = location.name = cf.hidden_field :disease_question_option_id = cf.datetime_field :pickup_time_start = cf.datetime_field :pickup_time_end = cf.text_field :pickup_timezone 

如果存在已保存的关系, option_included将返回true,否则返回false。