acts_as_recursive_tree 3.1.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +20 -4
- data/.gitignore +1 -0
- data/Appraisals +9 -2
- data/CHANGELOG.md +6 -0
- data/README.md +15 -11
- data/acts_as_recursive_tree.gemspec +1 -1
- data/gemfiles/ar_70.gemfile +2 -2
- data/gemfiles/ar_next.gemfile +10 -0
- data/lib/acts_as_recursive_tree/acts_macro.rb +2 -1
- data/lib/acts_as_recursive_tree/model.rb +15 -5
- data/lib/acts_as_recursive_tree/preloaders/descendants.rb +42 -0
- data/lib/acts_as_recursive_tree/version.rb +1 -1
- data/spec/acts_as_recursive_tree/builders/ancestors_spec.rb +17 -0
- data/spec/acts_as_recursive_tree/builders/descendants_spec.rb +18 -0
- data/spec/acts_as_recursive_tree/builders/leaves_spec.rb +18 -0
- data/spec/{values_spec.rb → acts_as_recursive_tree/options/values_spec.rb} +13 -13
- data/spec/acts_as_recursive_tree/preloaders/descendants_spec.rb +44 -0
- data/spec/model/location_spec.rb +1 -1
- data/spec/model/node_spec.rb +2 -11
- data/spec/model/relation_spec.rb +46 -35
- data/spec/spec_helper.rb +5 -1
- data/spec/{builders_spec.rb → support/shared_examples/builders.rb} +10 -57
- data/spec/support/tree_methods.rb +27 -0
- metadata +20 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b60be808a8b0a3e93edfbb83a83cc8b270eebade9888ddc6038bb4af4429d7da
|
4
|
+
data.tar.gz: 96622ef4e488bbd73de8dd4d0ae51c51a2b11843219d2a34669367ace4f77d60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fca6f5732cc344c395d1d74f3cc13ac5be4af9ea74eb545764b28ef1a7550fcb3300975cccce92a2772f5d3ca1e12fb1e4a6e124f07037f66099b86256918c57
|
7
|
+
data.tar.gz: 8b2cf07e6aa5ef4543f6d81200ac3421d918039140480314496c416f8ff3f6fce65a1ba43bdcae837f67df5e279e9281d8680ca76945d84a8115877eeb4cc7d2
|
data/.github/workflows/ci.yml
CHANGED
@@ -19,19 +19,35 @@ jobs:
|
|
19
19
|
runs-on: ubuntu-latest
|
20
20
|
strategy:
|
21
21
|
matrix:
|
22
|
-
ruby-version: ['2.5', '2.6', '2.7', '3.0']
|
22
|
+
ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2']
|
23
23
|
gemfile: [ar_52, ar_60, ar_61, ar_70]
|
24
24
|
exclude:
|
25
|
+
- ruby-version: '3.2'
|
26
|
+
gemfile: ar_52
|
27
|
+
- ruby-version: '3.2'
|
28
|
+
gemfile: ar_60
|
29
|
+
- ruby-version: '3.2'
|
30
|
+
gemfile: ar_61
|
31
|
+
- ruby-version: '3.1'
|
32
|
+
gemfile: ar_52
|
33
|
+
- ruby-version: '3.1'
|
34
|
+
gemfile: ar_60
|
35
|
+
- ruby-version: '3.1'
|
36
|
+
gemfile: ar_61
|
25
37
|
- ruby-version: '3.0'
|
26
38
|
gemfile: ar_52
|
27
|
-
- ruby-version: '2.
|
28
|
-
gemfile:
|
39
|
+
# - ruby-version: '2.6'
|
40
|
+
# gemfile: ar_next
|
29
41
|
- ruby-version: '2.6'
|
30
42
|
gemfile: ar_70
|
43
|
+
# - ruby-version: '2.5'
|
44
|
+
# gemfile: ar_next
|
45
|
+
- ruby-version: '2.5'
|
46
|
+
gemfile: ar_70
|
31
47
|
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
32
48
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
33
49
|
steps:
|
34
|
-
- uses: actions/checkout@
|
50
|
+
- uses: actions/checkout@v3
|
35
51
|
- name: Set up Ruby
|
36
52
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
37
53
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
data/.gitignore
CHANGED
data/Appraisals
CHANGED
@@ -16,6 +16,13 @@ appraise 'ar-61' do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
appraise 'ar-70' do
|
19
|
-
gem 'activerecord', '~> 7.0
|
20
|
-
gem 'activesupport', '~> 7.0
|
19
|
+
gem 'activerecord', '~> 7.0'
|
20
|
+
gem 'activesupport', '~> 7.0'
|
21
|
+
end
|
22
|
+
|
23
|
+
appraise 'ar-next' do
|
24
|
+
git 'https://github.com/rails/rails.git', branch: 'main' do
|
25
|
+
gem 'activerecord'
|
26
|
+
gem 'activesupport'
|
27
|
+
end
|
21
28
|
end
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -19,6 +19,17 @@ ActsAsRecursiveTree currently supports following ActiveRecord versions and is te
|
|
19
19
|
* ActiveRecord 6.1.x
|
20
20
|
* ActiveRecord 7.0.x
|
21
21
|
|
22
|
+
## Supported Rubies
|
23
|
+
ActsAsRecursiveTree is tested with following rubies:
|
24
|
+
* MRuby 2.5
|
25
|
+
* MRuby 2.6
|
26
|
+
* MRuby 2.7
|
27
|
+
* MRuby 3.0
|
28
|
+
* MRuby 3.1
|
29
|
+
* MRuby 3.2
|
30
|
+
|
31
|
+
Other Ruby implementations are not tested, but should also work.
|
32
|
+
|
22
33
|
## Installation
|
23
34
|
|
24
35
|
Add this line to your application's Gemfile:
|
@@ -123,7 +134,11 @@ __Additional methods:__
|
|
123
134
|
__Utility methods:__
|
124
135
|
* `root?` - returns true if this node is a root node
|
125
136
|
* `leaf?` - returns true if this node is a leave node
|
137
|
+
* `preload_tree` - fetches all descendants of this node and assigns the proper parent/children associations. You are then able to traverse the tree through the children/parent association without querying the database again. You can also pass arguments to `includes` which will be forwarded when fetching records.
|
126
138
|
|
139
|
+
```ruby
|
140
|
+
node.preload_tree(includes: [:association, :another_association])
|
141
|
+
```
|
127
142
|
|
128
143
|
## Customizing the recursion
|
129
144
|
|
@@ -201,17 +216,6 @@ sub_node_instance.descendants # => returns Node and SubNode instances
|
|
201
216
|
```
|
202
217
|
|
203
218
|
|
204
|
-
## Known Issues
|
205
|
-
|
206
|
-
When using PostgreSQL as underlying database system chances are good that you encounter following error message:
|
207
|
-
|
208
|
-
`
|
209
|
-
ActiveRecord::StatementInvalid: PG::ProtocolViolation: ERROR: bind message supplies 1 parameters, but prepared statement "" requires 2
|
210
|
-
`
|
211
|
-
|
212
|
-
This is a known ActiveRecord issue which should be fixed in Rails 5.2. Alternative
|
213
|
-
|
214
|
-
|
215
219
|
## Contributing
|
216
220
|
|
217
221
|
1. Fork it ( https://github.com/1and1/acts_as_recursive_tree/fork )
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_runtime_dependency 'zeitwerk', '>= 2.4'
|
29
29
|
|
30
30
|
spec.add_development_dependency 'appraisal', '~> 2.4'
|
31
|
-
spec.add_development_dependency 'database_cleaner', '~>
|
31
|
+
spec.add_development_dependency 'database_cleaner', '~> 2.0'
|
32
32
|
spec.add_development_dependency 'rake'
|
33
33
|
spec.add_development_dependency 'rspec-rails', '>= 3.5'
|
34
34
|
spec.add_development_dependency 'rubocop', '~> 1.23.0'
|
data/gemfiles/ar_70.gemfile
CHANGED
@@ -8,7 +8,8 @@ module ActsAsRecursiveTree
|
|
8
8
|
# * <tt>foreign_key</tt> - specifies the column name to use for tracking
|
9
9
|
# of the tree (default: +parent_id+)
|
10
10
|
def recursive_tree(parent_key: :parent_id, parent_type_column: nil)
|
11
|
-
class_attribute :
|
11
|
+
class_attribute(:_recursive_tree_config, instance_writer: false)
|
12
|
+
|
12
13
|
self._recursive_tree_config = Config.new(
|
13
14
|
model_class: self,
|
14
15
|
parent_key: parent_key.to_sym,
|
@@ -91,6 +91,16 @@ module ActsAsRecursiveTree
|
|
91
91
|
children.none?
|
92
92
|
end
|
93
93
|
|
94
|
+
#
|
95
|
+
# Fetches all descendants of this node and assigns the parent/children associations
|
96
|
+
#
|
97
|
+
# @param includes [Array|Hash] pass the same arguments that should be passed to the #includes() method.
|
98
|
+
#
|
99
|
+
def preload_tree(includes: nil)
|
100
|
+
ActsAsRecursiveTree::Preloaders::Descendants.new(self, includes: includes).preload!
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
94
104
|
def base_class
|
95
105
|
self.class.base_class
|
96
106
|
end
|
@@ -99,11 +109,11 @@ module ActsAsRecursiveTree
|
|
99
109
|
|
100
110
|
module ClassMethods
|
101
111
|
def self_and_ancestors_of(ids, &block)
|
102
|
-
Builders::Ancestors.build(self, ids, &block)
|
112
|
+
ActsAsRecursiveTree::Builders::Ancestors.build(self, ids, &block)
|
103
113
|
end
|
104
114
|
|
105
115
|
def ancestors_of(ids, &block)
|
106
|
-
Builders::Ancestors.build(self, ids, exclude_ids: true, &block)
|
116
|
+
ActsAsRecursiveTree::Builders::Ancestors.build(self, ids, exclude_ids: true, &block)
|
107
117
|
end
|
108
118
|
|
109
119
|
def roots_of(ids)
|
@@ -111,15 +121,15 @@ module ActsAsRecursiveTree
|
|
111
121
|
end
|
112
122
|
|
113
123
|
def self_and_descendants_of(ids, &block)
|
114
|
-
Builders::Descendants.build(self, ids, &block)
|
124
|
+
ActsAsRecursiveTree::Builders::Descendants.build(self, ids, &block)
|
115
125
|
end
|
116
126
|
|
117
127
|
def descendants_of(ids, &block)
|
118
|
-
Builders::Descendants.build(self, ids, exclude_ids: true, &block)
|
128
|
+
ActsAsRecursiveTree::Builders::Descendants.build(self, ids, exclude_ids: true, &block)
|
119
129
|
end
|
120
130
|
|
121
131
|
def leaves_of(ids, &block)
|
122
|
-
Builders::Leaves.build(self, ids, &block)
|
132
|
+
ActsAsRecursiveTree::Builders::Leaves.build(self, ids, &block)
|
123
133
|
end
|
124
134
|
end
|
125
135
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsRecursiveTree
|
4
|
+
module Preloaders
|
5
|
+
#
|
6
|
+
# Preloads all descendants records for a given node and sets the parent and child associations on each record
|
7
|
+
# based on the preloaded data. After this, calling #parent or #children will not trigger a database query.
|
8
|
+
#
|
9
|
+
class Descendants
|
10
|
+
def initialize(node, includes: nil)
|
11
|
+
@node = node
|
12
|
+
@parent_key = node._recursive_tree_config.parent_key
|
13
|
+
@includes = includes
|
14
|
+
end
|
15
|
+
|
16
|
+
def preload!
|
17
|
+
apply_records(@node)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def records
|
23
|
+
@records ||= begin
|
24
|
+
descendants = @node.descendants
|
25
|
+
descendants = descendants.includes(*@includes) if @includes
|
26
|
+
descendants.to_a
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def apply_records(parent_node)
|
31
|
+
children = records.select { |child| child.send(@parent_key) == parent_node.id }
|
32
|
+
|
33
|
+
parent_node.association(:children).target = children
|
34
|
+
|
35
|
+
children.each do |child|
|
36
|
+
child.association(:parent).target = parent_node
|
37
|
+
apply_records(child)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ActsAsRecursiveTree::Builders::Ancestors do
|
6
|
+
context 'basic' do
|
7
|
+
it_behaves_like 'build recursive query'
|
8
|
+
it_behaves_like 'ancestor query'
|
9
|
+
include_context 'context with ordering'
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with options' do
|
13
|
+
include_context 'setup with enforced ordering' do
|
14
|
+
it_behaves_like 'with ordering'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ActsAsRecursiveTree::Builders::Descendants do
|
6
|
+
context 'basic' do
|
7
|
+
it_behaves_like 'build recursive query'
|
8
|
+
it_behaves_like 'descendant query'
|
9
|
+
include_context 'context without ordering'
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with options' do
|
13
|
+
include_context 'setup with enforced ordering' do
|
14
|
+
let(:ordering) { true }
|
15
|
+
it_behaves_like 'with ordering'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ActsAsRecursiveTree::Builders::Leaves do
|
6
|
+
context 'basic' do
|
7
|
+
it_behaves_like 'build recursive query'
|
8
|
+
it_behaves_like 'descendant query'
|
9
|
+
include_context 'context without ordering'
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with options' do
|
13
|
+
include_context 'setup with enforced ordering' do
|
14
|
+
let(:ordering) { true }
|
15
|
+
it_behaves_like 'without ordering'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -2,21 +2,21 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
RSpec.describe ActsAsRecursiveTree::Options::Values do
|
6
|
+
shared_examples 'single values' do
|
7
|
+
subject(:value) { described_class.create(single_value) }
|
7
8
|
|
8
|
-
|
9
|
+
it { is_expected.to be_a described_class::SingleValue }
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
it 'apply_toes' do
|
12
|
+
expect(value.apply_to(attribute).to_sql).to end_with " = #{single_value}"
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
it 'apply_negated_toes' do
|
16
|
+
expect(value.apply_negated_to(attribute).to_sql).to end_with " != #{single_value}"
|
17
|
+
end
|
16
18
|
end
|
17
|
-
end
|
18
19
|
|
19
|
-
describe ActsAsRecursiveTree::Options::Values do
|
20
20
|
let(:table) { Arel::Table.new('test_table') }
|
21
21
|
let(:attribute) { table['test_attr'] }
|
22
22
|
|
@@ -44,7 +44,7 @@ describe ActsAsRecursiveTree::Options::Values do
|
|
44
44
|
|
45
45
|
let(:array) { [1, 2, 3] }
|
46
46
|
|
47
|
-
it { is_expected.to be_a
|
47
|
+
it { is_expected.to be_a described_class::MultiValue }
|
48
48
|
|
49
49
|
it 'apply_toes' do
|
50
50
|
expect(value.apply_to(attribute).to_sql).to end_with " IN (#{array.join(', ')})"
|
@@ -60,7 +60,7 @@ describe ActsAsRecursiveTree::Options::Values do
|
|
60
60
|
|
61
61
|
let(:range) { 1..3 }
|
62
62
|
|
63
|
-
it { is_expected.to be_a
|
63
|
+
it { is_expected.to be_a described_class::RangeValue }
|
64
64
|
|
65
65
|
it 'apply_toes' do
|
66
66
|
expect(value.apply_to(attribute).to_sql).to end_with "BETWEEN #{range.begin} AND #{range.end}"
|
@@ -83,7 +83,7 @@ describe ActsAsRecursiveTree::Options::Values do
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
it { is_expected.to be_a
|
86
|
+
it { is_expected.to be_a described_class::Relation }
|
87
87
|
|
88
88
|
it 'apply_toes' do
|
89
89
|
expect(value.apply_to(attribute).to_sql).to match(/IN \(SELECT.*\)/)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe ActsAsRecursiveTree::Preloaders::Descendants do
|
6
|
+
include TreeMethods
|
7
|
+
|
8
|
+
let(:preloader) { described_class.new(root.reload, includes: included_associations) }
|
9
|
+
let(:included_associations) { nil }
|
10
|
+
let(:root) { create_tree(2, create_node_info: true) }
|
11
|
+
let(:children) { root.children }
|
12
|
+
|
13
|
+
describe '#preload! will set the associations target attribute' do
|
14
|
+
before do
|
15
|
+
preloader.preload!
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets the children association' do
|
19
|
+
children.each do |child|
|
20
|
+
expect(child.association(:children).target).not_to be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets the parent association' do
|
25
|
+
children.each do |child|
|
26
|
+
expect(child.association(:parent).target).not_to be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#preload! will include associations' do
|
32
|
+
let(:included_associations) { :node_info }
|
33
|
+
|
34
|
+
before do
|
35
|
+
preloader.preload!
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sets the children association' do
|
39
|
+
children.each do |child|
|
40
|
+
expect(child.association(included_associations).target).not_to be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/model/location_spec.rb
CHANGED
data/spec/model/node_spec.rb
CHANGED
@@ -2,17 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe Node do
|
6
|
-
|
7
|
-
node = Node.create!(name: 'root') if node.nil?
|
8
|
-
|
9
|
-
1.upto(max_level - current_level) do |index|
|
10
|
-
child = node.children.create!(name: "child #{index} - level #{current_level}")
|
11
|
-
create_tree(max_level, current_level + 1, child)
|
12
|
-
end
|
13
|
-
|
14
|
-
node
|
15
|
-
end
|
5
|
+
RSpec.describe Node do
|
6
|
+
include TreeMethods
|
16
7
|
|
17
8
|
before do
|
18
9
|
@root = create_tree(3)
|
data/spec/model/relation_spec.rb
CHANGED
@@ -2,57 +2,68 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe 'Relation' do
|
6
|
-
|
7
|
-
node = Node.create!(name: 'root') if node.nil?
|
8
|
-
|
9
|
-
1.upto(max_level - current_level) do |index|
|
10
|
-
child = node.children.create!(name: "child #{index} - level #{current_level}", active: stop_at > current_level)
|
11
|
-
child.create_node_info(status: stop_at > current_level ? 'foo' : 'bar')
|
12
|
-
create_tree(max_level, current_level: current_level + 1, node: child, stop_at: stop_at)
|
13
|
-
end
|
5
|
+
RSpec.describe 'Relation' do
|
6
|
+
include TreeMethods
|
14
7
|
|
15
|
-
|
16
|
-
end
|
8
|
+
let(:root) { create_tree(4, stop_at: 2) }
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
10
|
+
describe '#descendants' do
|
11
|
+
context 'with simple relation' do
|
12
|
+
let(:descendants) { root.descendants { |opts| opts.condition = Node.where(active: true) }.to_a }
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
expect(node.active).to be_truthy
|
14
|
+
it 'returns only active nodes' do
|
15
|
+
descendants.each do |node|
|
16
|
+
expect(node.active).to be_truthy
|
17
|
+
end
|
28
18
|
end
|
29
19
|
end
|
30
20
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
context 'with condition on joined association' do
|
22
|
+
let(:descendants) do
|
23
|
+
root.descendants do |opts|
|
24
|
+
opts.condition = Node.joins(:node_info).where.not(node_infos: { status: 'bar' })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns only node with condition fulfilled' do
|
29
|
+
descendants.each do |node|
|
30
|
+
expect(node.node_info.status).to eql('foo')
|
31
|
+
end
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
ancestors
|
36
|
+
describe '#ancestors' do
|
37
|
+
context 'with simple_relation' do
|
38
|
+
let(:ancestors) { root.leaves.first.ancestors { |opts| opts.condition = Node.where(active: false) }.to_a }
|
42
39
|
|
43
|
-
|
44
|
-
|
40
|
+
it 'return only active nodes' do
|
41
|
+
ancestors.each do |node|
|
42
|
+
expect(node.active).to be_falsey
|
43
|
+
end
|
45
44
|
end
|
46
45
|
|
47
|
-
|
46
|
+
it 'does not return the root node' do
|
47
|
+
expect(ancestors).not_to include(root)
|
48
|
+
end
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
context 'with condition on joined association' do
|
52
|
+
let(:ancestors) do
|
53
|
+
root.leaves.first.ancestors do |opts|
|
54
|
+
opts.condition = Node.joins(:node_info).where.not(node_infos: { status: 'foo' })
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'return only nodes for the matching condition' do
|
59
|
+
ancestors.each do |node|
|
60
|
+
expect(node.node_info.status).to eql('bar')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not return the root node' do
|
65
|
+
expect(ancestors).not_to include(root)
|
54
66
|
end
|
55
|
-
expect(ancestors).not_to include(@root)
|
56
67
|
end
|
57
68
|
end
|
58
69
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,6 +11,10 @@ require_relative 'db/database'
|
|
11
11
|
|
12
12
|
require 'database_cleaner'
|
13
13
|
|
14
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
15
|
+
# in spec/support/ and its subdirectories.
|
16
|
+
Dir[File.join(__dir__, 'support/**/*.rb')].sort.each { |f| require f }
|
17
|
+
|
14
18
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
15
19
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
16
20
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
@@ -72,7 +76,7 @@ RSpec.configure do |config|
|
|
72
76
|
# # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
73
77
|
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
74
78
|
# # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
75
|
-
|
79
|
+
config.disable_monkey_patching!
|
76
80
|
#
|
77
81
|
# # This setting enables warnings. It's recommended, but in some cases may
|
78
82
|
# # be too noisy due to issues in dependencies.
|
@@ -1,15 +1,12 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
|
3
|
-
|
4
|
-
|
5
|
-
shared_context 'setup with enforced ordering' do
|
2
|
+
RSpec.shared_context 'setup with enforced ordering' do
|
6
3
|
let(:ordering) { false }
|
7
4
|
include_context 'base_setup' do
|
8
5
|
let(:proc) { ->(config) { config.ensure_ordering! } }
|
9
6
|
end
|
10
7
|
end
|
11
8
|
|
12
|
-
shared_context 'base_setup' do
|
9
|
+
RSpec.shared_context 'base_setup' do
|
13
10
|
subject(:query) { builder.build.to_sql }
|
14
11
|
|
15
12
|
let(:model_id) { 1 }
|
@@ -21,7 +18,7 @@ shared_context 'base_setup' do
|
|
21
18
|
end
|
22
19
|
end
|
23
20
|
|
24
|
-
shared_examples 'basic recursive examples' do
|
21
|
+
RSpec.shared_examples 'basic recursive examples' do
|
25
22
|
it { is_expected.to start_with "SELECT \"#{model_class.table_name}\".* FROM \"#{model_class.table_name}\"" }
|
26
23
|
|
27
24
|
it { is_expected.to match(/WHERE "#{model_class.table_name}"."#{model_class.primary_key}" = #{model_id}/) }
|
@@ -35,7 +32,7 @@ shared_examples 'basic recursive examples' do
|
|
35
32
|
}
|
36
33
|
end
|
37
34
|
|
38
|
-
shared_examples 'build recursive query' do
|
35
|
+
RSpec.shared_examples 'build recursive query' do
|
39
36
|
context 'simple id' do
|
40
37
|
context 'with simple class' do
|
41
38
|
include_context 'base_setup' do
|
@@ -67,79 +64,35 @@ shared_examples 'build recursive query' do
|
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
70
|
-
shared_examples 'ancestor query' do
|
67
|
+
RSpec.shared_examples 'ancestor query' do
|
71
68
|
include_context 'base_setup'
|
72
69
|
|
73
70
|
it { is_expected.to match(/"#{builder.travers_loc_table.name}"."#{model_class._recursive_tree_config.parent_key}" = "#{model_class.table_name}"."#{model_class.primary_key}"/) }
|
74
71
|
end
|
75
72
|
|
76
|
-
shared_examples 'descendant query' do
|
73
|
+
RSpec.shared_examples 'descendant query' do
|
77
74
|
include_context 'base_setup'
|
78
75
|
|
79
76
|
it { is_expected.to match(/"#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}" = "#{builder.travers_loc_table.name}"."#{model_class.primary_key}"/) }
|
80
77
|
it { is_expected.to match(/#{Regexp.escape(builder.travers_loc_table.project(builder.travers_loc_table[model_class.primary_key]).to_sql)}/) }
|
81
78
|
end
|
82
79
|
|
83
|
-
shared_context 'context with ordering' do
|
80
|
+
RSpec.shared_context 'context with ordering' do
|
84
81
|
include_context 'base_setup' do
|
85
82
|
it_behaves_like 'with ordering'
|
86
83
|
end
|
87
84
|
end
|
88
85
|
|
89
|
-
shared_context 'context without ordering' do
|
86
|
+
RSpec.shared_context 'context without ordering' do
|
90
87
|
include_context 'base_setup' do
|
91
88
|
it_behaves_like 'without ordering'
|
92
89
|
end
|
93
90
|
end
|
94
91
|
|
95
|
-
shared_examples 'with ordering' do
|
92
|
+
RSpec.shared_examples 'with ordering' do
|
96
93
|
it { is_expected.to match(/ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/) }
|
97
94
|
end
|
98
95
|
|
99
|
-
shared_examples 'without ordering' do
|
96
|
+
RSpec.shared_examples 'without ordering' do
|
100
97
|
it { is_expected.not_to match(/ORDER BY/) }
|
101
98
|
end
|
102
|
-
|
103
|
-
describe ActsAsRecursiveTree::Builders::Descendants do
|
104
|
-
context 'basic' do
|
105
|
-
it_behaves_like 'build recursive query'
|
106
|
-
it_behaves_like 'descendant query'
|
107
|
-
include_context 'context without ordering'
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'with options' do
|
111
|
-
include_context 'setup with enforced ordering' do
|
112
|
-
let(:ordering) { true }
|
113
|
-
it_behaves_like 'with ordering'
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe ActsAsRecursiveTree::Builders::Ancestors do
|
119
|
-
context 'basic' do
|
120
|
-
it_behaves_like 'build recursive query'
|
121
|
-
it_behaves_like 'ancestor query'
|
122
|
-
include_context 'context with ordering'
|
123
|
-
end
|
124
|
-
|
125
|
-
context 'with options' do
|
126
|
-
include_context 'setup with enforced ordering' do
|
127
|
-
it_behaves_like 'with ordering'
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
describe ActsAsRecursiveTree::Builders::Leaves do
|
133
|
-
context 'basic' do
|
134
|
-
it_behaves_like 'build recursive query'
|
135
|
-
it_behaves_like 'descendant query'
|
136
|
-
include_context 'context without ordering'
|
137
|
-
end
|
138
|
-
|
139
|
-
context 'with options' do
|
140
|
-
include_context 'setup with enforced ordering' do
|
141
|
-
let(:ordering) { true }
|
142
|
-
it_behaves_like 'without ordering'
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Helper methods for simple tree creation
|
4
|
+
module TreeMethods
|
5
|
+
def create_tree(max_level, current_level: 0, node: nil, create_node_info: false, stop_at: -1)
|
6
|
+
node = Node.create!(name: 'root') if node.nil?
|
7
|
+
|
8
|
+
1.upto(max_level - current_level) do |index|
|
9
|
+
child = node.children.create!(
|
10
|
+
name: "child #{index} - level #{current_level}",
|
11
|
+
active: stop_at > current_level
|
12
|
+
)
|
13
|
+
|
14
|
+
child.create_node_info(status: stop_at > current_level ? 'foo' : 'bar') if create_node_info
|
15
|
+
|
16
|
+
create_tree(
|
17
|
+
max_level,
|
18
|
+
current_level: current_level + 1,
|
19
|
+
node: child,
|
20
|
+
create_node_info: create_node_info,
|
21
|
+
stop_at: stop_at
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
node
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_recursive_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wolfgang Wedelich-John
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-01-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -85,14 +85,14 @@ dependencies:
|
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
88
|
+
version: '2.0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
95
|
+
version: '2.0'
|
96
96
|
- !ruby/object:Gem::Dependency
|
97
97
|
name: rake
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -205,6 +205,7 @@ files:
|
|
205
205
|
- gemfiles/ar_60.gemfile
|
206
206
|
- gemfiles/ar_61.gemfile
|
207
207
|
- gemfiles/ar_70.gemfile
|
208
|
+
- gemfiles/ar_next.gemfile
|
208
209
|
- lib/acts_as_recursive_tree.rb
|
209
210
|
- lib/acts_as_recursive_tree/acts_macro.rb
|
210
211
|
- lib/acts_as_recursive_tree/associations.rb
|
@@ -222,10 +223,15 @@ files:
|
|
222
223
|
- lib/acts_as_recursive_tree/options/depth_condition.rb
|
223
224
|
- lib/acts_as_recursive_tree/options/query_options.rb
|
224
225
|
- lib/acts_as_recursive_tree/options/values.rb
|
226
|
+
- lib/acts_as_recursive_tree/preloaders/descendants.rb
|
225
227
|
- lib/acts_as_recursive_tree/railtie.rb
|
226
228
|
- lib/acts_as_recursive_tree/scopes.rb
|
227
229
|
- lib/acts_as_recursive_tree/version.rb
|
228
|
-
- spec/
|
230
|
+
- spec/acts_as_recursive_tree/builders/ancestors_spec.rb
|
231
|
+
- spec/acts_as_recursive_tree/builders/descendants_spec.rb
|
232
|
+
- spec/acts_as_recursive_tree/builders/leaves_spec.rb
|
233
|
+
- spec/acts_as_recursive_tree/options/values_spec.rb
|
234
|
+
- spec/acts_as_recursive_tree/preloaders/descendants_spec.rb
|
229
235
|
- spec/db/database.rb
|
230
236
|
- spec/db/database.yml
|
231
237
|
- spec/db/models.rb
|
@@ -234,7 +240,8 @@ files:
|
|
234
240
|
- spec/model/node_spec.rb
|
235
241
|
- spec/model/relation_spec.rb
|
236
242
|
- spec/spec_helper.rb
|
237
|
-
- spec/
|
243
|
+
- spec/support/shared_examples/builders.rb
|
244
|
+
- spec/support/tree_methods.rb
|
238
245
|
homepage: https://github.com/1and1/acts_as_recursive_tree
|
239
246
|
licenses:
|
240
247
|
- MIT
|
@@ -261,7 +268,11 @@ signing_key:
|
|
261
268
|
specification_version: 4
|
262
269
|
summary: Drop in replacement for acts_as_tree but using recursive queries
|
263
270
|
test_files:
|
264
|
-
- spec/
|
271
|
+
- spec/acts_as_recursive_tree/builders/ancestors_spec.rb
|
272
|
+
- spec/acts_as_recursive_tree/builders/descendants_spec.rb
|
273
|
+
- spec/acts_as_recursive_tree/builders/leaves_spec.rb
|
274
|
+
- spec/acts_as_recursive_tree/options/values_spec.rb
|
275
|
+
- spec/acts_as_recursive_tree/preloaders/descendants_spec.rb
|
265
276
|
- spec/db/database.rb
|
266
277
|
- spec/db/database.yml
|
267
278
|
- spec/db/models.rb
|
@@ -270,4 +281,5 @@ test_files:
|
|
270
281
|
- spec/model/node_spec.rb
|
271
282
|
- spec/model/relation_spec.rb
|
272
283
|
- spec/spec_helper.rb
|
273
|
-
- spec/
|
284
|
+
- spec/support/shared_examples/builders.rb
|
285
|
+
- spec/support/tree_methods.rb
|