1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
module CollectiveIdea #:nodoc:
module Acts #:nodoc:
module NestedSet #:nodoc:
class Move
attr_reader :target, :position, :instance
def initialize(target, position, instance)
@target = target
@position = position
@instance = instance
end
def move
prevent_impossible_move
bound, other_bound = get_boundaries
# there would be no change
return if bound == right || bound == left
# we have defined the boundaries of two non-overlapping intervals,
# so sorting puts both the intervals and their boundaries in order
a, b, c, d = [left, right, bound, other_bound].sort
lock_nodes_between! a, d
nested_set_scope_without_default_scope.where(where_statement(a, d)).update_all(
conditions(a, b, c, d)
)
end
private
delegate :left, :right, :left_column_name, :right_column_name,
:quoted_left_column_name, :quoted_right_column_name,
:quoted_parent_column_name, :parent_column_name, :nested_set_scope_without_default_scope,
:primary_column_name, :quoted_primary_column_name, :primary_id,
:to => :instance
delegate :arel_table, :class, :to => :instance, :prefix => true
delegate :base_class, :to => :instance_class, :prefix => :instance
def where_statement(left_bound, right_bound)
instance_arel_table[left_column_name].between(left_bound..right_bound).
or(instance_arel_table[right_column_name].between(left_bound..right_bound))
end
# Before Arel 6, there was 'in' method, which was replaced
# with 'between' in Arel 6 and now gives deprecation warnings
# in verbose mode. This is patch to support rails 4.0 (Arel 4)
# and 4.1 (Arel 5).
module LegacyWhereStatementExt
def where_statement(left_bound, right_bound)
instance_arel_table[left_column_name].in(left_bound..right_bound).
or(instance_arel_table[right_column_name].in(left_bound..right_bound))
end
end
prepend LegacyWhereStatementExt unless Arel::Predications.method_defined?(:between)
def conditions(a, b, c, d)
_conditions = case_condition_for_direction(:quoted_left_column_name) +
case_condition_for_direction(:quoted_right_column_name) +
case_condition_for_parent
# We want the record to be 'touched' if it timestamps.
if @instance.respond_to?(:updated_at)
_conditions << ", updated_at = :timestamp"
end
[
_conditions,
{
:a => a, :b => b, :c => c, :d => d,
:primary_id => instance.primary_id,
:new_parent_id => new_parent_id,
:timestamp => Time.now.utc
}
]
end
def case_condition_for_direction(column_name)
column = send(column_name)
"#{column} = CASE " +
"WHEN #{column} BETWEEN :a AND :b " +
"THEN #{column} + :d - :b " +
"WHEN #{column} BETWEEN :c AND :d " +
"THEN #{column} + :a - :c " +
"ELSE #{column} END, "
end
def case_condition_for_parent
"#{quoted_parent_column_name} = CASE " +
"WHEN #{quoted_primary_column_name} = :primary_id THEN :new_parent_id " +
"ELSE #{quoted_parent_column_name} END"
end
def lock_nodes_between!(left_bound, right_bound)
# select the rows in the model between a and d, and apply a lock
instance_base_class.default_scoped.nested_set_scope.
right_of(left_bound).left_of_right_side(right_bound).
select(primary_column_name).
lock(true)
end
def root
position == :root
end
def new_parent_id
case position
when :child then target.primary_id
when :root then nil
else target[parent_column_name]
end
end
def get_boundaries
if (bound = target_bound) > right
bound -= 1
other_bound = right + 1
else
other_bound = left - 1
end
[bound, other_bound]
end
class ImpossibleMove < ActiveRecord::StatementInvalid
end
def prevent_impossible_move
if !root && !instance.move_possible?(target)
error_msg = "Impossible move, target node (#{target.class.name},ID: #{target.id})
cannot be inside moved tree (#{instance.class.name},ID: #{instance.id})."
raise ImpossibleMove, error_msg
end
end
def target_bound
case position
when :child then right(target)
when :left then left(target)
when :right then right(target) + 1
when :root then nested_set_scope_without_default_scope.pluck(right_column_name).max + 1
else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
end
end
end
end
end
end
|