acts_as_list

Active Record has three special relationships that let you explicitly model lists, trees, and nested sets: acts_as_list, acts_as_tree, and acts_as_nested_set, respectively. We’ll look at the two relationships required by Photo Share in this chapter: acts_as_list and acts_as_tree. acts_as_list lets you express items as an ordered list and also provides methods to move items around in the hierarchy. Figure 4-5 shows the mapping. In Photo Share, we’ll use acts_as_list to model a slideshow, which is an ordered list of slides. Later, we’ll use acts_as_tree to manage our nested categories.

acts_as_list allows an explicit ordering

Figure 4-5. acts_as_list allows an explicit ordering

First, let’s modify the existing app/models/slide.rb model. We want users to be able to move slides up and down in a show, so the slideshow needs an ordering. We’ll use the existing slides and add the Active Record macro acts_as_list.

class Slide < ActiveRecord::Base
  belongs_to :slideshow
  belongs_to :photo

  acts_as_list :scope => "slideshow_id"
end

This example builds a list of slides that compose a slideshow. belongs_to is a one-to-many relationship, imposing structure. The Slide model has a belongs_to relationship with Slideshow and Photo as the targets. acts_as_list is a helper macro, imposing order and introducing behavior related to a list. As of Rails 2.0, the macro is a plug-in and not part of the base library. To get it, type: script/plugin install acts_as_list.

$ script/plugin install acts_as_list
+ ./README
+ ./init.rb
+ ./lib/active_record/acts/list.rb
+ ./test/list_test.rb

That command loads the acts_as_list plug-in into the vendor/plugins/acts_as_list directory. From that point, you can use it just as if it were a native Rails macro. To Active Record, each macro is independent. You use the :scope parameter to tell Active Record which items belong in the list. In this case, we set the :scope parameter to :slideshow_id so all slides with the same slideshow_id will act as one independent list.

To capture ordering, Active Record uses a position attribute by default. Because you have a position column in the database, you don’t need to do anything more to the slides to support this list. However, for convenience, you’ll want the array of slides to be fetched and displayed in the right order, so make one small change to app/models/slideshow.rb:

class Slideshow < ActiveRecord::Base
  has_many :slides, :order => :position
end

We’re ready to use the list. You can use methods added by acts_as_list to change the order of slides in the slideshow, and to indicate which items are first and last:

>> show = Slideshow.find 1
=> #<Slideshow id: 1, name: "Interesting Pictures", created_at: "2008-05-11 00:37:27", 
updated_at: "2008-05-11 00:37:27">
>> show.slides.each {|slide| puts slide.photo.filename}
train.jpg
lighthouse.jpg
gargoyle.jpg
cat.jpg
cappucino.jpg
building.jpg
bridge.jpg
bear.jpg
baskets.jpg
=> [#<Slide id: 1, ...]
>> show.slides.first.photo.filename
=> "train.jpg"
>> show.slides.first.move_to_bottom
=> true
>> show.slides.last.photo.filename
=> "baskets.jpg"
>> show.reload
=> #<Slideshow id: 1, name: "Interesting Pictures", created_at: "2008-05-21 02:19:32", 
updated_at: "2008-05-21 02:19:32">
>> show.slides
=> [#<Slide id: 2, ...>, ...]
>> show.slides.last.photo.filename
=> "train.jpg"

By convention, positions start at 1 and are sequentially numbered through the end of the list. Position 1 is the top, and the biggest number is the bottom. You can move any item higher or lower, move items to the top or bottom, create items in any position, and get relative items in the list, as in Table 4-3. Keep in mind that moving something higher means making the position smaller, so you should think of the position as a priority. Higher positions mean higher priorities, so they’ll be closer to the front of the list.

Table 4-3 shows all the methods added by the acts_as_list relationship. Keep in mind that you’ll use acts_as_list on objects that already have a belongs_to relationship, so you’ll also get the methods and attributes provided by belongs_to. You’ll also inherit the methods from the array, so slideshow.slides[1] and slideshow.slides.first are both legal.

Table 4-3. Metaprogramming features for acts_as_list

Added feature—methods

Description

increment_position

Increments the position attribute of this list element:

slideshow.slides[1].increment_position

decrement_position

Decrements the position attribute of this list element:

slideshow.slides[2].decrement_position

higher_item

Returns the previous item in the list. Higher means closer to the front, or closer to index 1, as in priority:

slideshow.slides[2].higher_item

lower_item

Returns the next item in the list. Lower means closer to the back, or farther from index 1, as in priority:

slideshow.slides[1].lower_item

in_list?

Tests whether an object has been added to a list:

slide.in_list?

insert_at position

Inserts the current item at a given position. Default is position 1:

slide.insert_at(1)

first?

Returns true if position==1; false otherwise:

slide.first?

last?

Returns true if position is the largest in the list; return false otherwise:

slideshow.slides[7].last?

move_higher

Moves this item toward index 1:

slideshow.slides[4].move_lower

move_lower

Moves this item away from index 1:

slideshow.slides[3].move_higher

move_to_top

Moves this item to index 1:

slideshow.slides[3].move_to_top

move_to_bottom

Makes this item the last in the list:

slideshow.slides[3].move_to_bottom

remove_from_list

Removes this item from the list:

slideshow.slides[3].remove_from_list

Get Rails: Up and Running, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.