Rails has_many 通过带有附加属性的表单 [英] Rails has_many through form with additional attributes
问题描述
我正在尝试创建一个表单,允许用户向活动添加/编辑/删除位置.我目前找到的所有示例要么针对 HABTM
表单(不允许编辑 has_many through
配置中存在的其他属性),要么只列出现有的关系.
I am attempting to create a form that allows a user to add/edit/remove locations to a campaign. All the examples I have currently found are either for HABTM
forms (that do not allow the editing of additional attributes that exist in a has_many through
configuration) or only list out the existing relationships.
下面的图片显示了我正在尝试完成的任务.
Below is an image showing what I am trying to accomplish.
该列表将显示每个可用位置.通过campaign_locations 模型有关系的位置将被检查,并且它们的campaign_location 特定属性可编辑.未选中的位置应该能够被选中,输入特定于 Campaign_location 的数据,并在提交时创建新关系.
The list would show every available location. Locations that have a relationship via the campaign_locations model will be checked and have their campaign_location specific attributes editable. Locations that are non-checked should be able to be checked, campaign_location specific data entered, and a new relationship created upon submission.
下面是我目前已经实现的代码.我曾尝试使用 collection_check_boxes
,它非常接近我所需要的,只是它不允许我编辑 Campaign_location 属性.
Below is the code I currently have implemented. I have tried making use of collection_check_boxes
, which is very close to what I need except it does not allow me to edit the campaign_location attributes.
我已经能够成功编辑/删除现有的campaign_locations,但我不知道如何将其合并以显示所有可用的位置(如所附图片).
I have been able to successfully edit/remove existing campaign_locations, but I cannot figure out how to incorporate this to also show all available locations (like the attached image).
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
<小时>
查看
campaign/_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 的一部分
<input name="campaign[campaign_locations_attributes][0][_destroy]" type="hidden" value="true" /><input id="campaign_campaign_locations_attributes_0__destroy" name="campaign[campaign_locations_attributes][0][_destroy]" type="checkbox" value="false" />
<label for="campaign_campaign_locations_attributes_0_LOCATION 1">Location 1</label>
<label for="campaign_campaign_locations_attributes_0_pickup_time_start">Pickup time start</label>
<input id="campaign_campaign_locations_attributes_0_pickup_time_start" name="campaign[campaign_locations_attributes][0][pickup_time_start]" type="datetime" />
<label for="campaign_campaign_locations_attributes_0_pickup_time_end">Pickup time end</label>
<input id="campaign_campaign_locations_attributes_0_pickup_time_end" name="campaign[campaign_locations_attributes][0][pickup_time_end]" type="datetime" />
<input id="campaign_campaign_locations_attributes_0_location_id" name="campaign[campaign_locations_attributes][0][location_id]" type="hidden" value="1" />
<input id="campaign_campaign_locations_attributes_0_pickup_timezone" name="campaign[campaign_locations_attributes][0][pickup_timezone]" type="hidden" value="EST" />
<input name="campaign[campaign_locations_attributes][1][_destroy]" type="hidden" value="true" /><input id="campaign_campaign_locations_attributes_1__destroy" name="campaign[campaign_locations_attributes][1][_destroy]" type="checkbox" value="false" />
<label for="campaign_campaign_locations_attributes_1_LOCATION 2">Location 2</label>
<label for="campaign_campaign_locations_attributes_1_pickup_time_start">Pickup time start</label>
<input id="campaign_campaign_locations_attributes_1_pickup_time_start" name="campaign[campaign_locations_attributes][1][pickup_time_start]" type="datetime" />
<label for="campaign_campaign_locations_attributes_1_pickup_time_end">Pickup time end</label>
<input id="campaign_campaign_locations_attributes_1_pickup_time_end" name="campaign[campaign_locations_attributes][1][pickup_time_end]" type="datetime" />
<input id="campaign_campaign_locations_attributes_1_location_id" name="campaign[campaign_locations_attributes][1][location_id]" type="hidden" value="2" />
<input id="campaign_campaign_locations_attributes_1_pickup_timezone" name="campaign[campaign_locations_attributes][1][pickup_timezone]" type="hidden" value="EST" />
<input name="campaign[campaign_locations_attributes][2][_destroy]" type="hidden" value="true" /><input id="campaign_campaign_locations_attributes_2__destroy" name="campaign[campaign_locations_attributes][2][_destroy]" type="checkbox" value="false" />
<label for="campaign_campaign_locations_attributes_2_LOCATION 3">Location 3</label>
<label for="campaign_campaign_locations_attributes_2_pickup_time_start">Pickup time start</label>
<input id="campaign_campaign_locations_attributes_2_pickup_time_start" name="campaign[campaign_locations_attributes][2][pickup_time_start]" type="datetime" />
<label for="campaign_campaign_locations_attributes_2_pickup_time_end">Pickup time end</label>
<input id="campaign_campaign_locations_attributes_2_pickup_time_end" name="campaign[campaign_locations_attributes][2][pickup_time_end]" type="datetime" />
<input id="campaign_campaign_locations_attributes_2_location_id" name="campaign[campaign_locations_attributes][2][location_id]" type="hidden" value="3" />
<input id="campaign_campaign_locations_attributes_2_pickup_timezone" name="campaign[campaign_locations_attributes][2][pickup_timezone]" type="hidden" value="EST" />
推荐答案
您遇到的问题是空白位置尚未实例化,因此您的视图无法为其构建表单元素.要解决此问题,您需要在控制器的 new
和 edit
操作中构建空白位置.
The problem you're running into is that the blank locations haven't been instantiated, so your view has nothing to build form elements for. To fix this, you need to build the blank locations in your controller's new
and edit
actions.
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
然后,在您的 edit
和 update
操作中,您需要从 params
哈希中删除任何留空的位置,当用户提交表单.
Then, in your edit
and update
actions, you need to remove any locations that have been left blank from the params
hash when the user submits the form.
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
设置一个隐藏字段.
Also, I think you'll need a hidden field for the location_id
.
这篇关于Rails has_many 通过带有附加属性的表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!