34

I am using Rails 3.0.3 with ruby 1.9.2p0.

In my profiles_controller (edit function) I have this call

@profile = Profile.find(params[:id])
@profile_items = @profile.profile_items.order("pos")

to get the @profile_items in the correct order, sorted on "pos". In the _form.html.erb I have the following

<% @profile_items.each do |pi| %>
  <%= pi.pos %> | 
<% end %>
<%= f.fields_for :profile_items do |f2| %>
  <%= render 'profile_item_fields', :f => f2 %>
<% end %>

The 3 first lines are test code to show that the @profile_items are in the correct order. But when they are rendered they have lost the sorted order!

Now I have search a lot for an answer and I think this must be a common "trap" to fall into.

Thankful for any help...

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
Michael Lindström
  • 341
  • 1
  • 3
  • 3

2 Answers2

57

According to the Rails Documentation for fields_for, you can also specify the record object after the record name.

So something like this should work...

<%= f.fields_for :profile_items, @profile_items do |f2| %>
  <%= render 'profile_item_fields', :f => f2 %>
<% end %>
Terrell Miller
  • 953
  • 8
  • 13
  • 27
    Small amendment. If you want to keep records sorted, don't do it on the association. It will fetch stuff from database and blow away your temporary entries (like the ones with errors that didn't get saved). So instead of `@profile_items.order('position')` do something like this: `@profile_items.sort_by(&:position)`. – Grocery Dec 10 '12 at 21:38
  • 1
    Much, much, much, much, much better than using a default scope. – Jon Cairns Sep 03 '15 at 11:16
  • 1
    ... agree with Grocery on the problem - but the fields-for is implicit based on 'accepts nested attributes for' - so there is no @profile_items (using this example) ever defined. So how to sort the implicitly-generated collection? I found that answer here: https://stackoverflow.com/questions/10505853/how-to-maintain-the-ordering-for-nested-attributes-when-using-accepts-nested-att – JosephK Aug 20 '17 at 08:46
  • @Grocery Thank you! Was banging my head with those missing error messages! – Tashows Nov 18 '18 at 13:07
25

This can be accomplished with a default_scope on the nested model:

class YourModel < ActiveRecord::Base
  belongs_to :other_model
  default_scope { order(:name) }
end
Ben
  • 1,441
  • 4
  • 18
  • 20
  • Thanks for this. I was losing the will to continue – Al D Mar 03 '15 at 23:48
  • This is the best answer to anything I have ever seen. – Jesse Farmer Dec 28 '15 at 05:46
  • 1
    This is a good solution if you will _always_ want to sort this class the same way. If you will ultimately want to order it in others ways this can make things very complicated (you have to back out the default scope every time) and using a manual order or multiple named scopes is better. – Matt Sanders May 17 '16 at 19:27