closure_tree 3.2.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -11,6 +11,7 @@ Closure Tree is a mostly-API-compatible replacement for the
11
11
  * Support for polymorphism [STI](#sti) within the hierarchy
12
12
  * ```find_or_create_by_path``` for [building out hierarchies quickly and conveniently](#find_or_create_by_path)
13
13
  * Support for [deterministic ordering](#deterministic-ordering) of children
14
+ * Support for single-select depth-limited [nested hashes](#nested-hashes)
14
15
  * Excellent [test coverage](#testing) in a variety of environments
15
16
 
16
17
  See [Bill Karwin](http://karwin.blogspot.com/)'s excellent
@@ -25,7 +26,7 @@ Note that closure_tree only supports Rails 3.0 and later, and has test coverage
25
26
 
26
27
  2. Run ```bundle install```
27
28
 
28
- 3. Add ```acts_as_tree``` to your hierarchical model(s) (see the <em>Available options</em> section below for details).
29
+ 3. Add ```acts_as_tree``` to your hierarchical model(s). There are a number of [options](#available-options) you can pass in, too.
29
30
 
30
31
  4. Add a migration to add a ```parent_id``` column to the model you want to act_as_tree.
31
32
  You may want to also [add a column for deterministic ordering of children](#sort_order), but that's optional.
@@ -84,31 +85,31 @@ grandparent = Tag.create(:name => 'Grandparent')
84
85
  Child nodes are created by appending to the children collection:
85
86
 
86
87
  ```ruby
87
- child = parent.children.create(:name => 'Child')
88
+ parent = grandparent.children.create(:name => 'Parent')
88
89
  ```
89
90
 
90
91
  You can also append to the children collection:
91
92
 
92
93
  ```ruby
93
- child = Tag.create(:name => 'Child')
94
- parent.children << child
94
+ child1 = Tag.create(:name => 'First Child')
95
+ parent.children << child1
95
96
  ```
96
97
 
97
98
  Or call the "add_child" method:
98
99
 
99
100
  ```ruby
100
- parent = Tag.create(:name => 'Parent')
101
- grandparent.add_child parent
101
+ child2 = Tag.create(:name => 'Second Child')
102
+ parent.add_child child2
102
103
  ```
103
104
 
104
105
  Then:
105
106
 
106
107
  ```ruby
107
- puts grandparent.self_and_descendants.collect{ |t| t.name }.join(" > ")
108
- => "grandparent > parent > child"
108
+ grandparent.self_and_descendants.collect(&:name)
109
+ => ["Grandparent", "Parent", "First Child", "Second Child"]
109
110
 
110
- child.ancestry_path
111
- => ["grandparent", "parent", "child"]
111
+ child1.ancestry_path
112
+ => ["Grandparent", "Parent", "First Child"]
112
113
  ```
113
114
 
114
115
  ### find_or_create_by_path
@@ -146,13 +147,42 @@ h.ancestry_path
146
147
  => ["a", "b", "c", "d", "e", "f", "g", "h"]
147
148
  ```
148
149
 
150
+ ### Nested hashes
151
+
152
+ ```hash_tree``` provides a method for rendering a subtree as an
153
+ ordered nested hash:
154
+
155
+ ```ruby
156
+ b = Tag.find_or_create_by_path %w(a b)
157
+ a = b.parent
158
+ b2 = Tag.find_or_create_by_path %w(a b2)
159
+ d1 = b.find_or_create_by_path %w(c1 d1)
160
+ c1 = d1.parent
161
+ d2 = b.find_or_create_by_path %w(c2 d2)
162
+ c2 = d2.parent
163
+
164
+ Tag.hash_tree
165
+ => {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
166
+
167
+ Tag.hash_tree(:limit_depth => 2)
168
+ => {a => {b => {}, b2 => {}}}
169
+
170
+ b.hash_tree
171
+ => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
172
+
173
+ b.hash_tree(:limit_depth => 2)
174
+ => {b => {c1 => {}, c2 => {}}}
175
+ ```
176
+
177
+ HT: [ancestry](https://github.com/stefankroes/ancestry#arrangement) and [elhoyos](https://github.com/mceachen/closure_tree/issues/11)
178
+
149
179
  ### <a id="options"></a>Available options
150
180
 
151
181
  When you include ```acts_as_tree``` in your model, you can provide a hash to override the following defaults:
152
182
 
153
183
  * ```:parent_column_name``` to override the column name of the parent foreign key in the model's table. This defaults to "parent_id".
154
184
  * ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + "_hierarchies".
155
- * ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nil```.
185
+ * ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.
156
186
  * ```:nullify``` will simply set the parent column to null. Each child node will be considered a "root" node. This is the default.
157
187
  * ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)
158
188
  * ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)
@@ -166,6 +196,7 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
166
196
  * ```Tag.root``` returns an arbitrary root node
167
197
  * ```Tag.roots``` returns all root nodes
168
198
  * ```Tag.leaves``` returns all leaf nodes
199
+ * ```Tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.
169
200
 
170
201
  ### Instance methods
171
202
 
@@ -186,6 +217,7 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
186
217
  * ```tag.descendants``` returns a scope of all children, childrens' children, etc., excluding self ordered by depth.
187
218
  * ```tag.descendant_ids``` returns an array of the IDs of the descendants.
188
219
  * ```tag.self_and_descendants``` returns a scope of all children, childrens' children, etc., including self, ordered by depth.
220
+ * ```tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.
189
221
  * ```tag.destroy``` will destroy a node and do <em>something</em> to its children, which is determined by the ```:dependent``` option passed to ```acts_as_tree```.
190
222
 
191
223
  ## <a id="sti"></a>Polymorphic hierarchies with STI
@@ -285,6 +317,10 @@ Closure tree is [tested under every combination](https://secure.travis-ci.org/mc
285
317
 
286
318
  ## Change log
287
319
 
320
+ ### 3.3.0
321
+
322
+ * Added [```hash_tree```](#nested-hashes).
323
+
288
324
  ### 3.2.1
289
325
 
290
326
  * Added ```ancestor_ids```, ```descendant_ids```, and ```sibling_ids```
@@ -56,18 +56,18 @@ module ClosureTree
56
56
  has_many :ancestor_hierarchies,
57
57
  :class_name => hierarchy_class_name,
58
58
  :foreign_key => "descendant_id",
59
- :order => "generations asc",
59
+ :order => "#{quoted_hierarchy_table_name}.generations asc",
60
60
  :dependent => :destroy
61
61
 
62
62
  has_many :self_and_ancestors,
63
63
  :through => :ancestor_hierarchies,
64
64
  :source => :ancestor,
65
- :order => "generations asc"
65
+ :order => "#{quoted_hierarchy_table_name}.generations asc"
66
66
 
67
67
  has_many :descendant_hierarchies,
68
68
  :class_name => hierarchy_class_name,
69
69
  :foreign_key => "ancestor_id",
70
- :order => "generations asc",
70
+ :order => "#{quoted_hierarchy_table_name}.generations asc",
71
71
  :dependent => :destroy
72
72
  # TODO: FIXME: this collection currently ignores sort_order
73
73
  # (because the quoted_table_named would need to be joined in to get to the order column)
@@ -75,18 +75,22 @@ module ClosureTree
75
75
  has_many :self_and_descendants,
76
76
  :through => :descendant_hierarchies,
77
77
  :source => :descendant,
78
- :order => append_order("generations asc")
78
+ :order => append_order("#{quoted_hierarchy_table_name}.generations asc")
79
79
 
80
80
  def self.roots
81
81
  where(parent_column_name => nil)
82
82
  end
83
83
 
84
+ def self.hash_tree(options = {})
85
+ roots.inject(ActiveSupport::OrderedHash.new) { |h, ea| h.merge(ea.hash_tree(options)) }
86
+ end
87
+
84
88
  def self.leaves
85
89
  s = where("#{quoted_table_name}.#{primary_key} IN
86
90
  (SELECT ancestor_id
87
91
  FROM #{quoted_hierarchy_table_name}
88
92
  GROUP BY 1
89
- HAVING MAX(generations) = 0)")
93
+ HAVING MAX(#{quoted_hierarchy_table_name}.generations) = 0)")
90
94
  order_option ? s.order(order_option) : s
91
95
  end
92
96
  end
@@ -203,6 +207,22 @@ module ClosureTree
203
207
  node
204
208
  end
205
209
 
210
+ def hash_tree(options = {})
211
+ tree = ActiveSupport::OrderedHash.new
212
+ tree[self] = ActiveSupport::OrderedHash.new
213
+ id_to_hash = {self.id => tree[self]}
214
+ scope = descendants
215
+ if options[:limit_depth]
216
+ limit_depth = options[:limit_depth]
217
+ return {} if limit_depth <= 0
218
+ scope = scope.where("generations <= #{limit_depth - 1}")
219
+ end
220
+ scope.each do |ea|
221
+ id_to_hash[ea.ct_parent_id][ea] = (id_to_hash[ea.id] = ActiveSupport::OrderedHash.new)
222
+ end
223
+ tree
224
+ end
225
+
206
226
  protected
207
227
 
208
228
  def ct_validate
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "3.2.1" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "3.3.0" unless defined?(::ClosureTree::VERSION)
3
3
  end
data/spec/tag_spec.rb CHANGED
@@ -76,7 +76,7 @@ shared_examples_for Tag do
76
76
  end
77
77
 
78
78
  it "should create all tags" do
79
- Tag.all.should == [@root, @mid, @leaf]
79
+ Tag.all.should =~ [@root, @mid, @leaf]
80
80
  end
81
81
 
82
82
  it "should return a root and leaf without middle tag" do
@@ -118,6 +118,28 @@ shared_examples_for Tag do
118
118
  TagHierarchy.find_all_by_descendant_id(@root.id).should == root_hiers
119
119
  end
120
120
  end
121
+
122
+ it "builds hash_trees properly" do
123
+ b = Tag.find_or_create_by_path %w(a b)
124
+ a = b.parent
125
+ b2 = Tag.find_or_create_by_path %w(a b2)
126
+ d1 = b.find_or_create_by_path %w(c1 d1)
127
+ c1 = d1.parent
128
+ d2 = b.find_or_create_by_path %w(c2 d2)
129
+ c2 = d2.parent
130
+ Tag.hash_tree(:limit_depth => 0).should == {}
131
+ Tag.hash_tree(:limit_depth => 1).should == {a => {}}
132
+ Tag.hash_tree(:limit_depth => 2).should == {a => {b => {}, b2 => {}}}
133
+ tree = {a => {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}, b2 => {}}}
134
+ Tag.hash_tree(:limit_depth => 4).should == tree
135
+ Tag.hash_tree.should == tree
136
+ b.hash_tree(:limit_depth => 0).should == {}
137
+ b.hash_tree(:limit_depth => 1).should == {b => {}}
138
+ b.hash_tree(:limit_depth => 2).should == {b => {c1 => {}, c2 => {}}}
139
+ b_tree = {b => {c1 => {d1 => {}}, c2 => {d2 => {}}}}
140
+ b.hash_tree(:limit_depth => 3).should == b_tree
141
+ b.hash_tree.should == b_tree
142
+ end
121
143
  end
122
144
 
123
145
  describe Tag do
data/spec/user_spec.rb CHANGED
@@ -86,4 +86,22 @@ describe "empty db" do
86
86
  c2 = u.parent.contracts.create!
87
87
  u.root.indirect_contracts.to_a.should =~ [c1, c2]
88
88
  end
89
+
90
+ it "performs as the readme says it does" do
91
+ grandparent = Tag.create(:name => 'Grandparent')
92
+ parent = grandparent.children.create(:name => 'Parent')
93
+ child1 = Tag.create(:name => 'First Child')
94
+ parent.children << child1
95
+ child2 = Tag.create(:name => 'Second Child')
96
+ parent.add_child child2
97
+ grandparent.self_and_descendants.collect(&:name).should ==
98
+ ["Grandparent", "Parent", "First Child", "Second Child"]
99
+ child1.ancestry_path.should ==
100
+ ["Grandparent", "Parent", "First Child"]
101
+ d = Tag.find_or_create_by_path %w(a b c d)
102
+ h = Tag.find_or_create_by_path %w(e f g h)
103
+ e = h.root
104
+ d.add_child(e) # "d.children << e" would work too, of course
105
+ h.ancestry_path.should == %w(a b c d e f g h)
106
+ end
89
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: closure_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-20 00:00:00.000000000 Z
12
+ date: 2012-07-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -193,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
193
  version: '0'
194
194
  segments:
195
195
  - 0
196
- hash: -1184203510025686599
196
+ hash: 1888023415669580942
197
197
  required_rubygems_version: !ruby/object:Gem::Requirement
198
198
  none: false
199
199
  requirements:
@@ -202,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
202
  version: '0'
203
203
  segments:
204
204
  - 0
205
- hash: -1184203510025686599
205
+ hash: 1888023415669580942
206
206
  requirements: []
207
207
  rubyforge_project:
208
208
  rubygems_version: 1.8.21