acts_as_recursive_tree 2.2.1 → 3.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 +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
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
|
-
module
|
5
|
+
module Strategies
|
4
6
|
#
|
5
7
|
# Build a relation using an INNER JOIN.
|
6
8
|
#
|
@@ -19,12 +21,12 @@ module ActsAsRecursiveTree
|
|
19
21
|
|
20
22
|
relation = builder.klass.joins(final_select_mgr.join_sources)
|
21
23
|
|
22
|
-
|
23
|
-
relation
|
24
|
+
apply_order(builder, relation)
|
24
25
|
end
|
25
26
|
|
26
27
|
def self.apply_order(builder, relation)
|
27
28
|
return relation unless builder.ensure_ordering
|
29
|
+
|
28
30
|
relation.order(builder.recursive_temp_table[builder.depth_column].asc)
|
29
31
|
end
|
30
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Model
|
3
5
|
extend ActiveSupport::Concern
|
@@ -40,7 +42,7 @@ module ActsAsRecursiveTree
|
|
40
42
|
##
|
41
43
|
# Returns the root node of the tree.
|
42
44
|
def root
|
43
|
-
self_and_ancestors.where(
|
45
|
+
self_and_ancestors.where(_recursive_tree_config.parent_key => nil).first
|
44
46
|
end
|
45
47
|
|
46
48
|
##
|
@@ -48,7 +50,7 @@ module ActsAsRecursiveTree
|
|
48
50
|
#
|
49
51
|
# subchild1.siblings # => [subchild2]
|
50
52
|
def siblings
|
51
|
-
self_and_siblings.where.not(id:
|
53
|
+
self_and_siblings.where.not(id: id)
|
52
54
|
end
|
53
55
|
|
54
56
|
##
|
@@ -57,11 +59,11 @@ module ActsAsRecursiveTree
|
|
57
59
|
# root.self_and_children # => [root, child1]
|
58
60
|
def self_and_children
|
59
61
|
table = self.class.arel_table
|
60
|
-
id =
|
62
|
+
id = attributes[_recursive_tree_config.primary_key.to_s]
|
61
63
|
|
62
64
|
base_class.where(
|
63
|
-
table[
|
64
|
-
table[
|
65
|
+
table[_recursive_tree_config.primary_key].eq(id).or(
|
66
|
+
table[_recursive_tree_config.parent_key].eq(id)
|
65
67
|
)
|
66
68
|
)
|
67
69
|
end
|
@@ -73,13 +75,12 @@ module ActsAsRecursiveTree
|
|
73
75
|
base_class.leaves_of(self)
|
74
76
|
end
|
75
77
|
|
76
|
-
|
77
78
|
# Returns true if node has no parent, false otherwise
|
78
79
|
#
|
79
80
|
# subchild1.root? # => false
|
80
81
|
# root.root? # => true
|
81
82
|
def root?
|
82
|
-
|
83
|
+
attributes[_recursive_tree_config.parent_key.to_s].blank?
|
83
84
|
end
|
84
85
|
|
85
86
|
# Returns true if node has no children, false otherwise
|
@@ -87,7 +88,7 @@ module ActsAsRecursiveTree
|
|
87
88
|
# subchild1.leaf? # => true
|
88
89
|
# child1.leaf? # => false
|
89
90
|
def leaf?
|
90
|
-
|
91
|
+
children.none?
|
91
92
|
end
|
92
93
|
|
93
94
|
def base_class
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Options
|
3
5
|
class QueryOptions
|
4
|
-
|
5
|
-
STRATEGIES = %i[subselect, join].freeze
|
6
|
+
STRATEGIES = %i[subselect join].freeze
|
6
7
|
|
7
8
|
attr_accessor :condition
|
8
9
|
attr_reader :ensure_ordering, :query_strategy
|
@@ -21,6 +22,7 @@ module ActsAsRecursiveTree
|
|
21
22
|
|
22
23
|
def query_strategy=(strategy)
|
23
24
|
raise "invalid strategy #{strategy} - only #{STRATEGIES} are allowed" unless STRATEGIES.include?(strategy)
|
25
|
+
|
24
26
|
@query_strategy = strategy
|
25
27
|
end
|
26
28
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Options
|
3
5
|
module Values
|
@@ -13,13 +15,9 @@ module ActsAsRecursiveTree
|
|
13
15
|
value
|
14
16
|
end
|
15
17
|
|
16
|
-
def apply_to(attribute)
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def apply_negated_to(attribute)
|
18
|
+
def apply_to(attribute); end
|
21
19
|
|
22
|
-
end
|
20
|
+
def apply_negated_to(attribute); end
|
23
21
|
end
|
24
22
|
|
25
23
|
class SingleValue < Base
|
@@ -68,19 +66,19 @@ module ActsAsRecursiveTree
|
|
68
66
|
|
69
67
|
def self.create(value, config = nil)
|
70
68
|
klass = case value
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
69
|
+
when ::Numeric, ::String
|
70
|
+
SingleValue
|
71
|
+
when ::ActiveRecord::Relation
|
72
|
+
Relation
|
73
|
+
when Range
|
74
|
+
RangeValue
|
75
|
+
when Enumerable
|
76
|
+
MultiValue
|
77
|
+
when ::ActiveRecord::Base
|
78
|
+
ActiveRecord
|
79
|
+
else
|
80
|
+
raise "#{value.class} is not supported"
|
81
|
+
end
|
84
82
|
|
85
83
|
klass.new(value, config)
|
86
84
|
end
|
@@ -1,13 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Scopes
|
3
5
|
extend ActiveSupport::Concern
|
4
6
|
|
5
7
|
included do
|
6
|
-
scope :roots,
|
8
|
+
scope :roots, lambda {
|
7
9
|
rel = where(_recursive_tree_config.parent_key => nil)
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
if _recursive_tree_config.parent_type_column
|
11
|
+
rel = rel.or(
|
12
|
+
where.not(_recursive_tree_config.parent_type_column => to_s)
|
13
|
+
)
|
14
|
+
end
|
11
15
|
|
12
16
|
rel
|
13
17
|
}
|
data/spec/builders_spec.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
shared_context 'setup with enforced ordering' do
|
4
6
|
let(:ordering) { false }
|
5
7
|
include_context 'base_setup' do
|
6
|
-
let(:proc) { ->
|
8
|
+
let(:proc) { ->(config) { config.ensure_ordering! } }
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
12
|
shared_context 'base_setup' do
|
13
|
+
subject(:query) { builder.build.to_sql }
|
14
|
+
|
11
15
|
let(:model_id) { 1 }
|
12
16
|
let(:model_class) { Node }
|
13
17
|
let(:exclude_ids) { false }
|
@@ -15,19 +19,20 @@ shared_context 'base_setup' do
|
|
15
19
|
let(:builder) do
|
16
20
|
described_class.new(model_class, model_id, exclude_ids: exclude_ids, &proc)
|
17
21
|
end
|
18
|
-
subject(:query) { builder.build.to_sql }
|
19
22
|
end
|
20
23
|
|
21
24
|
shared_examples 'basic recursive examples' do
|
22
25
|
it { is_expected.to start_with "SELECT \"#{model_class.table_name}\".* FROM \"#{model_class.table_name}\"" }
|
23
26
|
|
24
|
-
it { is_expected.to match
|
27
|
+
it { is_expected.to match(/WHERE "#{model_class.table_name}"."#{model_class.primary_key}" = #{model_id}/) }
|
25
28
|
|
26
|
-
it { is_expected.to match
|
29
|
+
it { is_expected.to match(/WITH RECURSIVE "#{builder.travers_loc_table.name}" AS/) }
|
27
30
|
|
28
|
-
it { is_expected.to match
|
31
|
+
it { is_expected.to match(/SELECT "#{model_class.table_name}"."#{model_class.primary_key}", "#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}", 0 AS recursive_depth FROM "#{model_class.table_name}"/) }
|
29
32
|
|
30
|
-
it {
|
33
|
+
it {
|
34
|
+
expect(subject).to match(/SELECT "#{model_class.table_name}"."#{model_class.primary_key}", "#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}", \("#{builder.travers_loc_table.name}"."recursive_depth" \+ 1\) AS recursive_depth FROM "#{model_class.table_name}"/)
|
35
|
+
}
|
31
36
|
end
|
32
37
|
|
33
38
|
shared_examples 'build recursive query' do
|
@@ -65,14 +70,14 @@ end
|
|
65
70
|
shared_examples 'ancestor query' do
|
66
71
|
include_context 'base_setup'
|
67
72
|
|
68
|
-
it { is_expected.to match
|
73
|
+
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}"/) }
|
69
74
|
end
|
70
75
|
|
71
76
|
shared_examples 'descendant query' do
|
72
77
|
include_context 'base_setup'
|
73
78
|
|
74
|
-
it { is_expected.to match
|
75
|
-
it { is_expected.to match
|
79
|
+
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
|
+
it { is_expected.to match(/#{Regexp.escape(builder.travers_loc_table.project(builder.travers_loc_table[model_class.primary_key]).to_sql)}/) }
|
76
81
|
end
|
77
82
|
|
78
83
|
shared_context 'context with ordering' do
|
@@ -88,11 +93,11 @@ shared_context 'context without ordering' do
|
|
88
93
|
end
|
89
94
|
|
90
95
|
shared_examples 'with ordering' do
|
91
|
-
it { is_expected.to match
|
96
|
+
it { is_expected.to match(/ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/) }
|
92
97
|
end
|
93
98
|
|
94
99
|
shared_examples 'without ordering' do
|
95
|
-
it { is_expected.
|
100
|
+
it { is_expected.not_to match(/ORDER BY/) }
|
96
101
|
end
|
97
102
|
|
98
103
|
describe ActsAsRecursiveTree::Builders::Descendants do
|
@@ -116,6 +121,7 @@ describe ActsAsRecursiveTree::Builders::Ancestors do
|
|
116
121
|
it_behaves_like 'ancestor query'
|
117
122
|
include_context 'context with ordering'
|
118
123
|
end
|
124
|
+
|
119
125
|
context 'with options' do
|
120
126
|
include_context 'setup with enforced ordering' do
|
121
127
|
it_behaves_like 'with ordering'
|
data/spec/db/database.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
database_folder = "#{File.dirname(__FILE__)}/../db"
|
2
4
|
database_adapter = 'sqlite'
|
3
5
|
|
@@ -6,14 +8,14 @@ ActiveRecord::Base.logger = nil
|
|
6
8
|
|
7
9
|
ActiveRecord::Migration.verbose = false
|
8
10
|
|
9
|
-
ActiveRecord::Base.configurations = YAML
|
11
|
+
ActiveRecord::Base.configurations = YAML.safe_load(File.read("#{database_folder}/database.yml"))
|
10
12
|
|
11
13
|
if ActiveRecord.version >= Gem::Version.new('6.1.0')
|
12
|
-
config = ActiveRecord::Base.configurations.configs_for env_name: database_adapter, name:
|
14
|
+
config = ActiveRecord::Base.configurations.configs_for env_name: database_adapter, name: 'primary'
|
13
15
|
database = config.database
|
14
16
|
else
|
15
17
|
config = ActiveRecord::Base.configurations[database_adapter]
|
16
|
-
database = config[
|
18
|
+
database = config['database']
|
17
19
|
end
|
18
20
|
|
19
21
|
# remove database if present
|
@@ -25,4 +27,3 @@ ActiveRecord::Base.establish_connection(config)
|
|
25
27
|
# require schemata and models
|
26
28
|
require_relative 'schema'
|
27
29
|
require_relative 'models'
|
28
|
-
|
data/spec/db/database.yml
CHANGED
data/spec/db/models.rb
CHANGED
@@ -1,37 +1,38 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Base test class
|
4
|
+
class ApplicationRecord < ::ActiveRecord::Base
|
5
|
+
self.abstract_class = true
|
6
|
+
|
2
7
|
extend ActsAsRecursiveTree::ActsMacro
|
3
8
|
end
|
4
9
|
|
5
|
-
class Node <
|
10
|
+
class Node < ApplicationRecord
|
6
11
|
acts_as_tree
|
7
12
|
has_one :node_info
|
8
13
|
end
|
9
14
|
|
10
|
-
class NodeInfo <
|
15
|
+
class NodeInfo < ApplicationRecord
|
11
16
|
belongs_to :node
|
12
17
|
end
|
13
18
|
|
14
|
-
class NodeWithPolymorphicParent <
|
19
|
+
class NodeWithPolymorphicParent < ApplicationRecord
|
15
20
|
acts_as_tree parent_key: :other_id, parent_type_column: :other_type
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
class NodeWithOtherParentKey < ActiveRecord::Base
|
23
|
+
class NodeWithOtherParentKey < ApplicationRecord
|
20
24
|
acts_as_tree parent_key: :other_id
|
21
25
|
end
|
22
26
|
|
23
|
-
class Location <
|
27
|
+
class Location < ApplicationRecord
|
24
28
|
acts_as_tree
|
25
29
|
end
|
26
30
|
|
27
31
|
class Building < Location
|
28
|
-
|
29
32
|
end
|
30
33
|
|
31
34
|
class Floor < Location
|
32
|
-
|
33
35
|
end
|
34
36
|
|
35
37
|
class Room < Location
|
36
|
-
|
37
|
-
end
|
38
|
+
end
|
data/spec/db/schema.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
ActiveRecord::Schema.define(:version => 0) do
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
3
|
+
ActiveRecord::Schema.define(version: 0) do
|
5
4
|
create_table :nodes do |t|
|
6
5
|
t.integer :parent_id
|
7
6
|
t.string :name
|
@@ -31,4 +30,4 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
31
30
|
end
|
32
31
|
|
33
32
|
add_foreign_key(:locations, :locations, column: :parent_id)
|
34
|
-
end
|
33
|
+
end
|