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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +37 -0
  3. data/.github/workflows/lint.yml +31 -0
  4. data/.github/workflows/rubygem.yml +37 -0
  5. data/.gitignore +2 -1
  6. data/.rubocop.yml +30 -1
  7. data/.rubocop_todo.yml +28 -281
  8. data/Appraisals +10 -20
  9. data/CHANGELOG.md +10 -1
  10. data/Gemfile +2 -0
  11. data/README.md +3 -0
  12. data/Rakefile +8 -8
  13. data/acts_as_recursive_tree.gemspec +27 -18
  14. data/gemfiles/ar_52.gemfile +8 -0
  15. data/gemfiles/ar_60.gemfile +8 -0
  16. data/gemfiles/ar_61.gemfile +8 -0
  17. data/lib/acts_as_recursive_tree.rb +7 -11
  18. data/lib/acts_as_recursive_tree/acts_macro.rb +6 -6
  19. data/lib/acts_as_recursive_tree/associations.rb +10 -8
  20. data/lib/acts_as_recursive_tree/builders/ancestors.rb +3 -2
  21. data/lib/acts_as_recursive_tree/builders/descendants.rb +3 -1
  22. data/lib/acts_as_recursive_tree/builders/leaves.rb +7 -8
  23. data/lib/acts_as_recursive_tree/builders/relation_builder.rb +14 -10
  24. data/lib/acts_as_recursive_tree/builders/{strategy.rb → strategies.rb} +3 -9
  25. data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/ancestor.rb +3 -1
  26. data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/descendant.rb +3 -1
  27. data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/join.rb +5 -3
  28. data/lib/acts_as_recursive_tree/builders/{strategy → strategies}/subselect.rb +3 -1
  29. data/lib/acts_as_recursive_tree/config.rb +2 -0
  30. data/lib/acts_as_recursive_tree/model.rb +9 -8
  31. data/lib/acts_as_recursive_tree/options/depth_condition.rb +3 -2
  32. data/lib/acts_as_recursive_tree/options/query_options.rb +4 -2
  33. data/lib/acts_as_recursive_tree/options/values.rb +17 -19
  34. data/lib/acts_as_recursive_tree/railtie.rb +2 -0
  35. data/lib/acts_as_recursive_tree/scopes.rb +8 -4
  36. data/lib/acts_as_recursive_tree/version.rb +3 -1
  37. data/spec/builders_spec.rb +17 -11
  38. data/spec/db/database.rb +5 -4
  39. data/spec/db/database.yml +2 -5
  40. data/spec/db/models.rb +12 -11
  41. data/spec/db/schema.rb +3 -4
  42. data/spec/model/location_spec.rb +7 -11
  43. data/spec/model/node_spec.rb +35 -49
  44. data/spec/model/relation_spec.rb +6 -11
  45. data/spec/spec_helper.rb +54 -55
  46. data/spec/values_spec.rb +21 -17
  47. metadata +115 -33
  48. data/lib/acts_as_recursive_tree/builders.rb +0 -14
  49. 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 Strategy
5
+ module Strategies
4
6
  #
5
7
  # Strategy for building ancestors relation
6
8
  #
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  module Builders
3
- module Strategy
5
+ module Strategies
4
6
  #
5
7
  # Strategy for building descendants relation
6
8
  #
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  module Builders
3
- module Strategy
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
- relation = apply_order(builder, relation)
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,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  module Builders
3
- module Strategy
5
+ module Strategies
4
6
  #
5
7
  # Strategy for building a relation using an WHERE ID IN(...).
6
8
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  #
3
5
  # Stores the configuration of one Model class
@@ -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(self._recursive_tree_config.parent_key => nil).first
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: self.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 = self.attributes[self._recursive_tree_config.primary_key.to_s]
62
+ id = attributes[_recursive_tree_config.primary_key.to_s]
61
63
 
62
64
  base_class.where(
63
- table[self._recursive_tree_config.primary_key].eq(id).or(
64
- table[self._recursive_tree_config.parent_key].eq(id)
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
- self.attributes[self._recursive_tree_config.parent_key.to_s].blank?
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
- !children.any?
91
+ children.none?
91
92
  end
92
93
 
93
94
  def base_class
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  module Options
3
5
  class DepthCondition
4
-
5
6
  def ==(other)
6
7
  @value = Values.create(other)
7
8
  @operation = true
@@ -45,4 +46,4 @@ module ActsAsRecursiveTree
45
46
  end
46
47
  end
47
48
  end
48
- end
49
+ end
@@ -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
- when ::Numeric, ::String
72
- SingleValue
73
- when ::ActiveRecord::Relation
74
- Relation
75
- when Range
76
- RangeValue
77
- when Enumerable
78
- MultiValue
79
- when ::ActiveRecord::Base
80
- ActiveRecord
81
- else
82
- raise "#{value.class} is not supported"
83
- end
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
4
  class Railtie < Rails::Railtie
3
5
  initializer 'acts_as_recursive_tree.active_record_initializer' do
@@ -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
- rel = rel.or(
9
- where.not(_recursive_tree_config.parent_type_column => self.to_s)
10
- ) if _recursive_tree_config.parent_type_column
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
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsRecursiveTree
2
- VERSION = '2.2.1'.freeze
4
+ VERSION = '3.0.0'
3
5
  end
@@ -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) { -> (config) { config.ensure_ordering! } }
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 /WHERE "#{model_class.table_name}"."#{model_class.primary_key}" = #{model_id}/ }
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 /WITH RECURSIVE "#{builder.travers_loc_table.name}" AS/ }
29
+ it { is_expected.to match(/WITH RECURSIVE "#{builder.travers_loc_table.name}" AS/) }
27
30
 
28
- 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}"/ }
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 { is_expected.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}"/ }
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 /"#{builder.travers_loc_table.name}"."#{model_class._recursive_tree_config.parent_key}" = "#{model_class.table_name}"."#{model_class.primary_key}"/ }
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 /"#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}" = "#{builder.travers_loc_table.name}"."#{model_class.primary_key}"/ }
75
- it { is_expected.to match /#{Regexp.escape(builder.travers_loc_table.project(builder.travers_loc_table[model_class.primary_key]).to_sql)}/ }
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 /ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/ }
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.to_not match /ORDER BY/ }
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::load(File.read("#{database_folder}/database.yml"))
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: "primary"
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["database"]
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
@@ -1,12 +1,9 @@
1
- common: &common
1
+ sqlite:
2
2
  database:
3
3
  host: localhost
4
4
  pool: 50
5
5
  timeout: 5000
6
6
  reaping_frequency: 1000
7
7
  min_messages: ERROR
8
-
9
- sqlite:
10
- <<: *common
11
8
  adapter: sqlite3
12
- database: test.sqlite3
9
+ database: test.sqlite3
data/spec/db/models.rb CHANGED
@@ -1,37 +1,38 @@
1
- ActiveRecord::Base.class_exec do
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 < ActiveRecord::Base
10
+ class Node < ApplicationRecord
6
11
  acts_as_tree
7
12
  has_one :node_info
8
13
  end
9
14
 
10
- class NodeInfo < ActiveRecord::Base
15
+ class NodeInfo < ApplicationRecord
11
16
  belongs_to :node
12
17
  end
13
18
 
14
- class NodeWithPolymorphicParent < ActiveRecord::Base
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 < ActiveRecord::Base
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
- # encoding: UTF-8
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