h1. 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 endWhich "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._ h2. 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_childrenh2. 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: pre. @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: h3. 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: h3. Give a symbol with _id @ordered_tree :scope => :site_id@ h3. Override the scope_condition method For more complex stuff, in the model that has @ordered_tree@, define a method like this: pre. def scope_condition "site_id = #{site_id} AND user_login = '#{user_login}'" end h2. 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 => :accounth2. Install gem 'ordered_tree'