So it's disappointing and almost a little surprising when I encounter a feature of the Rails framework that doesn't just work automatically.
acts_as_list
is such a feature, requiring days of reading source files and experimenting with logfile to figure out the series of magical incantations needed to make the darn thing work.The theory behind
acts_as_list
is pretty simple. You add an integer position
column to a database table, and add acts_as_list
to the model class it maps to. At that point, anytime you use the object in a collection, the position column gets set with the sort order of that object relative to everything else in the collection. List order is even scoped: a child table can be ordered within the context of its parent table by adding :scope => :parent_model_name
to the model declaration.In practice, however, there are some problems I ran into which I wasn't expecting. Some of them are well documented in Agile Web Development with Rails, but some of them required a great deal of research to solve.
List items appear in order of record creation, not in order of
:position
If an ImageSet has_many TitledImages, and TitledImage acts_as_list over the scope of an ImageSet, you'd expect ImageSet.titled_images to return a collection in the order that's set in the
position
column, right? This won't happen unless you modify the parent class definition (ImageSet
, in this case) to specify an order column on your has_many
declaration: has_many :titled_images, :order => :position
Pagination loses list order
Having fixed this problem, if you page through a list of items, you'll discover that the items once again appear in order of record creation, ignoring the value set in
position
. Fixing this requires you to manually specify the order for paged items using the :order
option to paginate
:@image_pages, @titled_images = paginate(:titled_image,Adjusting list order doesn't update objects in memory
{:per_page => 10,
:conditions => conditions,
:order => 'position' })
Okay, this one took me the most time to figure out.
acts_as_list
has nothing to do with the order of your collection. Using array operators to move elements around in the collection returned by ImageSet.titled_image
does absolutely nothing to the position
column.Worse yet, using the
acts_as_list
position modifiers like insert_at
will not affect the objects in memory. So if you've been working with a collection, then call an acts_as_list
method that affects its position, saving the elements that of collection will overwrite the position with old values. The position manipulation operators are designed to minimize SQL statements executed: among other side-effects, they circumvent optimistic locking. You must reload your collections after doing any list manipulation.Moving list items from one parent object to another doesn't reorder their positions
Because
acts_as_list
pays little attention to order within a collection, removing an item from one parent and adding it to another requires explicit updates to the position
attribute. You should probably use remove_from_list
to zero out the position before you transfer items from one parent to another, but since this reshuffles position columns for all other items in the list, I'd be cautious about doing this within a loop. Since I was collating items from two different lists into a third, I just manually set the position:
0.upto(max_size-1) do |i|In my opinion,
# append the left element here
if i < left_size
new_set.titled_images << left_set.titled_images[i]
end
# append the right element
if i < right_size
new_set.titled_images << right_set.titled_images[i]
end
end
# this has no effect on acts as list unless I do it manually
1.upto(new_set.titled_images.size) do |i|
new_set.titled_images[i-1].position=i
end
acts_as_list
is still worth using — in fact, I'm about to work with its reordering functionality a lot. But I won't be surprised if I find myself experimenting with logfiles again.
Man, I'm glad I found this -- thanks!
ReplyDeletethis is true, plugins that help with collections have some unexpected side effects when they aren't updating the database when you expect it to. I've resorted to doing a collection reload whenever I make a change to the list, to make sure I'm looking at the freshest copy from the database. Not the most elegant method I know. I should probably dig more into the source and figure out a better way.
ReplyDeleteHenry
Rain Hats
looks like you need to install a plugin to use it these days:
ReplyDeleteruby script\plugin install git://github.com/rails/acts_as_list.git
wow thanks melissa, that helped alot!
ReplyDelete