closure_tree 6.6.0 → 7.0.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.
- 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)
|