closure_tree 6.6.0 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +7 -6
- data/Appraisals +59 -1
- data/CHANGELOG.md +21 -0
- data/Gemfile +0 -12
- data/README.md +40 -3
- data/closure_tree.gemspec +10 -7
- data/lib/closure_tree/finders.rb +15 -5
- data/lib/closure_tree/has_closure_tree.rb +2 -0
- data/lib/closure_tree/has_closure_tree_root.rb +2 -6
- data/lib/closure_tree/hash_tree_support.rb +1 -1
- data/lib/closure_tree/hierarchy_maintenance.rb +3 -3
- data/lib/closure_tree/model.rb +31 -1
- data/lib/closure_tree/numeric_deterministic_ordering.rb +11 -2
- data/lib/closure_tree/numeric_order_support.rb +3 -0
- data/lib/closure_tree/support.rb +7 -3
- data/lib/closure_tree/support_attributes.rb +9 -0
- data/lib/closure_tree/support_flags.rb +1 -4
- data/lib/closure_tree/version.rb +1 -1
- data/lib/generators/closure_tree/migration_generator.rb +8 -0
- data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
- metadata +28 -76
- data/gemfiles/activerecord_4.2.gemfile +0 -19
- data/gemfiles/activerecord_5.0.gemfile +0 -19
- data/gemfiles/activerecord_5.1.gemfile +0 -19
- data/gemfiles/activerecord_edge.gemfile +0 -20
- data/img/example.png +0 -0
- data/img/preorder.png +0 -0
- data/spec/cache_invalidation_spec.rb +0 -39
- data/spec/cuisine_type_spec.rb +0 -38
- data/spec/db/database.yml +0 -21
- data/spec/db/models.rb +0 -128
- data/spec/db/schema.rb +0 -166
- data/spec/fixtures/tags.yml +0 -98
- data/spec/generators/migration_generator_spec.rb +0 -48
- data/spec/has_closure_tree_root_spec.rb +0 -154
- data/spec/hierarchy_maintenance_spec.rb +0 -16
- data/spec/label_spec.rb +0 -554
- data/spec/matcher_spec.rb +0 -34
- data/spec/metal_spec.rb +0 -55
- data/spec/model_spec.rb +0 -9
- data/spec/namespace_type_spec.rb +0 -13
- data/spec/parallel_spec.rb +0 -159
- data/spec/pool_spec.rb +0 -27
- data/spec/spec_helper.rb +0 -24
- data/spec/support/database.rb +0 -52
- data/spec/support/database_cleaner.rb +0 -14
- data/spec/support/exceed_query_limit.rb +0 -18
- data/spec/support/hash_monkey_patch.rb +0 -13
- data/spec/support/query_counter.rb +0 -18
- data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
- data/spec/support_spec.rb +0 -14
- data/spec/tag_examples.rb +0 -665
- data/spec/tag_spec.rb +0 -6
- data/spec/user_spec.rb +0 -174
- data/spec/uuid_tag_spec.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: aad24f6acfed9da72a06c877919ef7a349680abb951a185307590b42484582e0
|
4
|
+
data.tar.gz: 543f9482cd9497d99174358b6d342caa6c860608acc51f5989e5d780c1747f58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69c034fe0ee0278b143758381412ca43663da441bd35a81b67d516d58defc5f19b79d83a28a9fea18984cb105b757b7028c9898cab34eaae98fccb56e7b0d71e
|
7
|
+
data.tar.gz: 53346203044b6eb82b784bc715105b6a2a5029d8ab8c48665d1bb02bf667bff643ba0768c6dfa6ec476b03b7f5567e28846c8b6b2c7b17344f2d17227ca1e24c
|
data/.travis.yml
CHANGED
@@ -2,14 +2,14 @@ cache: bundler
|
|
2
2
|
sudo: false
|
3
3
|
language: ruby
|
4
4
|
rvm:
|
5
|
-
- 2.
|
6
|
-
- 2.
|
7
|
-
- 2.
|
8
|
-
|
9
|
-
|
10
|
-
# - rbx
|
5
|
+
- 2.6.0
|
6
|
+
- 2.5.1
|
7
|
+
- 2.4.4
|
8
|
+
- 2.3.6
|
9
|
+
- 2.2.10
|
11
10
|
|
12
11
|
gemfile:
|
12
|
+
- gemfiles/activerecord_5.2.gemfile
|
13
13
|
- gemfiles/activerecord_5.1.gemfile
|
14
14
|
- gemfiles/activerecord_5.0.gemfile
|
15
15
|
- gemfiles/activerecord_4.2.gemfile
|
@@ -27,3 +27,4 @@ matrix:
|
|
27
27
|
- gemfile: gemfiles/activerecord_edge.gemfile
|
28
28
|
- rvm: jruby-head
|
29
29
|
- rvm: rbx
|
30
|
+
- rvm: 2.6.0
|
data/Appraisals
CHANGED
@@ -1,17 +1,75 @@
|
|
1
1
|
|
2
2
|
appraise 'activerecord-4.2' do
|
3
3
|
gem 'activerecord', '~> 4.2.0'
|
4
|
+
platforms :ruby do
|
5
|
+
gem 'mysql2', "< 0.5"
|
6
|
+
gem 'pg', "~> 0.21"
|
7
|
+
gem 'sqlite3'
|
8
|
+
end
|
9
|
+
|
10
|
+
platforms :jruby do
|
11
|
+
gem 'activerecord-jdbcmysql-adapter'
|
12
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
13
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
14
|
+
end
|
4
15
|
end
|
5
16
|
|
6
17
|
appraise 'activerecord-5.0' do
|
7
18
|
gem 'activerecord', '~> 5.0.0'
|
19
|
+
platforms :ruby do
|
20
|
+
gem 'mysql2'
|
21
|
+
gem 'pg'
|
22
|
+
gem 'sqlite3'
|
23
|
+
end
|
24
|
+
|
25
|
+
platforms :jruby do
|
26
|
+
gem 'activerecord-jdbcmysql-adapter'
|
27
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
28
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
29
|
+
end
|
8
30
|
end
|
9
31
|
|
10
32
|
appraise 'activerecord-5.1' do
|
11
33
|
gem 'activerecord', '~> 5.1.0'
|
34
|
+
platforms :ruby do
|
35
|
+
gem 'mysql2'
|
36
|
+
gem 'pg'
|
37
|
+
gem 'sqlite3'
|
38
|
+
end
|
39
|
+
|
40
|
+
platforms :jruby do
|
41
|
+
gem 'activerecord-jdbcmysql-adapter'
|
42
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
43
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
appraise 'activerecord-5.2' do
|
48
|
+
gem 'activerecord', '~> 5.2.0'
|
49
|
+
platforms :ruby do
|
50
|
+
gem 'mysql2'
|
51
|
+
gem 'pg'
|
52
|
+
gem 'sqlite3'
|
53
|
+
end
|
54
|
+
|
55
|
+
platforms :jruby do
|
56
|
+
gem 'activerecord-jdbcmysql-adapter'
|
57
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
58
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
59
|
+
end
|
12
60
|
end
|
13
61
|
|
14
62
|
appraise 'activerecord-edge' do
|
15
63
|
gem 'activerecord', github: 'rails/rails'
|
16
|
-
|
64
|
+
platforms :ruby do
|
65
|
+
gem 'mysql2'
|
66
|
+
gem 'pg'
|
67
|
+
gem 'sqlite3'
|
68
|
+
end
|
69
|
+
|
70
|
+
platforms :jruby do
|
71
|
+
gem 'activerecord-jdbcmysql-adapter'
|
72
|
+
gem 'activerecord-jdbcpostgresql-adapter'
|
73
|
+
gem 'activerecord-jdbcsqlite3-adapter'
|
74
|
+
end
|
17
75
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 7.0.0
|
4
|
+
Closure Tree is now tested against Rails 5.2
|
5
|
+
|
6
|
+
- Postpone configuration (database introspection)[PR 264](https://github.com/ClosureTree/closure_tree/pull/264)
|
7
|
+
- Fix "tree.find_by_path([])" [PR 288](https://github.com/ClosureTree/closure_tree/pull/288)
|
8
|
+
- Fixed generator specs and added migration version [PR 292](https://github.com/ClosureTree/closure_tree/pull/292)
|
9
|
+
- Eliminate deprecation warnings in ActiveRecord 5.2 [PR 296](https://github.com/ClosureTree/closure_tree/pull/296)
|
10
|
+
- When using 'oracle_enhanced', remove 'AS' on the table_name alias. [PR 298](https://github.com/ClosureTree/closure_tree/pull/298)
|
11
|
+
- README update [PR 301](https://github.com/ClosureTree/closure_tree/pull/301)
|
12
|
+
- Add `with_descendant` finder [PR 302](https://github.com/ClosureTree/closure_tree/pull/302)
|
13
|
+
- Fix pg version for rails prior 5.1 [PR 303](https://github.com/ClosureTree/closure_tree/pull/303)
|
14
|
+
- Test on Rails 5.2 & fix mysql for older Rails [PR 304](https://github.com/ClosureTree/closure_tree/pull/304)
|
15
|
+
- Test with ActiveRecord 5.2.0 [PR 307](https://github.com/ClosureTree/closure_tree/pull/307)
|
16
|
+
- README update [PR 310](https://github.com/ClosureTree/closure_tree/pull/310)
|
17
|
+
- FactoryBot linter failing for a model that uses closure_tree [PR 311](https://github.com/ClosureTree/closure_tree/pull/311)
|
18
|
+
- Added dont_order_roots option [PR 312](https://github.com/ClosureTree/closure_tree/pull/312)
|
19
|
+
- Added instance methods to determine the relationship between 2 nodes [PR 314](https://github.com/ClosureTree/closure_tree/pull/314)
|
20
|
+
- Add an instance method to check the relationship between 2 nodes: #family_of? [PR 319](https://github.com/ClosureTree/closure_tree/pull/319)
|
21
|
+
- Remove options restrictions on has_closure_tree_root [PR 321](https://github.com/ClosureTree/closure_tree/pull/321)
|
22
|
+
- Fix uninitialized variable warnings [PR 323](https://github.com/ClosureTree/closure_tree/pull/323)
|
23
|
+
|
3
24
|
### 6.6.0
|
4
25
|
|
5
26
|
Closure Tree is now tested against Rails 5.1, and just passed 50 contributors and
|
data/Gemfile
CHANGED
@@ -1,15 +1,3 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
platforms :ruby, :rbx do
|
4
|
-
gem 'mysql2'
|
5
|
-
gem 'pg'
|
6
|
-
gem 'sqlite3'
|
7
|
-
end
|
8
|
-
|
9
|
-
platforms :jruby do
|
10
|
-
gem 'activerecord-jdbcmysql-adapter'
|
11
|
-
gem 'activerecord-jdbcpostgresql-adapter'
|
12
|
-
gem 'activerecord-jdbcsqlite3-adapter'
|
13
|
-
end
|
14
|
-
|
15
3
|
gemspec
|
data/README.md
CHANGED
@@ -74,7 +74,7 @@ Note that closure_tree only supports ActiveRecord 4.2 and later, and has test co
|
|
74
74
|
end
|
75
75
|
```
|
76
76
|
|
77
|
-
Make sure you check out the [large number options](#available-options) that `has_closure_tree` accepts.
|
77
|
+
Make sure you check out the [large number of options](#available-options) that `has_closure_tree` accepts.
|
78
78
|
|
79
79
|
**IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and
|
80
80
|
`self.table_name =` lines in your model.**
|
@@ -88,7 +88,7 @@ Note that closure_tree only supports ActiveRecord 4.2 and later, and has test co
|
|
88
88
|
```ruby
|
89
89
|
class AddParentIdToTag < ActiveRecord::Migration
|
90
90
|
def change
|
91
|
-
add_column :
|
91
|
+
add_column :tags, :parent_id, :integer
|
92
92
|
end
|
93
93
|
end
|
94
94
|
```
|
@@ -346,15 +346,19 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
|
|
346
346
|
|
347
347
|
* ```tag.root``` returns the root for this node
|
348
348
|
* ```tag.root?``` returns true if this is a root node
|
349
|
+
* ```tag.root_of?(node)``` returns true if current node is root of another one
|
349
350
|
* ```tag.child?``` returns true if this is a child node. It has a parent.
|
350
351
|
* ```tag.leaf?``` returns true if this is a leaf node. It has no children.
|
351
352
|
* ```tag.leaves``` is scoped to all leaf nodes in self_and_descendants.
|
352
353
|
* ```tag.depth``` returns the depth, or "generation", for this node in the tree. A root node will have a value of 0.
|
353
354
|
* ```tag.parent``` returns the node's immediate parent. Root nodes will return nil.
|
355
|
+
* ```tag.parent_of?(node)``` returns true if current node is parent of another one
|
354
356
|
* ```tag.children``` is a ```has_many``` of immediate children (just those nodes whose parent is the current node).
|
355
357
|
* ```tag.child_ids``` is an array of the IDs of the children.
|
358
|
+
* ```tag.child_of?(node)``` returns true if current node is child of another one
|
356
359
|
* ```tag.ancestors``` is a ordered scope of [ parent, grandparent, great grandparent, … ]. Note that the size of this array will always equal ```tag.depth```.
|
357
360
|
* ```tag.ancestor_ids``` is an array of the IDs of the ancestors.
|
361
|
+
* ```tag.ancestor_of?(node)``` returns true if current node is ancestor of another one
|
358
362
|
* ```tag.self_and_ancestors``` returns a scope containing self, parent, grandparent, great grandparent, etc.
|
359
363
|
* ```tag.self_and_ancestors_ids``` returns IDs containing self, parent, grandparent, great grandparent, etc.
|
360
364
|
* ```tag.siblings``` returns a scope containing all nodes with the same parent as ```tag```, excluding self.
|
@@ -362,8 +366,10 @@ When you include ```has_closure_tree``` in your model, you can provide a hash to
|
|
362
366
|
* ```tag.self_and_siblings``` returns a scope containing all nodes with the same parent as ```tag```, including self.
|
363
367
|
* ```tag.descendants``` returns a scope of all children, childrens' children, etc., excluding self ordered by depth.
|
364
368
|
* ```tag.descendant_ids``` returns an array of the IDs of the descendants.
|
369
|
+
* ```tag.descendant_of?(node)``` returns true if current node is descendant of another one
|
365
370
|
* ```tag.self_and_descendants``` returns a scope of self, all children, childrens' children, etc., ordered by depth.
|
366
371
|
* ```tag.self_and_descendant_ids``` returns IDs of self, all children, childrens' children, etc., ordered by depth.
|
372
|
+
* ```tag.family_of?``` returns true if current node and another one have a same root.
|
367
373
|
* ```tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.
|
368
374
|
* ```tag.find_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```. See (#find_or_create_by_path).
|
369
375
|
* ```tag.find_or_create_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```, and will create the node if it doesn't exist already.See (#find_or_create_by_path).
|
@@ -389,6 +395,18 @@ class WhereTag < Tag ; end
|
|
389
395
|
class WhatTag < Tag ; end
|
390
396
|
```
|
391
397
|
|
398
|
+
Note that if you call `rebuild!` on any of the subclasses, the complete Tag hierarchy will be emptied, thus taking the hiearchies of all other subclasses with it (issue #275). However, only the hierarchies for the class `rebuild!` was called on will be rebuilt, leaving the other subclasses without hierarchy entries.
|
399
|
+
|
400
|
+
You can work around that by overloading the `rebuild!` class method in all your STI subclasses and call the super classes `rebuild!` method:
|
401
|
+
```ruby
|
402
|
+
class WhatTag < Tag
|
403
|
+
def self.rebuild!
|
404
|
+
Tag.rebuild!
|
405
|
+
end
|
406
|
+
end
|
407
|
+
```
|
408
|
+
This way, the complete hierarchy including all subclasses will be rebuilt.
|
409
|
+
|
392
410
|
## Deterministic ordering
|
393
411
|
|
394
412
|
By default, children will be ordered by your database engine, which may not be what you want.
|
@@ -411,7 +429,7 @@ and in your model:
|
|
411
429
|
|
412
430
|
```ruby
|
413
431
|
class OrderedTag < ActiveRecord::Base
|
414
|
-
has_closure_tree order: 'sort_order'
|
432
|
+
has_closure_tree order: 'sort_order', numeric_order: true
|
415
433
|
end
|
416
434
|
```
|
417
435
|
|
@@ -477,6 +495,25 @@ root.reload.children.pluck(:name)
|
|
477
495
|
=> ["b", "c", "a"]
|
478
496
|
```
|
479
497
|
|
498
|
+
### Ordering Roots
|
499
|
+
|
500
|
+
With numeric ordering, root nodes are, by default, assigned order values globally across the whole database
|
501
|
+
table. So for instance if you have 5 nodes with no parent, they will be ordered 0 through 4 by default.
|
502
|
+
If your model represents many separate trees and you have a lot of records, this can cause performance
|
503
|
+
problems, and doesn't really make much sense.
|
504
|
+
|
505
|
+
You can disable this default behavior by passing `dont_order_roots: true` as an option to your delcaration:
|
506
|
+
|
507
|
+
```
|
508
|
+
has_closure_tree order: 'sort_order', numeric_order: true, dont_order_roots: true
|
509
|
+
```
|
510
|
+
|
511
|
+
In this case, calling `prepend_sibling` and `append_sibling` on a root node or calling
|
512
|
+
`roots_and_descendants_preordered` on the model will raise a `RootOrderingDisabledError`.
|
513
|
+
|
514
|
+
The `dont_order_roots` option will be ignored unless `numeric_order` is set to true.
|
515
|
+
|
516
|
+
|
480
517
|
## Concurrency
|
481
518
|
|
482
519
|
Several methods, especially ```#rebuild``` and ```#find_or_create_by_path```, cannot run concurrently correctly.
|
data/closure_tree.gemspec
CHANGED
@@ -12,20 +12,23 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.description = gem.summary
|
13
13
|
gem.license = 'MIT'
|
14
14
|
|
15
|
-
gem.files
|
15
|
+
gem.files = `git ls-files`.split($/).reject do |f|
|
16
|
+
f.match(%r{^(spec|img|gemfiles)})
|
17
|
+
end
|
18
|
+
|
16
19
|
gem.test_files = gem.files.grep(%r{^spec/})
|
17
20
|
gem.required_ruby_version = '>= 2.0.0'
|
18
21
|
|
19
|
-
gem.add_runtime_dependency 'activerecord', '>= 4.
|
20
|
-
gem.add_runtime_dependency 'with_advisory_lock', '>=
|
22
|
+
gem.add_runtime_dependency 'activerecord', '>= 4.2.10'
|
23
|
+
gem.add_runtime_dependency 'with_advisory_lock', '>= 4.0.0'
|
21
24
|
|
25
|
+
gem.add_development_dependency 'appraisal'
|
26
|
+
gem.add_development_dependency 'database_cleaner'
|
27
|
+
gem.add_development_dependency 'generator_spec'
|
28
|
+
gem.add_development_dependency 'parallel'
|
22
29
|
gem.add_development_dependency 'rspec-instafail'
|
23
30
|
gem.add_development_dependency 'rspec-rails'
|
24
|
-
gem.add_development_dependency 'database_cleaner'
|
25
|
-
gem.add_development_dependency 'appraisal'
|
26
31
|
gem.add_development_dependency 'timecop'
|
27
|
-
gem.add_development_dependency 'parallel'
|
28
|
-
# gem.add_development_dependency 'ammeter', '1.1.2' # See https://github.com/mceachen/closure_tree/issues/181
|
29
32
|
# gem.add_development_dependency 'byebug'
|
30
33
|
# gem.add_development_dependency 'ruby-prof' # <- don't need this normally.
|
31
34
|
end
|
data/lib/closure_tree/finders.rb
CHANGED
@@ -41,7 +41,7 @@ module ClosureTree
|
|
41
41
|
WHERE ancestor_id = #{_ct.quote(self.id)}
|
42
42
|
GROUP BY descendant_id
|
43
43
|
HAVING MAX(#{_ct.quoted_hierarchy_table_name}.generations) = #{generation_level.to_i}
|
44
|
-
)
|
44
|
+
) #{ _ct.t_alias_keyword } descendants ON (#{_ct.quoted_table_name}.#{_ct.base_class.primary_key} = descendants.descendant_id)
|
45
45
|
SQL
|
46
46
|
_ct.scope_with_order(s)
|
47
47
|
end
|
@@ -76,7 +76,7 @@ module ClosureTree
|
|
76
76
|
FROM #{_ct.quoted_hierarchy_table_name}
|
77
77
|
GROUP BY ancestor_id
|
78
78
|
HAVING MAX(#{_ct.quoted_hierarchy_table_name}.generations) = 0
|
79
|
-
)
|
79
|
+
) #{ _ct.t_alias_keyword } leaves ON (#{_ct.quoted_table_name}.#{primary_key} = leaves.ancestor_id)
|
80
80
|
SQL
|
81
81
|
_ct.scope_with_order(s.readonly(false))
|
82
82
|
end
|
@@ -90,19 +90,28 @@ module ClosureTree
|
|
90
90
|
_ct.scope_with_order(scope)
|
91
91
|
end
|
92
92
|
|
93
|
+
def with_descendant(*descendants)
|
94
|
+
descendant_ids = descendants.map { |ea| ea.is_a?(ActiveRecord::Base) ? ea._ct_id : ea }
|
95
|
+
scope = descendant_ids.blank? ? all : joins(:descendant_hierarchies).
|
96
|
+
where("#{_ct.hierarchy_table_name}.descendant_id" => descendant_ids).
|
97
|
+
where("#{_ct.hierarchy_table_name}.generations > 0").
|
98
|
+
readonly(false)
|
99
|
+
_ct.scope_with_order(scope)
|
100
|
+
end
|
101
|
+
|
93
102
|
def find_all_by_generation(generation_level)
|
94
103
|
s = joins(<<-SQL.strip_heredoc)
|
95
104
|
INNER JOIN (
|
96
105
|
SELECT #{primary_key} as root_id
|
97
106
|
FROM #{_ct.quoted_table_name}
|
98
107
|
WHERE #{_ct.quoted_parent_column_name} IS NULL
|
99
|
-
)
|
108
|
+
) #{ _ct.t_alias_keyword } roots ON (1 = 1)
|
100
109
|
INNER JOIN (
|
101
110
|
SELECT ancestor_id, descendant_id
|
102
111
|
FROM #{_ct.quoted_hierarchy_table_name}
|
103
112
|
GROUP BY ancestor_id, descendant_id
|
104
113
|
HAVING MAX(generations) = #{generation_level.to_i}
|
105
|
-
)
|
114
|
+
) #{ _ct.t_alias_keyword } descendants ON (
|
106
115
|
#{_ct.quoted_table_name}.#{primary_key} = descendants.descendant_id
|
107
116
|
AND roots.root_id = descendants.ancestor_id
|
108
117
|
)
|
@@ -112,6 +121,7 @@ module ClosureTree
|
|
112
121
|
|
113
122
|
# Find the node whose +ancestry_path+ is +path+
|
114
123
|
def find_by_path(path, attributes = {}, parent_id = nil)
|
124
|
+
return nil if path.blank?
|
115
125
|
path = _ct.build_ancestry_attr_path(path, attributes)
|
116
126
|
if path.size > _ct.max_join_tables
|
117
127
|
return _ct.find_by_large_path(path, attributes, parent_id)
|
@@ -121,7 +131,7 @@ module ClosureTree
|
|
121
131
|
path.reverse.each_with_index do |ea, idx|
|
122
132
|
next_joined_table = "p#{idx}"
|
123
133
|
scope = scope.joins(<<-SQL.strip_heredoc)
|
124
|
-
INNER JOIN #{_ct.quoted_table_name}
|
134
|
+
INNER JOIN #{_ct.quoted_table_name} #{ _ct.t_alias_keyword } #{next_joined_table}
|
125
135
|
ON #{next_joined_table}.#{_ct.quoted_id_column_name} =
|
126
136
|
#{connection.quote_table_name(last_joined_table)}.#{_ct.quoted_parent_column_name}
|
127
137
|
SQL
|
@@ -1,15 +1,11 @@
|
|
1
1
|
module ClosureTree
|
2
2
|
class MultipleRootError < StandardError; end
|
3
|
+
class RootOrderingDisabledError < StandardError; end
|
3
4
|
|
4
5
|
module HasClosureTreeRoot
|
5
6
|
|
6
7
|
def has_closure_tree_root(assoc_name, options = {})
|
7
|
-
|
8
|
-
:class_name,
|
9
|
-
:foreign_key
|
10
|
-
)
|
11
|
-
|
12
|
-
options[:class_name] ||= assoc_name.to_s.sub(/\Aroot_/, "").classify
|
8
|
+
options[:class_name] ||= assoc_name.to_s.sub(/\Aroot_/, "").classify
|
13
9
|
options[:foreign_key] ||= self.name.underscore << "_id"
|
14
10
|
|
15
11
|
has_one assoc_name, -> { where(parent: nil) }, options
|
@@ -10,7 +10,7 @@ module ClosureTree
|
|
10
10
|
FROM #{quoted_hierarchy_table_name}
|
11
11
|
GROUP BY descendant_id
|
12
12
|
#{having_clause}
|
13
|
-
)
|
13
|
+
) #{ t_alias_keyword } generation_depth
|
14
14
|
ON #{quoted_table_name}.#{model_class.primary_key} = generation_depth.descendant_id
|
15
15
|
SQL
|
16
16
|
scope_with_order(scope.joins(generation_depth), 'generation_depth.depth')
|
@@ -20,7 +20,7 @@ module ClosureTree
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def _ct_validate
|
23
|
-
if
|
23
|
+
if !(defined? @_ct_skip_cycle_detection) &&
|
24
24
|
!new_record? && # don't validate for cycles if we're a new record
|
25
25
|
changes[_ct.parent_column_name] && # don't validate for cycles if we didn't change our parent
|
26
26
|
parent.present? && # don't validate if we're root
|
@@ -64,7 +64,7 @@ module ClosureTree
|
|
64
64
|
|
65
65
|
def rebuild!(called_by_rebuild = false)
|
66
66
|
_ct.with_advisory_lock do
|
67
|
-
delete_hierarchy_references unless @was_new_record
|
67
|
+
delete_hierarchy_references unless (defined? @was_new_record) && @was_new_record
|
68
68
|
hierarchy_class.create!(:ancestor => self, :descendant => self, :generations => 0)
|
69
69
|
unless root?
|
70
70
|
_ct.connection.execute <<-SQL.strip_heredoc
|
@@ -102,7 +102,7 @@ module ClosureTree
|
|
102
102
|
FROM #{_ct.quoted_hierarchy_table_name}
|
103
103
|
WHERE ancestor_id = #{_ct.quote(id)}
|
104
104
|
OR descendant_id = #{_ct.quote(id)}
|
105
|
-
)
|
105
|
+
) #{ _ct.t_alias_keyword } x )
|
106
106
|
SQL
|
107
107
|
end
|
108
108
|
end
|
data/lib/closure_tree/model.rb
CHANGED
@@ -13,7 +13,7 @@ module ClosureTree
|
|
13
13
|
touch: _ct.options[:touch],
|
14
14
|
optional: true)
|
15
15
|
|
16
|
-
order_by_generations = "#{_ct.quoted_hierarchy_table_name}.generations
|
16
|
+
order_by_generations = -> { Arel.sql("#{_ct.quoted_hierarchy_table_name}.generations ASC") }
|
17
17
|
|
18
18
|
has_many :children, *_ct.has_many_with_order_option(
|
19
19
|
class_name: _ct.model_class.to_s,
|
@@ -134,6 +134,36 @@ module ClosureTree
|
|
134
134
|
_ct.ids_from(siblings)
|
135
135
|
end
|
136
136
|
|
137
|
+
# node's parent is this record
|
138
|
+
def parent_of?(node)
|
139
|
+
self == node.parent
|
140
|
+
end
|
141
|
+
|
142
|
+
# node's root is this record
|
143
|
+
def root_of?(node)
|
144
|
+
self == node.root
|
145
|
+
end
|
146
|
+
|
147
|
+
# node's ancestors include this record
|
148
|
+
def ancestor_of?(node)
|
149
|
+
node.ancestors.include? self
|
150
|
+
end
|
151
|
+
|
152
|
+
# node is record's ancestor
|
153
|
+
def descendant_of?(node)
|
154
|
+
self.ancestors.include? node
|
155
|
+
end
|
156
|
+
|
157
|
+
# node is record's parent
|
158
|
+
def child_of?(node)
|
159
|
+
self.parent == node
|
160
|
+
end
|
161
|
+
|
162
|
+
# node and record have a same root
|
163
|
+
def family_of?(node)
|
164
|
+
self.root == node.root
|
165
|
+
end
|
166
|
+
|
137
167
|
# Alias for appending to the children collection.
|
138
168
|
# You can also add directly to the children collection, if you'd prefer.
|
139
169
|
def add_child(child_node)
|