acts_as_recursive_tree 2.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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +321 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +175 -0
- data/Rakefile +15 -0
- data/acts_as_recursive_tree.gemspec +30 -0
- data/lib/acts_as_recursive_tree/acts_macro.rb +28 -0
- data/lib/acts_as_recursive_tree/associations.rb +25 -0
- data/lib/acts_as_recursive_tree/builders/ancestors.rb +17 -0
- data/lib/acts_as_recursive_tree/builders/descendants.rb +9 -0
- data/lib/acts_as_recursive_tree/builders/leaves.rb +26 -0
- data/lib/acts_as_recursive_tree/builders/relation_builder.rb +121 -0
- data/lib/acts_as_recursive_tree/builders.rb +13 -0
- data/lib/acts_as_recursive_tree/model.rb +125 -0
- data/lib/acts_as_recursive_tree/options/depth_condition.rb +48 -0
- data/lib/acts_as_recursive_tree/options/query_options.rb +21 -0
- data/lib/acts_as_recursive_tree/options/values.rb +77 -0
- data/lib/acts_as_recursive_tree/options.rb +9 -0
- data/lib/acts_as_recursive_tree/railtie.rb +9 -0
- data/lib/acts_as_recursive_tree/scopes.rb +16 -0
- data/lib/acts_as_recursive_tree/version.rb +3 -0
- data/lib/acts_as_recursive_tree.rb +14 -0
- data/spec/builders_spec.rb +136 -0
- data/spec/db/database.rb +22 -0
- data/spec/db/database.yml +12 -0
- data/spec/db/models.rb +37 -0
- data/spec/db/schema.rb +34 -0
- data/spec/model/location_spec.rb +55 -0
- data/spec/model/node_spec.rb +129 -0
- data/spec/model/relation_spec.rb +63 -0
- data/spec/spec_helper.rb +119 -0
- data/spec/values_spec.rb +86 -0
- metadata +182 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Builders
|
3
|
+
class Ancestors < RelationBuilder
|
4
|
+
|
5
|
+
def build_join_condition
|
6
|
+
travers_loc_table[config.parent_key].eq(base_table[config.primary_key])
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_query_options(_)
|
10
|
+
opts = super
|
11
|
+
opts.ensure_ordering!
|
12
|
+
opts
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Builders
|
3
|
+
class Leaves < Descendants
|
4
|
+
|
5
|
+
def create_select_manger
|
6
|
+
select_manager = super
|
7
|
+
|
8
|
+
select_manager.where(
|
9
|
+
travers_loc_table[config.primary_key].not_in(
|
10
|
+
travers_loc_table.where(
|
11
|
+
travers_loc_table[config.parent_key].not_eq(nil)
|
12
|
+
).project(travers_loc_table[config.parent_key])
|
13
|
+
)
|
14
|
+
)
|
15
|
+
select_manager
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_query_options(_)
|
20
|
+
# do not allow any custom options
|
21
|
+
ActsAsRecursiveTree::Options::QueryOptions.new
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Builders
|
3
|
+
class RelationBuilder
|
4
|
+
|
5
|
+
def self.build(klass, ids, exclude_ids: false, &block)
|
6
|
+
new(klass, ids, exclude_ids: exclude_ids, &block).build
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :klass, :ids, :recursive_temp_table, :travers_loc_table
|
10
|
+
attr_reader :query_opts, :without_ids
|
11
|
+
mattr_reader(:random) { Random.new }
|
12
|
+
|
13
|
+
def initialize(klass, ids, exclude_ids: false, &block)
|
14
|
+
@klass = klass
|
15
|
+
@ids = ActsAsRecursiveTree::Options::Values.create(ids, config)
|
16
|
+
@without_ids = exclude_ids
|
17
|
+
|
18
|
+
@query_opts = get_query_options(block)
|
19
|
+
|
20
|
+
rand_int = random.rand(1_000_000)
|
21
|
+
@recursive_temp_table = Arel::Table.new("recursive_#{klass.table_name}_#{rand_int}_temp")
|
22
|
+
@travers_loc_table = Arel::Table.new("traverse_#{rand_int}_loc")
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_query_options(proc)
|
26
|
+
opts = ActsAsRecursiveTree::Options::QueryOptions.new
|
27
|
+
|
28
|
+
proc.call(opts) if proc
|
29
|
+
|
30
|
+
opts
|
31
|
+
end
|
32
|
+
|
33
|
+
def base_table
|
34
|
+
klass.arel_table
|
35
|
+
end
|
36
|
+
|
37
|
+
def config
|
38
|
+
klass._recursive_tree_config
|
39
|
+
end
|
40
|
+
|
41
|
+
def build
|
42
|
+
final_select_mgr = base_table.join(
|
43
|
+
create_select_manger.as(recursive_temp_table.name)
|
44
|
+
).on(
|
45
|
+
base_table[config.primary_key].eq(recursive_temp_table[config.primary_key])
|
46
|
+
)
|
47
|
+
|
48
|
+
relation = klass.joins(final_select_mgr.join_sources)
|
49
|
+
|
50
|
+
relation = apply_except_id(relation)
|
51
|
+
relation = apply_depth(relation)
|
52
|
+
relation = apply_order(relation)
|
53
|
+
|
54
|
+
relation
|
55
|
+
end
|
56
|
+
|
57
|
+
def apply_except_id(relation)
|
58
|
+
return relation unless without_ids
|
59
|
+
relation.where(ids.apply_negated_to(base_table[config.primary_key]))
|
60
|
+
end
|
61
|
+
|
62
|
+
def apply_depth(relation)
|
63
|
+
return relation unless query_opts.depth_present?
|
64
|
+
|
65
|
+
relation.where(query_opts.depth.apply_to(recursive_temp_table[config.depth_column]))
|
66
|
+
end
|
67
|
+
|
68
|
+
def apply_order(relation)
|
69
|
+
return relation unless query_opts.ensure_ordering
|
70
|
+
relation.order(recursive_temp_table[config.depth_column].asc)
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_select_manger
|
74
|
+
travers_loc_table.project(Arel.star).with(:recursive, build_cte_table)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_cte_table
|
78
|
+
Arel::Nodes::As.new(
|
79
|
+
travers_loc_table,
|
80
|
+
build_base_select.union(build_union_select)
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_base_select
|
85
|
+
id_node = base_table[config.primary_key]
|
86
|
+
|
87
|
+
base_table.where(
|
88
|
+
ids.apply_to(id_node)
|
89
|
+
).project(
|
90
|
+
id_node,
|
91
|
+
base_table[config.parent_key],
|
92
|
+
Arel.sql('0').as(config.depth_column.to_s)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_union_select
|
97
|
+
select_manager = base_table.join(travers_loc_table).on(
|
98
|
+
build_join_condition
|
99
|
+
)
|
100
|
+
|
101
|
+
# need to use ActiveRecord here for merging relation
|
102
|
+
relation = klass.select(
|
103
|
+
base_table[config.primary_key],
|
104
|
+
base_table[config.parent_key],
|
105
|
+
Arel.sql(
|
106
|
+
(travers_loc_table[config.depth_column] + 1).to_sql
|
107
|
+
).as(config.depth_column.to_s)
|
108
|
+
).unscope(where: :type).joins(select_manager.join_sources)
|
109
|
+
|
110
|
+
relation = relation.merge(query_opts.condition) unless query_opts.condition.nil?
|
111
|
+
relation = relation.where(config.parent_type_column => klass.to_s) if config.parent_type_column
|
112
|
+
relation.arel
|
113
|
+
end
|
114
|
+
|
115
|
+
def build_join_condition
|
116
|
+
raise 'not implemented'
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Builders
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Values
|
6
|
+
autoload :DepthCondition
|
7
|
+
autoload :QueryOptions
|
8
|
+
autoload :RelationBuilder
|
9
|
+
autoload :Descendants
|
10
|
+
autoload :Ancestors
|
11
|
+
autoload :Leaves
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
##
|
6
|
+
# Returns list of ancestors, starting from parent until root.
|
7
|
+
#
|
8
|
+
# subchild1.ancestors # => [child1, root]
|
9
|
+
#
|
10
|
+
def ancestors(&block)
|
11
|
+
base_class.ancestors_of(self, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns ancestors and current node itself.
|
15
|
+
#
|
16
|
+
# subchild1.self_and_ancestors # => [subchild1, child1, root]
|
17
|
+
#
|
18
|
+
def self_and_ancestors(&block)
|
19
|
+
base_class.self_and_ancestors_of(self, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns list of descendants, starting from current node, not including current node.
|
24
|
+
#
|
25
|
+
# root.descendants # => [child1, child2, subchild1, subchild2, subchild3, subchild4]
|
26
|
+
#
|
27
|
+
def descendants(&block)
|
28
|
+
base_class.descendants_of(self, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns list of descendants, starting from current node, including current node.
|
33
|
+
#
|
34
|
+
# root.self_and_descendants # => [root, child1, child2, subchild1, subchild2, subchild3, subchild4]
|
35
|
+
#
|
36
|
+
def self_and_descendants(&block)
|
37
|
+
base_class.self_and_descendants_of(self, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Returns the root node of the tree.
|
42
|
+
def root
|
43
|
+
self_and_ancestors.where(self._recursive_tree_config.parent_key => nil).first
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Returns all siblings of the current node.
|
48
|
+
#
|
49
|
+
# subchild1.siblings # => [subchild2]
|
50
|
+
def siblings
|
51
|
+
self_and_siblings.where.not(id: self.id)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Returns children (without subchildren) and current node itself.
|
56
|
+
#
|
57
|
+
# root.self_and_children # => [root, child1]
|
58
|
+
def self_and_children
|
59
|
+
table = self.class.arel_table
|
60
|
+
id = self.attributes[self._recursive_tree_config.primary_key.to_s]
|
61
|
+
|
62
|
+
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
|
+
)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Returns all Leaves
|
71
|
+
#
|
72
|
+
def leaves
|
73
|
+
base_class.leaves_of(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Returns true if node has no parent, false otherwise
|
78
|
+
#
|
79
|
+
# subchild1.root? # => false
|
80
|
+
# root.root? # => true
|
81
|
+
def root?
|
82
|
+
self.attributes[self._recursive_tree_config.parent_key.to_s].blank?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if node has no children, false otherwise
|
86
|
+
#
|
87
|
+
# subchild1.leaf? # => true
|
88
|
+
# child1.leaf? # => false
|
89
|
+
def leaf?
|
90
|
+
!children.any?
|
91
|
+
end
|
92
|
+
|
93
|
+
def base_class
|
94
|
+
self.class.base_class
|
95
|
+
end
|
96
|
+
|
97
|
+
private :base_class
|
98
|
+
|
99
|
+
module ClassMethods
|
100
|
+
def self_and_ancestors_of(ids, &block)
|
101
|
+
Builders::Ancestors.build(self, ids, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def ancestors_of(ids, &block)
|
105
|
+
Builders::Ancestors.build(self, ids, exclude_ids: true, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
def roots_of(ids)
|
109
|
+
self_and_ancestors_of(ids).roots
|
110
|
+
end
|
111
|
+
|
112
|
+
def self_and_descendants_of(ids, &block)
|
113
|
+
Builders::Descendants.build(self, ids, &block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def descendants_of(ids, &block)
|
117
|
+
Builders::Descendants.build(self, ids, exclude_ids: true, &block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def leaves_of(ids, &block)
|
121
|
+
Builders::Leaves.build(self, ids, &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Options
|
3
|
+
class DepthCondition
|
4
|
+
|
5
|
+
def ==(other)
|
6
|
+
@value = Values.create(other)
|
7
|
+
@operation = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def !=(other)
|
11
|
+
@value = Values.create(other)
|
12
|
+
@operation = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def <(other)
|
16
|
+
@value = other
|
17
|
+
@operation = :lt
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=(other)
|
21
|
+
@value = other
|
22
|
+
@operation = :lteq
|
23
|
+
end
|
24
|
+
|
25
|
+
def >(other)
|
26
|
+
@value = other
|
27
|
+
@operation = :gt
|
28
|
+
end
|
29
|
+
|
30
|
+
def >=(other)
|
31
|
+
@value = other
|
32
|
+
@operation = :gteq
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply_to(attribute)
|
36
|
+
if @value.is_a?(Values::Base)
|
37
|
+
if @operation
|
38
|
+
@value.apply_to(attribute)
|
39
|
+
else
|
40
|
+
@value.apply_negated_to(attribute)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
attribute.send(@operation, @value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Options
|
3
|
+
class QueryOptions
|
4
|
+
|
5
|
+
attr_accessor :condition
|
6
|
+
attr_reader :ensure_ordering
|
7
|
+
|
8
|
+
def depth
|
9
|
+
@depth ||= DepthCondition.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def ensure_ordering!
|
13
|
+
@ensure_ordering = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def depth_present?
|
17
|
+
@depth.present?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Options
|
3
|
+
module Values
|
4
|
+
class Base
|
5
|
+
attr_reader :value, :config
|
6
|
+
|
7
|
+
def initialize(value, config)
|
8
|
+
@value = value
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def prepared_value
|
13
|
+
value
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply_to(attribute)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def apply_negated_to(attribute)
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SingleValue < Base
|
26
|
+
def apply_to(attribute)
|
27
|
+
attribute.eq(prepared_value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def apply_negated_to(attribute)
|
31
|
+
attribute.not_eq(prepared_value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ActiveRecord < SingleValue
|
36
|
+
def prepared_value
|
37
|
+
value.id
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class MultiValue < Base
|
42
|
+
def apply_to(attribute)
|
43
|
+
attribute.in(prepared_value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply_negated_to(attribute)
|
47
|
+
attribute.not_in(prepared_value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Relation < MultiValue
|
52
|
+
def prepared_value
|
53
|
+
select_manager = value.arel
|
54
|
+
select_manager.projections.clear
|
55
|
+
select_manager.project(select_manager.froms.last[config.primary_key])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.create(value, config = nil)
|
60
|
+
klass = case value
|
61
|
+
when ::Numeric, ::String
|
62
|
+
SingleValue
|
63
|
+
when ::ActiveRecord::Relation
|
64
|
+
Relation
|
65
|
+
when Enumerable
|
66
|
+
MultiValue
|
67
|
+
when ::ActiveRecord::Base
|
68
|
+
ActiveRecord
|
69
|
+
else
|
70
|
+
raise "#{value.class} is not supported"
|
71
|
+
end
|
72
|
+
|
73
|
+
klass.new(value, config)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActsAsRecursiveTree
|
2
|
+
module Scopes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
scope :roots, -> {
|
7
|
+
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
|
11
|
+
|
12
|
+
rel
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require_relative 'acts_as_recursive_tree/railtie' if defined?(Rails)
|
3
|
+
|
4
|
+
module ActsAsRecursiveTree
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :ActsMacro
|
8
|
+
autoload :Model
|
9
|
+
autoload :Associations
|
10
|
+
autoload :Scopes
|
11
|
+
autoload :Version
|
12
|
+
autoload :Options
|
13
|
+
autoload :Builders
|
14
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_context 'setup with enforced ordering' do
|
4
|
+
let(:ordering) { false }
|
5
|
+
include_context 'base_setup' do
|
6
|
+
let(:proc) { -> (config) { config.ensure_ordering! } }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_context 'base_setup' do
|
11
|
+
let(:model_id) { 1 }
|
12
|
+
let(:model_class) { Node }
|
13
|
+
let(:exclude_ids) { false }
|
14
|
+
let(:proc) { nil }
|
15
|
+
let(:builder) do
|
16
|
+
described_class.new(model_class, model_id, exclude_ids: exclude_ids, &proc)
|
17
|
+
end
|
18
|
+
subject(:query) { builder.build.to_sql }
|
19
|
+
end
|
20
|
+
|
21
|
+
shared_examples 'basic recursive examples' do
|
22
|
+
it { is_expected.to start_with "SELECT \"#{model_class.table_name}\".* FROM \"#{model_class.table_name}\"" }
|
23
|
+
it { is_expected.to match /WHERE "#{model_class.table_name}"."#{model_class.primary_key}" = #{model_id}/ }
|
24
|
+
it { is_expected.to match /WITH RECURSIVE "#{builder.travers_loc_table.name}" AS/ }
|
25
|
+
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}"/ }
|
26
|
+
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}"/ }
|
27
|
+
it { is_expected.to match /#{Regexp.escape(builder.travers_loc_table.project(Arel.star).to_sql)}/ }
|
28
|
+
it { is_expected.to match /"#{model_class.table_name}"."#{model_class.primary_key}" = "#{builder.recursive_temp_table.name}"."#{model_class.primary_key}"/ }
|
29
|
+
end
|
30
|
+
|
31
|
+
shared_examples 'build recursive query' do
|
32
|
+
context 'simple id' do
|
33
|
+
context 'with simple class' do
|
34
|
+
include_context 'base_setup' do
|
35
|
+
let(:model_class) { Node }
|
36
|
+
it_behaves_like 'basic recursive examples'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with class with different parent key' do
|
41
|
+
include_context 'base_setup' do
|
42
|
+
let(:model_class) { NodeWithOtherParentKey }
|
43
|
+
it_behaves_like 'basic recursive examples'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with Subclass' do
|
48
|
+
include_context 'base_setup' do
|
49
|
+
let(:model_class) { Floor }
|
50
|
+
it_behaves_like 'basic recursive examples'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with polymorphic parent relation' do
|
55
|
+
include_context 'base_setup' do
|
56
|
+
let(:model_class) { NodeWithPolymorphicParent }
|
57
|
+
it_behaves_like 'basic recursive examples'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
shared_examples 'ancestor query' do
|
64
|
+
include_context 'base_setup'
|
65
|
+
|
66
|
+
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}"/ }
|
67
|
+
end
|
68
|
+
|
69
|
+
shared_examples 'descendant query' do
|
70
|
+
include_context 'base_setup'
|
71
|
+
|
72
|
+
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}"/ }
|
73
|
+
end
|
74
|
+
|
75
|
+
shared_context 'context with ordering' do
|
76
|
+
include_context 'base_setup' do
|
77
|
+
it_behaves_like 'with ordering'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
shared_context 'context without ordering' do
|
82
|
+
include_context 'base_setup' do
|
83
|
+
it_behaves_like 'without ordering'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
shared_examples 'with ordering' do
|
88
|
+
it { is_expected.to match /ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/ }
|
89
|
+
end
|
90
|
+
|
91
|
+
shared_examples 'without ordering' do
|
92
|
+
it { is_expected.to_not match /ORDER BY/ }
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ActsAsRecursiveTree::Builders::Descendants do
|
96
|
+
context 'basic' do
|
97
|
+
it_behaves_like 'build recursive query'
|
98
|
+
it_behaves_like 'descendant query'
|
99
|
+
include_context 'context without ordering'
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with options' do
|
103
|
+
include_context 'setup with enforced ordering' do
|
104
|
+
let(:ordering) { true }
|
105
|
+
it_behaves_like 'with ordering'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe ActsAsRecursiveTree::Builders::Ancestors do
|
111
|
+
context 'basic' do
|
112
|
+
it_behaves_like 'build recursive query'
|
113
|
+
it_behaves_like 'ancestor query'
|
114
|
+
include_context 'context with ordering'
|
115
|
+
end
|
116
|
+
context 'with options' do
|
117
|
+
include_context 'setup with enforced ordering' do
|
118
|
+
it_behaves_like 'with ordering'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe ActsAsRecursiveTree::Builders::Leaves do
|
124
|
+
context 'basic' do
|
125
|
+
it_behaves_like 'build recursive query'
|
126
|
+
it_behaves_like 'descendant query'
|
127
|
+
include_context 'context without ordering'
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with options' do
|
131
|
+
include_context 'setup with enforced ordering' do
|
132
|
+
let(:ordering) { true }
|
133
|
+
it_behaves_like 'without ordering'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/spec/db/database.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
database_folder = "#{File.dirname(__FILE__)}/../db"
|
2
|
+
database_adapter = 'sqlite'
|
3
|
+
|
4
|
+
# Logger setup
|
5
|
+
ActiveRecord::Base.logger = nil
|
6
|
+
|
7
|
+
ActiveRecord::Migration.verbose = false
|
8
|
+
|
9
|
+
ActiveRecord::Base.configurations = YAML::load(File.read("#{database_folder}/database.yml"))
|
10
|
+
|
11
|
+
config = ActiveRecord::Base.configurations[database_adapter]
|
12
|
+
|
13
|
+
# remove database if present
|
14
|
+
FileUtils.rm config['database'], force: true
|
15
|
+
|
16
|
+
ActiveRecord::Base.establish_connection(database_adapter.to_sym)
|
17
|
+
ActiveRecord::Base.establish_connection(config)
|
18
|
+
|
19
|
+
# require schemata and models
|
20
|
+
require_relative 'schema'
|
21
|
+
require_relative 'models'
|
22
|
+
|