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
data/Appraisals
CHANGED
@@ -1,26 +1,16 @@
|
|
1
|
-
|
2
|
-
ruby '~> 2'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
appraise 'ar-52' do
|
4
|
+
gem 'activerecord', '~> 5.2.0'
|
5
|
+
gem 'activesupport', '~> 5.2.0'
|
6
6
|
end
|
7
7
|
|
8
|
-
appraise
|
9
|
-
|
10
|
-
|
11
|
-
gem 'activerecord', '~> 5.1.0'
|
12
|
-
end
|
13
|
-
|
14
|
-
appraise "ar-52" do
|
15
|
-
ruby '~> 2'
|
16
|
-
|
17
|
-
gem 'activerecord', '~> 5.2.0'
|
18
|
-
end
|
19
|
-
|
20
|
-
appraise "ar-60" do
|
21
|
-
gem 'activerecord', '~> 6.0.0'
|
8
|
+
appraise 'ar-60' do
|
9
|
+
gem 'activerecord', '~> 6.0.0'
|
10
|
+
gem 'activesupport', '~> 6.0.0'
|
22
11
|
end
|
23
12
|
|
24
|
-
appraise
|
25
|
-
|
13
|
+
appraise 'ar-61' do
|
14
|
+
gem 'activerecord', '~> 6.1.0'
|
15
|
+
gem 'activesupport', '~> 6.1.0'
|
26
16
|
end
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
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
|
+
|
1
10
|
### Version 2.2.0
|
2
11
|
- Rails 6.0 support
|
3
12
|
|
@@ -32,4 +41,4 @@
|
|
32
41
|
- BUGFIX: ordering result when querying ancestors
|
33
42
|
|
34
43
|
### Version 1.0.0
|
35
|
-
-
|
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
|
|
30
|
+
spec.add_development_dependency 'appraisal', '~> 2.4'
|
25
31
|
spec.add_development_dependency 'database_cleaner', '~> 1.5'
|
26
|
-
spec.add_development_dependency 'rake'
|
27
|
-
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
|
+
|
28
38
|
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
29
|
-
spec.add_development_dependency 'appraisal', '~> 2.4'
|
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,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
class Ancestors < RelationBuilder
|
4
|
-
self.traversal_strategy = ActsAsRecursiveTree::Builders::
|
6
|
+
self.traversal_strategy = ActsAsRecursiveTree::Builders::Strategies::Ancestor
|
5
7
|
|
6
8
|
def get_query_options(_)
|
7
9
|
opts = super
|
8
10
|
opts.ensure_ordering!
|
9
11
|
opts
|
10
12
|
end
|
11
|
-
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
class Descendants < RelationBuilder
|
4
|
-
self.traversal_strategy = ActsAsRecursiveTree::Builders::
|
6
|
+
self.traversal_strategy = ActsAsRecursiveTree::Builders::Strategies::Descendant
|
5
7
|
end
|
6
8
|
end
|
7
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,10 +1,11 @@
|
|
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
|
@@ -12,6 +13,7 @@ module ActsAsRecursiveTree
|
|
12
13
|
class_attribute :traversal_strategy, instance_writer: false
|
13
14
|
|
14
15
|
attr_reader :klass, :ids, :recursive_temp_table, :travers_loc_table, :without_ids
|
16
|
+
|
15
17
|
mattr_reader(:random) { Random.new }
|
16
18
|
|
17
19
|
# Delegators for easier accessing config and query options
|
@@ -41,7 +43,7 @@ module ActsAsRecursiveTree
|
|
41
43
|
def get_query_options(proc)
|
42
44
|
opts = ActsAsRecursiveTree::Options::QueryOptions.new
|
43
45
|
|
44
|
-
proc
|
46
|
+
proc&.call(opts)
|
45
47
|
|
46
48
|
opts
|
47
49
|
end
|
@@ -51,14 +53,14 @@ module ActsAsRecursiveTree
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def build
|
54
|
-
relation =
|
56
|
+
relation = Strategies.for_query_options(@query_opts).build(self)
|
55
57
|
|
56
|
-
|
57
|
-
relation
|
58
|
+
apply_except_id(relation)
|
58
59
|
end
|
59
60
|
|
60
61
|
def apply_except_id(relation)
|
61
62
|
return relation unless without_ids
|
63
|
+
|
62
64
|
relation.where(ids.apply_negated_to(base_table[primary_key]))
|
63
65
|
end
|
64
66
|
|
@@ -70,10 +72,10 @@ module ActsAsRecursiveTree
|
|
70
72
|
|
71
73
|
def create_select_manger(column = nil)
|
72
74
|
projections = if column
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
travers_loc_table[column]
|
76
|
+
else
|
77
|
+
Arel.star
|
78
|
+
end
|
77
79
|
|
78
80
|
select_mgr = travers_loc_table.project(projections).with(:recursive, build_cte_table)
|
79
81
|
|
@@ -114,7 +116,8 @@ module ActsAsRecursiveTree
|
|
114
116
|
end
|
115
117
|
|
116
118
|
def apply_parent_type_column(arel_condition)
|
117
|
-
return arel_condition
|
119
|
+
return arel_condition if parent_type_column.blank?
|
120
|
+
|
118
121
|
arel_condition.and(base_table[parent_type_column].eq(klass.base_class))
|
119
122
|
end
|
120
123
|
|
@@ -131,6 +134,7 @@ module ActsAsRecursiveTree
|
|
131
134
|
def apply_query_opts_condition(relation)
|
132
135
|
# check with nil? and not #present?/#blank? which will execute the query
|
133
136
|
return relation if condition.nil?
|
137
|
+
|
134
138
|
relation.merge(condition)
|
135
139
|
end
|
136
140
|
end
|
@@ -1,17 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsRecursiveTree
|
2
4
|
module Builders
|
3
5
|
#
|
4
6
|
# Strategy module for different strategies of how to build the resulting query.
|
5
7
|
#
|
6
|
-
module
|
7
|
-
extend ActiveSupport::Autoload
|
8
|
-
|
9
|
-
autoload :Join
|
10
|
-
autoload :Subselect
|
11
|
-
|
12
|
-
autoload :Descendant
|
13
|
-
autoload :Ancestor
|
14
|
-
|
8
|
+
module Strategies
|
15
9
|
#
|
16
10
|
# Returns a Strategy appropriate for query_opts
|
17
11
|
#
|