acts_as_sane_tree 1.4 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,10 @@ This is a drop in replacement for acts_as_tree on systems with Postgresql >= 8.4
7
7
 
8
8
  A fast way to build trees.
9
9
 
10
+ == What version of Rails
11
+
12
+ As of version 2, acts_as_sane_tree now proudly offers support on Rails 2 as well as Rails 3
13
+
10
14
  == Requirements
11
15
 
12
16
  * PostgreSQL version >= 8.4
@@ -16,18 +20,26 @@ A fast way to build trees.
16
20
 
17
21
  Same as acts_as_tree. Basically: Specify a parent_id or the column that holds the parent ID information.
18
22
 
23
+ class MyFancyTree < ActiveRecord::Base
24
+ acts_as_sane_tree
25
+ end
26
+
19
27
  == Extras
20
28
 
21
29
  A few extras are provided. Of note are:
22
30
 
23
31
  * #depth -> depth from root of the current node
24
- * #descendents -> all descendents of the current node (provided in either flat array or nested hash)
32
+ * #descendents -> all descendents of the current node (provided in either scope or nested hash)
25
33
  * nodes_within?(src, chk) - Returns true if chk contains any nodes found within src and all ancestors of nodes within src
26
34
  * nodes_within(src, chk) - Returns any matching nodes from chk found within src and all ancestors within src
27
- * nodes_and_descendents(*args) - Returns all nodes and descendents for given IDs or records.
35
+ * nodes_and_descendents(*args) - Returns all nodes and descendents for given IDs or records. Provided in either scope or nested hash.
36
+ * Works properly with STI
37
+ * Adds 'depth' attribute to returned model instances
28
38
 
29
39
  == Documentation
30
40
 
41
+ Yes, there is documentation. Please read it and find all the fun tools at your fingertips:
42
+
31
43
  http://chrisroberts.github.com/acts_as_sane_tree
32
44
 
33
45
  == Thanks
@@ -37,8 +49,8 @@ http://chrisroberts.github.com/acts_as_sane_tree
37
49
 
38
50
  == License
39
51
 
40
- A large majority of this was copied directly from the original acts as tree, with the original license:
52
+ A (now not so large) majority of this was copied directly from the original acts as tree, with the original license:
41
53
  * Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
42
54
 
43
55
  The new additions continue:
44
- * Copyright (c) 2010 Chris Roberts, released under the MIT license
56
+ * Copyright (c) 2011 Chris Roberts, released under the MIT license
@@ -1,233 +1,6 @@
1
- require 'active_record'
1
+ require 'acts_as_sane_tree/acts_as_sane_tree'
2
+ require 'acts_as_sane_tree/version'
2
3
 
3
- module ActsAsSaneTree
4
-
5
- def self.included(base) # :nodoc:
6
- base.extend(ClassMethods)
7
- end
8
-
9
- # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
10
- # association. This requires that you have a foreign key column, which by default is called +parent_id+.
11
- #
12
- # class Category < ActiveRecord::Base
13
- # acts_as_sane_tree :order => "name"
14
- # end
15
- #
16
- # Example:
17
- # root
18
- # \_ child1
19
- # \_ subchild1
20
- # \_ subchild2
21
- #
22
- # root = Category.create("name" => "root")
23
- # child1 = root.children.create("name" => "child1")
24
- # subchild1 = child1.children.create("name" => "subchild1")
25
- #
26
- # root.parent # => nil
27
- # child1.parent # => root
28
- # root.children # => [child1]
29
- # root.children.first.children.first # => subchild1
30
- #
31
- # The following class methods are also added:
32
- #
33
- # * <tt>nodes_within?(src, chk)</tt> - Returns true if chk contains any nodes found within src and all ancestors of nodes within src
34
- # * <tt>nodes_within(src, chk)</tt> - Returns any matching nodes from chk found within src and all ancestors within src
35
- # * <tt>nodes_and_descendents(*args)</tt> - Returns all nodes and descendents for given IDs or records. Accepts multiple IDs and records. Valid options:
36
- # * :raw - No Hash nesting
37
- # * :no_self - Will not return given nodes in result set
38
- # * {:depth => n} - Will set maximum depth to query
39
- # * {:to_depth => n} - Alias for :depth
40
- # * {:at_depth => n} - Will return times at given depth (takes precedence over :depth/:to_depth)
41
- module ClassMethods
42
- # Configuration options are:
43
- #
44
- # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
45
- # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
46
- # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
47
- def acts_as_sane_tree(options = {})
48
- configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil, :max_depth => 10000 }
49
- configuration.update(options) if options.is_a?(Hash)
50
-
51
- belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
52
- has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
53
-
54
- validates_each configuration[:foreign_key] do |record, attr, value|
55
- record.errors.add attr, 'Cannot be own parent.' if !record.id.nil? && value.to_i == record.id.to_i
56
- end
57
-
58
- class_eval <<-EOV
59
- include ActsAsSaneTree::InstanceMethods
60
-
61
- def self.roots
62
- find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
63
- end
64
-
65
- def self.root
66
- find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
67
- end
68
-
69
- def self.nodes_within?(src, chk)
70
- s = (src.is_a?(Array) ? src : [src]).map{|x|x.is_a?(ActiveRecord::Base) ? x.id : x.to_i}
71
- c = (chk.is_a?(Array) ? chk : [chk]).map{|x|x.is_a?(ActiveRecord::Base) ? x.id : x.to_i}
72
- if(s.empty? || c.empty?)
73
- false
74
- else
75
- q = self.connection.select_all(
76
- "WITH RECURSIVE crumbs AS (
77
- SELECT #{self.table_name}.*, 0 AS level FROM #{self.table_name} WHERE id in (\#{s.join(', ')})
78
- UNION ALL
79
- SELECT alias1.*, crumbs.level + 1 FROM crumbs JOIN #{self.table_name} alias1 on alias1.parent_id = crumbs.id
80
- ) SELECT count(*) as count FROM crumbs WHERE id in (\#{c.join(', ')})"
81
- )
82
- q.first['count'].to_i > 0
83
- end
84
- end
85
-
86
- def self.nodes_within(src, chk)
87
- s = (src.is_a?(Array) ? src : [src]).map{|x|x.is_a?(ActiveRecord::Base) ? x.id : x.to_i}
88
- c = (chk.is_a?(Array) ? chk : [chk]).map{|x|x.is_a?(ActiveRecord::Base) ? x.id : x.to_i}
89
- if(s.empty? || c.empty?)
90
- nil
91
- else
92
- self.find_by_sql(
93
- "WITH RECURSIVE crumbs AS (
94
- SELECT #{self.table_name}.*, 0 AS level FROM #{self.table_name} WHERE id in (\#{s.join(', ')})
95
- UNION ALL
96
- SELECT alias1.*, crumbs.level + 1 FROM crumbs JOIN #{self.table_name} alias1 on alias1.parent_id = crumbs.id
97
- #{configuration[:max_depth] ? "WHERE crumbs.level + 1 < #{configuration[:max_depth].to_i}" : ''}
98
- ) SELECT * FROM crumbs WHERE id in (\#{c.join(', ')})"
99
- )
100
- end
101
- end
102
-
103
- def self.nodes_and_descendents(*args)
104
- raw = args.delete(:raw)
105
- no_self = args.delete(:no_self)
106
- at_depth = nil
107
- depth = nil
108
- hash = args.detect{|x|x.is_a?(Hash)}
109
- if(hash)
110
- args.delete(hash)
111
- depth = hash[:depth] || hash[:to_depth]
112
- at_depth = hash[:at_depth]
113
- end
114
- depth ||= #{configuration[:max_depth].to_i}
115
- depth_restriction = "WHERE crumbs.level + 1 < \#{depth}" if depth
116
- depth_clause = nil
117
- if(at_depth)
118
- depth_clause = "level + 1 = \#{at_depth.to_i}"
119
- elsif(depth)
120
- depth_clause = "level + 1 < \#{depth.to_i}"
121
- end
122
- base_ids = args.map{|x| x.is_a?(ActiveRecord::Base) ? x.id : x.to_i}
123
- q = self.find_by_sql(
124
- "WITH RECURSIVE crumbs AS (
125
- SELECT #{self.table_name}.*, \#{no_self ? -1 : 0} AS level FROM #{self.table_name} WHERE \#{base_ids.empty? ? 'parent_id IS NULL' : "id in (\#{base_ids.join(', ')})"}
126
- UNION ALL
127
- SELECT alias1.*, crumbs.level + 1 FROM crumbs JOIN #{self.table_name} alias1 on alias1.parent_id = crumbs.id
128
- \#{depth_restriction}
129
- ) SELECT * FROM crumbs WHERE level >= 0 \#{"AND " + depth_clause if depth_clause} ORDER BY level, parent_id ASC"
130
- )
131
- unless(raw)
132
- res = {}
133
- cache = {}
134
- q.each do |item|
135
- cache[item.id] = {}
136
- if(cache[item.parent_id])
137
- cache[item.parent_id][item] = cache[item.id]
138
- else
139
- res[item] = cache[item.id]
140
- end
141
- end
142
- res
143
- else
144
- q
145
- end
146
- end
147
-
148
- EOV
149
- end
150
- end
151
-
152
- module InstanceMethods
153
-
154
- # Returns all ancestors of the current node.
155
- def ancestors
156
- self.class.find_by_sql "WITH RECURSIVE crumbs AS (
157
- SELECT #{self.class.table_name}.*,
158
- 1 AS level
159
- FROM #{self.class.table_name}
160
- WHERE id = #{id}
161
- UNION ALL
162
- SELECT alias1.*,
163
- level + 1
164
- FROM crumbs
165
- JOIN #{self.class.table_name} alias1 ON alias1.id = crumbs.parent_id
166
- ) SELECT * FROM crumbs WHERE id != #{id} ORDER BY level DESC"
167
- end
168
-
169
- # Returns the root node of the tree.
170
- def root
171
- ancestors.first
172
- end
173
-
174
- # Returns all siblings of the current node.
175
- #
176
- # subchild1.siblings # => [subchild2]
177
- def siblings
178
- self_and_siblings - [self]
179
- end
180
-
181
- # Returns all siblings and a reference to the current node.
182
- #
183
- # subchild1.self_and_siblings # => [subchild1, subchild2]
184
- def self_and_siblings
185
- parent ? parent.children : self.class.roots
186
- end
187
-
188
- # Returns if the current node is a root
189
- def root?
190
- parent_id.nil?
191
- end
192
-
193
- # Returns all descendents of the current node. Each level
194
- # is within its own hash, so for a structure like:
195
- # root
196
- # \_ child1
197
- # \_ subchild1
198
- # \_ subsubchild1
199
- # \_ subchild2
200
- # the resulting hash would look like:
201
- #
202
- # {child1 =>
203
- # {subchild1 =>
204
- # {subsubchild1 => {}},
205
- # subchild2 => {}}}
206
- #
207
- # This method will accept two parameters.
208
- # * :raw -> Result is flat array. No Hash tree is built
209
- # * {:depth => n} -> Will only search for descendents to the given depth of n
210
- def descendents(*args)
211
- args.delete_if{|x| !x.is_a?(Hash) && x != :raw}
212
- self.class.nodes_and_descendents(:no_self, self, *args)
213
- end
214
-
215
- # Returns the depth of the current node. 0 depth represents the root of the tree
216
- def depth
217
- res = self.class.connection.select_all(
218
- "WITH RECURSIVE crumbs AS (
219
- SELECT parent_id, 0 AS level
220
- FROM #{self.class.table_name}
221
- WHERE id = #{id}
222
- UNION ALL
223
- SELECT alias1.parent_id, level + 1
224
- FROM crumbs
225
- JOIN #{self.class.table_name} alias1 ON alias1.id = crumbs.parent_id
226
- ) SELECT level FROM crumbs ORDER BY level DESC LIMIT 1"
227
- )
228
- res.empty? ? nil : res.first['level']
229
- end
230
- end
4
+ if(defined?(Rails))
5
+ ActiveRecord::Base.send :include, ActsAsSaneTree
231
6
  end
232
-
233
- ActiveRecord::Base.send :include, ActsAsSaneTree
@@ -1,3 +1,3 @@
1
1
  module ActsAsSaneTree
2
- VERSION = '1.4'
2
+ VERSION = '2.0'
3
3
  end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_sane_tree
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
5
- prerelease: false
4
+ hash: 3
5
+ prerelease:
6
6
  segments:
7
- - 1
8
- - 4
9
- version: "1.4"
7
+ - 2
8
+ - 0
9
+ version: "2.0"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Chris Roberts
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-25 00:00:00 -08:00
17
+ date: 2011-03-08 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements: []
78
78
 
79
79
  rubyforge_project:
80
- rubygems_version: 1.3.7
80
+ rubygems_version: 1.5.1
81
81
  signing_key:
82
82
  specification_version: 3
83
83
  summary: Sane tree builder for ActiveRecord and Postgresql