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/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
|
+
[![CI Status](https://github.com/1and1/acts_as_recursive_tree/workflows/CI/badge.svg?branch=main)](https://github.com/1and1/acts_as_recursive_tree/actions?query=workflow%3ACI+branch%3Amaster)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/acts_as_recursive_tree.svg)](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
|
#
|