closure_tree 3.8.1 → 3.8.2

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
@@ -13,9 +13,10 @@ closure_tree has some great features:
13
13
 
14
14
  * __Best-in-class select performance__:
15
15
  * Fetch your whole ancestor lineage in 1 SELECT.
16
- * Grab all your descendants: 1 SELECT.
17
- * Get all your siblings: 1 SELECT.
18
- * Fetch all [7-degrees-of-bacon in a nested hash](#nested-hashes): 1 SELECT.
16
+ * Grab all your descendants in 1 SELECT.
17
+ * Get all your siblings in 1 SELECT.
18
+ * Fetch all [7-degrees-of-bacon in a nested hash](#nested-hashes) in 1 SELECT.
19
+ * [Find a node by path](#find_or_create_by_path) in 1 SELECT.
19
20
  * __Best-in-class mutation performance__:
20
21
  * 2 SQL INSERTs on node creation
21
22
  * 3 SQL INSERT/UPDATEs on node reparenting
@@ -425,6 +426,10 @@ Parallelism is not tested with Rails 3.0.x nor 3.1.x due to this
425
426
 
426
427
  ## Change log
427
428
 
429
+ ### 3.8.2
430
+
431
+ * find_by_path uses 1 SELECT now. BOOM.
432
+
428
433
  ### 3.8.1
429
434
 
430
435
  * Double-check locking for find_or_create_by_path
@@ -45,6 +45,14 @@ module ClosureTree
45
45
  connection.quote_column_name parent_column_name
46
46
  end
47
47
 
48
+ def quoted_name_column
49
+ connection.quote_column_name name_column
50
+ end
51
+
52
+ def ct_quote(field)
53
+ connection.quote(field)
54
+ end
55
+
48
56
  def order_option
49
57
  closure_tree_options[:order]
50
58
  end
@@ -124,12 +124,9 @@ module ClosureTree
124
124
 
125
125
  # Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+.
126
126
  def find_by_path(path)
127
- path = path.is_a?(Enumerable) ? path.dup : [path]
128
- node = self
129
- while !path.empty? && node
130
- node = node.children.where(name_sym => path.shift).first
131
- end
132
- node
127
+ return self if path.empty?
128
+ parent_constraint = "#{quoted_parent_column_name} = #{ct_quote(id)}"
129
+ ct_class.ct_scoped_to_path(path, parent_constraint).first
133
130
  end
134
131
 
135
132
  # Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+
@@ -207,13 +204,14 @@ module ClosureTree
207
204
  delete_hierarchy_references unless @was_new_record
208
205
  hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
209
206
  unless root?
210
- connection.execute <<-SQL
211
- INSERT INTO #{quoted_hierarchy_table_name}
212
- (ancestor_id, descendant_id, generations)
213
- SELECT x.ancestor_id, #{ct_quote(id)}, x.generations + 1
214
- FROM #{quoted_hierarchy_table_name} x
215
- WHERE x.descendant_id = #{ct_quote(self.ct_parent_id)}
207
+ sql = <<-SQL
208
+ INSERT INTO #{quoted_hierarchy_table_name}
209
+ (ancestor_id, descendant_id, generations)
210
+ SELECT x.ancestor_id, #{ct_quote(id)}, x.generations + 1
211
+ FROM #{quoted_hierarchy_table_name} x
212
+ WHERE x.descendant_id = #{ct_quote(self.ct_parent_id)}
216
213
  SQL
214
+ connection.execute sql.strip
217
215
  end
218
216
  children.each { |c| c.rebuild! }
219
217
  end
@@ -255,10 +253,6 @@ module ClosureTree
255
253
  end
256
254
  end
257
255
 
258
- def ct_quote(field)
259
- self.class.connection.quote(field)
260
- end
261
-
262
256
  # TODO: _parent_id will be removed in the next major version
263
257
  alias :_parent_id :ct_parent_id
264
258
 
@@ -322,9 +316,22 @@ module ClosureTree
322
316
 
323
317
  # Find the node whose +ancestry_path+ is +path+
324
318
  def find_by_path(path)
325
- subpath = path.dup
326
- root = roots.where(name_sym => subpath.shift).first
327
- root.find_by_path(subpath) if root
319
+ parent_constraint = "#{quoted_parent_column_name} IS NULL"
320
+ ct_scoped_to_path(path, parent_constraint).first
321
+ end
322
+
323
+ def ct_scoped_to_path(path, parent_constraint)
324
+ path = path.is_a?(Enumerable) ? path.dup : [path]
325
+ scope = scoped.where(name_sym => path.last).readonly(false)
326
+ path[0..-2].reverse.each_with_index do |ea, idx|
327
+ subtable = idx == 0 ? quoted_table_name : "p#{idx - 1}"
328
+ scope = scope.joins(<<-SQL)
329
+ INNER JOIN #{quoted_table_name} AS p#{idx} ON p#{idx}.id = #{subtable}.#{parent_column_name}
330
+ SQL
331
+ scope = scope.where("p#{idx}.#{quoted_name_column} = #{ct_quote(ea)}")
332
+ end
333
+ root_table_name = path.size > 1 ? "p#{path.size - 2}" : quoted_table_name
334
+ scope.where("#{root_table_name}.#{parent_constraint}")
328
335
  end
329
336
 
330
337
  # Find or create nodes such that the +ancestry_path+ is +path+
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "3.8.1" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "3.8.2" unless defined?(::ClosureTree::VERSION)
3
3
  end
@@ -146,6 +146,81 @@ shared_examples_for "Tag (1)" do
146
146
  end
147
147
 
148
148
  end
149
+
150
+ context "paths" do
151
+ before :each do
152
+ @child = Tag.find_or_create_by_path(%w(grandparent parent child))
153
+ @child.title = "Kid"
154
+ @parent = @child.parent
155
+ @parent.title = "Mom"
156
+ @grandparent = @parent.parent
157
+ @grandparent.title = "Nonnie"
158
+ [@child, @parent, @grandparent].each { |ea| ea.save! }
159
+ end
160
+
161
+ it "should build ancestry path" do
162
+ @child.ancestry_path.should == %w{grandparent parent child}
163
+ @child.ancestry_path(:name).should == %w{grandparent parent child}
164
+ @child.ancestry_path(:title).should == %w{Nonnie Mom Kid}
165
+ end
166
+
167
+ it "should find by path" do
168
+ # class method:
169
+ Tag.find_by_path(%w{grandparent parent child}).should == @child
170
+ # instance method:
171
+ @parent.find_by_path(%w{child}).should == @child
172
+ @grandparent.find_by_path(%w{parent child}).should == @child
173
+ @parent.find_by_path(%w{child larvae}).should be_nil
174
+ end
175
+
176
+ it "finds correctly rooted paths" do
177
+ decoy = Tag.find_or_create_by_path %w(a b c d)
178
+ b_d = Tag.find_or_create_by_path %w(b c d)
179
+ Tag.find_by_path(%w(b c d)).should == b_d
180
+ Tag.find_by_path(%w(c d)).should be_nil
181
+ end
182
+
183
+ it "find_by_path for 1 node" do
184
+ b = Tag.find_or_create_by_path %w(a b)
185
+ b2 = b.root.find_by_path(%w(b))
186
+ b2.should == b
187
+ end
188
+
189
+ it "find_by_path for 2 nodes" do
190
+ c = Tag.find_or_create_by_path %w(a b c)
191
+ c.root.find_by_path(%w(b c)).should == c
192
+ c.root.find_by_path(%w(a c)).should be_nil
193
+ c.root.find_by_path(%w(c)).should be_nil
194
+ end
195
+
196
+ it "find_by_path for 3 nodes" do
197
+ d = Tag.find_or_create_by_path %w(a b c d)
198
+ d.root.find_by_path(%w(b c d)).should == d
199
+ Tag.find_by_path(%w(a b c d)).should == d
200
+ Tag.find_by_path(%w(d)).should be_nil
201
+ end
202
+
203
+ it "should return nil for missing nodes" do
204
+ Tag.find_by_path(%w{missing}).should be_nil
205
+ Tag.find_by_path(%w{grandparent missing}).should be_nil
206
+ Tag.find_by_path(%w{grandparent parent missing}).should be_nil
207
+ Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
208
+ end
209
+
210
+ it "should find or create by path" do
211
+ # class method:
212
+ grandparent = Tag.find_or_create_by_path(%w{grandparent})
213
+ grandparent.should == @grandparent
214
+ child = Tag.find_or_create_by_path(%w{grandparent parent child})
215
+ child.should == @child
216
+ Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
217
+ a = Tag.find_or_create_by_path(%w{a})
218
+ a.ancestry_path.should == %w{a}
219
+ # instance method:
220
+ a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
221
+ end
222
+ end
223
+
149
224
  end
150
225
 
151
226
  shared_examples_for "Tag (2)" do
@@ -316,43 +391,6 @@ shared_examples_for "Tag (2)" do
316
391
  end
317
392
  end
318
393
 
319
- context "paths" do
320
-
321
- it "should build ancestry path" do
322
- tags(:child).ancestry_path.should == %w{grandparent parent child}
323
- tags(:child).ancestry_path(:name).should == %w{grandparent parent child}
324
- tags(:child).ancestry_path(:title).should == %w{Nonnie Mom Kid}
325
- end
326
-
327
- it "should find by path" do
328
- # class method:
329
- Tag.find_by_path(%w{grandparent parent child}).should == tags(:child)
330
- # instance method:
331
- tags(:parent).find_by_path(%w{child}).should == tags(:child)
332
- tags(:grandparent).find_by_path(%w{parent child}).should == tags(:child)
333
- tags(:parent).find_by_path(%w{child larvae}).should be_nil
334
- end
335
-
336
- it "should return nil for missing nodes" do
337
- Tag.find_by_path(%w{missing}).should be_nil
338
- Tag.find_by_path(%w{grandparent missing}).should be_nil
339
- Tag.find_by_path(%w{grandparent parent missing}).should be_nil
340
- Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
341
- end
342
-
343
- it "should find or create by path" do
344
- # class method:
345
- grandparent = Tag.find_or_create_by_path(%w{grandparent})
346
- grandparent.should == tags(:grandparent)
347
- child = Tag.find_or_create_by_path(%w{grandparent parent child})
348
- child.should == tags(:child)
349
- Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
350
- a = Tag.find_or_create_by_path(%w{a})
351
- a.ancestry_path.should == %w{a}
352
- # instance method:
353
- a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
354
- end
355
- end
356
394
 
357
395
  def validate_city_tag city
358
396
  tags(:california).children.include?(city).should_not be_nil
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.8.1
4
+ version: 3.8.2
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: 2013-02-25 00:00:00.000000000 Z
12
+ date: 2013-03-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -248,7 +248,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
248
248
  version: '0'
249
249
  segments:
250
250
  - 0
251
- hash: -3183043301926645290
251
+ hash: 625197628069455012
252
252
  required_rubygems_version: !ruby/object:Gem::Requirement
253
253
  none: false
254
254
  requirements:
@@ -257,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
257
  version: '0'
258
258
  segments:
259
259
  - 0
260
- hash: -3183043301926645290
260
+ hash: 625197628069455012
261
261
  requirements: []
262
262
  rubyforge_project:
263
263
  rubygems_version: 1.8.23