Delete association fields in Rails 3.1 view - ruby-on-rails

I am trying to make a nested model form in which I can add/delete association objects on the fly.
In Rails 3.0.x that would work properly if I created a hidden input with the "_destroy" name that when set to 1 it would have deleted the association record.
Now whenever I submit the form with the hidden input _destroy set to 1 (or true) it doesn't do anything.
Any ideas?
Thanks

Did you write:
:allow_destroy => true
?
# model.rb
accepts_nested_attributes_for :model, :allow_destroy => true
# view
<%= f.fields_for :model do |fields| %>
...
Delete: <%= fields.check_box :_destroy %>
<% end %>

Related

Simple form remove associated record

I'm using simple_form and I would give user ability to quickly remove an associated record. (eg. "checking/uncheking")
How is it possibile with simple_form? Is there another gem to help with this?
Parent has many children
<%= simple_form_for #parent do |f| %>
<%= f.simple_fields_for :childens do |p| %>
<%= p.input :title, as: :boolean %>
<% end %>
<% end %>
Rails 5.2
You don't need another gem for that. There are several things you need to do:
Add allow_destroy: true to the accepts_nested_attributes_for :children in the parent model
Add a <%= p.input :_destroy, as: :boolean %> to the nested form
Whitelist the _destroy pseudo attribute in your controller by listing it in children_attributes in the permit call
Essentially this is a feature of Rails' accepts_nested_attributes_for - it sets up the children_attributes setter to not only create/update associated records but also delete them in the presence of _destroy in the passed hash.

Rails nested polymorphic form with javascripting

I want the following on my form:
Degree is polymorphic partial with has_many relation to profile. This partial is called by main form - profile. By pressing 'Add a Qualification' user can add as many degrees. I've been able to attach only one degree to profile which is the last one others are all ignored. I even know why it's happening because link_to is not able to pass profile instance. so I have to create new profile in degrees_controller as you can see in my code here. Can final profile instance pick up all others above when submitting 'Create Profile'.
Kindly any help so that I can have all of the degrees attached with form, I'm stuck on this for last couple of days with all permutations and combinations from SO and google. I'm ready to change code even....any help with this will be appreciated.
I've got this little helper to get me through these cases. Its reusable so you don't ever have to write all this stuff for different cases in the same app.
First the code:
module FormsHelper
def data_for_field(f, subclass, options={})
new_object = f.object.class.new.send(subclass.to_s).new(options[:attributes])
id = new_object.object_id
partial_path = options[:path] || new_object
fields = f.fields_for(subclass, new_object, child_index: id) do |builder|
render(partial_path, f: builder)
end
{id: id, fields: fields.gsub("\n", "")}
end
end
Some JS magic:
$(document).on('click', '.add-fields', function(e){
var time = new Date().getTime();
var regexp = new RegExp($(this).data('id'), 'g');
var content = $(this).data('fields').replace(regexp, time);
$(target).append(content);
e.preventDefault();
});
Now a view call:
<%= form_tag .... do |f|
<div id="list"></div>
<%= link_to 'Add an Organisation', '#', class: 'add-fields', data: data_for_field(f, :degrees, path: "/degrees/fields").merge(target: "#list") %></p>
<% end %>
Now create a partial where the degree fields to use.
/degrees/_fields.html.erb
<%= content_tag :div do %>
<%= f.input :name %>
<% end %>
Now the explanation:
For data_for_field you pass: f (form), :degrees (the plural name of the associated model as a symbol), and any additional options like the path to the partial for the fields. It basically creates a scaffold of the form fields and sets them as a data attribute on the "Add Qualification" link. I've merged in a data-target which has the css ID of where to insert the new fields into the page as well.
When you click the JS event fires and replaces the id in the form fields with a timestamp so for each qualification you'll have a unique key.
I also usually have a little extra JS event for removing the fields as well.
Hope this helps.
This is a polymorphic nested association and can be handled on client side with javascript. So, finally for nested fields I used the plugin Numerous.js. Just follow as the steps given in the qucikstart part of link by downloading the numerous.js file from the Github and saving to assets/javascripts.
In my code,
profile.rb
class Profile < ApplicationRecord
has_many :degrees, :as => :degreeable
accepts_nested_attributes_for :degrees, :reject_if => :all_blank, allow_destroy: true
belongs_to :user, :class_name => 'User', :foreign_key => 'user_id'
end
degree.rb
class Degree < ApplicationRecord
belongs_to :degreeable, polymorphic: true
end
profiles/_form.html.erb
<div id="fields-for-list" class="numerous">
<%= f.fields_for :degrees, Degree.new, :child_index => 'replace_this' do |degree_form| %>
<%= degree_form.select :level, options_for_select(Job::EDUCATION, params[:level]), include_blank: "Select Degree", :class => 'span5' %>
<%= degree_form.text_field :description, placeholder: "Add a new Degree here..."%>
<%= link_to 'x Remove', '#', :class => 'numerous-remove', type: 'button' %>
<% end %>
</div>
<div id="list"></div>
<%= link_to (fa_icon 'plus').to_s + 'Add a Qualification', '#', :id => 'add-to-list' %>
and finally, with strong parameters,
def profile_params
params.require(:profile).permit(:user_id, :first_name, :last_name, degrees_attributes:[:id, :level, :description])
end
Note that I had already set-up degree table with 2 extra fields for polymorphic association:- "degreeable_id" & "degreeable_type" and when entering in DB, the two fields were automatically filled with newly created profile_id and 'Profile'(the name of the model which is associating polymorphically with degree).
The trick in numerous.js is creating each nested form record(here degree) with unique temporary id such as Time.now.to_i so now each degree record created/destroyed at client side will have a diff degree_attribute 'id'. Hope it helps others.

Rails nested model form is showing random order on child objects

I've followed and used Ryan Bate's nested model form tutorial to create tracks for my releases (in the tutorial it's questions for surveys).
This works really well until I noticed that the order the tracks get added to the DB is seemingly random not as they appear or are entered in the form I need that to be the case.
The tracks are built using the following definition in the release model:
def track_attributes=(track_attributes)
track_attributes.each do |attributes|
tracks.build(attributes)
end
end
Then in the release _form partial I have:
<%= f.fields_for :tracks do |builder| %>
<%= render 'track_fields', :f => builder %>
<% end %>
That pulls in the _track_fields partial, containing:
<%= f.text_field :name, :class => "text" %>
<%= f.text_field :isrc, :class => "text" %>
<%= f.check_box :_destroy %>
etc
Any ideas why the array of tracks is losing the order they were entered?
I am using acts_as_list in the releases_tracks has many through model that works fine, but it takes the order from what's been incorrectly added to the tracks table.
EDIT:
It seems my tracks are being saved with:
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
Not via the track_attributes=(track_attributes) def as I had thought.
Does anyone know how to write a before_save method that will sort the tracks by a position field I've now added to the form?
The order in which SQL queries are executed is random in general. This can apply both to a bulk insert and to a select. If you need your records in a certain order, you must use an ORDER BY clause. Depending on how the records are saved, you might be able to sort on the id, otherwise consider adding a field that represents the list position of each item.

Use accepts_nested_attributes_for to create new records or update existing

Read the big update for the latest information.
Hey everyone,
I've got a many-to-many relationship in a rails app that involves three tables: a user table, an interests table, and a join user_interests table that also has a rating value so a user can rate each of their interests on a 1-10 scale.
I am basically looking for a way for a new user to create their rating when they sign up and edit them at a future date along with any of their profile information at the same time.
I tried to follow this question Rails nested form with has_many :through, how to edit attributes of join model? but the problem I'm having is trying to incorporate a select list into the mix and having multiple interests to rate for the user.
Model Code:
user.rb
has_many :user_interests, :dependent => :destroy
has_many :interests, :through => :user_interests, :foreign_key => :user_id
accepts_nested_attributes_for :user_interests
interest.rb
has_many :user_interests, :dependent => :destroy
has_many :users, :through => :user_interests, :foreign_key => :interest_id, :dependent => :destroy
user_interest.rb
belongs_to :user
belongs_to :interest
View Code:
app/views/user/_form.html.erb
<%= form_for(#user) do |form| %>
... user fields
<%= form.fields_for :user_interests do |ui_form| %>
... loop through ALL interests
<% Interest.all.each do |interest| %>
<%= ui_form.select :rating, options_for_select(1..10) %>
<%= ui_form.hidden_field :interest_id, :value => interest.id %>
<% end %>
<% end %>
<% end %>
I also included the following in the new/edit actions in my controller #user.interests.build.build_interest
The problem I'm running into is that only one interest rating is being passed in the params hash when I want to have multiple. Also I am getting an exception thrown by rails
Interest(#2172840620) expected, got Array(#2148226700)
What tiny detail did I miss or get wrong that is causing the problem?
EDIT:
I found a way to force this to work but it requires manually editing the HTML in chrome developer tools, the :name attribute of my form elements are being generated as user[user_interests_attributes][rating] but if I change it to user[user_interests_attributes][][rating] it will work when I update a record. However I can't manually specify the :name of a form element that is tied to a form object. So what can I do to show that multiple interest ratings are being passed instead of just one that rails thinks?
BIG Update:
I got a semi functional version going with some slight changes:
View code:
<% form.fields_for :user_interests do |ui_form| %>
<p>
<%= ui_form.select :rating, options_for_select(1..5), :selected => :rating %>
<%= ui_form.label :interest_title %>
<%= ui_form.hidden_field :interest_id %>
</p>
<% end %>
Controller code:
def new
#user = User.new
Interest.all.each { |int| #user.user_interests.build({ :interest_id => int.id }) }
end
def edit
#user = #current_user
Interest.unrated_by_user_id(#user.id).each { |int| #user.user_interests.build({ :interest_id => int.id }) }
end
Now I am able to edit and get my user_interests updated or created if no rating exists, but I get an error that user is empty when I try to create a new user. Also I am unable to access any of the interest attributes in the form to display the interest the user is actually rating. Can anyone help with those caveats?
You only need #user.interests.build because its a has_many relationship. build_interest is for when there is a has_one/belongs_to relationship.
When using fields_for :user_interests you're telling the User model that an instance of one or more user_interest objects will be in the parameters hash when the user is created/updated. The form is not creating or updating any user_interests but it is sending back an array of user_interest_attributes hashes that represent the user_interests for the user the form references. This is an array of user_interests rating values for which no user_interests exist as you reference them in the form which is the reason you get the error.
Since you are passing a range to the select form helper you aren't actually providing any interests to the form for selection. The select will set a value for the rating column in the user_interests table with a value between 1 and 10. No user_interest exists for the rating to be set on even if the user_interests table has a rating column.
passing :multiple => true in the options hash of the select tag will create a multiple select list but I don't think that is what you want. I think you want many items on a page the user can put an interest rating on.
If you do want a user to be able to select many interests this is how to use fields_for with accepts_nested_attributes_for on a has_many :through relationship:
<%= form_for(#user) do |f| %>
<% f.fields_for :interest_ids do |interest| %>
<ul>
<% Interest.all.each do |choice,i| %>
<li class="selection">
<%= interest.check_box [], { :checked => f.object.user_interest_ids.include?(choice.id) }, choice.id, '' %>
<%= interest.label [], choice.name %>
</li>
<% end %>
</ul>
<% end %>
<% end %>

How to use group_by with fields_for in rails?

I have a model with many children (selections). I need to display the children using fields for but I really want to group them based on an attribute on each selection using group_by.
Currently I am using
accepts_nested_attributes_for :selections, :allow_destroy => true
So my form looks a bit like this:
<% form_for #match do |form| %>
<% form.fields_for :selections do |child_form| %>
<%= child_form.object.first_name %>
<%= child_form.check_box '_delete' %>
<%= child_form.label '_delete', 'Remove' %>
<% end %>
<%= form.submit %>
<% end %>
Not quite sure how I could group the :selections using group_by. Any advice?
The question is a little vague. My interpretation is that you want to group similar selections by attribute as they appear in the form. Kind of like this:
form for Match
form for Selections
fields for Selection with attribute A
fields for Selection with attribute A
fields for Selection with attribute A
fields for Selection with attribute B
fields for Selection with attribute B
fields for Selection with attribute C
etc.
The group_by operator is not what you want. It will condense all selections that meet the criteria to a single entry.
The better option would be to use the order option when populating the list for the selection. Might even work out better for you to specify that order in the association. This will do what you want without changing your form.
has_many :selections, :order => "attribute"
But this will cause all your selection queries from match to be ordered by attributes. If this is a problem, you could add a second has_many relationship for selections.
has_many :selections
has_many :grouped_selections, :class_name => "selection", :order => "attribute"
accepts_nested_attributes_for :selections, :grouped_selections :allow_destroy => true
And all that needs to change in your form is <% form.fields_for :grouped_selections %>.
The only option I see is to group them through the association. In your model:
has_many :selections, :order => 'attribute DESC'
It's not the cleanest way to do it (that's how the selections will automatically be ordered throughout the rest of your application, too), but it'll work.

Resources