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
@@ -0,0 +1,67 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/position'
|
4
|
+
require 'acts_as_ordered_tree/transaction/create'
|
5
|
+
require 'acts_as_ordered_tree/transaction/destroy'
|
6
|
+
require 'acts_as_ordered_tree/transaction/move'
|
7
|
+
require 'acts_as_ordered_tree/transaction/passthrough'
|
8
|
+
require 'acts_as_ordered_tree/transaction/reorder'
|
9
|
+
|
10
|
+
module ActsAsOrderedTree
|
11
|
+
module Transaction
|
12
|
+
# @api private
|
13
|
+
module Factory
|
14
|
+
# Creates previous and current position objects for node
|
15
|
+
# @api private
|
16
|
+
class PositionFactory
|
17
|
+
def initialize(node)
|
18
|
+
@node = node
|
19
|
+
end
|
20
|
+
|
21
|
+
def previous
|
22
|
+
Position.new @node, @node.parent_id_was, @node.position_was
|
23
|
+
end
|
24
|
+
|
25
|
+
def current
|
26
|
+
Position.new @node, @node.parent_id, @node.position
|
27
|
+
end
|
28
|
+
|
29
|
+
def transition
|
30
|
+
Position::Transition.new(previous, current)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private_constant :PositionFactory
|
34
|
+
|
35
|
+
# Creates proper transaction according to +node+
|
36
|
+
#
|
37
|
+
# @param [ActsAsOrderedTree::Node] node
|
38
|
+
# @param [true, false] destroy set to true if node should be destroyed
|
39
|
+
# @return [ActsAsOrderedTree::Transaction::Base]
|
40
|
+
def create(node, destroy = false)
|
41
|
+
pos = PositionFactory.new(node)
|
42
|
+
|
43
|
+
case
|
44
|
+
when destroy
|
45
|
+
Destroy.new(node, pos.previous)
|
46
|
+
when node.record.new_record?
|
47
|
+
Create.new(node, pos.current)
|
48
|
+
else
|
49
|
+
create_from_transition(node, pos.transition)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
module_function :create
|
53
|
+
|
54
|
+
def create_from_transition(node, transition)
|
55
|
+
case
|
56
|
+
when transition.movement?
|
57
|
+
Move.new(node, transition)
|
58
|
+
when transition.reorder?
|
59
|
+
Reorder.new(node, transition)
|
60
|
+
else
|
61
|
+
Passthrough.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
module_function :create_from_transition
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/transaction/update'
|
4
|
+
|
5
|
+
module ActsAsOrderedTree
|
6
|
+
module Transaction
|
7
|
+
class Move < Update
|
8
|
+
before 'trigger_callback(:before_remove, from.parent)'
|
9
|
+
before 'trigger_callback(:before_add, to.parent)'
|
10
|
+
|
11
|
+
after :update_descendants_depth, :if => [
|
12
|
+
'transition.movement?',
|
13
|
+
'tree.columns.depth?',
|
14
|
+
'transition.level_changed?',
|
15
|
+
'record.children.size > 0'
|
16
|
+
]
|
17
|
+
|
18
|
+
after 'trigger_callback(:after_add, to.parent)'
|
19
|
+
after 'trigger_callback(:after_remove, from.parent)'
|
20
|
+
after 'transition.update_counters'
|
21
|
+
|
22
|
+
finalize
|
23
|
+
|
24
|
+
private
|
25
|
+
def update_values
|
26
|
+
updates = Hash[
|
27
|
+
position => position_value,
|
28
|
+
parent_id => parent_id_value
|
29
|
+
]
|
30
|
+
|
31
|
+
updates[depth] = depth_value if tree.columns.depth? && transition.level_changed?
|
32
|
+
|
33
|
+
updates
|
34
|
+
end
|
35
|
+
|
36
|
+
# Records to be updated
|
37
|
+
def update_scope
|
38
|
+
filter = (id == record.id) | (parent_id == from.parent_id) | (parent_id == to.parent_id)
|
39
|
+
node.scope.where(filter.to_sql)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parent_id_value
|
43
|
+
switch.when(id == record.id, to.parent_id).else(parent_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def position_value
|
47
|
+
switch.
|
48
|
+
when(id == record.id).
|
49
|
+
then(@to.position).
|
50
|
+
# decrement lower positions in old parent
|
51
|
+
when((parent_id == from.parent_id) & (position > from.position)).
|
52
|
+
then(position - 1).
|
53
|
+
# increment positions in new parent
|
54
|
+
when((parent_id == to.parent_id) & (position >= to.position)).
|
55
|
+
then(position + 1).
|
56
|
+
else(position)
|
57
|
+
end
|
58
|
+
|
59
|
+
def depth_value
|
60
|
+
switch.
|
61
|
+
when(id == record.id, to.depth).
|
62
|
+
else(depth)
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_descendants_depth
|
66
|
+
record.descendants.update_all set depth => depth + (to.depth - from.depth)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/transaction/update'
|
4
|
+
|
5
|
+
module ActsAsOrderedTree
|
6
|
+
module Transaction
|
7
|
+
class Reorder < Update
|
8
|
+
finalize
|
9
|
+
|
10
|
+
protected
|
11
|
+
# if we reorder node then we cannot put it to position higher than highest
|
12
|
+
def push_to_bottom
|
13
|
+
to.position = highest_position.zero? ? 1 : highest_position
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def update_scope
|
18
|
+
to.siblings.where(positions_range)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_values
|
22
|
+
{ position => position_value }
|
23
|
+
end
|
24
|
+
|
25
|
+
def positions_range
|
26
|
+
position.in([from.position, to.position].min..[from.position, to.position].max)
|
27
|
+
end
|
28
|
+
|
29
|
+
def position_value
|
30
|
+
expr = switch.
|
31
|
+
when(position == from.position).then(to.position).
|
32
|
+
else(position)
|
33
|
+
|
34
|
+
if to.position > from.position
|
35
|
+
expr.when(positions_range).then(position - 1)
|
36
|
+
else
|
37
|
+
expr.when(positions_range).then(position + 1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end # class Reorder
|
41
|
+
end # module Transaction
|
42
|
+
end # module ActsAsOrderedTree
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/transaction/base'
|
4
|
+
|
5
|
+
module ActsAsOrderedTree
|
6
|
+
module Transaction
|
7
|
+
class Save < Base
|
8
|
+
attr_reader :to
|
9
|
+
|
10
|
+
before 'to.lock!'
|
11
|
+
before :set_scope!, :if => 'to.parent?'
|
12
|
+
before :push_to_bottom, :if => :push_to_bottom?
|
13
|
+
before 'to.position = 1', :if => 'to.position <= 0'
|
14
|
+
|
15
|
+
around :copy_attributes
|
16
|
+
|
17
|
+
# @param [ActsAsOrderedTree::Node] node
|
18
|
+
# @param [ActsAsOrderedTree::Position] to to which position given +node+ is saved
|
19
|
+
def initialize(node, to)
|
20
|
+
super(node)
|
21
|
+
@to = to
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
# Copies parent_id, position and depth from destination to record
|
26
|
+
def copy_attributes
|
27
|
+
record.parent = to.parent
|
28
|
+
node.position = to.position
|
29
|
+
node.depth = to.depth if tree.columns.depth?
|
30
|
+
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns highest position within node's siblings
|
35
|
+
def highest_position
|
36
|
+
@highest_position ||= to.siblings.maximum(tree.columns.position) || 0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Should be fired when given position is empty
|
40
|
+
def push_to_bottom
|
41
|
+
to.position = highest_position + 1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns true if record should be pushed to bottom of list
|
45
|
+
def push_to_bottom?
|
46
|
+
to.position.blank? ||
|
47
|
+
position_out_of_bounds?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def set_scope!
|
52
|
+
tree.columns.scope.each do |column|
|
53
|
+
record[column] = to.parent[column]
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def position_out_of_bounds?
|
60
|
+
to.position > highest_position
|
61
|
+
end
|
62
|
+
end # class Save
|
63
|
+
end # module Transaction
|
64
|
+
end # module ActsAsOrderedTree
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'active_support/core_ext/object/with_options'
|
4
|
+
|
5
|
+
require 'acts_as_ordered_tree/position'
|
6
|
+
require 'acts_as_ordered_tree/transaction/save'
|
7
|
+
require 'acts_as_ordered_tree/transaction/dsl'
|
8
|
+
|
9
|
+
module ActsAsOrderedTree
|
10
|
+
module Transaction
|
11
|
+
# Update transaction includes Move and Reorder
|
12
|
+
#
|
13
|
+
# @abstract
|
14
|
+
# @api private
|
15
|
+
class Update < Save
|
16
|
+
include DSL
|
17
|
+
|
18
|
+
attr_reader :from, :transition
|
19
|
+
|
20
|
+
around :update_tree
|
21
|
+
|
22
|
+
# @param [ActsAsOrderedTree::Node] node
|
23
|
+
# @param [ActsAsOrderedTree::Position::Transition] transition
|
24
|
+
def initialize(node, transition)
|
25
|
+
@transition = transition
|
26
|
+
@from = transition.from
|
27
|
+
|
28
|
+
super(node, transition.to)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def update_tree
|
33
|
+
callbacks = transition.reorder? ? :reorder : :move
|
34
|
+
|
35
|
+
record.run_callbacks(callbacks) do
|
36
|
+
record.hook_update do |update|
|
37
|
+
update.scope = update_scope
|
38
|
+
update.values = update_values.merge(changed_attributes)
|
39
|
+
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_scope
|
46
|
+
# implement in successors
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_values
|
50
|
+
# implement in successors
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
# Returns hash of UPDATE..SET expressions for each
|
55
|
+
# changed record attribute (except tree attributes)
|
56
|
+
#
|
57
|
+
# @return [Hash<String => Arel::Nodes::Node>]
|
58
|
+
def changed_attributes
|
59
|
+
changed_attributes_names.each_with_object({}) do |attr, hash|
|
60
|
+
hash[attr] = attribute_value(attr)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def attribute_value(attr)
|
65
|
+
attr_value = record.read_attribute(attr)
|
66
|
+
quoted = record.class.connection.quote(attr_value)
|
67
|
+
|
68
|
+
switch.
|
69
|
+
when(id == record.id).then(Arel.sql(quoted)).
|
70
|
+
else(attribute(attr))
|
71
|
+
end
|
72
|
+
|
73
|
+
def changed_attributes_names
|
74
|
+
record.changed - (tree.columns.to_a - tree.columns.scope)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'acts_as_ordered_tree/compatibility'
|
4
|
+
|
5
|
+
require 'acts_as_ordered_tree/tree/callbacks'
|
6
|
+
require 'acts_as_ordered_tree/tree/columns'
|
7
|
+
require 'acts_as_ordered_tree/tree/children_association'
|
8
|
+
require 'acts_as_ordered_tree/tree/deprecated_columns_accessors'
|
9
|
+
require 'acts_as_ordered_tree/tree/parent_association'
|
10
|
+
require 'acts_as_ordered_tree/tree/perseverance'
|
11
|
+
require 'acts_as_ordered_tree/tree/scopes'
|
12
|
+
|
13
|
+
require 'acts_as_ordered_tree/hooks'
|
14
|
+
|
15
|
+
require 'acts_as_ordered_tree/adapters'
|
16
|
+
require 'acts_as_ordered_tree/validators'
|
17
|
+
|
18
|
+
require 'acts_as_ordered_tree/instance_methods'
|
19
|
+
|
20
|
+
module ActsAsOrderedTree
|
21
|
+
# ActsAsOrderedTree::Tree
|
22
|
+
class Tree
|
23
|
+
# Default ordered tree options
|
24
|
+
DEFAULT_OPTIONS = {
|
25
|
+
:parent_column => :parent_id,
|
26
|
+
:position_column => :position,
|
27
|
+
:depth_column => :depth
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
PROTECTED_ATTRIBUTES = :left_sibling, :left_sibling_id,
|
31
|
+
:higher_item, :higher_item_id,
|
32
|
+
:right_sibling, :right_sibling_id,
|
33
|
+
:lower_item, :lower_item_id
|
34
|
+
|
35
|
+
attr_reader :klass
|
36
|
+
|
37
|
+
# @!attribute [r] columns
|
38
|
+
# Columns information aggregator
|
39
|
+
#
|
40
|
+
# @return [ActsAsOrderedTree::Tree::Columns] column object
|
41
|
+
attr_reader :columns
|
42
|
+
|
43
|
+
# @!attribute [r] callbacks
|
44
|
+
# :before_add, :after_add, :before_remove and :after_remove callbacks storage
|
45
|
+
#
|
46
|
+
# @return [ActsAsOrderedTree::Tree::Callbacks] callbacks object
|
47
|
+
attr_reader :callbacks
|
48
|
+
|
49
|
+
# @!attribute [r] adapter
|
50
|
+
# Ordered tree adapter which contains implementation of some traverse methods
|
51
|
+
#
|
52
|
+
# @return [ActsAsOrderedTree::Adapters::Abstract] adapter object
|
53
|
+
attr_reader :adapter
|
54
|
+
|
55
|
+
# @!attribute [r] options
|
56
|
+
# Ordered tree options
|
57
|
+
#
|
58
|
+
# @return [Hash]
|
59
|
+
attr_reader :options
|
60
|
+
|
61
|
+
# Create and setup tree object
|
62
|
+
#
|
63
|
+
# @param [Class] klass
|
64
|
+
# @param [Hash] options
|
65
|
+
def self.setup!(klass, options)
|
66
|
+
klass.ordered_tree = new(klass, options).setup
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Class] klass
|
70
|
+
# @param [Hash] options
|
71
|
+
def initialize(klass, options)
|
72
|
+
@klass = klass
|
73
|
+
@options = DEFAULT_OPTIONS.merge(options).freeze
|
74
|
+
@columns = Columns.new(klass, @options)
|
75
|
+
@callbacks = Callbacks.new(klass, @options)
|
76
|
+
@children = ChildrenAssociation.new(self)
|
77
|
+
@parent = ParentAssociation.new(self)
|
78
|
+
@adapter = Adapters.lookup(klass.connection.adapter_name).new(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Builds associations, callbacks, validations etc.
|
82
|
+
def setup
|
83
|
+
setup_associations
|
84
|
+
setup_once
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns Class object which will be used for associations,
|
90
|
+
# scopes and tree traversals.
|
91
|
+
#
|
92
|
+
# @return [Class]
|
93
|
+
def base_class
|
94
|
+
if klass.finder_needs_type_condition?
|
95
|
+
klass.base_class
|
96
|
+
else
|
97
|
+
klass
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def already_setup?
|
103
|
+
klass.ordered_tree?
|
104
|
+
end
|
105
|
+
|
106
|
+
def setup_once
|
107
|
+
return if already_setup?
|
108
|
+
|
109
|
+
setup_validations
|
110
|
+
setup_callbacks
|
111
|
+
protect_attributes *PROTECTED_ATTRIBUTES
|
112
|
+
|
113
|
+
klass.class_eval do
|
114
|
+
extend Scopes
|
115
|
+
extend DeprecatedColumnsAccessors
|
116
|
+
|
117
|
+
include InstanceMethods
|
118
|
+
include Perseverance
|
119
|
+
include Hooks
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def setup_associations
|
124
|
+
@parent.build
|
125
|
+
@children.build
|
126
|
+
end
|
127
|
+
|
128
|
+
def setup_validations
|
129
|
+
if columns.scope?
|
130
|
+
klass.validates_with Validators::ScopeValidator, :on => :update, :if => :parent
|
131
|
+
end
|
132
|
+
|
133
|
+
klass.validates_with Validators::CyclicReferenceValidator, :on => :update, :if => :parent
|
134
|
+
end
|
135
|
+
|
136
|
+
def setup_callbacks
|
137
|
+
klass.define_model_callbacks(:move, :reorder)
|
138
|
+
klass.around_save(:save_ordered_tree_node)
|
139
|
+
klass.around_destroy(:destroy_ordered_tree_node)
|
140
|
+
end
|
141
|
+
|
142
|
+
def protect_attributes(*attributes)
|
143
|
+
Compatibility.version '< 4.0.0' do
|
144
|
+
klass.attr_protected *attributes
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|