acts_as_recursive_tree 2.1.0 → 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 +3 -1
- data/.rubocop.yml +30 -1
- data/.rubocop_todo.yml +28 -281
- data/Appraisals +16 -0
- data/CHANGELOG.md +17 -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 -5
- data/lib/acts_as_recursive_tree/builders/descendants.rb +3 -3
- data/lib/acts_as_recursive_tree/builders/leaves.rb +7 -8
- data/lib/acts_as_recursive_tree/builders/relation_builder.rb +25 -28
- data/lib/acts_as_recursive_tree/builders/{strategy.rb → strategies.rb} +4 -7
- data/lib/acts_as_recursive_tree/builders/strategies/ancestor.rb +19 -0
- data/lib/acts_as_recursive_tree/builders/strategies/descendant.rb +19 -0
- data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/join.rb +10 -4
- 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 +10 -1
- data/lib/acts_as_recursive_tree/options/values.rb +28 -18
- 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 +21 -12
- data/spec/db/database.rb +11 -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 +23 -19
- metadata +111 -25
- data/lib/acts_as_recursive_tree/builders.rb +0 -14
- data/lib/acts_as_recursive_tree/options.rb +0 -9
data/Appraisals
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
appraise 'ar-52' do
|
4
|
+
gem 'activerecord', '~> 5.2.0'
|
5
|
+
gem 'activesupport', '~> 5.2.0'
|
6
|
+
end
|
7
|
+
|
8
|
+
appraise 'ar-60' do
|
9
|
+
gem 'activerecord', '~> 6.0.0'
|
10
|
+
gem 'activesupport', '~> 6.0.0'
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise 'ar-61' do
|
14
|
+
gem 'activerecord', '~> 6.1.0'
|
15
|
+
gem 'activesupport', '~> 6.1.0'
|
16
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
### Version 3.0.0
|
2
|
+
- BREAKING: Dropped support for Rails < 5.2
|
3
|
+
- BREAKING: Increased minimum Ruby version to 2.5
|
4
|
+
- ADD: initial support for Rails < 7
|
5
|
+
- CHANGE: Using zeitwerk for auto loading
|
6
|
+
|
7
|
+
### Version 2.2.1
|
8
|
+
- Rails 6.1 support
|
9
|
+
|
10
|
+
### Version 2.2.0
|
11
|
+
- Rails 6.0 support
|
12
|
+
|
13
|
+
### Version 2.1.1
|
14
|
+
- Enabled subselect query when using depth
|
15
|
+
- new QueryOption query_strategy for forcing a specific strategy (:join, :subselect)
|
16
|
+
|
1
17
|
### Version 2.1.0
|
2
18
|
- BUGFIX association self_and_siblings not working
|
3
19
|
- BUGFIX primary_key of model is retrieved on first usage and not on setup
|
@@ -25,4 +41,4 @@
|
|
25
41
|
- BUGFIX: ordering result when querying ancestors
|
26
42
|
|
27
43
|
### Version 1.0.0
|
28
|
-
-
|
44
|
+
- initial release using AREL
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# ActsAsRecursiveTree
|
2
2
|
|
3
|
+
[](https://github.com/1and1/acts_as_recursive_tree/actions?query=workflow%3ACI+branch%3Amaster)
|
4
|
+
[](https://badge.fury.io/rb/acts_as_recursive_tree)
|
5
|
+
|
3
6
|
Use the power of recursive SQL statements in your Rails application.
|
4
7
|
|
5
8
|
When you have tree based data in your application, you always to struggle with retrieving data. There are solutions, but the always come at a price:
|
data/Rakefile
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/gem_tasks'
|
2
|
-
|
3
|
-
require 'rspec/core/rake_task'
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
4
|
+
require 'rspec/core/rake_task'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task default: [:spec]
|
9
9
|
|
10
10
|
desc 'Deletes temporary files'
|
11
11
|
task :clean_tmp_files do
|
12
|
-
%w
|
13
|
-
File.delete(file) if File.
|
12
|
+
%w[db.log test.sqlite3].each do |file|
|
13
|
+
File.delete(file) if File.exist?(file)
|
14
14
|
end
|
15
15
|
end
|
@@ -1,30 +1,39 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'acts_as_recursive_tree/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.summary
|
12
|
-
spec.description
|
13
|
-
This is a ruby gem that provides drop in replacement for acts_as_tree but makes use of SQL recursive statement. Be sure to have a DBMS that supports recursive queries when using this gem (e.g. PostgreSQL or SQLite).
|
14
|
-
spec.homepage
|
15
|
-
spec.license
|
16
|
-
|
17
|
-
|
8
|
+
spec.name = 'acts_as_recursive_tree'
|
9
|
+
spec.version = ActsAsRecursiveTree::VERSION
|
10
|
+
spec.authors = ['Wolfgang Wedelich-John', 'Willem Mulder']
|
11
|
+
spec.email = %w[wolfgang.wedelich@ionos.com 14mRh4X0r@gmail.com]
|
12
|
+
spec.summary = 'Drop in replacement for acts_as_tree but using recursive queries'
|
13
|
+
spec.description = '
|
14
|
+
This is a ruby gem that provides drop in replacement for acts_as_tree but makes use of SQL recursive statement. Be sure to have a DBMS that supports recursive queries when using this gem (e.g. PostgreSQL or SQLite). '
|
15
|
+
spec.homepage = 'https://github.com/1and1/acts_as_recursive_tree'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
spec.metadata = {
|
18
|
+
'bug_tracker_uri' => 'https://github.com/1and1/acts_as_recursive_tree/issues',
|
19
|
+
'changelog_uri' => 'https://github.com/1and1/acts_as_recursive_tree/CHANGELOG.md'
|
20
|
+
}
|
21
|
+
spec.required_ruby_version = '>= 2.5.0'
|
18
22
|
spec.files = `git ls-files -z`.split("\x0")
|
19
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
23
|
spec.test_files = spec.files.grep(%r{^spec/})
|
21
24
|
spec.require_paths = ['lib']
|
22
25
|
|
23
|
-
spec.add_runtime_dependency 'activerecord', '>= 5.
|
26
|
+
spec.add_runtime_dependency 'activerecord', '>= 5.2.0', '< 7.0'
|
27
|
+
spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '< 7.0'
|
28
|
+
spec.add_runtime_dependency 'zeitwerk', '>= 2.4'
|
24
29
|
|
25
|
-
spec.add_development_dependency '
|
30
|
+
spec.add_development_dependency 'appraisal', '~> 2.4'
|
26
31
|
spec.add_development_dependency 'database_cleaner', '~> 1.5'
|
27
|
-
spec.add_development_dependency 'rake'
|
28
|
-
spec.add_development_dependency 'rspec-rails', '
|
32
|
+
spec.add_development_dependency 'rake'
|
33
|
+
spec.add_development_dependency 'rspec-rails', '>= 3.5'
|
34
|
+
spec.add_development_dependency 'rubocop', '>= 1.8.0'
|
35
|
+
spec.add_development_dependency 'rubocop-rails', '>= 2.9.0'
|
36
|
+
spec.add_development_dependency 'rubocop-rspec', '>= 2.1.0'
|
37
|
+
|
29
38
|
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
30
39
|
end
|
@@ -1,15 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'acts_as_recursive_tree/railtie' if defined?(Rails)
|
4
|
+
require 'zeitwerk'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
7
|
+
loader.setup
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
autoload :Model
|
10
|
-
autoload :Associations
|
11
|
-
autoload :Scopes
|
12
|
-
autoload :Version
|
13
|
-
autoload :Options
|
14
|
-
autoload :Builders
|
9
|
+
module ActsAsRecursiveTree
|
10
|
+
# nothing special here
|
15
11
|
end
|
@@ -1,17 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module ActsMacro
|
3
|
-
|
4
5
|
##
|
5
6
|
# Configuration options are:
|
6
7
|
#
|
7
8
|
# * <tt>foreign_key</tt> - specifies the column name to use for tracking
|
8
9
|
# of the tree (default: +parent_id+)
|
9
10
|
def recursive_tree(parent_key: :parent_id, parent_type_column: nil)
|
10
|
-
|
11
11
|
class_attribute :_recursive_tree_config
|
12
12
|
self._recursive_tree_config = Config.new(
|
13
|
-
model_class:
|
14
|
-
parent_key:
|
13
|
+
model_class: self,
|
14
|
+
parent_key: parent_key.to_sym,
|
15
15
|
parent_type_column: parent_type_column.try(:to_sym)
|
16
16
|
)
|
17
17
|
|
@@ -20,6 +20,6 @@ module ActsAsRecursiveTree
|
|
20
20
|
include ActsAsRecursiveTree::Scopes
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
alias acts_as_tree recursive_tree
|
24
24
|
end
|
25
|
-
end
|
25
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
2
4
|
|
3
5
|
module ActsAsRecursiveTree
|
@@ -6,20 +8,20 @@ module ActsAsRecursiveTree
|
|
6
8
|
|
7
9
|
included do
|
8
10
|
belongs_to :parent,
|
9
|
-
class_name:
|
10
|
-
foreign_key:
|
11
|
-
inverse_of:
|
12
|
-
optional:
|
11
|
+
class_name: base_class.to_s,
|
12
|
+
foreign_key: _recursive_tree_config.parent_key,
|
13
|
+
inverse_of: :children,
|
14
|
+
optional: true
|
13
15
|
|
14
16
|
has_many :children,
|
15
|
-
class_name:
|
16
|
-
foreign_key:
|
17
|
-
inverse_of:
|
17
|
+
class_name: base_class.to_s,
|
18
|
+
foreign_key: _recursive_tree_config.parent_key,
|
19
|
+
inverse_of: :parent
|
18
20
|
|
19
21
|
has_many :self_and_siblings,
|
20
22
|
through: :parent,
|
21
23
|
source: :children,
|
22
|
-
class_name:
|
24
|
+
class_name: base_class.to_s
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -1,17 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
class Ancestors < RelationBuilder
|
4
|
-
|
5
|
-
def build_join_condition
|
6
|
-
travers_loc_table[parent_key].eq(base_table[primary_key])
|
7
|
-
end
|
6
|
+
self.traversal_strategy = ActsAsRecursiveTree::Builders::Strategies::Ancestor
|
8
7
|
|
9
8
|
def get_query_options(_)
|
10
9
|
opts = super
|
11
10
|
opts.ensure_ordering!
|
12
11
|
opts
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
class Descendants < RelationBuilder
|
4
|
-
|
5
|
-
base_table[parent_key].eq(travers_loc_table[primary_key])
|
6
|
-
end
|
6
|
+
self.traversal_strategy = ActsAsRecursiveTree::Builders::Strategies::Descendant
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -1,26 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
class Leaves < Descendants
|
4
|
-
|
5
6
|
def create_select_manger(column = nil)
|
6
7
|
select_manager = super
|
7
8
|
|
8
9
|
select_manager.where(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
travers_loc_table[primary_key].not_in(
|
11
|
+
travers_loc_table.where(
|
12
|
+
travers_loc_table[parent_key].not_eq(nil)
|
13
|
+
).project(travers_loc_table[parent_key])
|
14
|
+
)
|
14
15
|
)
|
15
16
|
select_manager
|
16
|
-
|
17
17
|
end
|
18
18
|
|
19
19
|
def get_query_options(_)
|
20
20
|
# do not allow any custom options
|
21
21
|
ActsAsRecursiveTree::Options::QueryOptions.new
|
22
22
|
end
|
23
|
-
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
@@ -1,15 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
#
|
4
6
|
# Constructs the Arel necessary for recursion.
|
5
7
|
#
|
6
8
|
class RelationBuilder
|
7
|
-
|
8
9
|
def self.build(klass, ids, exclude_ids: false, &block)
|
9
10
|
new(klass, ids, exclude_ids: exclude_ids, &block).build
|
10
11
|
end
|
11
12
|
|
13
|
+
class_attribute :traversal_strategy, instance_writer: false
|
14
|
+
|
12
15
|
attr_reader :klass, :ids, :recursive_temp_table, :travers_loc_table, :without_ids
|
16
|
+
|
13
17
|
mattr_reader(:random) { Random.new }
|
14
18
|
|
15
19
|
# Delegators for easier accessing config and query options
|
@@ -39,7 +43,7 @@ module ActsAsRecursiveTree
|
|
39
43
|
def get_query_options(proc)
|
40
44
|
opts = ActsAsRecursiveTree::Options::QueryOptions.new
|
41
45
|
|
42
|
-
proc
|
46
|
+
proc&.call(opts)
|
43
47
|
|
44
48
|
opts
|
45
49
|
end
|
@@ -49,36 +53,33 @@ module ActsAsRecursiveTree
|
|
49
53
|
end
|
50
54
|
|
51
55
|
def build
|
52
|
-
relation =
|
56
|
+
relation = Strategies.for_query_options(@query_opts).build(self)
|
53
57
|
|
54
|
-
|
55
|
-
relation
|
58
|
+
apply_except_id(relation)
|
56
59
|
end
|
57
60
|
|
58
61
|
def apply_except_id(relation)
|
59
62
|
return relation unless without_ids
|
63
|
+
|
60
64
|
relation.where(ids.apply_negated_to(base_table[primary_key]))
|
61
65
|
end
|
62
66
|
|
63
|
-
def apply_depth(
|
64
|
-
return
|
65
|
-
|
66
|
-
relation.where(depth.apply_to(recursive_temp_table[depth_column]))
|
67
|
-
end
|
67
|
+
def apply_depth(select_manager)
|
68
|
+
return select_manager unless depth_present?
|
68
69
|
|
69
|
-
|
70
|
-
return relation unless ensure_ordering
|
71
|
-
relation.order(recursive_temp_table[depth_column].asc)
|
70
|
+
select_manager.where(depth.apply_to(travers_loc_table[depth_column]))
|
72
71
|
end
|
73
72
|
|
74
73
|
def create_select_manger(column = nil)
|
75
74
|
projections = if column
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
travers_loc_table[column]
|
76
|
+
else
|
77
|
+
Arel.star
|
78
|
+
end
|
79
|
+
|
80
|
+
select_mgr = travers_loc_table.project(projections).with(:recursive, build_cte_table)
|
80
81
|
|
81
|
-
|
82
|
+
apply_depth(select_mgr)
|
82
83
|
end
|
83
84
|
|
84
85
|
def build_cte_table
|
@@ -101,7 +102,9 @@ module ActsAsRecursiveTree
|
|
101
102
|
end
|
102
103
|
|
103
104
|
def build_union_select
|
104
|
-
join_condition = apply_parent_type_column(
|
105
|
+
join_condition = apply_parent_type_column(
|
106
|
+
traversal_strategy.build(self)
|
107
|
+
)
|
105
108
|
|
106
109
|
select_manager = base_table.join(travers_loc_table).on(join_condition)
|
107
110
|
|
@@ -113,7 +116,8 @@ module ActsAsRecursiveTree
|
|
113
116
|
end
|
114
117
|
|
115
118
|
def apply_parent_type_column(arel_condition)
|
116
|
-
return arel_condition
|
119
|
+
return arel_condition if parent_type_column.blank?
|
120
|
+
|
117
121
|
arel_condition.and(base_table[parent_type_column].eq(klass.base_class))
|
118
122
|
end
|
119
123
|
|
@@ -130,16 +134,9 @@ module ActsAsRecursiveTree
|
|
130
134
|
def apply_query_opts_condition(relation)
|
131
135
|
# check with nil? and not #present?/#blank? which will execute the query
|
132
136
|
return relation if condition.nil?
|
133
|
-
relation.merge(condition)
|
134
|
-
end
|
135
137
|
|
136
|
-
|
137
|
-
# U
|
138
|
-
#
|
139
|
-
def build_join_condition
|
140
|
-
raise 'not implemented'
|
138
|
+
relation.merge(condition)
|
141
139
|
end
|
142
|
-
|
143
140
|
end
|
144
141
|
end
|
145
142
|
end
|