Rails - Cocoon/simple form nested fields not showing - ruby-on-rails

I'm trying to create nested fields in the edit/create section of a recipe book for ingredients, directions, and utensils, but only the outline of the fields show up, without any of the nested bits.
Models
class Recipe < ApplicationRecord
has_many :ingredients
has_many :directions
has_many :utensils
accepts_nested_attributes_for :ingredients, reject_if: proc { |attributes|
attributes['name'].blank? }, allow_destroy: true
accepts_nested_attributes_for :directions, reject_if: proc { |attributes|
attributes['step'].blank? }, allow_destroy: true
accepts_nested_attributes_for :utensils, reject_if: proc { |attributes|
attributes['name'].blank? }, allow_destroy: true
end
class Ingredient < ApplicationRecord
belongs_to :recipe
end
class Direction < ApplicationRecord
belongs_to :recipe
end
class Utensil < ApplicationRecord
belongs_to :recipe
end
Recipes Controller (params)
def recipe_params
params.require(:recipe).permit(:title, :description, :image,
ingredients_attributes: [:id, :name, :_destroy], directions_attributes:
[:id, :step, :_destroy], utensils_attributes: [:id, :name, :_destroy])
end
View
<div class="row">
<div class="col-md-4">
<h3>Ingredients</h3>
<div id="ingredients">
<%= f.simple_fields_for :ingredients do |ingredient| %>
<%= render "ingredients_fields", f: ingredient %>
<div class="links">
<%= link_to_add_association "Add Ingredient", f, :ingredients, class: "btn btn-default add-button" %>
</div>
<% end %>
</div>
</div>
<div class="col-md-4">
<h3>Directions</h3>
<div id="directions">
<%= f.simple_fields_for :directions do |direction| %>
<%= render "directions_fields", f: direction %>
<div class="links">
<%= link_to_add_association "Add Step", f, :directions, class: "btn btn-default add-button" %>
</div>
<% end %>
</div>
</div>
<div class="col-md-4">
<h3>Utensils</h3>
<div id="utensils">
<%= f.simple_fields_for :utensils do |utensil| %>
<%= render "utensils_fields", f: utensil %>
<div class="links">
<%= link_to_add_association "Add Utensil", f, :utensils, class: "btn btn-default add-button" %>
</div>
<% end %>
</div>
</div>
</div>
<%= f.button :submit, class: "btn btn-primary" %>
</div>
Partial (for directions)
<div class="form-inline.clearfix">
<div class="nested-fields">
<%= f.input :step, input_html: { class: "form-input form-control" } %>
<%= link_to_remove_association "Remove", f, class: "form-button btn btn-default" %>
</div>
</div>
The outline for utensils also isn't showing up and I'm not sure why.
Here's what it looks like: nested forms not showing
Any help would be appreciated, thank you!

Are there any nested items?
Because if there are not it would explain the behaviour: your link_to_add_association is inside the simple_fields_for loop and so it will be shown for each nested item, but it will also never be shown if there are none (note: this could be desired behaviour, but I am guessing in most cases it is not).
All examples on the cocoon documentation place the link_to_add_association outside the simple_fields_for loop. If you have trouble reading haml, check the ERB examples.

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

Nested form not appearing

I am using cocoon gem for nested forms. My rails version is 4.2.6.
The association set between two models is as follows
Question.rb
class Question
include Mongoid::Document
field :title, type: String
has_many :choice_options, dependent: :destroy
accepts_nested_attributes_for :choice_options, :reject_if => :all_blank, :allow_destroy => true
validates :topic, presence: true
end
ChoiceOption.rb
class ChoiceOption
include Mongoid::Document
field :title, type: String
belongs_to :question
end
And I have new and question_params set as follows in questions controller
questions_controller.rb
class QuestionsController < ApplicationController
def new
#question = Question.new
end
private
def question_params
params.require(:question).permit(:title, choice_options_attributes: [:id, :title, :_destroy])
end
end
The partial form to create a new question and nested choice is as follows.
views/questions/_form.html.erb
<%= form_for #question do |f| %>
<%= render 'shared/errors', object: #question %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_area :title, rows: 3, required: true, class: 'form-control' %>
</div>
<div class="panel panel-default">
<div class="panel-heading">Options</div>
<div class="panel-body">
<%= f.fields_for :choice_options do |options_form| %>
<%= render 'choice_option_fields', f: options_form %>
<% end %>
<div class="links">
<%= link_to_add_association 'add option', f, :choice_options %>
</div>
</div>
</div>
<%= f.submit 'Create', class: 'btn btn-primary btn-lg' %>
<% end %>
And then I have created a partial form for choices which I am rendering in the above page.
views/questions/_choice_option_fields.html.erb
<div class="nested-fields">
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control', required: true %>
</div>
<%= link_to_remove_association "remove option", f %>
</div>
Now when I am trying to click the add option link from the partial form, its not appearing. the url is ending at
http://localhost:3000/questions/new#
You forgot to initiate the choice options in new
class QuestionsController < ApplicationController
def new
#question = Question.new
#question.choice_options.build
end
end

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

Two nested form objects

I have two nested objects (Attachment inside Widget, Widget inside Lesson).
Lesson > Widgets > Attachments
Lesson > Widgets > Links
Everything is fine, but if I click on "Remove attachment", and then I click on "Update Lesson", nested attachments in the widget are not removed. Is that because I click on "Update Lesson", instead of "Updated Widget" ?
Lesson
class Lesson < ActiveRecord::Base
has_many :widgets, as: :widgetable, autosave: true
accepts_nested_attributes_for :widgets, allow_destroy: true
Widget
class Widget < ActiveRecord::Base
belongs_to :widgetable, polymorphic: true
has_many :attachments, as: :attachable, autosave: true
accepts_nested_attributes_for :attachments, allow_destroy: true
has_many :links, as: :linkable, autosave: true
accepts_nested_attributes_for :links, allow_destroy: true
Attachment
class Attachment < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
mount_uploader :file, AttachmentUploader
Link
class Link < ActiveRecord::Base
belongs_to :linkable, polymorphic: true
Lesson (Edit Form)
<%= f.fields_for :widgets do |w| %>
<div class="row">
<div class="col-xs-8">
<h2>Name</h2>
<%=w.text_field :name, placeholder: "Name of widget", class: "form-control"%>
<%= w.fields_for :attachments do |h| %>
<h4>Attachments</h4>
<div class="row">
<div class="col-sm-6">
<%=h.text_field :name, placeholder: "Title", class: "form-control"%>
</div>
<div class="col-sm-6">
<%=h.file_field :file, class: "form-control"%>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<br />
<%=h.text_area :description, placeholder: "Write a description", class: "form-control autogrow"%>
</div>
</div>
<%= w.link_to_remove "Remove attachment", class: "text-right"%>
<hr /><br />
<% end %>
<%= w.fields_for :links do |l| %>
<h4>Links</h4>
<%=l.text_field :title, placeholder: "Title", class: "form-control"%>
<%=l.text_field :description, placeholder: "Description", class: "form-control"%>
<%=l.text_field :url, placeholder: "http://", class: "form-control"%>
<%= l.link_to_remove "Remove Link", class: "text-right"%>
<hr /><br />
<% end %>
<p>
<%=w.link_to_add "Add attachment", :attachments, class: "btn btn-sm btn-success" %>
<%=w.link_to_add "Add Link", :links, class: "btn btn-sm btn-success" %>
</p>
</div>
<div class="col-xs-4 text-right">
<%= w.link_to_remove "Remove widget", class: "btn btn-sm btn-danger" %>
</div>
</div>
<hr />
<p></p>
<% end %>
<p><%= f.link_to_add "Add widget", :widgets, class: "btn btn-sm btn-success pull-right" %></p>
Change this line
<%= w.link_to_remove "Remove attachment", class: "text-right"%>
to
<%= h.link_to_remove "Remove attachment", class: "text-right"%>
Assuming you are using rails 4, have you checked strong parameters to ensure :_destroy is specified for attachment_attributes?
https://github.com/ryanb/nested_form#strong-parameters
Tip: You can check your development log output when you submit the form to see if there are any unpermitted parameters.

Works for 'edit', fails for 'new'... undefined method `association' for #<ActionView::Helpers::FormBuilder:

Following the railscast #196 on nested forms... I have the following models:
class DealContact < ActiveRecord::Base
belongs_to :deal
belongs_to :contact
class Contact < ActiveRecord::Base
has_many :deal_contacts
has_many :deals, through: :deal_contacts
accepts_nested_attributes_for :deal_contacts, :allow_destroy => true
class Deal < ActiveRecord::Base
has_many :deal_contacts
has_many :contacts, through: :deal_contacts
accepts_nested_attributes_for :deal_contacts, :allow_destroy => true
In my deals form I have this
<div class="row">
<div class="span12"><h4>Contacts Associated with this Deal</h4></div>
<%= f.fields_for :deal_contacts do |builder| %>
<%= render 'deal_contact_fields', f: builder %>
<% end %>
<div class="span1"><%= link_to_add_contact "Add", f, :deal_contacts %></div>
</div>
</div>
And deal_contact_fields just contains:
<fieldset>
<div class="span4">
<%= f.association :contact, collection: Contact.all(order: 'contact_name'), label_method: :full_desc %>
</div>
<div class="span6">
<%= f.label :details, "Details " %>
<%= f.text_field :details %>
</div>
<div class="span1" style="margin-top: 30px">
<%= f.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</div>
</fieldset>
This all works great for editing existing deals, however when I create a new one I get the following error:
undefined method `association' for #<ActionView::Helpers::FormBuilder:0x007fe6fba55840>
:-(
.association is only available in simple_form gem
You'll have to use collection_select to achieve what you want without that gem:
<%= f.collection_select :contact, Contact.all(order: 'contact_name'), :id, :name %>

Resources