Rails Simple_form (nested) - duplicate entries on save - ruby-on-rails

I am using Rails 5 and simple form to build an app. I'm trying to have each nested model display its fields in a different tab or column using Twitter Bootstrap. I now have this case, where when creating a new or editing/updating an existing parent record (here "document type", the existing records of the nested model (here "keywords") get duplicated. Deletion using :_destroy does not function either.
I am able to create and delete separately. Now looking to add it to the parent and allow edit. It is something I would be using much more. be great to get it to work. How does this come / to fix it?
These are my models:
class Documenttype < ApplicationRecord
has_many :annotations, dependent: :restrict_with_error
has_many :documents, dependent: :restrict_with_error
has_many :tagtypes, dependent: :restrict_with_error
has_many :keywords, dependent: :destroy
accepts_nested_attributes_for :keywords, allow_destroy: true
validates :name, presence: true, uniqueness: true, length: { minimum: 5 }
scope :active, -> { where(active: true) }
end
class Keyword < ApplicationRecord
belongs_to :documenttype
validates :keywords, presence: true
validates :language, presence: true
end
this is the form:
<%= simple_form_for #documenttype, html: { class: 'form-horizontal', multipart: true },
wrapper: :horizontal_form,
wrapper_mappings: {
check_boxes: :horizontal_radio_and_checkboxes,
radio_buttons: :horizontal_radio_and_checkboxes,
boolean: :horizontal_boolean
} do |f| %>
<div class="btn-toolbar btn-group" role="toolbar">
<%= f.button :submit, :class => "btn btn-xs btn-default" %> <%= link_to 'List' , documenttypes_path, :class => "btn btn-xs btn-default" %>
</div>
<h4>Document Type</h4>
<div class="col-md-6">
<%= f.error_notification %>
<%= f.input :name, placeholder: 'Enter name' %>
<%= f.input :description, placeholder: 'Description' %>
<%= f.input :active, as: :boolean %>
</div>
<div class="col-md-6">
<%= f.simple_fields_for :keywords do |ff| %>
<div class="panel panel-body panel-default">
<div class="col-md-3">
<%= ff.input :language, :collection => ["NL","EN"], :label => false %>
</div>
<div class="col-md-8">
<%= ff.input :keywords, placeholder: 'add keywords separated by " ; " - example: document number; document date' %>
</div>
<div class="col-md-1">
<%= ff.check_box :_destroy, label: "del"%>
</div>
</div>
<% end -%>
</div>
<% end -%>

Learning from this issue:
to destroy nested records, ensure that (besides :id) also :_destroy is added to strongparams
to use validations for nested fields, use accepts_nested_attributes_for :keywords, allow_destroy: true, reject_if: :something_to_check

Related

Rails 2 level nested forms

I slightly modified and extended the example for building nested forms to implement the second level of nesting. Everything is displayed perfectly on the form. Data for the person is displayed at both nesting levels correctly. The corresponding JS scripts work to add and remove nested forms. All 3 are generated using scaffold.
But when I click on update, only the main form and the first nesting level (addresses) are updated. The second nesting level (nested addresses) is not updated. Although I also get parameters from the second nesting level in the controller ("name"=>"UPDATE NAME OF NESTED ADDRESS").
{
"_method"=>"patch",
"authenticity_token"=>"VZ09CR-aO2D4Wv3AwEa5PHXo-mA_--c6QPUN6f0Gb_9SJJSL2gIwzCl4G4SbzRy2t3wxJHytBWiPwysNJMrWgg",
"person"=>{
"first_name"=>"Liz",
"last_name"=>"Smith",
"addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"kind"=>"Some kind",
"street"=>"Some street",
"nested_addresses_attributes"=>{
"0"=>{
"_destroy"=>"false",
"name"=>"UPDATE NAME OF NESTED ADDRESS",
"id"=>"1"
}
},
"id"=>"10"}}},
"commit"=>"Update Person",
"controller"=>"people",
"action"=>"update",
"id"=>"3"}
I understand that even the first nesting level is magically handled behind the scenes, but I don't understand how? And how to handle the second level as well? In general, the Create Update, Delete methods do not work for the second nesting level.
Models
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person, :dependent => :destroy
has_many :nested_addresses, through: :addresses, inverse_of: :person, :dependent => :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :first_name, presence: true
validates :last_name, presence: true
end
class NestedAddress < ApplicationRecord
belongs_to :address
validates :name, presence: true
end
class Address < ApplicationRecord
belongs_to :person, optional: true
has_many :nested_addresses, inverse_of: :address, :dependent => :destroy
accepts_nested_attributes_for :nested_addresses, allow_destroy: true, reject_if: :all_blank
validates :kind, presence: true
validates :street, presence: true
end
Controllers
def person_params
params.require(:person).permit(:first_name, :last_name, addresses_attributes: [:id, :kind, :street, :_destroy], nested_addresses_attributes: [:id, :name, :_destroy])
end
def address_params
params.require(:address).permit(:kind, :street, :person_id, nested_addresses_attributes: [:id, :name, :_destroy])
end
def nested_address_params
params.require(:nested_address).permit(:name, :address_id)
end
people/_form.html.erb
<%= form_with model: #person, local: true do |f| %>
<%= render "shared/validation-messages", object: #person %>
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control' %>
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control' %>
<br>
<fieldset>
<legend>Addresses:</legend>
<%= f.fields_for :addresses do |addresses_form| %>
<%= render "address_fields", f: addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Addresses", f, :addresses, 'btn btn-outline-secondary'%>
</fieldset>
<br>
<%= f.submit class: 'btn btn-success' %>
<% if params[:action] === "edit" && params[:controller] === "people" %>
<%= link_to "Delete Person", person_path(#person), method: :delete, data: { confirm: "Are You Sure?" }, class: 'btn btn-outline-danger' %>
<% end %>
<% end %>
people/_address_fields.html.erb
<div class="card nested-fields">
<div class="card-header">
<div><%= f.object.id %></div>
</div>
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :kind %>
<%= f.text_field :kind, class: 'form-control' %>
</div>
<div>
<%= f.label :street %>
<%= f.text_field :street, class: 'form-control' %>
</div>
<br>
<fieldset>
<legend>Nested addresses:</legend>
<%= f.fields_for :nested_addresses do |nested_addresses_form| %>
<%= render "nested_address_fields", f: nested_addresses_form %>
<% end %>
<br>
<%= link_to_add_fields "Add Nested Addresses", f, :nested_addresses, 'btn btn-outline-secondary btn-sm' %>
</fieldset>
<br>
<div>
<%= link_to "Remove address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
</div>
people/_nested_address_fields.html.erb
<div class="card nested-fields">
<div class="card-body">
<%= f.hidden_field :_destroy %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<br>
<div>
<%= link_to "Remove nested address", '#', class: "remove_fields btn btn-outline-danger btn-sm" %>
</div>
</div>
<br>
</div>
helpers/application_helper.rb
def link_to_add_fields(name, f, association, cl)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: 'add_fields ' + cl, data: {id: id, fields: fields.gsub("\n", "")}, role: 'button')
end
Your strong parameters permit call should reflect the actual nesting structure of parameters:
def person_params
params.require(:person).permit(
:first_name, :last_name,
addresses_attributes: [
:id, :kind, :street, :_destroy,
{ nested_addresses_attributes: [:id, :name, :_destroy] } # <= note the hash
]
)
end

rails 5 with cocoon nested form records creation all at once

i wanted to know if it possible, giving the following scenario, create a record in a join table using has_many :through relations in Rails 5 with cocoon and simple_form using the :id of the record that it's not created yet. I ask this, because the only way i found to create these records in the "join-table" is to edit the product and then add inputs, but for the user experience i believe it would be better to do all at once, enter the new product parameters, add inputs and after form submission, create the product with it's ID, the join-table records for the linked inputs, etc:
Scenario:
Given i open a new form to create a "product"
And i fill the "product" parameters in the form
And i add with cocoon a dynamic field to link the product with an "input" that exists in order to create a new record in the join table "product_inputs"
And i click the "submit" button
Then the ID of the product created is submited in the new "product_inputs" table record, linked with the "input" id
The Models i setup are the following:
class Product < ApplicationRecord
belongs_to :user, inverse_of: :products
has_many :product_inputs
has_many :inputs, through: :product_inputs, :class_name => 'Input'
accepts_nested_attributes_for :inputs
accepts_nested_attributes_for :product_inputs, :reject_if => :all_blank, :allow_destroy => true
validates :name, :presence => true
validates :description, :presence => true
validates_presence_of :user_id
end
class Input < ApplicationRecord
belongs_to :user, inverse_of: :inputs
has_many :input_providers
has_many :providers, through: :input_providers
has_many :product_inputs
has_many :products, through: :product_inputs
accepts_nested_attributes_for :products
accepts_nested_attributes_for :product_inputs, :reject_if => :all_blank
accepts_nested_attributes_for :providers
accepts_nested_attributes_for :input_providers, :reject_if => :all_blank, :allow_destroy => true
validates :name, :presence => true
validates :description, :presence => true
validates_presence_of :user_id
end
class ProductInput < ApplicationRecord
belongs_to :product
belongs_to :input
accepts_nested_attributes_for :input, :reject_if => :all_blank
accepts_nested_attributes_for :product, :reject_if => :all_blank
validates_presence_of :input
validates_presence_of :product
validates :quantity, numericality: { only_integer: true }, :allow_nil => true
end
Product Controller required params:
def product_params
parameters = params.require(:product).permit(:id, :name, :description, :stock, :price, :user_id,
product_inputs_attributes: [:id, :input_id, :product_id, :quantity, :_destroy,
input_attributes: [:id, :name, :description, :_destroy]])
parameters[:user_id] = current_user.id
parameters
end
Product _form:
<%= simple_form_for #product, :html => { class: "smart-form product_validations" } do |f| %>
<fieldset>
<div class="row">
<section class="col col-4">
<%= f.input :name, placeholder: 'Product Name', label: false %>
</section>
<section class="col col-4">
<%= f.input :description, placeholder: 'Enter Description', label: false %>
</section>
<section class="col col-4">
<%= f.input :stock, placeholder: 'Stock', label: false %>
</section>
</div>
<% if params[:id] %>
<div class="row">
<section class="col col-6">
<%= link_to_add_association 'add an input', f, :product_inputs, class: 'btn btn-success btn-sm' %>
</section>
</div>
<div class="row smart-form">
<section id="inputs" class="col col-12">
<%= f.simple_fields_for :product_inputs do |product_input| %>
<%= render 'product_input_fields', :f => product_input %>
<% end %>
</section>
</div>
<% end %>
</fieldset>
<footer>
<%= link_to 'Cancel', products_path, class: 'btn btn-default' %>
<%= f.button :submit, :class => 'btn btn-primary' %>
</footer>
<% end %>
_product_input_fields.html.erb
<div class="nested-fields product_input-fields">
<fieldset>
<div class="row">
<section class="input_from_list col col-4">
<%= f.association :input, :required => true,
collection: Input.order(:name),
prompt: 'Choose an existing input', label: false %>
</section>
<section class="nested-fields col col-4">
<%= f.input :quantity, :required => true, :placeholder => 'Enter the input quantity', :label => false %>
</section>
<section class="col col-4">
<%= link_to_remove_association f, class: 'remove-tag btn btn-danger btn-xs' do %>
<div class="glyphicon glyphicon-remove"></div>
<% end %>
</section>
</div>
<div class="row">
<section class="col col-4">
<%= link_to_add_association 'or create a new input', f, :input, class: 'btn btn-default btn-xs add-input' %>
</section>
</div>
</fieldset>
</div>
_input_fields.html.erb
<div class="nested-fields">
<section class="col col-6">
<div class="new_input">
<%= f.input :name, :placeholder => 'Create this new input', :label => false %>
</div>
</section>
<section class="col col-6">
<div class="new_input">
<%= f.input :description, :placeholder => 'Enter a Description', :label => false %>
</div>
</section>
<%= link_to 'Cancel', class: 'btn btn-default' %>
</div>
Product.js
$('#inputs').on('cocoon:after-insert',
function (e, input) {
console.log('inserting new input ...');
$(".product-input-fields a.add-tag").data("association-insertion-position", 'after').data("association-insertion-node", 'this');
$(this).find('.product-input-fields').bind('cocoon:after-insert',
function () {
console.log('insert new input ...');
console.log($(this));
$(this).find(".input_from_list").remove();
$(this).find("a.add_fields").hide();
});
});
$('.product-input-fields').bind('cocoon:after-insert',
function (e) {
console.log('replace OLD input ...');
e.stopPropagation();
console.log($(this));
$(this).find(".input_from_list").remove();
$(this).find("a.add_fields").hide();
});
From your code you did check the example project and I can verify that that works for rails 5. However in your code I see you are missing the inverse_of: specifiers of the has_many associations. See recent commits
Rails 5 has been changed so the belongs_to relation is required by default. When saving new items the id is only set after saving, so validation will fail. However, correctly specifying the inverse_of rails can correctly validate your new items.

Rails 5 Select Not Validating

I have an issue where a validation is suddenly not working. The field is a select box, and it's not setting the value. So when I try to submit the form, it says that Payee can't be blank. This is a 1 to 1 relationship between Expense and Payee. What happened? Do you see any issues here?
The field in question. It's in a helper but I've tried putting it back into the form with no luck.
def expense_payee_id_field(f)
f.select :payee_id, Payee.all.collect { |p| [p.display_name, p.id] }, { prompt: "Choose Payee"},{class:"fluid ui dropdown"}
end
The form:
<%= form_for #expense, html: { :class => "ui form segment" }, :remote => true do |f|%>
<div class="field">
<%= f.label :date%>
<div class="ui small input">
<%= f.date_field :date %>
</div>
</div>
<div class="field">
<%= f.label :amount %>
<div class="ui small input">
<%= f.text_field :amount %>
</div>
</div>
<div class="field">
<%= f.label :check_number %>
<div class="ui small input">
<%= f.text_field :check_number %>
</div>
</div>
<div class="field">
<%= f.label :payee %>
<div class="ui input">
<%= expense_payee_id_field(f)%>
</div>
</div>
<%= f.submit class: "ui blue button" %>
<% end %>
My models:
expense.rb
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories, inverse_of: :expense
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
validates_associated :expense_expense_categories
validates :date, presence: true
validates_numericality_of :amount, presence: true, greater_than: 0
validates :expense_expense_category_ids, presence: true
validates_presence_of :payee_id
end
payee.rb
class Payee < ApplicationRecord
belongs_to :expense
validate :company_or_name
def full_name
[first_name, last_name].join(" ")
end
def display_name
if company.blank?
full_name
else
company
end
end
private
def company_or_name
if company.blank? && first_name.blank?
errors[:base] << "You must specify company or name"
end
end
end

Rails 4 form that saves parent id values in a children record in a join table

i'm developing in Rails 4 with bootstrap rails forms and i'm trying to make a form that saves a product to a table. The product has many inputs and the input could belong to many products. The DB is postgres.
My main issue here is to save the product_id and the input_id to a join table I've created, named "productinput". this could be an example of what i'm trying to accomplish:
productinput table values example to generate each time i generate a new product with many inputs selected from a checkbox select:
product_id: 1 input_id: 1
product_id: 1 input_id: 2
product_id: 1 input_id: 3
product_id: 1 input_id: 4
I've done most of the configuration in my app. please take a look:
**productinput model**
class Productinput < ActiveRecord::Base
belongs_to :input, inverse_of: :productinputs
belongs_to :product, inverse_of: :productinputs
validates_presence_of :user_id
validates_presence_of :input_id
validates_presence_of :product_id
end
**product model**
class Product < ActiveRecord::Base
has_many :productinputs, inverse_of: :product, :dependent => :destroy
has_many :inputs, :through => :productinputs
accepts_nested_attributes_for :inputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true
has_many :clientproducts, inverse_of: :product
has_many :worktasks, inverse_of: :product
validates_presence_of :user_id
accepts_nested_attributes_for :clientproducts, :allow_destroy => true
accepts_nested_attributes_for :worktasks
end
**input model**
class Input < ActiveRecord::Base
has_many :productinputs, inverse_of: :input, :dependent => :destroy
has_many :products, :through => :productinputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true
has_many :inputproviders, inverse_of: :input
validates_presence_of :user_id
accepts_nested_attributes_for :inputproviders
end
Then the product form i'd like to have updated with the Input field to add inputs dinamically with a multiple select:
<%= bootstrap_form_for( #product, layout: :horizontal, label_col: "col-sm-2 hide", control_col: "col-sm-12", label_errors: true) do |f| %>
<div class="container-fluid">
<div class="row">
<%= f.alert_message "Please fix the errors below." %>
</div>
<div class="row">
<div class="col-xs-12">
<%= f.text_field :name, hide_label: true, placeholder: "Name", icon: "tag" %>
<%= f.text_field :description, hide_label: true, placeholder: "Description", icon: "align-justify" %>
<%= f.text_field :stock, hide_label: true, placeholder: "Stock", icon: "book" %>
<%= f.text_field :price, hide_label: true, placeholder: "Price", icon: "usd" %>
</div>
</div>
<div class="row text-center">
<%= f.submit "Submit Product", :class => 'btn btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
products_path, :class => 'btn btn-default' %>
</div>
</div>
i've tried cocoon, nested_form, formtastic, simple_form gems, many tutorials and more that 2 weeks investigating this and i still don't understand how can i manage to do this, would you give me a hand please ?
Thanks,
This is how i manage to create children records in the join table from the parent form view:
Models:
class Product < ActiveRecord::Base
has_many :productinputs, :dependent => :destroy
has_many :inputs, through: :productinputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true
has_many :clientproducts, inverse_of: :product
has_many :worktasks, inverse_of: :product
validates_presence_of :user_id
end
class Productinput < ActiveRecord::Base
belongs_to :product
belongs_to :input
#validates_presence_of :user_id
#validates_presence_of :input_id
#validates_presence_of :product_id
end
class Input < ActiveRecord::Base
has_many :productinputs
has_many :products, through: :productinputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true
has_many :inputproviders, inverse_of: :input
validates_presence_of :user_id
accepts_nested_attributes_for :inputproviders
end
Controller:
Key here, the :inputs_ids[] fills up with an array of input elements from the "check_box_tag "product[input_ids][]"" in the form:
def product_params
parameters = params.require(:product).permit(:name, :description, :stock, :price, :input_ids => [], productinputs_attributes: [:user_id, :quantity])
parameters[:user_id] = current_user.id
parameters
end
Form:
<%= simple_form_for #product, defaults: { input_html: { class: 'form-horizontal' } } do |f| %>
<div class="row"> <%= f.error_notification id: 'user_error_message', class: 'form_error' %>
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<%= f.input :name, placeholder: 'Product Name', error: 'Username is mandatory, please specify one', label: false %>
<%= f.input :description, placeholder: 'Enter Description', label: false %>
<%= f.input :stock, placeholder: 'Stock', label: false %>
<%= f.input :price, placeholder: 'Price', label: false %>
</div>
<div class="col-xs-12 col-md-6">
<%= hidden_field_tag "product[input_ids][]", nil %>
<% Input.where("user_id = ?", current_user.id).each do |input| %>
<%= check_box_tag "product[input_ids][]", input.id,
#product.input_ids.include?(input.id), id: dom_id(input) %>
<%= label_tag dom_id(input), input.name %><br>
<% end %>
</div>
</div>
<div class="row text-center">
<%= f.button :submit, :class => 'btn btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
products_path, :class => 'btn btn-default' %>
</div>
<% end %>
Hope this helps anyone who has the same issue that i did.
Sorry if some people didn't understand what i was looking for.
P.S: just to let you know, i could simplify the multiple checkbox with simple form from this:
<%= hidden_field_tag "product[input_ids][]", nil %>
<% Input.where("user_id = ?", current_user.id).each do |input| %>
<%= check_box_tag "product[input_ids][]", input.id, #product.input_ids.include?(input.id), id: dom_id(input) %>
<%= label_tag dom_id(input), input.name %><br>
<% end %>
to this:
<%= f.association :inputs, :as => :check_boxes %>

How can I avoid ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection errors in my setup?

I have the following models in my rails app:
class SaleContact < ActiveRecord::Base
validates :key_contact_id, presence: true, uniqueness: { scope: :sales_opportunity_id, message: "Contact already added!" }
validates :sales_opportunity_id, presence: true
belongs_to :key_contact, inverse_of: :sale_contacts
belongs_to :sales_opportunity, inverse_of: :sale_contacts
has_many :phone_numbers, :through => :key_contact
accepts_nested_attributes_for :phone_numbers
end
I'm trying to create a sale_contact from the sales_opportunity screen by selecting a key_contact (assume a key_contact already exists). I would also like the ability to add a phone_number at the same time using fields_for and nested attributes.
class KeyContact < ActiveRecord::Base
validates :first_name, :last_name, :company_id, presence: true
has_many :phone_numbers, dependent: :destroy
belongs_to :company
has_many :sales_opportunities, :through => :sale_contacts
has_many :sale_contacts, dependent: :destroy
end
Assume I've already created the key_contact and assigned it to the company that owns it.
class PhoneNumber < ActiveRecord::Base
validates :number, :key_contact_id, presence: true
belongs_to :key_contact
end
Nothing magical here - just a very basic model for a phone number.
On the sales_opportunity page I can load a modal that adds a new sale_contact. It's a bootstrap modal loaded by AJAX, but I don't think that matters much (I've only included the form parts for brevity):
<%= form_for(#sale_contact, :html => {role: :form, 'data-model' => 'sale_contact'}, remote: true) do |f| %>
<% if #sale_contact.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#sale_contact.errors.count, "error") %> prohibited this sale_contact from being saved:</h2>
<ul>
<% #sale_contact.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group" id= "contact_error">
<%= f.label :key_contact_id, :class => "control-label" %>
<div id="contact_select">
<%= f.collection_select :key_contact_id, #sales_opportunity.company.key_contacts(:full_name), :id, :full_name %>
</div>
<span class="warning-block"></span>
<span class="help-block"></span>
</div>
<div class="form-group">
<%= f.label :role, :class => "control-label" %>
</br>
<%= f.select(:role, options_for_select(#roles.collect { |r| [r[0].humanize, r[0]] }, selected: #sale_contact.role), {}) %>
<span class="help-block"></span>
</div>
<div class="form-group">
<%= f.label :preference, :class => "control-label" %>
</br>
<%= f.select(:preference, options_for_select(#preferences.collect { |r| [r[0].humanize, r[0]] }, selected: #sale_contact.preference), {}) %>
<span class="help-block"></span>
</div>
<%= f.fields_for(:phone_numbers) do |phone| %>
<div class="form-group">
<%= phone.label :number, "Phone Number", :class => "control-label" %>
</br>
<%= phone.text_field :number, :placeholder => "Enter phone number (optional)" %>
<span class="help-block"></span>
</div>
<div>
<%= phone.hidden_field :key_contact_id %>
</div>
<% end %>
<div class="form-group">
<%= f.hidden_field :sales_opportunity_id, :value => #sales_opportunity.id %>
</div>
<%= f.submit "Save", class: "btn btn-large btn-success", data: { disable_with: "Submitting..." }%>
<% end %>
And from my sale_contact_controller new action:
def new
#sale_contact = SaleContact.new
#sale_contact.phone_numbers.build
#sales_opportunity = SalesOpportunity.find(params[:sales_opportunity_id])
#company = #sales_opportunity.company
#roles = SaleContact.roles
#preferences = SaleContact.preferences
render :modal_form
end
def sale_contact_params
params.require(:sale_contact).permit(:key_contact_id, :sales_opportunity_id, :role, :preference, phone_numbers_attributes: [:number, :id])
end
I run a javascript snippet when the modal is being retrieved via AJAX or the key_contact select dropdown changes to bring in the key_contact_id to the phone_numbers_attributes; if I don't do that I get a 422 unprocessable entity error for not sending through my key_contact_id.
Using this setup the modal pops into view and has the correct fields (both for sale_contact and phone_number), but will not save either of these models - I get a 500 error:
Completed 500 Internal Server Error in 17ms
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection (Cannot modify association 'SaleContact#phone_numbers' because the source reflection class 'PhoneNumber' is associated to 'KeyContact' via :has_many.):
app/controllers/sale_contacts_controller.rb:50:in `block in create'
app/controllers/sale_contacts_controller.rb:49:in `create'
I've tried other methods, such as setting the key_contact_id in the sale_contact create method, but I get the same result. I can't work out why this setup complains when I don't pass a key_contact_id (422 error) and complains when I do (500 internal server error as the key_contact_id is being set internally by the rails associations).
What does Rails want me to do here?
I had a similar issue with my form, but I was able to get passed it by using nested field_for tags
Proposal.rb
has_many :proposal_section_reviews, :through => :proposal_review_status
has_one :proposal_review_status, :dependent => :destroy
attr_accessible :proposal_review_status_attributes, :proposal_review_status_attributes, :proposal_section_reviews_attributes
accepts_nested_attributes_for :proposal_review_status, :proposal_section_reviews
ProposalSectionReview.rb
belongs_to :proposal_review_status
ProposalReviewStatus.rb
has_many :proposal_section_reviews, :dependent => :destroy
_form.html.haml
= form_for proposal do |f|
= f.fields_for :proposal_review_status do |ff|
= ff.fields_for :proposal_section_reviews
So in your instance I would try
<%= f.fields_for(:key_contact) do |key| %>
<%= key.fields_for(:phone_numbers) do |phone| %>
<div class="form-group">
<%= phone.label :number, "Phone Number", :class => "control-label" %>
</br>
<%= phone.text_field :number, :placeholder => "Enter phone number (optional)" %>
<span class="help-block"></span>
</div>
<div>
<%= phone.hidden_field :key_contact_id %>
</div>
<% end %>
<% end %>
Hope this helps

Resources