Rails - Destroy children for nested attributes - ruby-on-rails

I'm banging my head trying to figure this one out. I have the following form, which works great!
<%= bootstrap_form_for #template do |f| %>
<%= f.text_field :prompt, :class => :span6, :placeholder => "Which one is running?", :autocomplete => :off %>
<%= f.select 'group_id', options_from_collection_for_select(#groups, 'id', 'name') %>
<% 4.times do |num| %>
<%= f.fields_for :template_assignments do |builder| %>
<%= builder.hidden_field :choice_id %>
<%= builder.check_box :correct %>
<% end %>
<% end %>
<% end %>
Then I have my model:
class Template < ActiveRecord::Base
belongs_to :group
has_many :template_assignments
has_many :choices, :through => :template_assignments
accepts_nested_attributes_for :template_assignments, :reject_if => lambda { |a| a[:choice_id].blank? }, allow_destroy: true
end
When the form is submitted, the nested attributes are added beautifully. However, when I want to delete a Template, I also want it to delete all the children "template_assignments" as well, which is what I assumed "allow_destroy => true"
So, I am trying this from the Rails Console (which could be my issue??) I'll do something like:
>> t = Template.find(1)
#Which then finds the correct template, and I can even type:
>> t.template_assignments
#Which then displays the nested attributes no problem, but then I try:
>> t.destroy
#And it only destroys the main parent, and none of those nested columns in the TemplateAssignment Model.
Any ideas what I am doing wrong here? Is it because you can't do it in the Rails Console? Do I need to do it in a form instead? If so, how do I achieve this in a form?
Any help would be great!

Specify that child template_assignments should be destroyed upon the destruction of the parent Template:
# app/models/template.rb
has_many :template_assignments, dependent: :destroy
The following depicts the steps that Rails takes to destroy an object's dependent associations:
# from the Rails console
>> t = Template.find(1)
#=> #<Template id:1>
>> t.template_assignments.first
#=> #<TemplateAssignment id:1>
>> t.destroy
#=> SELECT "template_assignments".* FROM "template_assignments" WHERE "template_assignments"."template_id" = 1
#=> DELETE FROM "template_assignments" WHERE "template_assignments"."id" = ? [["id", 1]]
>> TemplateAssignment.find(1)
#=> ActiveRecord::RecordNotFound: Couldn't find TemplateAssignment with id=1
The :dependent option can be specified on either side of the association, so it can alternatively be declared on the child association, rather than the parent:
# app/models/template_assignment.rb
belongs_to :template, dependent: :destroy
From the Rails docs:
has_many, has_one and belongs_to associations support the :dependent option. This allows you to specify that associated records should be deleted when the owner is deleted

Related

Why is updating a form duplicating my form fields and duplicating records with habtm and nested form?

I have a nested form inside a parent form that allows you to add fields inside the parent form. The idea is that a book can have many schools and the school belongs to the book(habtm) relationship. here are my models:
books.rb
accepts_nested_attributes_for :author
accepts_nested_attributes_for :gallery, :allow_destroy => true
accepts_nested_attributes_for :schools, :reject_if => :find_school, :allow_destroy => true
accepts_nested_attributes_for :images, :allow_destroy => true
def find_school(school)
if existing_school = School.find_by_school_name(school['school_name'])
self.schools << existing_school
return true
else
return false
end
end
school.rb
class School < ActiveRecord::Base
has_and_belongs_to_many :books
validates :school_name, :address, presence: true
#validates_uniqueness_of :school_name
end
In my form for the book I have this setup:
<div id="school" class="field">
<h3>Schools reading this book (add the name and full address of the school)</h3>
<%= f.simple_fields_for :schools, :wrapper => 'inline' do |builder| %>
<%= render 'school_fields', :f => builder %>
<%= link_to_add_association 'add school', f, :schools, :render_options => {:wrapper => 'inline' }, :class => 'fa fa-plus' %>
<% end %>
</div>
In here it should render the fields but I get this
The issue i have is that in my book.rb model the find_school method keeps returning multiple values of the same books when I update the book. The idea is that the find_school method basically stops duplicate schools being created as records and just assigns them as a relationship to the book instead. What I am finding is that this self.schools << existing_school on update just keeps duplicating the fields in the edit form and adds the fields as entries on the item when I check in the params output.
Can anyone help with this

Rails 4 nested form issue

Hi I'm using the nested form plugin and trying to make it work for rails 4 instead of rails 3. Basically my model looks like this:
has_many :item, :dependent => :destroy
accepts_nested_attributes_for :item, :reject_if => lambda { |a| a[:item].blank? }, :allow_destroy => true
and my view looks like this:
<%= nested_form_for(#store) do |f| %>
<%= f.fields_for :item do |item_form| %>
<%= item_form.text_field :name %>
<%= item_form.link_to_remove "Remove this item" %>
<% end %>
<% end %>
this works (in terms of presentation - you can add and delete fields like you should be able to) but doesn't save the item names.
I tried this in my controller (these are the protected attributes/params):
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes)
end
but it still comes up with:
Unpermitted parameters: item_attributes
Thanks for all help!
You're going to have to permit the fields of item (like name) to be allowed as well.
So try this:
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes: [:name])
end
sometimes you have to specify the :id like this:
def store_params
params.require(:store).permit(:name, :owner, :description,:url, :user, item_attributes: [:id, :name])
end
in a similar case I had last week, not specifying the :id made Rails 4 create an new entity instead of updating the existing one.

Rails - Manipulate has_many with join model through single form

I have the following three models:
class Site < AR::Base
has_many :installed_templates, :as => :installed_templateable, :dependent => :destroy
accepts_nested_attributes_for :installed_templates, :allow_destroy => true, :update_only => true, :reject_if => lambda { |t| t[:template_id].nil? }
end
class Template < AR::Base
has_many :installed_templates, :inverse_of => :template
end
class InstalledTemplate < AR::Base
belongs_to :template, :inverse_of => :installed_template
belongs_to :installed_templateable, :polymorphic => true
end
The business logic is that several Templates exist when I create a Site and I can associate as many as I'd like by creating an InstalledTemplate for each. A Site can only have unique Templates - I can't associate the same Template twice to a single Site.
I have the following on the form for Site:
<% Template.all.each_with_index do |template| %>
<%= hidden_field_tag "site[installed_templates_attributes][][id]", Hash[#site.installed_templates.map { |i| [i.template_id, i.id] }][template.id] %>
<%= check_box_tag "site[installed_templates_attributes][]]template_id]", template.id, (#site.installed_templates.map(&:template_id).include?(template.id) %>
<%= label_tag "template_#{template.id}", template.name %>
<% end %>
The above is the only thing that seems to work after lots of experimentation. I wasn't able to achieve this at all using the form_for and fields_for helpers.
It seems like a pretty straight forward relationship and I'm afraid I'm missing something. Anyone have advice on how to accomplish the above in a cleaner fashion?
Thanks
Try the following
<% form_for #site do |f| %>
<%f.fields_for :installed_templates do |af|%>
<%= af.text_field :name %>
<%end%>
<%end%>
I think you are trying to do two different things here.
Select a list of Templates for which you want to create Installed Template joins to the Site.
From each selected Template create an instance of an Installed Template which links to the Site.
I think I would handle this through an accessor on the model.
On the Site Model (assuming standard rails naming conventions)
attr_accessor :template_ids
def template_ids
installed_templates.collect{|i_t| i_t.template.id}
end
def template_ids=(ids, *args)
ids.each{|i| i.installed_template.build(:site_id => self.id, :template_id => i) }
end
Then the form becomes pretty simple
<% form_for #site do |f| %>
<% f.collection_select :template_ids, Template.all, :id, :name, {}, :multiple => true %>
<% end %>
Is a join model necessary here?
class Site < ActiveRecord::Base
has_and_belongs_to_many :templates
end
In the form, easiest if using simple_form:
<%= form.input :template_ids, :as => :radio_buttons, :collection => Template.order(:name), :label => 'Templates installed:' %>
If the join model is necessary, then I would have a dropdown or list of templates I could add, each with a button that submits the form and adds that template. Then I'd use the update_only nested attributes form to display the currently installed templates with their settings.
class Site < ActiveRecord::Base
...
attr_accessor :install_template_id
before_validation :check_for_newly_installed_template
def check_for_newly_installed_template
if install_template_id.present?
template = Template.find install_template_id
if template.present? and not templates.include?(template)
self.templates << template
end
end
end
end
That works kind of like Drupal, where you have to enable the theme before you can edit its settings or select it as the current theme.

Rails 3.2: multiple nested data from one model

How to create form and action for multiple nested attributes if:
LineItem:
has_many :item_options, :dependent => :destroy
has_many :product_options, :through => :item_options
ProductOption:
belongs_to :product
belongs_to :option
has_many :item_options
has_many :line_items, :through => :item_options
ItemOption:
attr_accessible :line_item_id, :product_option_id
belongs_to :line_item, :foreign_key => "line_item_id"
belongs_to :product_option,:foreign_key => "product_option_id"
When I'm creating new LineItem, I need to create new ItemOption(s). This is my form:
<%= form_for(LineItem.new) do |f| %>
<%= f.hidden_field :product_id, value: #product.id %>
<%= f.fields_for :item_options do |io| %>
<% #product.options.uniq.each do |o| %>
<%= o.name %>:
<%= io.collection_select :product_option_id, o.product_options.where(:product_id => #product.id), :id, :value %>
<% end %>
<%= f.submit %>
<% end %>
When I'm clicking on Add To Cart, I've get:
ItemOption(#70296453751440) expected, got Array(#70296430421140)
When Adding accepts_nested_attributes_for :item_options to LineItem, my selects not diplayed :(
With
<%= select_tag "product_option_id", options_from_collection_for_select(o.product_options.where(:product_id => #product.id), :id, :value) %>
#item_options not created:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/WM5/MqPn1yCxjKWoJQmjfko2pR4RiYV0S2KeTTpA3w=", "line_item"=>{"product_id"=>"1"}, "product_option_id"=>"5", "commit"=>"add"}
And last one, I've create action like this:
#line_item = LineItem.new(params[:line_item])
#line_item.item_options.build
....
Where am I wrong? :( I'm totally confused.
ps. similar question Rails 3.2 has_many through form submission
This is form:
Looks this line:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/WM5/MqPn1yCxjKWoJQmjfko2pR4RiYV0S2KeTTpA3w=", "line_item"=>{"product_id"=>"1"}, "product_option_id"=>"5", "commit"=>"add"}
The parameter product_option_id is outside line_item hash, and will be inside. Maybe you need write the select like this:
<%= select_tag "line_item[product_option_id]", options_from_collection_for_select(o.product_options.where(:product_id => #product.id), :id, :value) %>
I'm not sure, but maybe is this. Maybe I need more information, like the exact line where is failing.
Extra, the :foreign_key => "line_item_id" and :foreign_key => "product_option_id" are not necesary, because, the belongs_to model name is the same and will use these foreign_key. From api.
Specify the foreign key used for the association. By default this is
guessed to be the name of the association with an “_id” suffix. So
a class that defines a **belongs_to :person** association will use
“person_id” as the default :foreign_key. Similarly, belongs_to
:favorite_person, :class_name => "Person" will use a foreign key of
“favorite_person_id”.
Edit
Sorry, the unknown attribute: product_option_id is because the attribute name is product_option_ids, and is an array, not a unique value. For a has_many relationship, the column name is collection_singular_ids, and the select should be:
<%= select_tag "line_item[product_option_ids][]", options_from_collection_for_select(o.product_options.where(:product_id => #product.id), :id, :value) %>
This should work, I think :)...

Assigning a nested attribute with Formtastic

I've been trying to figure this one out for a while but still no luck. I have a company_relationships table that joins Companies and People, storing an extra field to describe the nature of the relationship called 'corp_credit_id'. I can get the forms working fine to add company_relationships for a Person, but I can't seem to figure out how to set that modifier field when doing so. Any ideas?
More about my project: People have many companies through company_relationships. With that extra field in there I am using it to group all of the specific relationships together. So I can group a person's Doctors, Contractors, etc.
My models:
Company.rb (abridged)
class Company < ActiveRecord::Base
include ApplicationHelper
has_many :company_relationships
has_many :people, :through => :company_relationships
Person.rb (abridged)
class Person < ActiveRecord::Base
include ApplicationHelper
has_many :company_relationships
has_many :companies, :through => :company_relationships
accepts_nested_attributes_for :company_relationships
company_relationship.rb
class CompanyRelationship < ActiveRecord::Base
attr_accessible :company_id, :person_id, :corp_credits_id
belongs_to :company
belongs_to :person
belongs_to :corp_credits
end
My form partial, using formtastic.
<% semantic_form_for #person do |f| %>
<%= f.error_messages %>
<% f.inputs do %>
...
<%= f.input :companies, :as => :check_boxes, :label => "Favorite Coffee Shops", :label_method => :name, :collection => Company.find(:all, :conditions => {:coffee_shop => 't'}, :order => "name ASC"), :required => false %>
So what I would like to do is something like :corp_credit_id => '1' in that input to assign that attribute for Coffee Shop. But formtastic doesn't appear to allow this assignment to happen.
Any ideas on how to do this?
Are you looking for something like
<% semantic_form_for #person do |form| %>
<% form.semantic_fields_for :company_relationships do |cr_f| %>
<%= cr_f.input :corp_credit_id %>
<% end %>
It is in the documentation

Resources