acts_as_recursive_tree 3.1.0 → 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.
- 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
|