acts_as_sane_tree 1.4 → 2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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