closure_tree 3.2.1 → 3.3.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.
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