ActsAsOrderedTree
v.1.2
+----+-----------+----------+---------+
node_1 | id | parent_id | position | name |
\_ node_2 +----+-----------+----------+---------+
\_ node_3 | 1 | 0 | 1 | Node_1 |
| \_ node_4 | 2 | 1 | 1 | Node_2 |
| \_ node_5 | 3 | 1 | 2 | Node_3 |
| | \_ node_8 | 4 | 3 | 1 | Node_4 |
| | \_ node_9 | 5 | 3 | 2 | Node_5 |
| \_ node_10 | 6 | 1 | 3 | Node_6 |
| \_ node_11 | 7 | 1 | 4 | Node_7 |
\_ node_6 | 8 | 5 | 1 | Node_8 |
\_ node_7 | 9 | 5 | 2 | Node_9 |
| | 10 | 3 | 3 | Node_10 |
| | 11 | 3 | 4 | Node_11 |
node_12 | 12 | 0 | 2 | Node_12 |
\_ node_13 | 13 | 12 | 1 | Node_13 |
\_ node_14 | 14 | 12 | 2 | Node_14 |
| \_ node_15 | 15 | 14 | 1 | Node_15 |
| \_ node_16 | 16 | 14 | 2 | Node_16 |
| | \_ node_19 | 17 | 12 | 3 | Node_17 |
| | \_ node_20 | 18 | 12 | 4 | Node_18 |
| \_ node_21 | 19 | 16 | 1 | Node_19 |
| \_ node_22 | 20 | 16 | 2 | Node_20 |
\_ node_17 | 21 | 14 | 3 | Node_21 |
\_ node_18 | 22 | 14 | 4 | Node_22 |
+----+-----------+----------+---------+
class Person < ActiveRecord::Base
ordered_tree :foreign_key => :parent_id,
:order => :position
end
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :parent_id , :integer ,:null => false ,:default => 0
t.column :position , :integer
end
add_index(:people, :parent_id)
end
end
Which “in effect” sets up the following:
belongs_to :parent,
:class_name => Person,
:foreign_key => :parent_id
has_many :children,
:class_name => Person,
:foreign_key => :parent_id,
:order => :position
Note: :parent_id
and :position
are default values for the foreign_key
and order
, respectively.
Overview
Actions Tree Methods List Method
--------------------------------------------------------------------------------------------------
Create
To create a child object at a specific position,
use one of the following:
Person.create(:parent_id => parent.id, :position => 2)
parent.children << Person.new(:position => 3)
parent.children.create(:position => 5)
To create a new 'root', use:
Person.create(:position => 2)
:position will default to the bottom of the parent's list
:parent_id defaults to 0 (Class.roots)
Read
roots (class method) self_and_siblings
root siblings
parent position_in_list
ancestors
children
descendants
Update
shift_to(parent = nil, position = nil) move_above(sibling = nil)
orphan move_higher
orphan_children move_lower
parent_adopts_children move_to_top
orphan_self_and_children move_to_bottom
orphan_self_and_parent_adopts_children
Destroy
destroy (deletes all descendants)
destroy_and_orphan_children
destroy_and_parent_adopts_children
Setting the scope
If you want to have multiple trees in the same database (let’s say you want to have multiple sets of pages for the different tenants on your database), you should set a scope. A scope basically says: “work within this scope when possible”.
How does it work?
Let’s say I have a multi-tenant CMS app. I want each site to have their own Page tree. So Page uses ordered_tree :scope => :site
so that when I do this:
@site_1 = Site.create :name => "First site"
@site_2 = Site.create :name => "Second site"
Page.create(:site => @site_1).position # returns 1
Page.create(:site => @site_2).position # returns 1
Page.create(:site => @site_2).position # returns 2
Page.create(:site => @site_1).position # returns 2
Warning: Page.roots
will always return all the root pages (all pages with parent_id of 0). That’s because there’s no way to know which pages you want to see. That means, when you want to get a site’s root pages, go through the site: @site.pages.roots
Here are all the ways to define the scope:
Give a symbol without _id:
ordered_tree :scope => :site
ordered_tree
will add _id
to :site
so it becomes: :site_id
Thus, you can also pass:
Give a symbol with _id
ordered_tree :scope => :site_id
Override the scope_condition method
For more complex stuff, in the model that has ordered_tree
, define a method like this:
def scope_condition
"site_id = #{site_id} AND user_login = '#{user_login}'"
end
Making parent_id (the foreign key) point to something else
If you want parent_id
to point to something else instead of the id, then supply :primary_key => :alternate_id
to the ordered_tree
method. This will only probably be useful in tandem with a scope:
ordered_tree :primary_key => :relative_id, :scope => :account
Install
gem ‘ordered_tree’