public
Description: Extras for DataMapper, including bridges to DataObjects::Migrations and Merb::DataMapper
Homepage: http://datamapper.org
Clone URL: git://github.com/sam/dm-more.git
dm-more / dm-is-tree / lib / dm-is-tree / is / tree.rb
100644 127 lines (113 sloc) 4.679 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
module DataMapper
  module Is
    module Tree
      def self.included(base)
        base.extend(ClassMethods)
      end
 
      # An extension to DataMapper to easily allow the creation of tree
      # structures from your DataMapper models.
      # This requires a foreign key property for your model, which by default
      # would be called :parent_id.
      #
      # Example:
      #
      # class Category
      # include DataMapper::Resource
      # include DataMapper::Is::Tree
      #
      # property :id, Integer
      # property :parent_id, Integer
      # property :name, String
      #
      # is_a_tree :order => "name"
      # end
      #
      # root
      # +- child
      # +- grandchild1
      # +- grandchild2
      #
      # root = Category.create("name" => "root")
      # child = root.children.create("name" => "child")
      # grandchild1 = child1.children.create("name" => "grandchild1")
      # grandchild2 = child2.children.create("name" => "grandchild2")
      #
      # root.parent # => nil
      # child.parent # => root
      # root.children # => [child]
      # root.children.first.children.first # => grandchild1
      # Category.first_root # => root
      # Category.roots # => [root]
      #
      # The following instance methods are added:
      # * <tt>children</tt> - Returns all nodes with the current node as their parent, in the order specified by
      # <tt>:order</tt> (<tt>[grandchild1, grandchild2]</tt> when called on <tt>child</tt>)
      # * <tt>parent</tt> - Returns the node referenced by the foreign key (<tt>:parent_id</tt> by
      # default) (<tt>root</tt> when called on <tt>child</tt>)
      # * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node
      # (<tt>[grandchild2]</tt> when called on <tt>grandchild1</tt>)
      # * <tt>generation</tt> - Returns all the children of the parent, including the current node (<tt>
      # [grandchild1, grandchild2]</tt> when called on <tt>grandchild1</tt>)
      # * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[root, child1]</tt>
      # when called on <tt>grandchild2</tt>)
      # * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>grandchild2</tt>)
      #
      # Author:: Timothy Bennett (http://lanaer.com)
      module ClassMethods
        # Configuration options are:
        #
        # * <tt>child_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
        def is_a_tree(options = {})
          configuration = { :child_key => :parent_id }
          configuration.update(options) if Hash === options
 
          belongs_to :parent, :class_name => name, :child_key => [ configuration[:child_key] ]
          has n, :children, :class_name => name, :child_key => [ configuration[:child_key] ]
 
          include DataMapper::Is::Tree::InstanceMethods
 
          class_eval <<-CLASS, __FILE__, __LINE__
def self.roots
all :#{configuration[:child_key]} => nil, :order => #{configuration[:order].inspect}
end
 
def self.first_root
first :#{configuration[:child_key]} => nil, :order => #{configuration[:order].inspect}
end
CLASS
 
          class << self
            alias_method :root, :first_root # for people used to the ActiveRecord acts_as_tree
          end
        end
 
        alias_method :can_has_tree, :is_a_tree # just for fun ;)
      end
 
      module InstanceMethods
        # Returns list of ancestors, starting with the root.
        #
        # grandchild1.ancestors # => [root, child]
        def ancestors
          node, nodes = self, []
          nodes << node = node.parent while node.parent
          nodes.reverse
        end
 
        # Returns the root node of the current node’s tree.
        #
        # grandchild1.root # => root
        def root
          node = self
          node = node.parent while node.parent
          node
        end
 
        # Returns all siblings of the current node.
        #
        # grandchild1.siblings # => [grandchild2]
        def siblings
          generation - [self]
        end
 
        # Returns all children of the current node’s parent.
        #
        # grandchild1.generation # => [grandchild1, grandchild2]
        def generation
          parent ? parent.children : self.class.roots
        end
 
        alias_method :self_and_siblings, :generation # for those used to the ActiveRecord acts_as_tree
      end
 
    end # Tree
  end # Is
end # DataMapper