acts_as_ordered_tree 1.3.1 → 2.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|