One Model With acts_as_list and acts_as_tree
Ruby on Rails provides nice ways to create a list (acts_as_list), that is to keep order of items, and to create a tree (acts_as_tree), that is to organise items with parent–children relationships. But what to do, if you need both functionalities at once?
My model has name Outline and it holds one textual information.
Firstly we will create a model and its migration:
-
ruby script/generate model Outline
The output should be:
-
exists app/models/
-
exists test/unit/
-
exists test/fixtures/
-
create app/models/outline.rb
-
create test/unit/outline_test.rb
-
create test/fixtures/outlines.yml
-
create db/migrate
-
create db/migrate/001_create_outlines.rb
The next two sections describe how to modify the generated migration and model files.
Migration
The migration is stored in the generated file db/migrate/001_create_outlines.rb. Of course, if you already created some migrations, the 001 number will be higher.
The model (and the table too) has to contain columns: parent_id for the acts_as_tree declaration, position for the acts_as_list declaration and name to hold textual data.
The content of the migration with three new columns:
-
class CreateOutlines < ActiveRecord::Migration
-
def self.up
-
create_table :outlines do |t|
-
t.column :parent_id, :integer
-
t.column :name, :string, :null => false
-
t.column :position, :integer, :null => false
-
end
-
end
-
-
def self.down
-
drop_table :outlines
-
end
-
end
Do not forget to apply the new migration:
-
rake db:migrate
This part was simple — the main part of the magic is in the model file…
Model
The model is stored in the generated file app/models/outline.rb.
Let’s add all necessary parts for acts_as_list and acts_as_tree declarations and mix them up gently (thanks to Kris for noticing):
-
class Outline < ActiveRecord::Base
-
acts_as_list :scope => :parent_id
-
acts_as_tree :order => "position"
-
end
That is it :)
Test
Here are an example, how it works (it is a modified output from ruby script/console):
-
#testing of the list features
-
o1 = Outline.new :name => "first" #create an instance with name "first"
-
=> #…
-
o1.save!
-
=> true
-
o1.position #its position is 1
-
=> 1
-
o2 = Outline.new :name => "second" #create another instance with name "second"
-
=> #…
-
o2.save!
-
=> true
-
o2.position #its position is 2
-
=> 2
-
o2.move_higher #move second item higher
-
=> true
-
o2.position #it is on the top; its position is 1
-
=> 1
-
o1.reload #it is necessary to reload it to reflect the previous change
-
=> #…
-
o1.position
-
=> 2
-
-
#testing of the tree features
-
o11 = Outline.new :name => "first’s child" #let’s create a child
-
=> #…
-
o1.children << o11 #assign it to its parent
-
=> [#…]
-
o11.position #the position is 1, because it is the first child of "first"
-
=> 1
-
>> o11.parent.name #what is the parents name? should be "first"
-
=> "first"
-
>> o1.children.each do |c| puts c.name end #what are the children names?
-
first’s child
-
=> [#…]
I hope it will help you. Enjoy
P.S.
This is the previous version of the model that Kris mentioned in his comment.
-
class Outline < ActiveRecord::Base
-
belongs_to :parent,
-
:class_name => "Outline"
-
-
acts_as_list :scope => :parent_id
-
-
has_many :children,
-
:class_name => "Outline",
-
:foreign_key => "parent_id",
-
:order => "position",
-
:dependent => :destroy
-
-
acts_as_tree :order => "position"
-
end










