Reordering associated "has_many" objects


#1

Hi,

I have two objects, lets call them Collection and Foobar (see below).
I want to be able to specify the order of foobars in a collection
using e.g.

   @collection.foobar_order = [4, 2, 1, 0, 3]
   @collection.save

In irb (script/console), stuff like “a = [:a,:b, :c] ; a = [1 2
0].collect {|x| a[x]}” works fine.
Also in irb, “self.foobar_order.collect { |x| self.foobars[x] }”
correctly returns the re-sorted elements.
However, in Collection#do_reorder, “self.foobars = …” does not
update the object’s “foobars”.

Why?

Thank you! :slight_smile:

Code:

def Collection < AR::Base
has_many :foobars, :order => :position
before_validation :do_reorder
private
def do_reorder
self.foobars = self.foobar_order.collect { |x| self.foobars[x] }
self.foobars.each_with_index do |f,i|
f.position = i # save position in DB for later fetching in
correct order
end
end
end

def Foobar < AR::Base
belongs_to :collection
end

Additional info (not strictly needed for my question):

I have looked at acts_as_list’s features, but I do not need to move
objects up and down, and acts_as_list also has the (big) downside of
acting directly on the DB, and not updating the object instances.
All objects get loaded once and all sorting is done use a
Scriptaculous Sortable instance using pure JS, no AJAX (= no server
load). Upon form submit, Sortable saves the current sorting order as
an array in a hidden field called “foobar_order”, which is then used
by the above code.

Possibly related question:
I have tried to implement Array#reorder and Array#reorder! (in-place)
using the above code like

def Array

e.g. " a=[:a,:b,:c] ; o=[1 2 0] ; a.reorder(o) #=> [:b, :c, :a]

def reorder(o)
o.collect {|x| self[x] }
end
def reorder!(o)
self = self.reorder(o)
end
end

reorder works, but reorder! does not work since you cannot assign
something to “self”.
How would you implement in-place reordering of Arrays? Perhaps even
without having to create copies (since it might be an Array of AR
objects)?

Thank you!

Jens


#2

On 12 Jan 2009, at 11:22, Jens wrote:

In irb (script/console), stuff like “a = [:a,:b, :c] ; a = [1 2
0].collect {|x| a[x]}” works fine.
Also in irb, “self.foobar_order.collect { |x| self.foobars[x] }”
correctly returns the re-sorted elements.
However, in Collection#do_reorder, “self.foobars = …” does not
update the object’s “foobars”.

Because it’s not an actual array you’re assigning to. It’s futzing
with db stuff and actually doesn’t touch objects that don’t need to be
added/removed to the collection. The relevant bit of code is at
http://github.com/rails/rails/tree/2-2-stable/activerecord/lib/active_record/associations/association_collection.rb#L309

Fred

before_validation :do_reorder
def Foobar < AR::Base
belongs_to :collection
end

reorder works, but reorder! does not work since you cannot assign
something to “self”.
How would you implement in-place reordering of Arrays? Perhaps even
without having to create copies (since it might be an Array of AR
objects)?

Look at Array#replace (and either way right now you’re not copying AR
objects)