开发者

How do I preserve the order of associations in Rails3?

开发者 https://www.devze.com 2023-02-01 15:25 出处:网络
I have an array of locations locations = [\"california\", \"new york\", \"florida\"] I have a model called foobar that has_many :places.

I have an array of locations

locations = ["california", "new york", "florida"]

I have a model called foobar that has_many :places. the Place model belongs_to :foobar and has one relevant column - name:string.

I have a nested form that allows me to select locations applicable for the foobar. To offer the options, I build the places based on my locations list in my controller:

class FoobarController < ApplicationController
  def edit
    @foobar = Foobar.find(params[:id])
    APP_CONFIG['locations'].each do |location|
       @foobar.places.find_by_name(location) || @foobar.places.build(:name => location)
    end
  end
end

This works almost without a problem. The reason I'm including the edit method is because that's the problematic one. The list of places DOES get populated, but the Foobar places that already existed appear on the top.


Example time! :)

  1. I create a new Foobar. In my form I scroll down to places and I see the options "california", "new york" and "florida". I check "california" and "florida" and create Foobar.

  2. Next I go to edit Foobar. Again, I scroll down to the places and I see that I checked two options. That's great, except that the order in which the options appear is "california", "florida", "new york" - checked places are on the top.

How can I preserve the order of the locations from my original array?

Thanks!


Update: the view code:

<%= f.fields_for :places do |place| %>
    <div class="place">
        <%= place.hidden_field开发者_运维百科 :name %>
        <%= place.check_box :checked %>
        <%= place.label :checked, place.object.name %>
    </div>
<% end %>


You can't... At least not the way you are doing it. Think about what rails does. It automatically orders by the id (PK) column of a table. Since the records you are BUILDING wont have an id, they will be placed at the end - every time. You COULD finagle and use CREATE and fudge ids, but that's messy and could run into serious problems.

In order to maintain order (pun), you'll need some kind of array-pointer system:

class FoobarController < ApplicationController
  def edit
    @foobar = Foobar.find(params[:id])
    @places = Array.new
    APP_CONFIG['locations'].each do |location|
      @places << @foobar.places.build(:name => location)
    end
  end
end

Then, in your view you'll need to loop over the array and manually build the form. I recommend viewing the source and seeing how the nested form appears first.

Here's how I would approach it - build every time (no find_by_name). Then, in your model you want:

class Foo < ActiveRecord
  has_many :places

  accepts_nested_attributes_for :places, :reject_if => { |a| !APP_CONFIG['locations'].include?(a) || Place.find_by_name(a).nil? || !Place.find_by_name_and_foo_id(a, self.id).nil? }
end

Now, I haven't actually tested that code exactly for your case, so I'll break it down;

1.) Reject if the submitted attribute isn't in the global array (more of a security concern)

2.) Reject if the name isn't a valid place (again, security)

3.) Reject if the place ALREADY exists for that foo (your class that has_many :places)


Check out acts_as_list >> https://github.com/rails/acts_as_list, It explicitly stores the order of items in a has_many collection in a column called "position".

0

精彩评论

暂无评论...
验证码 换一张
取 消