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
Related
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
I cannot figure out what I am doing wrong. I have two models:
class Product < ActiveRecord::Base
has_one :review, dependent: :destroy
accepts_nested_attributes_for :review, allow_destroy: true
end
class Review < ActiveRecord::Base
belongs_to :product
end
They have a has_one relationship. The database has the product_id column in the reviews table.
My controller is straight forward on the new (#product = Product.new) and edit action has nothing. Here are my strong params:
def product_params
params.require(:product).permit(:name, ..., review_attributes: [:id, :rating, :text, :author, :name] )
end
My form is as follows:
<%= form_for(#product, :html => {multipart: true, :class => "form-horizontal"}) do |f| %>
...
<%= f.fields_for :review do |ff| %>
<%= ff.hidden_field :author, :value => 'Yes' %>
<%= ff.label :rating, "Enter a Rating" %>
<%= ff.number_field :rating, class: "form-control input-md", min: 0, max: 5, step: 0.5 %>
<%= ff.label :name, "Title of Review" %>
<%= ff.text_field :name, class: "form-control input-md" %>
<%= ff.label :text, "Review Description" %>
<%= ff.text_area :text, class: "form-control" %>
<% end %>
<%= f.submit "Create Product", :class => 'btn btn-default btn-lg' %>
<% end %>
I cannot figure out why the nested form does not appear when I have the accepts_nested_attributes in the model, whether or not I need that accepts_nested_attributes, and why I get an error saying "unpermitted parameters: review" when I do not have the accepts_nested_attributes and submit the form. Any help is greatly appreciated.
In the controller, try to build the review object in the method that is rendering that form...
def new
#product = Product.new
#product.build_review
end
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
I want to create a nested form with relations between Clientes has_many enderecos, but in my form nothing is show up.
Look my models:
class Cliente < ActiveRecord::Base
has_many :enderecos, dependent: :destroy
validates :nome, :sexo, presence: true
validates :cpf, :email, :username, presence:true, uniqueness: true
has_secure_password
accepts_nested_attributes_for :enderecos
end
And endereco:
class Endereco < ActiveRecord::Base
belongs_to :cliente
end
And my form:
<%= form_for(#cliente) do |f| %>
<div class="field">
<%= f.text_field :nome, placeholder: 'Nome completo', size: '50px' %>
</div>
<% f.fields_for :endereco do |endereco_form| %>
<div class="field">
<%= endereco_form.label :cep, 'placeholder: ' %>
<%= endereco_form.text_field :cep, placeholder: 'CEP' %>
</div>
<div class="field">
<%= endereco_form.text_field :numero, placeholder: 'Número' %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
But inside nested part nothing is show up, what's happening?
You forgot the '=' sign:
<%= f.fields_for :endereco do |endereco_form| %>
I'm doing a nested form in Rails 3.2.5, but when I add the accepts_nested_attributes_for my fields_for disappear (they just stop showing in my form).
This are my models:
class Product < ActiveRecord::Base
attr_accessible :title, :description, :variants_attributes
has_many :variants
accepts_nested_attributes_for :variants
validates :title, presence: true
end
My second model is
class Variant < ActiveRecord::Base
belongs_to :product
attr_accessible :price, :sku, :weight, :inventory_quantity, :product_id
end
And in my view I have
<%= form_for [:admin, #product], :html => {:multipart => true} do |f| %>
<%= render 'admin/shared/error_messages', object: f.object %>
<fieldset>
<legend>Producto Nuevo</legend>
<div class="control-group">
<%= f.label :title, 'Título', class: 'control-label' %>
<div class="controls">
<%= f.text_field :title %>
</div>
</div>
**<%= f.fields_for :variants do |variant| %>
<%= render 'inventory_fields', f: variant %>
<% end %>**
<div class="actions">
<%= f.submit 'Guardar', class: 'btn btn-primary' %>
</div>
<% end %>
_inventory_fields.html.erb
<div class="control-group">
<%= f.label :inventory_quantity, 'How many are in stock?', class: 'control-label' %>
<div class="controls">
<%= f.text_field :inventory_quantity, class: 'span1', value: '1' %>
</div>
</div>
The part between the ** is the one that is not being printed. And when I remove accepts_nested_attributes_for in my Product model fields_for start showing again but my form won't work.
What's happening?!?!
In the controller new action call
#product.varients.build
That should create 1 in memory varient in the product varient collection and it should bind to the fields for