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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/lib/acts_as_ordered_tree.rb +22 -100
  3. data/lib/acts_as_ordered_tree/adapters.rb +17 -0
  4. data/lib/acts_as_ordered_tree/adapters/abstract.rb +23 -0
  5. data/lib/acts_as_ordered_tree/adapters/postgresql.rb +150 -0
  6. data/lib/acts_as_ordered_tree/adapters/recursive.rb +157 -0
  7. data/lib/acts_as_ordered_tree/compatibility.rb +22 -0
  8. data/lib/acts_as_ordered_tree/compatibility/active_record/association_scope.rb +9 -0
  9. data/lib/acts_as_ordered_tree/compatibility/active_record/default_scoped.rb +19 -0
  10. data/lib/acts_as_ordered_tree/compatibility/active_record/null_relation.rb +71 -0
  11. data/lib/acts_as_ordered_tree/compatibility/features.rb +153 -0
  12. data/lib/acts_as_ordered_tree/deprecate.rb +24 -0
  13. data/lib/acts_as_ordered_tree/hooks.rb +38 -0
  14. data/lib/acts_as_ordered_tree/hooks/update.rb +86 -0
  15. data/lib/acts_as_ordered_tree/instance_methods.rb +92 -453
  16. data/lib/acts_as_ordered_tree/iterators/arranger.rb +35 -0
  17. data/lib/acts_as_ordered_tree/iterators/level_calculator.rb +52 -0
  18. data/lib/acts_as_ordered_tree/iterators/orphans_pruner.rb +58 -0
  19. data/lib/acts_as_ordered_tree/node.rb +78 -0
  20. data/lib/acts_as_ordered_tree/node/attributes.rb +48 -0
  21. data/lib/acts_as_ordered_tree/node/movement.rb +62 -0
  22. data/lib/acts_as_ordered_tree/node/movements.rb +111 -0
  23. data/lib/acts_as_ordered_tree/node/predicates.rb +98 -0
  24. data/lib/acts_as_ordered_tree/node/reloading.rb +49 -0
  25. data/lib/acts_as_ordered_tree/node/siblings.rb +139 -0
  26. data/lib/acts_as_ordered_tree/node/traversals.rb +53 -0
  27. data/lib/acts_as_ordered_tree/persevering_transaction.rb +93 -0
  28. data/lib/acts_as_ordered_tree/position.rb +143 -0
  29. data/lib/acts_as_ordered_tree/relation/arrangeable.rb +33 -0
  30. data/lib/acts_as_ordered_tree/relation/iterable.rb +41 -0
  31. data/lib/acts_as_ordered_tree/relation/preloaded.rb +46 -11
  32. data/lib/acts_as_ordered_tree/transaction/base.rb +57 -0
  33. data/lib/acts_as_ordered_tree/transaction/callbacks.rb +67 -0
  34. data/lib/acts_as_ordered_tree/transaction/create.rb +68 -0
  35. data/lib/acts_as_ordered_tree/transaction/destroy.rb +34 -0
  36. data/lib/acts_as_ordered_tree/transaction/dsl.rb +214 -0
  37. data/lib/acts_as_ordered_tree/transaction/factory.rb +67 -0
  38. data/lib/acts_as_ordered_tree/transaction/move.rb +70 -0
  39. data/lib/acts_as_ordered_tree/transaction/passthrough.rb +12 -0
  40. data/lib/acts_as_ordered_tree/transaction/reorder.rb +42 -0
  41. data/lib/acts_as_ordered_tree/transaction/save.rb +64 -0
  42. data/lib/acts_as_ordered_tree/transaction/update.rb +78 -0
  43. data/lib/acts_as_ordered_tree/tree.rb +148 -0
  44. data/lib/acts_as_ordered_tree/tree/association.rb +20 -0
  45. data/lib/acts_as_ordered_tree/tree/callbacks.rb +57 -0
  46. data/lib/acts_as_ordered_tree/tree/children_association.rb +120 -0
  47. data/lib/acts_as_ordered_tree/tree/columns.rb +102 -0
  48. data/lib/acts_as_ordered_tree/tree/deprecated_columns_accessors.rb +24 -0
  49. data/lib/acts_as_ordered_tree/tree/parent_association.rb +31 -0
  50. data/lib/acts_as_ordered_tree/tree/perseverance.rb +19 -0
  51. data/lib/acts_as_ordered_tree/tree/scopes.rb +56 -0
  52. data/lib/acts_as_ordered_tree/validators.rb +1 -1
  53. data/lib/acts_as_ordered_tree/version.rb +1 -1
  54. data/spec/acts_as_ordered_tree_spec.rb +80 -909
  55. data/spec/adapters/postgresql_spec.rb +14 -0
  56. data/spec/adapters/recursive_spec.rb +12 -0
  57. data/spec/adapters/shared.rb +272 -0
  58. data/spec/callbacks_spec.rb +177 -0
  59. data/spec/counter_cache_spec.rb +31 -0
  60. data/spec/create_spec.rb +110 -0
  61. data/spec/destroy_spec.rb +57 -0
  62. data/spec/inheritance_spec.rb +176 -0
  63. data/spec/move_spec.rb +94 -0
  64. data/spec/node/movements/concurrent_movements_spec.rb +354 -0
  65. data/spec/node/movements/move_higher_spec.rb +46 -0
  66. data/spec/node/movements/move_lower_spec.rb +46 -0
  67. data/spec/node/movements/move_to_child_of_spec.rb +147 -0
  68. data/spec/node/movements/move_to_child_with_index_spec.rb +124 -0
  69. data/spec/node/movements/move_to_child_with_position_spec.rb +85 -0
  70. data/spec/node/movements/move_to_left_of_spec.rb +120 -0
  71. data/spec/node/movements/move_to_right_of_spec.rb +120 -0
  72. data/spec/node/movements/move_to_root_spec.rb +67 -0
  73. data/spec/node/predicates_spec.rb +211 -0
  74. data/spec/node/reloading_spec.rb +42 -0
  75. data/spec/node/siblings_spec.rb +193 -0
  76. data/spec/node/traversals_spec.rb +71 -0
  77. data/spec/persevering_transaction_spec.rb +98 -0
  78. data/spec/relation/arrangeable_spec.rb +88 -0
  79. data/spec/relation/iterable_spec.rb +104 -0
  80. data/spec/relation/preloaded_spec.rb +57 -0
  81. data/spec/reorder_spec.rb +83 -0
  82. data/spec/spec_helper.rb +30 -38
  83. data/spec/support/db/boot.rb +22 -0
  84. data/spec/{db → support/db}/config.travis.yml +2 -0
  85. data/spec/{db → support/db}/config.yml +1 -0
  86. data/spec/{db → support/db}/schema.rb +9 -0
  87. data/spec/support/factories.rb +2 -2
  88. data/spec/support/matchers.rb +67 -58
  89. data/spec/support/models.rb +6 -14
  90. data/spec/support/tree_factory.rb +315 -0
  91. data/spec/tree/children_association_spec.rb +72 -0
  92. data/spec/tree/columns_spec.rb +65 -0
  93. data/spec/tree/scopes_spec.rb +39 -0
  94. metadata +161 -43
  95. data/lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb +0 -104
  96. data/lib/acts_as_ordered_tree/arrangeable.rb +0 -80
  97. data/lib/acts_as_ordered_tree/class_methods.rb +0 -72
  98. data/lib/acts_as_ordered_tree/relation/base.rb +0 -26
  99. data/lib/acts_as_ordered_tree/tenacious_transaction.rb +0 -30
  100. data/spec/concurrency_support_spec.rb +0 -156
@@ -0,0 +1,98 @@
1
+ # coding: utf-8
2
+
3
+ module ActsAsOrderedTree
4
+ class Node
5
+ module Predicates
6
+ # Returns true if this is a root node.
7
+ def root?
8
+ !parent_id?
9
+ end
10
+
11
+ # Returns true if this is the end of a branch.
12
+ def leaf?
13
+ record.persisted? && if children.loaded? || tree.columns.counter_cache?
14
+ # no SQL-queries here
15
+ children.empty?
16
+ else
17
+ !children.exists?
18
+ end
19
+ end
20
+
21
+ # Returns true if node contains any children.
22
+ def has_children?
23
+ !leaf?
24
+ end
25
+
26
+ # Returns true is node is not a root node.
27
+ def has_parent?
28
+ !root?
29
+ end
30
+
31
+ # Returns true if current node is descendant of +other+ node.
32
+ #
33
+ # @param [ActiveRecord::Base] other
34
+ def is_descendant_of?(other)
35
+ same_scope?(other) &&
36
+ ancestors.include?(other) &&
37
+ ancestors.all? { |x| x.ordered_tree_node.same_scope?(record) }
38
+ end
39
+
40
+ # Returns true if current node is equal to +other+ node or is descendant of +other+ node.
41
+ #
42
+ # @param [ActiveRecord::Base] other
43
+ def is_or_is_descendant_of?(other)
44
+ record == other || is_descendant_of?(other)
45
+ end
46
+
47
+ # Returns true if current node is ancestor of +other+ node.
48
+ #
49
+ # @param [ActiveRecord::Base] other
50
+ def is_ancestor_of?(other)
51
+ same_scope?(other) && other.is_descendant_of?(record)
52
+ end
53
+
54
+ # Returns true if current node is equal to +other+ node or is ancestor of +other+ node.
55
+ #
56
+ # @param [ActiveRecord::Base] other
57
+ def is_or_is_ancestor_of?(other)
58
+ same_scope?(other) && other.is_or_is_descendant_of?(record)
59
+ end
60
+
61
+ # Return +true+ if this object is the first in the list.
62
+ def first?
63
+ position <= 1
64
+ end
65
+
66
+ # Return +true+ if this object is the last in the list.
67
+ def last?
68
+ if tree.columns.counter_cache? && parent
69
+ parent.children.size == position
70
+ else
71
+ !right_sibling
72
+ end
73
+ end
74
+
75
+ # Check if other node is in the same scope.
76
+ #
77
+ # @api private
78
+ def same_scope?(other)
79
+ same_kind?(other) && tree.columns.scope.all? do |attr|
80
+ record[attr] == other[attr]
81
+ end
82
+ end
83
+
84
+ # Check if other node has the same parent
85
+ #
86
+ # @api private
87
+ def same_parent?(other)
88
+ same_scope?(other) && parent_id == other.ordered_tree_node.parent_id
89
+ end
90
+
91
+ private
92
+ # Check if other node belongs to same class hierarchy.
93
+ def same_kind?(other)
94
+ other.ordered_tree && other.ordered_tree.base_class == tree.base_class
95
+ end
96
+ end # module Predicates
97
+ end # class Node
98
+ end # module ActsAsOrderedTree
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/compatibility'
4
+
5
+ module ActsAsOrderedTree
6
+ class Node
7
+ module Reloading
8
+ Compatibility.version '< 4.0.0' do
9
+ # Reloads node's attributes related to tree structure
10
+ def reload(options = {})
11
+ record.reload(options.merge(:select => tree_columns))
12
+ end
13
+ end
14
+
15
+ Compatibility.version '>= 4.0.0' do
16
+ # Reloads node's attributes related to tree structure
17
+ def reload(options = {})
18
+ record.association_cache.delete(:parent)
19
+ record.association_cache.delete(:children)
20
+
21
+ fresh_object = reload_scope(options).find(record.id)
22
+
23
+ fresh_object.attributes.each_pair do |key, value|
24
+ record[key] = value
25
+ end
26
+
27
+ record.instance_eval do
28
+ # @attributes.update(fresh_object.instance_variable_get(:@attributes))
29
+ @attributes_cache = {}
30
+ end
31
+
32
+ record
33
+ end
34
+
35
+ private
36
+ def reload_scope(options)
37
+ options ||= {}
38
+ lock_value = options.fetch(:lock, false)
39
+ record.class.unscoped.select(tree_columns).lock(lock_value)
40
+ end
41
+ end
42
+
43
+ private
44
+ def tree_columns
45
+ tree.columns.to_a
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,139 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/node/predicates'
4
+
5
+ module ActsAsOrderedTree
6
+ class Node
7
+ module Siblings
8
+ include Predicates
9
+
10
+ # Returns collection of all children of the parent, including self
11
+ #
12
+ # @return [ActiveRecord::Relation]
13
+ def self_and_siblings
14
+ scope.where( tree.columns.parent => parent_id ).preorder
15
+ end
16
+
17
+ # Returns collection of all children of the parent, except self
18
+ #
19
+ # @return [ActiveRecord::Relation]
20
+ def siblings
21
+ self_and_siblings.where( table[tree.columns.id].not_eq(id) )
22
+ end
23
+
24
+ # Returns siblings lying to the left of (upper than) current node.
25
+ #
26
+ # @return [ActiveRecord::Relation]
27
+ def left_siblings
28
+ siblings.where( table[tree.columns.position].lteq(position) )
29
+ end
30
+ alias :higher_items :left_siblings
31
+
32
+ # Returns a left (upper) sibling of node.
33
+ #
34
+ # @return [ActiveRecord::Base, nil]
35
+ def left_sibling
36
+ higher_items.last
37
+ end
38
+ alias :higher_item :left_sibling
39
+
40
+ # Set node new left (upper) sibling.
41
+ # Just changes node's parent_id and position attributes.
42
+ #
43
+ # @param [ActiveRecord::Base] node new left sibling
44
+ # @raise [ActiveRecord::AssociationTypeMismatch] if +node+ class does not
45
+ # match current node class.
46
+ def left_sibling=(node)
47
+ return node if record == node
48
+
49
+ to = validate_sibling!(node)
50
+
51
+ self.position = higher_than?(node) ? to.position : to.position + 1
52
+ self.parent_id = to.parent_id
53
+
54
+ node
55
+ end
56
+ alias :higher_item= :left_sibling=
57
+
58
+ # Set node new left sibling by its ID.
59
+ # Changes node's parent_id and position.
60
+ #
61
+ # @param [Fixnum] id new left sibling ID
62
+ # @raise [ActiveRecord::RecordNotFound] if given +id+ was not found
63
+ def left_sibling_id=(id)
64
+ assign_sibling_by_id(id, :left)
65
+ end
66
+ alias :higher_item_id= :left_sibling_id=
67
+
68
+ # Returns siblings lying to the right of (lower than) current node.
69
+ #
70
+ # @return [ActiveRecord::Relation]
71
+ def right_siblings
72
+ siblings.where( table[tree.columns.position].gteq(position) )
73
+ end
74
+ alias :lower_items :right_siblings
75
+
76
+ # Returns a right (lower) sibling of the node
77
+ #
78
+ # @return [ActiveRecord::Base, nil]
79
+ def right_sibling
80
+ right_siblings.first
81
+ end
82
+ alias :lower_item :right_sibling
83
+
84
+ # Set node new right (lower) sibling.
85
+ # Just changes node's parent_id and position attributes.
86
+ #
87
+ # @param [ActiveRecord::Base] node new right sibling
88
+ # @raise [ActiveRecord::AssociationTypeMismatch] if +node+ class does not
89
+ # match current node class.
90
+ def right_sibling=(node)
91
+ to = validate_sibling!(node)
92
+
93
+ self.position = higher_than?(node) ? to.position - 1 : to.position
94
+ self.parent_id = to.parent_id
95
+
96
+ node
97
+ end
98
+ alias :lower_item= :right_sibling=
99
+
100
+ # Set node new right sibling by its ID.
101
+ # Changes node's parent_id and position.
102
+ #
103
+ # @param [Fixnum] id new right sibling ID
104
+ # @raise [ActiveRecord::RecordNotFound] if given +id+ was not found
105
+ def right_sibling_id=(id)
106
+ assign_sibling_by_id(id, :right)
107
+ end
108
+ alias :lower_item_id= :right_sibling_id=
109
+
110
+ private
111
+ def higher_than?(other)
112
+ same_parent?(other) && position < other.ordered_tree_node.position
113
+ end
114
+
115
+ # Raises exception if +other+ is kind of wrong class
116
+ #
117
+ # @return [ActsAsOrderedTree::Node]
118
+ def validate_sibling!(other)
119
+ unless other.is_a?(tree.base_class)
120
+ message = "#{tree.base_class.name} expected, got #{other.class.name}"
121
+ raise ActiveRecord::AssociationTypeMismatch, message
122
+ end
123
+
124
+ other.ordered_tree_node
125
+ end
126
+
127
+ # @api private
128
+ def assign_sibling_by_id(id, position)
129
+ node = tree.base_class.find(id)
130
+
131
+ case position
132
+ when :left, :higher then self.left_sibling = node
133
+ when :right, :lower then self.right_sibling = node
134
+ else raise RuntimeError, 'Unknown sibling position'
135
+ end
136
+ end
137
+ end # module Siblings
138
+ end # class Node
139
+ end # module ActsAsOrderedTree
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+
3
+ require 'acts_as_ordered_tree/relation/arrangeable'
4
+ require 'acts_as_ordered_tree/relation/iterable'
5
+
6
+ module ActsAsOrderedTree
7
+ class Node
8
+ module Traversals
9
+ # Returns relation that contains all node's parents, starting from root.
10
+ #
11
+ # @return [ActiveRecord::Relation]
12
+ def ancestors
13
+ iterable tree.adapter.ancestors(record)
14
+ end
15
+
16
+ # Returns relation that containt all node's parents
17
+ # and node itself, starting from root.
18
+ #
19
+ # @return [ActiveRecord::Relation]
20
+ def self_and_ancestors
21
+ iterable tree.adapter.self_and_ancestors(record)
22
+ end
23
+
24
+ # Returns collection of all node's children including their nested children.
25
+ #
26
+ # @return [ActiveRecord::Relation]
27
+ def descendants
28
+ iterable tree.adapter.descendants(record)
29
+ end
30
+
31
+ # Returns collection of all node's children including their
32
+ # nested children, and node itself.
33
+ #
34
+ # @return [ActiveRecord::Relation]
35
+ def self_and_descendants
36
+ iterable tree.adapter.self_and_descendants(record)
37
+ end
38
+
39
+ # Returns very first ancestor of current node. If current node is root,
40
+ # then method returns node itself.
41
+ #
42
+ # @return [ActiveRecord::Base]
43
+ def root
44
+ root? ? record : ancestors.first
45
+ end
46
+
47
+ private
48
+ def iterable(scope)
49
+ scope.extending(Relation::Arrangeable, Relation::Iterable)
50
+ end
51
+ end # module Traversals
52
+ end # module Node
53
+ end # module ActsAsOrderedTree
@@ -0,0 +1,93 @@
1
+ # coding: utf-8
2
+
3
+ module ActsAsOrderedTree
4
+ class PerseveringTransaction
5
+ module State
6
+ # Generate helper methods for given +state+.
7
+ # AR adapter calls :committed! and :rolledback! methods
8
+ #
9
+ # @api private
10
+ def state_method(state)
11
+ define_method "#{state}!" do |*|
12
+ @state = state
13
+ end
14
+
15
+ define_method "#{state}?" do
16
+ @state == state
17
+ end
18
+ end
19
+ end
20
+ extend State
21
+
22
+ # Which errors should be treated as deadlocks
23
+ DEADLOCK_MESSAGES = Regexp.new [
24
+ 'Deadlock found when trying to get lock',
25
+ 'Lock wait timeout exceeded',
26
+ 'deadlock detected',
27
+ 'database is locked'
28
+ ].join(?|).freeze
29
+ # How many times we should retry transaction
30
+ RETRY_COUNT = 10
31
+
32
+ attr_reader :connection, :attempts
33
+ delegate :logger, :to => :connection
34
+
35
+ state_method :committed
36
+ state_method :rolledback
37
+
38
+ def initialize(connection)
39
+ @connection = connection
40
+ @attempts = 0
41
+ @callbacks = []
42
+ @state = nil
43
+ end
44
+
45
+ # Starts persevering transaction
46
+ def start(&block)
47
+ @attempts += 1
48
+
49
+ with_transaction_state(&block)
50
+ rescue ActiveRecord::StatementInvalid => error
51
+ raise unless connection.open_transactions.zero?
52
+ raise unless error.message =~ DEADLOCK_MESSAGES
53
+ raise if attempts >= RETRY_COUNT
54
+
55
+ logger.info "Deadlock detected on attempt #{attempts}, restarting transaction"
56
+
57
+ pause and retry
58
+ end
59
+
60
+ # Execute given +block+ when after transaction _real_ commit
61
+ def after_commit(&block)
62
+ @callbacks << block if block_given?
63
+ end
64
+
65
+ # This method is called by AR adapter
66
+ # @api private
67
+ def has_transactional_callbacks?
68
+ true
69
+ end
70
+
71
+ # Marks this transaction as committed and executes its commit callbacks
72
+ # @api private
73
+ def committed_with_callbacks!
74
+ committed_without_callbacks!
75
+ @callbacks.each { |callback| callback.call }
76
+ end
77
+ alias_method_chain :committed!, :callbacks
78
+
79
+ private
80
+ def pause
81
+ sleep(rand(attempts) * 0.1)
82
+ end
83
+
84
+ # Runs real transaction and remembers its state
85
+ def with_transaction_state
86
+ connection.transaction do
87
+ connection.add_transaction_record(self)
88
+
89
+ yield
90
+ end
91
+ end
92
+ end # class PerseveringTransaction
93
+ end # module ActsAsOrderedTree
@@ -0,0 +1,143 @@
1
+ # coding: utf-8
2
+
3
+ module ActsAsOrderedTree
4
+ # Position structure aggregates knowledge about node's position in the tree
5
+ #
6
+ # @api private
7
+ class Position
8
+ # This class represents node position change
9
+ #
10
+ # @api private
11
+ class Transition
12
+ # @return [ActsAsOrderedTree::Position]
13
+ attr_reader :from
14
+
15
+ # @return [ActsAsOrderedTree::Position]
16
+ attr_reader :to
17
+
18
+ # @param [ActsAsOrderedTree::Position] from
19
+ # @param [ActsAsOrderedTree::Position] to
20
+ def initialize(from, to)
21
+ @from, @to = from, to
22
+ end
23
+
24
+ def changed?
25
+ from != to
26
+ end
27
+
28
+ def reorder?
29
+ changed? && from.parent_id == to.parent_id
30
+ end
31
+
32
+ def movement?
33
+ changed? && from.parent_id != to.parent_id
34
+ end
35
+
36
+ def level_changed?
37
+ from.depth != to.depth
38
+ end
39
+
40
+ def update_counters
41
+ if movement?
42
+ from.decrement_counter
43
+ to.increment_counter
44
+ end
45
+ end
46
+ end
47
+
48
+ attr_reader :node, :position
49
+ attr_accessor :parent_id
50
+
51
+ delegate :record, :to => :node
52
+
53
+ # @param [ActsAsOrderedTree::Node] node
54
+ # @param [Integer] parent_id
55
+ # @param [Integer] position
56
+ def initialize(node, parent_id, position)
57
+ @node, @parent_id, self.position = node, parent_id, position
58
+ end
59
+
60
+ # attr_writer with coercion to [nil or Integer]
61
+ def position=(value)
62
+ @position = value.presence && value.to_i
63
+ end
64
+
65
+ def klass
66
+ record.class
67
+ end
68
+
69
+ def parent
70
+ return @parent if defined?(@parent)
71
+
72
+ @parent = parent_id ? fetch_parent : nil
73
+ end
74
+
75
+ def parent?
76
+ parent.present?
77
+ end
78
+
79
+ def root?
80
+ parent.blank?
81
+ end
82
+
83
+ def depth
84
+ @depth ||= parent ? parent.level + 1 : 0
85
+ end
86
+
87
+ # Locks current position. Technically, it means that pessimistic
88
+ # lock will be obtained on parent node (or all root nodes if position is root)
89
+ def lock!
90
+ if parent
91
+ parent.lock!
92
+ else
93
+ siblings.lock.reload
94
+ end
95
+ end
96
+
97
+ # predicate
98
+ def position?
99
+ position.present?
100
+ end
101
+
102
+ # Returns all nodes within given position
103
+ def siblings
104
+ node.scope.where(klass.ordered_tree.columns.parent => parent_id)
105
+ end
106
+
107
+ # Returns all nodes that are lower than current position
108
+ def lower
109
+ position? ?
110
+ siblings.where(klass.arel_table[klass.ordered_tree.columns.position].gteq(position)) :
111
+ siblings
112
+ end
113
+
114
+ def increment_counter
115
+ update_counter(:increment_counter)
116
+ end
117
+
118
+ def decrement_counter
119
+ update_counter(:decrement_counter)
120
+ end
121
+
122
+ # @param [ActsAsOrderedTree::Node::Position] other
123
+ def ==(other)
124
+ other.is_a?(self.class) &&
125
+ other.record == record &&
126
+ other.parent_id == parent_id &&
127
+ other.position == position
128
+ end
129
+
130
+ private
131
+ def fetch_parent
132
+ parent_id == record[klass.ordered_tree.columns.parent] ?
133
+ record.parent :
134
+ node.scope.find(parent_id)
135
+ end
136
+
137
+ def update_counter(method)
138
+ if (column = klass.ordered_tree.columns.counter_cache) && parent_id
139
+ klass.send(method, column, parent_id)
140
+ end
141
+ end
142
+ end # class Position
143
+ end