10 Oct
Posted by ProCOM
on October 10, 2007 – 11:46 am - 378 views
If you're new here, you may want to subscribe to my RSS feed. So that you can read the latest updates about Web2.0 tools, Making Money Online, Tips in SEO, Ajax and many more. Thanks for visiting ProgramimiCOM!
In addition to allowing on-the-fly schema mapping, the Active Record provides a variety of services helpful to complicated table situations. Keep reading to learn more.In the previous part of this
series, I introduced the concept of Active Record. Going by the philosophy of “Convention-over-Configuration,” Active Record eliminated the need for configuration files and brought forward on-the-fly schema mapping. In other words, with the least lines of code, one could map tables to the corresponding classes without worrying about re-mapping the tables and classes when the schema changed.
However, all this was discussed from the perspective of a single participating table. The real world is totally different; there may be hundreds of participating tables with half of them having various relationships between them.
In this discussion I will be focusing on those services provided by Active Record that facilitate advanced concepts. These include participating relationships, composition mechanisms, and so forth.
In the next two sections of this article, I will be discussing the advanced services provided by Active Record. In the final section, I will be putting the concepts introduced in the earlier two sections into practice. That’s the outline for this discussion.
Active Record: Into the Deep
Active Record’s specialty lies in providing services for advance mapping and data validation techniques without the usually inherent language complexity. Without much ado let’s look at these services. There are two services that solely focus on mapping. They are Relationship Mapping and Act As Mapping. They can be thought of as basic and advanced mapping. Relationship Mapping provides basic database-based relationship mapping, whereas Act As Mapping provides fine grained and advanced mapping based on the basic functionality of Relationship Mapping. Here are the details.
Relationship Mapping
In real world applications, tables never exist in isolation. They are related at the database level using relationships based on the foreign key concept. One table can be related to another table through one-to-one, one-to-many or many-to-one relationships. Active Record bases its object-to-object relationship on how the corresponding tables are related at the database level. On the basis of types of database level relationships, there are three types of basic mapping in Active Record:
Each of these are mapped using two-way mapping. That means the classes on both sides of relationships know how they are related to the other class.
In one-to-one mapping, a foreign key in one row of a table references at most a single row of another table. For example, let’s take two tables - INVOICES and ORDERS. The following diagram shows the relation between the tables:
To map tables with a one-to-one relationship, Active Record provides two declarations that have to be added to the corresponding classes. They are belongs_to and has_one. The belongs_to declaration appears in the class/model for the table that contains the foreign key, whereas has_one is declared in the class which is on the other end i.e. the one whose primary key is acting as the foreign key. To map the classes according to the relationship between INVOICES and ORDERS, the addition to the classes would be:
class Order < ActiveRecord::Base
has_one :invoice
. . .
class Invoice < ActiveRecord::Base
belongs_to :order
Since for each Order there is at most one Invoice, the Order class contains the has_one declaration connecting it to the object of the Invoice class. Similarly, the Invoice class contains the belongs_to declaration to map it with the Order class, more specifically the object of Order class.
In a One-to-many relationship, a primary key in one row of a table references or is associated with an arbitrary number of rows in another table. For example, the relationship between ORDERS and LINE_ITEMS is that of one-to-many. The DDL of the tables will make it more clear:
DDL for ORDERS
create table orders (
id int not null auto_increment,
name varchar(100) not null,
/* … */
primary key (id)
);
DDL for LINE_ITEMS
create table line_items (
id int not null auto_increment,
order_id int not null,
quantity int not null default 0,
unit_price float(10,2) not null,
constraint fk_items_order foreign key (order_id) references orders(id),
primary key (id)
);
An order can be associated with any number of line-items. To map such a relationship, one must use the declarations has_many and belongs_to. The declaration has_many must be declared in the class representing the table at the “one” end of a one-to-many relationship, whereas belongs_to is declared in the class representing the table at the “many” end of the relationship. To illustrate further, below are the classes corresponding to the ORDERS and LINE_ITEMS tables with the mapping declaration embedded:
class Order < ActiveRecord::Base
has_many :line_items
. . .
class LineItem < ActiveRecord::Base
belongs_to :order
Using the has_many declaration, the Order class is associated with the line_items class and tells the framework that one instance/object of the Order class is associated with many instances of the Line_Items class. The belongs_to declaration works in the same way as it works in one-to-one relationship mapping.
When many records of one table are associated with many records of another table, then the tables have a many-to-many relationship. To implement such a relationship, a link/join table needs to be used. The join table contains a foreign key for each of the tables its linking, so each row in the join table represents a linkage between the two tables. For example, PRODUCT and CATEGORIES have a many-to-many relationship. To implement the relationship, a link table has to be introduced: CATEGORIES_PRODUCT. It contains foreign key references to both tables. Here are the DDL of all three tables:
DDL for PRODUCTS
create table products (
id int not null auto_increment,
title varchar(100) not null,
/* . . . */
primary key (id)
);
DDL for CATEGORIES
create table categories (
id int not null auto_increment,
name varchar(100) not null,
/* … */
primary key (id)
);
DDL for CATEGORIES_PRODUCTS
create table categories_products (
product_id int not null,
category_id int not null,
constraint fk_cp_product foreign key (product_id) references products(id),
constraint fk_cp_category foreign key (category_id) references categories(id)
);
The conventional way of mapping a many-to-many relationship would contain a declaration for the join/link table too. But that is not done with Active Record. Only the main tables are required to be mapped. It assumes that the join table is named after the two tables it joins (with the names in alphabetical order). To map the main tables, the classes corresponding to them must have the following declaration: has_and_belongs_to_many. This declaration links them both through the join/link table which is taken care by the Active Record.
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
. . .
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
In the above example there is a mention of the link/join table. The reason is that once Active Record sees the declaration has_and_belongs_to_many in both tables, it checks for the link table categories_products automatically. Thus “Convention-over-Configuration” works here too.
Acts As Mapping
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.
Mapping and Rails in the Real World
Now let’s look how the mapping discussed fits into the whole Rails framework. I will be extending the existing application developed in the previous part by adding a new table and corresponding operations. I will provide a custom view part to show the use of the tree data structure. So let’s get started. First comes the table. It’s the CATEGORIES table, and here is the DDL for it:
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 comes the model corresponding to the table. The command to generate the model class is:
ruby script/generate model User
In the generated class I am giving the declaration acts_as_tree to tell the framework that I need the tree data structure.
class Category < ActiveRecord::Base
acts_as_tree :order => “name”
end
As you can see, I am reusing the table and model discussed in the previous section.
Next comes the controller. The command to create the controller is:
ruby scriptgenerate controller Mapping
class MappingController < ApplicationController
scaffold:category
def list
end
end
Every other operation will be provided with a default scaffold except for listing/view. Next comes the definition of listing. Since the application is for a tree structure, the operation for creating the tree will be done in the list method.
class MappingController < ApplicationController
scaffold:category
def list
@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”)
end
end
Next is the list.rhtml. Here is the code for list.rhtml.
<ul>
<li>
<%=@root.name%>
<ul>
<li>
<% subcat=@root. root.children.first%>
<%=subcat.name%>
<!-same for all others
</li>
</ul>
</li>
</ul>
That brings us to the end of this part. However, the services provided by Active Record are many. The most important are data validation, transactions and single table inheritance. These will be topics of discussion in the future. Till then…
—
by A.P.Rajshekhar
Print This Post
Email This Post
Comments RSS
TrackBack Identifier URI
You must be logged in to post a comment.