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.
Related
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.
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.
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 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
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