Set Position Value from Index for nested model attributes

1.3k views Asked by At

How can I set a position field in the attributes for a nested model so that my has_many relationship remembers it's sort order?

I want to set the position from the index so that it reflects the order the relations have been dragged into.

I have a form with Nested fields, using the cocoon gem with JQuery Sortable allowing each field-set to be drag-sortable.

I want to update the order of all the fields on saving the form.

4

There are 4 answers

1
Will On

This can't be the best way to do it, but I have got this working in my controller.

p = 1
experiences = []
params[:user][:resume_attributes][:experiences_attributes].each do |e|          
    e = e.last.merge(:position=>p)
    experiences << e
    p = p + 1
end
params[:user][:resume_attributes][:experiences_attributes] = experiences    

which at least illustrates what i want to achieve.

4
Max Williams On

Ok, non ajaxy version.

Let's assume that your form is a form_for @resume, which means that it will call the resumes#create or resumes#update action.

If all the education rows in your list have a hidden field like this

<%= hidden_field_tag "resume[education_ids][]", education.id %>

then when the form is submitted, they will go through into an array in params[:resume][:education_ids] in the order in which they appear in the page when the form was submitted, which is what you want.

The association gives you the setter method Resume#education_ids, allowing you to set the associations, in order, this way.

Ie, your update action will (if it's a normal update action) be saying something like

@resume = Resume.find_by_id(params[:id])
if @resume.update_attributes(params[:resume])
  ...

in this case, this will be saying

@resume.update_attributes(:education_ids => [5,6,2,1])

which is like saying "set my educations to be those with ids 5,6,2,1, in that order.

CAVEAT: in my version of rails (this might be fixed in subsequent version), if you use this _ids method, and it already has associations, but in a different order, it WILL NOT reorder them. Give it a go and see what happens.

1
Max Williams On

If you have the standard CRUD/restful routes and controller actions set up, then all you are wanting to do is to call "update_attributes(:position => 3)" on an instance of the nested class (education in this case).

The usual route to update an education which is nested under "resume" would be

UPDATE  /resumes/123/educations/456

so, you'll be making an ajax call to this url. The "UPDATE" method isn't really an update method, it's sort of spoofed by passing through a parameter "_method" = "UPDATE", so your ajax call will need to include this parameter too.

So, basically, on the "finished dragging" event, you're going to be making an ajax call to this url

"/resumes/<%= @resume.id %>/educations/<%= education.id %>"

and passing data like

{"_method": "UPDATE", "education[position]": <new position>}

This should update the position of the education object.

The other remaining issue is that, with acts_as_list, when we change the position of one item, we want the others to adjust their position automatically, too. I'm not sure if acts_as_list does this automatically. Try it.

0
Beu On

Try to use "act_as_list" gem.

class TodoList < ActiveRecord::Base
  has_many :todo_items, -> { order(position: :asc) }
end

class TodoItem < ActiveRecord::Base
  belongs_to :todo_list
  acts_as_list scope: :todo_list
end

todo_list = TodoList.find(...)
todo_list.todo_items.first.move_to_bottom
todo_list.todo_items.last.move_higher

Refer: https://github.com/swanandp/acts_as_list