acts_as_ordered_tree 1.3.1 → 2.0.0.beta3
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/lib/acts_as_ordered_tree.rb +22 -100
- data/lib/acts_as_ordered_tree/adapters.rb +17 -0
- data/lib/acts_as_ordered_tree/adapters/abstract.rb +23 -0
- data/lib/acts_as_ordered_tree/adapters/postgresql.rb +150 -0
- data/lib/acts_as_ordered_tree/adapters/recursive.rb +157 -0
- data/lib/acts_as_ordered_tree/compatibility.rb +22 -0
- data/lib/acts_as_ordered_tree/compatibility/active_record/association_scope.rb +9 -0
- data/lib/acts_as_ordered_tree/compatibility/active_record/default_scoped.rb +19 -0
- data/lib/acts_as_ordered_tree/compatibility/active_record/null_relation.rb +71 -0
- data/lib/acts_as_ordered_tree/compatibility/features.rb +153 -0
- data/lib/acts_as_ordered_tree/deprecate.rb +24 -0
- data/lib/acts_as_ordered_tree/hooks.rb +38 -0
- data/lib/acts_as_ordered_tree/hooks/update.rb +86 -0
- data/lib/acts_as_ordered_tree/instance_methods.rb +92 -453
- data/lib/acts_as_ordered_tree/iterators/arranger.rb +35 -0
- data/lib/acts_as_ordered_tree/iterators/level_calculator.rb +52 -0
- data/lib/acts_as_ordered_tree/iterators/orphans_pruner.rb +58 -0
- data/lib/acts_as_ordered_tree/node.rb +78 -0
- data/lib/acts_as_ordered_tree/node/attributes.rb +48 -0
- data/lib/acts_as_ordered_tree/node/movement.rb +62 -0
- data/lib/acts_as_ordered_tree/node/movements.rb +111 -0
- data/lib/acts_as_ordered_tree/node/predicates.rb +98 -0
- data/lib/acts_as_ordered_tree/node/reloading.rb +49 -0
- data/lib/acts_as_ordered_tree/node/siblings.rb +139 -0
- data/lib/acts_as_ordered_tree/node/traversals.rb +53 -0
- data/lib/acts_as_ordered_tree/persevering_transaction.rb +93 -0
- data/lib/acts_as_ordered_tree/position.rb +143 -0
- data/lib/acts_as_ordered_tree/relation/arrangeable.rb +33 -0
- data/lib/acts_as_ordered_tree/relation/iterable.rb +41 -0
- data/lib/acts_as_ordered_tree/relation/preloaded.rb +46 -11
- data/lib/acts_as_ordered_tree/transaction/base.rb +57 -0
- data/lib/acts_as_ordered_tree/transaction/callbacks.rb +67 -0
- data/lib/acts_as_ordered_tree/transaction/create.rb +68 -0
- data/lib/acts_as_ordered_tree/transaction/destroy.rb +34 -0
- data/lib/acts_as_ordered_tree/transaction/dsl.rb +214 -0
- data/lib/acts_as_ordered_tree/transaction/factory.rb +67 -0
- data/lib/acts_as_ordered_tree/transaction/move.rb +70 -0
- data/lib/acts_as_ordered_tree/transaction/passthrough.rb +12 -0
- data/lib/acts_as_ordered_tree/transaction/reorder.rb +42 -0
- data/lib/acts_as_ordered_tree/transaction/save.rb +64 -0
- data/lib/acts_as_ordered_tree/transaction/update.rb +78 -0
- data/lib/acts_as_ordered_tree/tree.rb +148 -0
- data/lib/acts_as_ordered_tree/tree/association.rb +20 -0
- data/lib/acts_as_ordered_tree/tree/callbacks.rb +57 -0
- data/lib/acts_as_ordered_tree/tree/children_association.rb +120 -0
- data/lib/acts_as_ordered_tree/tree/columns.rb +102 -0
- data/lib/acts_as_ordered_tree/tree/deprecated_columns_accessors.rb +24 -0
- data/lib/acts_as_ordered_tree/tree/parent_association.rb +31 -0
- data/lib/acts_as_ordered_tree/tree/perseverance.rb +19 -0
- data/lib/acts_as_ordered_tree/tree/scopes.rb +56 -0
- data/lib/acts_as_ordered_tree/validators.rb +1 -1
- data/lib/acts_as_ordered_tree/version.rb +1 -1
- data/spec/acts_as_ordered_tree_spec.rb +80 -909
- data/spec/adapters/postgresql_spec.rb +14 -0
- data/spec/adapters/recursive_spec.rb +12 -0
- data/spec/adapters/shared.rb +272 -0
- data/spec/callbacks_spec.rb +177 -0
- data/spec/counter_cache_spec.rb +31 -0
- data/spec/create_spec.rb +110 -0
- data/spec/destroy_spec.rb +57 -0
- data/spec/inheritance_spec.rb +176 -0
- data/spec/move_spec.rb +94 -0
- data/spec/node/movements/concurrent_movements_spec.rb +354 -0
- data/spec/node/movements/move_higher_spec.rb +46 -0
- data/spec/node/movements/move_lower_spec.rb +46 -0
- data/spec/node/movements/move_to_child_of_spec.rb +147 -0
- data/spec/node/movements/move_to_child_with_index_spec.rb +124 -0
- data/spec/node/movements/move_to_child_with_position_spec.rb +85 -0
- data/spec/node/movements/move_to_left_of_spec.rb +120 -0
- data/spec/node/movements/move_to_right_of_spec.rb +120 -0
- data/spec/node/movements/move_to_root_spec.rb +67 -0
- data/spec/node/predicates_spec.rb +211 -0
- data/spec/node/reloading_spec.rb +42 -0
- data/spec/node/siblings_spec.rb +193 -0
- data/spec/node/traversals_spec.rb +71 -0
- data/spec/persevering_transaction_spec.rb +98 -0
- data/spec/relation/arrangeable_spec.rb +88 -0
- data/spec/relation/iterable_spec.rb +104 -0
- data/spec/relation/preloaded_spec.rb +57 -0
- data/spec/reorder_spec.rb +83 -0
- data/spec/spec_helper.rb +30 -38
- data/spec/support/db/boot.rb +22 -0
- data/spec/{db → support/db}/config.travis.yml +2 -0
- data/spec/{db → support/db}/config.yml +1 -0
- data/spec/{db → support/db}/schema.rb +9 -0
- data/spec/support/factories.rb +2 -2
- data/spec/support/matchers.rb +67 -58
- data/spec/support/models.rb +6 -14
- data/spec/support/tree_factory.rb +315 -0
- data/spec/tree/children_association_spec.rb +72 -0
- data/spec/tree/columns_spec.rb +65 -0
- data/spec/tree/scopes_spec.rb +39 -0
- metadata +161 -43
- data/lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb +0 -104
- data/lib/acts_as_ordered_tree/arrangeable.rb +0 -80
- data/lib/acts_as_ordered_tree/class_methods.rb +0 -72
- data/lib/acts_as_ordered_tree/relation/base.rb +0 -26
- data/lib/acts_as_ordered_tree/tenacious_transaction.rb +0 -30
- data/spec/concurrency_support_spec.rb +0 -156
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43c7e01b6a7953300b7be948777fc396f0c3d00a
|
4
|
+
data.tar.gz: 73aa9566feb83f5edbafaa39b9a01d3438cd9934
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35b94b1c3a68e9f11ac53fc6ebe07cf88635d0955dfa3ac6af11172766c57f9be7597c0ee1a139567ff1c7c7ee6316cb9e1b6409139dab120cbfa6ac267561f1
|
7
|
+
data.tar.gz: 08ff712d44b295769c4761d9bed62cb722c6ab3c5e7b6b0cf63cf7c2e77bfb389cfd452da9dfe1ae52caabd19fcf364d2d2e0b244900f3e2564a46741e2b62e8
|
data/lib/acts_as_ordered_tree.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
-
require 'active_record'
|
2
1
|
require 'acts_as_ordered_tree/version'
|
3
|
-
require '
|
4
|
-
require 'acts_as_ordered_tree/instance_methods'
|
5
|
-
require 'acts_as_ordered_tree/validators'
|
2
|
+
require 'active_support/lazy_load_hooks'
|
6
3
|
|
7
4
|
module ActsAsOrderedTree
|
8
|
-
|
9
|
-
defined?(ProtectedAttributes)
|
5
|
+
autoload :Tree, 'acts_as_ordered_tree/tree'
|
10
6
|
|
11
|
-
#
|
12
|
-
|
7
|
+
# @!attribute [r] ordered_tree
|
8
|
+
# @return [ActsAsOrderedTree::Tree] ordered tree object
|
13
9
|
|
14
10
|
# == Usage
|
15
11
|
# class Category < ActiveRecord::Base
|
@@ -19,101 +15,27 @@ module ActsAsOrderedTree
|
|
19
15
|
# :counter_cache => :children_count
|
20
16
|
# end
|
21
17
|
def acts_as_ordered_tree(options = {})
|
22
|
-
options
|
23
|
-
|
24
|
-
:position_column => :position,
|
25
|
-
:depth_column => :depth
|
26
|
-
}.merge(options)
|
27
|
-
|
28
|
-
class_attribute :acts_as_ordered_tree_options, :instance_writer => false
|
29
|
-
self.acts_as_ordered_tree_options = options
|
30
|
-
|
31
|
-
acts_as_ordered_tree_options[:depth_column] = nil unless
|
32
|
-
columns_hash.include?(acts_as_ordered_tree_options[:depth_column].to_s)
|
33
|
-
|
34
|
-
extend Columns
|
35
|
-
include Columns
|
36
|
-
|
37
|
-
has_many_children_options = {
|
38
|
-
:class_name => "::#{base_class.name}",
|
39
|
-
:foreign_key => options[:parent_column],
|
40
|
-
:inverse_of => (:parent unless options[:polymorphic]),
|
41
|
-
:dependent => :destroy
|
42
|
-
}
|
43
|
-
|
44
|
-
[:before_add, :after_add, :before_remove, :after_remove].each do |callback|
|
45
|
-
has_many_children_options[callback] = options[callback] if options.key?(callback)
|
46
|
-
end
|
47
|
-
|
48
|
-
if PLAIN_ORDER_OPTION_SUPPORTED
|
49
|
-
has_many_children_options[:order] = options[:position_column]
|
50
|
-
|
51
|
-
if scope_column_names.any?
|
52
|
-
has_many_children_options[:conditions] = proc do
|
53
|
-
[scope_column_names.map { |c| "#{c} = ?" }.join(' AND '),
|
54
|
-
scope_column_names.map { |c| self[c] }]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
has_many :children, has_many_children_options
|
59
|
-
else
|
60
|
-
scope = ->(parent) {
|
61
|
-
relation = order(options[:position_column])
|
62
|
-
|
63
|
-
if scope_column_names.any?
|
64
|
-
relation = relation.where(
|
65
|
-
Hash[scope_column_names.map { |c| [c, parent[c]]}]
|
66
|
-
)
|
67
|
-
end
|
68
|
-
|
69
|
-
relation
|
70
|
-
}
|
71
|
-
|
72
|
-
has_many :children, scope, has_many_children_options
|
73
|
-
end
|
74
|
-
|
75
|
-
# create parent association
|
76
|
-
belongs_to :parent,
|
77
|
-
:class_name => "::#{base_class.name}",
|
78
|
-
:foreign_key => options[:parent_column],
|
79
|
-
:counter_cache => options[:counter_cache],
|
80
|
-
:inverse_of => (:children unless options[:polymorphic])
|
81
|
-
|
82
|
-
include ClassMethods
|
83
|
-
include InstanceMethods
|
84
|
-
setup_ordered_tree_adapter
|
85
|
-
setup_ordered_tree_callbacks
|
86
|
-
setup_ordered_tree_validations
|
87
|
-
end # def acts_as_ordered_tree
|
88
|
-
|
89
|
-
# Mixed into both classes and instances to provide easy access to the column names
|
90
|
-
module Columns
|
91
|
-
extend ActiveSupport::Concern
|
92
|
-
|
93
|
-
included do
|
94
|
-
attr_protected depth_column, position_column if PROTECTED_ATTRIBUTES_SUPPORTED
|
95
|
-
end
|
96
|
-
|
97
|
-
def parent_column
|
98
|
-
acts_as_ordered_tree_options[:parent_column]
|
99
|
-
end
|
100
|
-
|
101
|
-
def position_column
|
102
|
-
acts_as_ordered_tree_options[:position_column]
|
103
|
-
end
|
18
|
+
Tree.setup!(self, options)
|
19
|
+
end
|
104
20
|
|
105
|
-
|
106
|
-
|
107
|
-
|
21
|
+
# @api private
|
22
|
+
def self.extended(base)
|
23
|
+
base.class_attribute :ordered_tree, :instance_writer => false
|
24
|
+
end
|
108
25
|
|
109
|
-
|
110
|
-
|
111
|
-
|
26
|
+
# Rebuild ordered tree structure for subclasses. It needs to be rebuilt
|
27
|
+
# mainly because of :children and :parent associations, which are created
|
28
|
+
# with option :class_name. It matters for class hierarchies without STI,
|
29
|
+
# they can't work properly with associations inherited from superclass.
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def inherited(subclass)
|
33
|
+
super
|
112
34
|
|
113
|
-
|
114
|
-
Array(acts_as_ordered_tree_options[:scope]).compact
|
115
|
-
end
|
35
|
+
subclass.acts_as_ordered_tree(ordered_tree.options) if ordered_tree?
|
116
36
|
end
|
117
37
|
end # module ActsAsOrderedTree
|
118
38
|
|
119
|
-
|
39
|
+
ActiveSupport.on_load(:active_record) do
|
40
|
+
extend ActsAsOrderedTree
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require 'acts_as_ordered_tree/adapters/recursive'
|
5
|
+
require 'acts_as_ordered_tree/adapters/postgresql'
|
6
|
+
|
7
|
+
module ActsAsOrderedTree
|
8
|
+
module Adapters
|
9
|
+
# adapters map
|
10
|
+
ADAPTERS = HashWithIndifferentAccess['PostgreSQL' => PostgreSQL]
|
11
|
+
ADAPTERS.default = Recursive
|
12
|
+
|
13
|
+
def self.lookup(name)
|
14
|
+
ADAPTERS[name]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module ActsAsOrderedTree
|
4
|
+
module Adapters
|
5
|
+
class Abstract
|
6
|
+
attr_reader :tree
|
7
|
+
|
8
|
+
# @param [ActsAsOrderedTree::Tree] tree
|
9
|
+
def initialize(tree)
|
10
|
+
@tree = tree
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
def preloaded(records)
|
15
|
+
tree.klass.where(nil).extending(Relation::Preloaded).records(records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def none
|
19
|
+
tree.klass.where(nil).none
|
20
|
+
end
|
21
|
+
end # class Abstract
|
22
|
+
end # module Adapters
|
23
|
+
end # module ActsAsOrderedTree
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'active_record/hierarchical_query'
|
4
|
+
|
5
|
+
require 'acts_as_ordered_tree/adapters/abstract'
|
6
|
+
require 'acts_as_ordered_tree/relation/preloaded'
|
7
|
+
|
8
|
+
module ActsAsOrderedTree
|
9
|
+
module Adapters
|
10
|
+
# PostgreSQL adapter implements traverse operations with CTEs
|
11
|
+
class PostgreSQL < Abstract
|
12
|
+
attr_reader :tree
|
13
|
+
|
14
|
+
delegate :columns, :to => :tree
|
15
|
+
delegate :quote_column_name, :to => 'tree.klass.connection'
|
16
|
+
|
17
|
+
def self_and_descendants(node, &block)
|
18
|
+
traverse_down(node) do
|
19
|
+
descendants_scope(node.ordered_tree_node, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def descendants(node, &block)
|
24
|
+
traverse_down(node) do
|
25
|
+
without(node) { self_and_descendants(node, &block) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self_and_ancestors(node, &block)
|
30
|
+
traverse_up(node, [node]) do
|
31
|
+
ancestors_scope(node.ordered_tree_node, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ancestors(node, &block)
|
36
|
+
traverse_up(node) do
|
37
|
+
without(node) { self_and_ancestors(node, &block) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def without(node)
|
43
|
+
scope = yield
|
44
|
+
scope.where(scope.table[columns.id].not_eq(node.id))
|
45
|
+
end
|
46
|
+
|
47
|
+
def traverse_down(node)
|
48
|
+
if node && node.persisted?
|
49
|
+
yield
|
50
|
+
else
|
51
|
+
none
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Yields to block if record is persisted and its parent was not changed.
|
56
|
+
# Returns empty scope (or scope with +including+ records) if record is root.
|
57
|
+
# Otherwise recursively fetches ancestors and returns preloaded relation.
|
58
|
+
def traverse_up(node, including = [])
|
59
|
+
return none unless node
|
60
|
+
|
61
|
+
if can_traverse_up?(node)
|
62
|
+
if node.ordered_tree_node.has_parent?
|
63
|
+
yield
|
64
|
+
else
|
65
|
+
including.empty? ? none : preloaded(including)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
preloaded(persisted_ancestors(node) + including)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Generates scope that traverses tree down to deep, starting from given +scope+
|
73
|
+
def descendants_scope(node)
|
74
|
+
node.scope.join_recursive do |query|
|
75
|
+
query.connect_by(join_columns(columns.id => columns.parent))
|
76
|
+
.start_with(node.to_relation)
|
77
|
+
|
78
|
+
yield query if block_given?
|
79
|
+
|
80
|
+
query.order_siblings(position)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Generates scope that traverses tree up to root, starting from given +scope+
|
85
|
+
def ancestors_scope(node, &block)
|
86
|
+
if columns.depth?
|
87
|
+
build_ancestors_query(node, &block).reorder(depth)
|
88
|
+
else
|
89
|
+
build_ancestors_query(node) do |query|
|
90
|
+
query.start_with { |start| start.select Arel.sql('0').as('__depth') }
|
91
|
+
.select(query.prior['__depth'] - 1, :start_with => false)
|
92
|
+
|
93
|
+
yield query if block_given?
|
94
|
+
end.reorder('__depth')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_ancestors_query(node)
|
99
|
+
node.scope.join_recursive do |query|
|
100
|
+
query.connect_by(join_columns(columns.parent => columns.id))
|
101
|
+
.start_with(node.to_relation)
|
102
|
+
|
103
|
+
yield query if block_given?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def attribute(name)
|
108
|
+
@tree.klass.arel_table[name]
|
109
|
+
end
|
110
|
+
|
111
|
+
def depth
|
112
|
+
attribute(columns.depth)
|
113
|
+
end
|
114
|
+
|
115
|
+
def position
|
116
|
+
attribute(columns.position)
|
117
|
+
end
|
118
|
+
|
119
|
+
def can_traverse_up?(node)
|
120
|
+
node.persisted? && !node.ordered_tree_node.parent_id_changed?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Recursively fetches node's parents until one of them will be persisted.
|
124
|
+
# Returns persisted ancestor and array of non-persistent ancestors
|
125
|
+
def persisted_ancestors(node)
|
126
|
+
queue = []
|
127
|
+
|
128
|
+
parent = node
|
129
|
+
|
130
|
+
while (parent = parent.parent)
|
131
|
+
break if parent && parent.persisted?
|
132
|
+
|
133
|
+
queue.unshift(parent)
|
134
|
+
end
|
135
|
+
|
136
|
+
ancestors(parent) + [parent].compact + queue
|
137
|
+
end
|
138
|
+
|
139
|
+
def scope_columns_hash
|
140
|
+
Hash[tree.columns.scope.map { |x| [x, x] }]
|
141
|
+
end
|
142
|
+
|
143
|
+
def join_columns(hash)
|
144
|
+
scope_columns_hash.merge(hash).each_with_object({}) do |(k, v), h|
|
145
|
+
h[k.to_sym] = v.to_sym
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end # class PostgreSQL
|
149
|
+
end # module Adapters
|
150
|
+
end # module ActsAsOrderedTree
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/adapters/abstract'
|
4
|
+
|
5
|
+
module ActsAsOrderedTree
|
6
|
+
module Adapters
|
7
|
+
# Recursive adapter implements tree traversal in pure Ruby.
|
8
|
+
class Recursive < Abstract
|
9
|
+
def self_and_ancestors(node, &block)
|
10
|
+
return none unless node
|
11
|
+
|
12
|
+
ancestors_scope(node, :include_first => true, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def ancestors(node, &block)
|
16
|
+
ancestors_scope(node, :include_first => false, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def descendants(node, &block)
|
20
|
+
descendants_scope(node, :include_first => false, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self_and_descendants(node, &block)
|
24
|
+
descendants_scope(node, :include_first => true, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def ancestors_scope(node, options, &block)
|
29
|
+
traversal = Traversal.new(node, options, &block)
|
30
|
+
traversal.follow :parent
|
31
|
+
traversal.to_scope.reverse_order!
|
32
|
+
end
|
33
|
+
|
34
|
+
def descendants_scope(node, options, &block)
|
35
|
+
return none unless node.persisted?
|
36
|
+
|
37
|
+
traversal = Traversal.new(node, options, &block)
|
38
|
+
traversal.follow :children
|
39
|
+
traversal.to_scope
|
40
|
+
end
|
41
|
+
|
42
|
+
class Traversal
|
43
|
+
delegate :klass, :to => :@start_record
|
44
|
+
attr_accessor :include_first
|
45
|
+
|
46
|
+
def initialize(start_record, options = {})
|
47
|
+
@start_record = start_record
|
48
|
+
@start_with = nil
|
49
|
+
@order_values = []
|
50
|
+
@where_values = []
|
51
|
+
@include_first = options[:include_first]
|
52
|
+
follow(options[:follow]) if options.key?(:follow)
|
53
|
+
|
54
|
+
yield self if block_given?
|
55
|
+
end
|
56
|
+
|
57
|
+
def follow(association_name)
|
58
|
+
@association = association_name
|
59
|
+
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_with(scope = nil, &block)
|
64
|
+
@start_with = scope || block
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def order_siblings(*values)
|
70
|
+
@order_values << values
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
alias_method :order, :order_siblings
|
75
|
+
|
76
|
+
def where(*values)
|
77
|
+
@where_values << values
|
78
|
+
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def table
|
83
|
+
klass.arel_table
|
84
|
+
end
|
85
|
+
|
86
|
+
def klass
|
87
|
+
@start_record.class
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_scope
|
91
|
+
null_scope.records(to_enum.to_a)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def each(&block)
|
96
|
+
return unless validate_start_conditions
|
97
|
+
|
98
|
+
yield @start_record if include_first
|
99
|
+
|
100
|
+
expand(@start_record, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_start_conditions
|
104
|
+
start_scope ? start_scope.exists? : true
|
105
|
+
end
|
106
|
+
|
107
|
+
def start_scope
|
108
|
+
return nil unless @start_with
|
109
|
+
|
110
|
+
if @start_with.is_a?(Proc)
|
111
|
+
@start_with.call klass.where(klass.primary_key => @start_record.id)
|
112
|
+
else
|
113
|
+
@start_with
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def expand(record, &block)
|
118
|
+
expand_association(record).each do |child|
|
119
|
+
yield child
|
120
|
+
|
121
|
+
expand(child, &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def expand_association(record)
|
126
|
+
if constraints?
|
127
|
+
build_scope(record)
|
128
|
+
else
|
129
|
+
follow_association(record)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def build_scope(record)
|
134
|
+
scope = record.association(@association).scope
|
135
|
+
|
136
|
+
@where_values.each { |v| scope = scope.where(*v) }
|
137
|
+
scope = scope.except(:order).order(*@order_values.flatten) if @order_values.any?
|
138
|
+
|
139
|
+
scope
|
140
|
+
end
|
141
|
+
|
142
|
+
def follow_association(record)
|
143
|
+
Array.wrap(record.send(@association))
|
144
|
+
end
|
145
|
+
|
146
|
+
def null_scope
|
147
|
+
klass.where(nil).extending(Relation::Preloaded)
|
148
|
+
end
|
149
|
+
|
150
|
+
def constraints?
|
151
|
+
@where_values.any? || @order_values.any?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
private_constant :Traversal
|
155
|
+
end # class Recursive
|
156
|
+
end # module Adapters
|
157
|
+
end # module ActsAsOrderedTree
|