acts_as_recursive_tree 2.2.1 → 3.0.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 +37 -0
- data/.github/workflows/lint.yml +31 -0
- data/.github/workflows/rubygem.yml +37 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +30 -1
- data/.rubocop_todo.yml +28 -281
- data/Appraisals +10 -20
- data/CHANGELOG.md +10 -1
- data/Gemfile +2 -0
- data/README.md +3 -0
- data/Rakefile +8 -8
- data/acts_as_recursive_tree.gemspec +27 -18
- data/gemfiles/ar_52.gemfile +8 -0
- data/gemfiles/ar_60.gemfile +8 -0
- data/gemfiles/ar_61.gemfile +8 -0
- data/lib/acts_as_recursive_tree.rb +7 -11
- data/lib/acts_as_recursive_tree/acts_macro.rb +6 -6
- data/lib/acts_as_recursive_tree/associations.rb +10 -8
- data/lib/acts_as_recursive_tree/builders/ancestors.rb +3 -2
- data/lib/acts_as_recursive_tree/builders/descendants.rb +3 -1
- data/lib/acts_as_recursive_tree/builders/leaves.rb +7 -8
- data/lib/acts_as_recursive_tree/builders/relation_builder.rb +14 -10
- data/lib/acts_as_recursive_tree/builders/{strategy.rb → strategies.rb} +3 -9
- data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/ancestor.rb +3 -1
- data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/descendant.rb +3 -1
- data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/join.rb +5 -3
- data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/subselect.rb +3 -1
- data/lib/acts_as_recursive_tree/config.rb +2 -0
- data/lib/acts_as_recursive_tree/model.rb +9 -8
- data/lib/acts_as_recursive_tree/options/depth_condition.rb +3 -2
- data/lib/acts_as_recursive_tree/options/query_options.rb +4 -2
- data/lib/acts_as_recursive_tree/options/values.rb +17 -19
- data/lib/acts_as_recursive_tree/railtie.rb +2 -0
- data/lib/acts_as_recursive_tree/scopes.rb +8 -4
- data/lib/acts_as_recursive_tree/version.rb +3 -1
- data/spec/builders_spec.rb +17 -11
- data/spec/db/database.rb +5 -4
- data/spec/db/database.yml +2 -5
- data/spec/db/models.rb +12 -11
- data/spec/db/schema.rb +3 -4
- data/spec/model/location_spec.rb +7 -11
- data/spec/model/node_spec.rb +35 -49
- data/spec/model/relation_spec.rb +6 -11
- data/spec/spec_helper.rb +54 -55
- data/spec/values_spec.rb +21 -17
- metadata +115 -33
- data/lib/acts_as_recursive_tree/builders.rb +0 -14
- data/lib/acts_as_recursive_tree/options.rb +0 -9
data/spec/model/location_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Location do
|
@@ -10,7 +12,6 @@ describe Location do
|
|
10
12
|
@building.children << floor
|
11
13
|
|
12
14
|
1.upto(5) do |index_room|
|
13
|
-
|
14
15
|
floor.children << Room.create!(name: "#{index_room}. Room")
|
15
16
|
end
|
16
17
|
end
|
@@ -22,34 +23,29 @@ describe Location do
|
|
22
23
|
end
|
23
24
|
|
24
25
|
context '::descendants_of' do
|
25
|
-
|
26
26
|
context 'with Room' do
|
27
27
|
let(:rooms) { Room.descendants_of(@building) }
|
28
28
|
|
29
|
-
it '
|
29
|
+
it 'has 25 rooms' do
|
30
30
|
expect(rooms.count).to eq(25)
|
31
31
|
end
|
32
32
|
|
33
|
-
it '
|
33
|
+
it 'alls be of type Room' do
|
34
34
|
expect(rooms.all).to all(be_an(Room))
|
35
35
|
end
|
36
|
-
|
37
36
|
end
|
38
37
|
|
39
38
|
context 'with Floor' do
|
40
39
|
let(:floors) { Floor.descendants_of(@building) }
|
41
40
|
|
42
|
-
it '
|
41
|
+
it 'has 5 Floors' do
|
43
42
|
expect(floors.count).to eq(5)
|
44
43
|
end
|
45
44
|
|
46
|
-
it '
|
45
|
+
it 'alls be of type Floor' do
|
47
46
|
expect(floors.all).to all(be_an(Floor))
|
48
47
|
end
|
49
|
-
|
50
48
|
end
|
51
|
-
|
52
49
|
end
|
53
50
|
end
|
54
|
-
|
55
|
-
end
|
51
|
+
end
|
data/spec/model/node_spec.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Node do
|
4
|
-
|
5
6
|
def create_tree(max_level, current_level = 0, node = nil)
|
6
|
-
|
7
|
-
if node.nil?
|
8
|
-
node = Node.create!(name: 'root')
|
9
|
-
end
|
7
|
+
node = Node.create!(name: 'root') if node.nil?
|
10
8
|
|
11
9
|
1.upto(max_level - current_level) do |index|
|
12
10
|
child = node.children.create!(name: "child #{index} - level #{current_level}")
|
@@ -21,80 +19,72 @@ describe Node do
|
|
21
19
|
@child = @root.children.first
|
22
20
|
end
|
23
21
|
|
24
|
-
|
25
|
-
it '
|
26
|
-
expect(@root.children.count).to
|
22
|
+
describe '#children' do
|
23
|
+
it 'has 3 children' do
|
24
|
+
expect(@root.children.count).to be(3)
|
27
25
|
end
|
28
26
|
|
29
|
-
it '
|
30
|
-
expect(@root.children).
|
27
|
+
it 'does not include root node ' do
|
28
|
+
expect(@root.children).not_to include(@root)
|
31
29
|
end
|
32
|
-
|
33
30
|
end
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
it 'should have 15 descendants' do
|
32
|
+
describe '#descendants' do
|
33
|
+
it 'has 15 descendants' do
|
38
34
|
expect(@root.descendants.count).to eql(3 + (3 * 2) + (3 * 2 * 1))
|
39
35
|
end
|
40
36
|
|
41
|
-
it '
|
42
|
-
expect(@root.descendants).
|
37
|
+
it 'does not include root' do
|
38
|
+
expect(@root.descendants).not_to include(@root)
|
43
39
|
end
|
44
40
|
end
|
45
|
-
context '#self_and_descendants' do
|
46
41
|
|
47
|
-
|
42
|
+
describe '#self_and_descendants' do
|
43
|
+
it 'has 15 descendants and self' do
|
48
44
|
expect(@root.self_and_descendants.count).to eql(@root.descendants.count + 1)
|
49
45
|
end
|
50
46
|
|
51
|
-
it '
|
47
|
+
it 'includes self' do
|
52
48
|
expect(@root.self_and_descendants.all).to include(@root)
|
53
49
|
end
|
54
50
|
end
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
expect(@root.root?).to be_truthy
|
52
|
+
describe '#root?' do
|
53
|
+
it 'is true for root node' do
|
54
|
+
expect(@root).to be_root
|
60
55
|
end
|
61
56
|
|
62
|
-
it '
|
63
|
-
expect(@child
|
57
|
+
it 'is false for children' do
|
58
|
+
expect(@child).not_to be_root
|
64
59
|
end
|
65
|
-
|
66
60
|
end
|
67
61
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
expect(@root.leaf?).to be_falsey
|
62
|
+
describe '#leaf?' do
|
63
|
+
it 'is false for root node' do
|
64
|
+
expect(@root).not_to be_leaf
|
72
65
|
end
|
73
66
|
|
74
|
-
it '
|
75
|
-
expect(@root.leaves.first
|
67
|
+
it 'is true for children' do
|
68
|
+
expect(@root.leaves.first).to be_leaf
|
76
69
|
end
|
77
|
-
|
78
70
|
end
|
79
71
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
expect(@root.leaves.count).to eql(6)
|
72
|
+
describe '#leaves' do
|
73
|
+
it 'has 6 leaves' do
|
74
|
+
expect(@root.leaves.count).to be(6)
|
84
75
|
end
|
85
76
|
end
|
86
77
|
|
87
78
|
describe 'child' do
|
88
|
-
|
89
|
-
it 'should have root as parent' do
|
79
|
+
it 'has root as parent' do
|
90
80
|
expect(@child.parent).to eql(@root)
|
91
81
|
end
|
92
82
|
|
93
|
-
it '
|
94
|
-
expect(@child.ancestors.count).to
|
83
|
+
it 'has 1 ancestor' do
|
84
|
+
expect(@child.ancestors.count).to be(1)
|
95
85
|
end
|
96
86
|
|
97
|
-
it '
|
87
|
+
it 'has root as only ancestor' do
|
98
88
|
expect(@child.ancestors.first).to eql(@root)
|
99
89
|
end
|
100
90
|
|
@@ -111,19 +101,15 @@ describe Node do
|
|
111
101
|
end
|
112
102
|
end
|
113
103
|
|
114
|
-
|
115
104
|
describe 'scopes' do
|
116
|
-
|
117
105
|
context 'roots' do
|
118
|
-
|
119
106
|
it 'has only one root node' do
|
120
|
-
expect(
|
107
|
+
expect(described_class.roots.count).to be(1)
|
121
108
|
end
|
122
109
|
|
123
110
|
it 'is the @root node' do
|
124
|
-
expect(
|
111
|
+
expect(described_class.roots.first).to eql(@root)
|
125
112
|
end
|
126
113
|
end
|
127
|
-
|
128
114
|
end
|
129
|
-
end
|
115
|
+
end
|
data/spec/model/relation_spec.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe 'Relation' do
|
4
|
-
|
5
6
|
def create_tree(max_level, current_level: 0, node: nil, stop_at: nil)
|
6
|
-
|
7
|
-
if node.nil?
|
8
|
-
node = Node.create!(name: 'root')
|
9
|
-
end
|
7
|
+
node = Node.create!(name: 'root') if node.nil?
|
10
8
|
|
11
9
|
1.upto(max_level - current_level) do |index|
|
12
10
|
child = node.children.create!(name: "child #{index} - level #{current_level}", active: stop_at > current_level)
|
@@ -23,7 +21,6 @@ describe 'Relation' do
|
|
23
21
|
end
|
24
22
|
|
25
23
|
context 'descendants' do
|
26
|
-
|
27
24
|
it 'works with simple relation' do
|
28
25
|
desc = @root.descendants { |opts| opts.condition = Node.where(active: true) }
|
29
26
|
desc.all.each do |node|
|
@@ -40,7 +37,6 @@ describe 'Relation' do
|
|
40
37
|
end
|
41
38
|
|
42
39
|
context 'ancestors' do
|
43
|
-
|
44
40
|
it 'works with simple relation' do
|
45
41
|
ancestors = @root.leaves.first.ancestors { |opts| opts.condition = Node.where(active: false) }.to_a
|
46
42
|
|
@@ -48,7 +44,7 @@ describe 'Relation' do
|
|
48
44
|
expect(node.active).to be_falsey
|
49
45
|
end
|
50
46
|
|
51
|
-
expect(ancestors).
|
47
|
+
expect(ancestors).not_to include(@root)
|
52
48
|
end
|
53
49
|
|
54
50
|
it 'works with joins relation' do
|
@@ -56,8 +52,7 @@ describe 'Relation' do
|
|
56
52
|
ancestors.all.each do |node|
|
57
53
|
expect(node.node_info.status).to eql('bar')
|
58
54
|
end
|
59
|
-
expect(ancestors).
|
55
|
+
expect(ancestors).not_to include(@root)
|
60
56
|
end
|
61
57
|
end
|
62
|
-
|
63
|
-
end
|
58
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
|
4
|
+
|
5
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
4
6
|
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
5
7
|
require 'active_record'
|
6
8
|
|
@@ -9,7 +11,6 @@ require_relative 'db/database'
|
|
9
11
|
|
10
12
|
require 'database_cleaner'
|
11
13
|
|
12
|
-
|
13
14
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
14
15
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
15
16
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
@@ -54,64 +55,62 @@ RSpec.configure do |config|
|
|
54
55
|
|
55
56
|
# The settings below are suggested to provide a good initial experience
|
56
57
|
# with RSpec, but feel free to customize to your heart's content.
|
57
|
-
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
config.
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# - http://
|
73
|
-
# - http://
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
Kernel.srand config.seed
|
107
|
-
=end
|
58
|
+
# # These two settings work together to allow you to limit a spec run
|
59
|
+
# # to individual examples or groups you care about by tagging them with
|
60
|
+
# # `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
61
|
+
# # get run.
|
62
|
+
# config.filter_run :focus
|
63
|
+
# config.run_all_when_everything_filtered = true
|
64
|
+
#
|
65
|
+
# # Allows RSpec to persist some state between runs in order to support
|
66
|
+
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
67
|
+
# # you configure your source control system to ignore this file.
|
68
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
69
|
+
#
|
70
|
+
# # Limits the available syntax to the non-monkey patched syntax that is
|
71
|
+
# # recommended. For more details, see:
|
72
|
+
# # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
73
|
+
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
74
|
+
# # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
75
|
+
# config.disable_monkey_patching!
|
76
|
+
#
|
77
|
+
# # This setting enables warnings. It's recommended, but in some cases may
|
78
|
+
# # be too noisy due to issues in dependencies.
|
79
|
+
# config.warnings = true
|
80
|
+
#
|
81
|
+
# # Many RSpec users commonly either run the entire suite or an individual
|
82
|
+
# # file, and it's useful to allow more verbose output when running an
|
83
|
+
# # individual spec file.
|
84
|
+
# if config.files_to_run.one?
|
85
|
+
# # Use the documentation formatter for detailed output,
|
86
|
+
# # unless a formatter has already been configured
|
87
|
+
# # (e.g. via a command-line flag).
|
88
|
+
# config.default_formatter = 'doc'
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# # Print the 10 slowest examples and example groups at the
|
92
|
+
# # end of the spec run, to help surface which specs are running
|
93
|
+
# # particularly slow.
|
94
|
+
# config.profile_examples = 10
|
95
|
+
#
|
96
|
+
# # Run specs in random order to surface order dependencies. If you find an
|
97
|
+
# # order dependency and want to debug it, you can fix the order by providing
|
98
|
+
# # the seed, which is printed after each run.
|
99
|
+
# # --seed 1234
|
100
|
+
# config.order = :random
|
101
|
+
#
|
102
|
+
# # Seed global randomization in this process using the `--seed` CLI option.
|
103
|
+
# # Setting this allows you to use `--seed` to deterministically reproduce
|
104
|
+
# # test failures related to randomization by passing the same `--seed` value
|
105
|
+
# # as the one that triggered the failure.
|
106
|
+
# Kernel.srand config.seed
|
108
107
|
|
109
108
|
config.before(:suite) do
|
110
109
|
DatabaseCleaner.strategy = :transaction
|
111
110
|
DatabaseCleaner.clean_with(:truncation)
|
112
111
|
end
|
113
112
|
|
114
|
-
config.around
|
113
|
+
config.around do |example|
|
115
114
|
DatabaseCleaner.cleaning do
|
116
115
|
example.run
|
117
116
|
end
|
data/spec/values_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
shared_examples 'single values' do
|
@@ -5,11 +7,11 @@ shared_examples 'single values' do
|
|
5
7
|
|
6
8
|
it { is_expected.to be_a ActsAsRecursiveTree::Options::Values::SingleValue }
|
7
9
|
|
8
|
-
it '
|
10
|
+
it 'apply_toes' do
|
9
11
|
expect(value.apply_to(attribute).to_sql).to end_with " = #{single_value}"
|
10
12
|
end
|
11
13
|
|
12
|
-
it '
|
14
|
+
it 'apply_negated_toes' do
|
13
15
|
expect(value.apply_negated_to(attribute).to_sql).to end_with " != #{single_value}"
|
14
16
|
end
|
15
17
|
end
|
@@ -19,8 +21,8 @@ describe ActsAsRecursiveTree::Options::Values do
|
|
19
21
|
let(:attribute) { table['test_attr'] }
|
20
22
|
|
21
23
|
context 'invalid agurment' do
|
22
|
-
it '
|
23
|
-
expect{described_class.create(nil)}.to raise_exception
|
24
|
+
it 'raises exception' do
|
25
|
+
expect { described_class.create(nil) }.to raise_exception(/is not supported/)
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -34,52 +36,54 @@ describe ActsAsRecursiveTree::Options::Values do
|
|
34
36
|
it_behaves_like 'single values' do
|
35
37
|
let(:value_obj) { Node.new(id: single_value) }
|
36
38
|
end
|
37
|
-
|
38
39
|
end
|
39
40
|
|
40
41
|
context 'multi value' do
|
41
42
|
context 'Array' do
|
42
|
-
let(:array) { [1, 2, 3] }
|
43
43
|
subject(:value) { described_class.create(array) }
|
44
44
|
|
45
|
+
let(:array) { [1, 2, 3] }
|
46
|
+
|
45
47
|
it { is_expected.to be_a ActsAsRecursiveTree::Options::Values::MultiValue }
|
46
48
|
|
47
|
-
it '
|
49
|
+
it 'apply_toes' do
|
48
50
|
expect(value.apply_to(attribute).to_sql).to end_with " IN (#{array.join(', ')})"
|
49
51
|
end
|
50
52
|
|
51
|
-
it '
|
53
|
+
it 'apply_negated_toes' do
|
52
54
|
expect(value.apply_negated_to(attribute).to_sql).to end_with " NOT IN (#{array.join(', ')})"
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
56
58
|
context 'Range' do
|
57
|
-
let(:range) { 1..3 }
|
58
59
|
subject(:value) { described_class.create(range) }
|
59
60
|
|
61
|
+
let(:range) { 1..3 }
|
62
|
+
|
60
63
|
it { is_expected.to be_a ActsAsRecursiveTree::Options::Values::RangeValue }
|
61
64
|
|
62
|
-
it '
|
65
|
+
it 'apply_toes' do
|
63
66
|
expect(value.apply_to(attribute).to_sql).to end_with "BETWEEN #{range.begin} AND #{range.end}"
|
64
67
|
end
|
65
68
|
|
66
|
-
it '
|
67
|
-
expect(value.apply_negated_to(attribute).to_sql).to match
|
69
|
+
it 'apply_negated_toes' do
|
70
|
+
expect(value.apply_negated_to(attribute).to_sql).to match(/< #{range.begin} OR.* > #{range.end}/)
|
68
71
|
end
|
69
72
|
end
|
70
73
|
|
71
74
|
context 'Relation' do
|
72
|
-
let(:relation) { Node.where(name: 'test') }
|
73
75
|
subject(:value) { described_class.create(relation, OpenStruct.new(primary_key: :id)) }
|
74
76
|
|
77
|
+
let(:relation) { Node.where(name: 'test') }
|
78
|
+
|
75
79
|
it { is_expected.to be_a ActsAsRecursiveTree::Options::Values::Relation }
|
76
80
|
|
77
|
-
it '
|
78
|
-
expect(value.apply_to(attribute).to_sql).to match
|
81
|
+
it 'apply_toes' do
|
82
|
+
expect(value.apply_to(attribute).to_sql).to match(/IN \(SELECT.*\)/)
|
79
83
|
end
|
80
84
|
|
81
|
-
it '
|
82
|
-
expect(value.apply_negated_to(attribute).to_sql).to match
|
85
|
+
it 'apply_negated_toes' do
|
86
|
+
expect(value.apply_negated_to(attribute).to_sql).to match(/NOT IN \(SELECT.*\)/)
|
83
87
|
end
|
84
88
|
end
|
85
89
|
end
|