Creating a Directory of Nested Topics with acts_as_treeProblemDatabase tables are simply a set of rows. However, you often want those rows to behave in some other way. If the data in your table represents a tree structure, how do you work with it as a tree? For example, you have a web site organized by topic. Topics can have subtopics, as can the subtopics themselves. You want to model these topics as a tree structure and store them in a single database table. SolutionFirst, create a topics table that includes a parent_id column, and populate it with some topics. Use the following migration for this: db/migrate/001_create_topics.rb: class CreateTopics < ActiveRecord::Migration def self.up create_table :topics do |t| t.column :parent_id, :integer t.column :name, :string end Topic.create :name => 'Programming and Development' Topic.create :parent_id => 1, :name => 'Algorithms' Topic.create :parent_id => 1, :name => 'Methodologies' Topic.create :parent_id => 3, :name => 'Extreme Programming' Topic.create :parent_id => 3, :name => 'Object-Oriented Programming' Topic.create :parent_id => 3, :name => 'Functional Languages' Topic.create :parent_id => 2, :name => 'Sorting' Topic.create :parent_id => 7, :name => 'Bubble sort' Topic.create :parent_id => 7, :name => 'Heap sort' Topic.create :parent_id => 7, :name => 'Merge sort' Topic.create :parent_id => 7, :name => 'Quick sort' Topic.create :parent_id => 7, :name => 'Shell sort' end def self.down drop_table :topics end end Declare that this model is to act as a tree structure: app/models/topic.rb: class Topic < ActiveRecord::Base acts_as_tree :order => "name" end DiscussionCalling the acts_as_tree class method on a model gives instances of that model some additional methods for inspecting the their relationships within the tree. These methods include:
Let's open up a Rails console session and inspect the topics tree that was created by the solution. First, get the root node, which we know has a parent_id of null: >> root = Topic.find(:first, :conditions => "parent_id is null") => #<Topic:0x4092ae74 @attributes={"name"=>"Programming and Development", "id"=>"1", "parent_id"=>nil}> We can show the root node's children with: >> root.children => [#<Topic:0x4090da04 @attributes={"name"=>"Algorithms", "id"=>"2", "parent_id"=>"1"}>, #<Topic:0x4090d9c8 @attributes={"name"=>"Methodologies", "id"=>"3", "parent_id"=>"1"}>] The following returns a hash of the attributes of the first node in the root node's array of children: >> root.children.first.attributes => {"name"=>"Algorithms", "id"=>2, "parent_id"=>1} We can find a leaf node from the root by alternating calls to children and first. From the leaf node, a single call to root finds the root node: >> leaf = root.children.first.children.first.children.first => #<Topic:0x408dd804 @attributes={"name"=>"Bubble sort", "id"=>"8", "parent_id"=>"7"}, @children=[]> >> leaf.root => #<Topic:0x408cffd8 @attributes={"name"=>"Programming and Development", "id"=>"1", "parent_id"=>nil}, @parent=nil> In addition to the topics loaded in the solution, we can create more directly from the Rails console. Let's create another root node named Shapes and give it two children nodes of its own: >> r = Topic.create(:name => "Shapes") => #<Topic:0x4092e9d4 @attributes={"name"=>"Shapes", "id"=>13, "parent_id"=>nil}, @new_record_before_save=false, @errors=#<ActiveRecord::Errors:0x4092baf4 @errors={}, @base=#<Topic:0x4092e9d4 ...>>, @new_record=false> >> r.siblings => [#<Topic:0x4092508c @attributes={"name"=>"Programming and Development", "id"=>"1", "parent_id"=>nil}>] >> r.children.create(:name => "circle") => #<Topic:0x40921ab8 @attributes={"name"=>"circle", "id"=>14, "parent_id"=>13}, @new_record_before_save=false, @errors=#<ActiveRecord::Errors:0x40921108 @errors={}, @base=#<Topic:0x40921ab8 ...>>, @new_record=false> >> r.children.create(:name => "square") => #<Topic:0x4091c57c @attributes={"name"=>"square", "id"=>15, "parent_id"=>13}, @new_record_before_save=false, @errors=#<ActiveRecord::Errors:0x4091bbcc @errors={}, @base=#<Topic:0x4091c57c ...>>, @new_record=false> From mysql, we can verify that the three new elements were added to the database as expected. mysql> select * from topics; +----+-----------+-----------------------------+ | id | parent_id | name | +----+-----------+-----------------------------+ | 1 | NULL | Programming and Development | | 2 | 1 | Algorithms | | 3 | 1 | Methodologies | | 4 | 3 | Extreme Programming | | 5 | 3 | Object-Oriented Programming | | 6 | 3 | Functional Languages | | 7 | 2 | Sorting | | 8 | 7 | Bubble sort | | 9 | 7 | Heap sort | | 10 | 7 | Merge sort | | 11 | 7 | Quick sort | | 12 | 7 | Shell sort | | 13 | NULL | Shapes | | 14 | 13 | circle | | 15 | 13 | square | +----+-----------+-----------------------------+ 15 rows in set (0.00 sec) acts_as_tree and acts_as_nested_set are significantly different from each other, even though they appear to do the same thing. acts_as_tree scales much better with a big table because each row does not have to be updated when a new row is added. With acts_as_nested_set, position information for each record has to be updated whenever an item is added or removed. The default behavior is to use a column named parent_id to store parent nodes in the tree. You can change this behavior by specifying a different column name with the :foreign_key option of the acts_as_tree options hash. See Also
|