Delving Deeper into the Active Record with Ruby-on-Rails - Acts As Mapping
(Page 3 of 4 )
The Relationship Mapping previously discussed provides mapping that is in one-to-one correspondence with the underlying database. In most cases, such a mapping is all that is required. However, this is simple in terms of variation in data structures. There are situations where data may be required to be presented in a different way. This is where Acts As Mapping plays a key role.
Acts As Mapping is built on top of a has_ relationship. Active Record provides two types of Acts As Mapping: Acts As List and Acts As Tree. As the name suggests the former provides List data structure and the later provides Tree data structure.
Acts As List
In a has_ relationship, if acts_as_list is declared in a class representing a child (the many end of the one-to-many relationship), the class representing the parent can view the child like a list. In other words, it gives the child list-like behavior. It facilitates the parent to traverse the children around in a list as well as remove the children from the list.
To implement such a behavior, the child table needs a column called position. Here again convention takes precedence. If the column is named position, Active Record will automatically use it. Active Record uses this column to record the position of the child. An example will make the concept more clear. For brevity I will be using tables named PARENTS and CHILDREN. Here are the DDL:
create table parents (
id int not null auto_increment,
primary key (id)
);
create table children (
id int not null auto_increment,
parent_id int not null,
name varchar(20),
position int,
constraint fk_parent foreign key (parent_id) references parents(id),
primary key (id)
);
The following are the corresponding classes:
class Parent < ActiveRecord::Base
has_many :children, :order => :position
end
class Child < ActiveRecord::Base
belongs_to :parent
acts_as_list :scope => :parent_id
end
The Parent class has one extra argument to the has_many declaration: :order. It ensures that the array is fetched from the database in the correct order. In the Child class, acts_as_list is declared and the scope is that of the parent_id or in other words, ensures that the children are placed in the corresponding parent's list instead of a global list. Now let's set up some test data - three children named One, Two and Three for a parent as follows:
parent = Parent.new
%w{ One Two Three}.each do |name|
parent.children.create(:name => name)
end
parent.save
The following statements show how the children display behavior akin to that of a list:
#A simple method to let us examine the contents of the list.
def display_children(parent)
puts parent.children.map {|child| child.name }.join(", ")
end
display_children(parent) #=> One, Two, Three, Four
puts parent.children[0].first? #=> true
two = parent.children[1]
puts two.lower_item.name #=> Three
puts two.higher_item.name #=> One
parent.children[0].move_lower
parent.reload
display_children(parent) #=> Two, One, Three, Four
parent.children[2].move_to_top
parent.reload
display_children(parent) #=>
The various move operations update the position. The parent must know this change immediately, hence the reload operation is called after the move_lower or move_higher operation.
Acts As Tree
The other kind of data structure that is commonly used is Tree. There are scenarios where the rows of a table need to be represented in the form of a tree structure. For this Active Record provides the Acts As Tree mapping. To use this, the corresponding table needs to have one additional column -- parent_id -- which is a foreign key reference back into the same table, linking child rows to their parent row. The pictorial representation would be as follows:

The best example of such a scenario is the previously discussed CATEGORIES table. Each category may have a sub-category and each sub-category may have its own sub-category. To make matters simple, let's create a CATEGORIES table having id, name and parent_id. So here is the DDL:
create table categories (
id int not null auto_increment,
name varchar(100) not null,
parent_id int,
constraint fk_category foreign key (parent_id) references categories(id),
primary key (id)
);
Next, in the corresponding class, the declaration acts_as_tree has to be declared.
class Category < ActiveRecord::Base
acts_as_tree :order => "name"
end
The parameter :order tells the framework that the children of a particular node must be displayed according to the value of the name column. The following statements set up the test data as one would populate a tree:
root = Category.create(:name => "Books")
fiction = root.children.create(:name => "Fiction")
non_fiction = root.children.create(:name => "Non Fiction")
non_fiction.children.create(:name => "Computers")
non_fiction.children.create(:name => "Science")
non_fiction.children.create(:name => "Art History")
fiction.children.create(:name => "Mystery")
fiction.children.create(:name => "Romance")
fiction.children.create(:name => "Science Fiction")
Now let's see how to access the data:
display_children(root) # Fiction, Non Fiction
sub_category = root.children.first
puts sub_category.children.size #=> 3
display_children(sub_category) #=> Mystery, Romance, Science Fiction
non_fiction = root.children.find(:first, :conditions => "name = 'Non Fiction'")
display_children(non_fiction) #=> Art History, Computers, Science
puts non_fiction.parent.name #=> Books
Here I have used the display function created previously. So creating a tree structure using application code cannot get any simpler than this.
That brings us to the end of this section. In the next section I will develop an application that utilizes the types of mapping just discussed.
Next: Mapping and Rails in the Real World >>
More Ruby-on-Rails Articles
More By A.P.Rajshekhar