Rails has_many through a form with additional attributes

I am trying to create a form that allows the user to add / edit / delete locations in a campaign. All the examples that I have found at present are either for HABTM forms (which do not allow editing additional attributes existing in the has_many through configuration), or list only existing relationships.

Below is an image showing what I'm trying to accomplish.

List of locations to add to a campaign

The list will show all available places. Locations that are related using the campaign_locations model and have their own campaign_location-specific attributes for editing will be checked. Verifiable locations that are not verified must be verified, specific campaign details and new relationships created after submission must be entered.

Below is the code I just executed. I tried using collection_check_boxes , which is very close to what I need, but it does not allow me to edit campaign_location attributes.

I was able to successfully edit / delete existing campaign_locations, but I can’t figure out how to enable it to also show all available places (for example, an attached image).


Models

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 

View

campaigns /_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 form portion

  <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" /> 
+5
source share
2 answers

The problem you are facing is that the empty spaces were not created, so your view has nothing to create form elements. To fix this, you need to create empty locations in the new and edit controller 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 

Then in your edit and update actions, you need to remove all locations that were left empty 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 

Also, I think you will need a hidden field for location_id .

+2
source

You must add a flag for the non-model attribute to your model and form, which means saving or deleting the relationship. Add a hidden field with the identifier of the relationship to the form, and finally redefine accepts_nested_attributes_for to save or destroy based on this flag and call 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 

The form:

 = 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 will return true if there is a stored relation, otherwise false.

0
source

Source: https://habr.com/ru/post/1214103/


All Articles