File: movable.rb

package info (click to toggle)
ruby-awesome-nested-set 3.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 172 kB
  • sloc: ruby: 1,044; makefile: 2
file content (152 lines) | stat: -rw-r--r-- 5,109 bytes parent folder | download
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
151
152
require 'awesome_nested_set/move'

module CollectiveIdea #:nodoc:
  module Acts #:nodoc:
    module NestedSet #:nodoc:
      module Model
        module Movable

          def move_possible?(target)
            self != target && # Can't target self
              same_scope?(target) && # can't be in different scopes
              # detect impossible move
              within_bounds?(target.left, target.left) &&
              within_bounds?(target.right, target.right)
          end

          # Shorthand method for finding the left sibling and moving to the left of it.
          def move_left
            move_to_left_of left_sibling
          end

          # Shorthand method for finding the right sibling and moving to the right of it.
          def move_right
            move_to_right_of right_sibling
          end

          # Move the node to the left of another node
          def move_to_left_of(node)
            move_to node, :left
          end

          # Move the node to the right of another node
          def move_to_right_of(node)
            move_to node, :right
          end

          # Move the node to the child of another node
          def move_to_child_of(node)
            if node == :root
              move_to_root
            else
              move_to node, :child
            end
          end

          # Move the node to the child of another node with specify index
          def move_to_child_with_index(node, index)
            siblings = node == :root ? roots : node.children
            if siblings.empty?
              move_to_child_of(node)
            elsif siblings.count == index
              move_to_right_of(siblings.last)
            else
              my_position = siblings.index(self)
              if my_position && my_position < index
                # e.g. if self is at position 0 and we want to move self to position 1 then self
                # needs to move to the *right* of the node at position 1. That's because the node
                # that is currently at position 1 will be at position 0 after the move completes.
                move_to_right_of(siblings[index])
              elsif my_position && my_position == index
                # do nothing. already there.
              else
                move_to_left_of(siblings[index])
              end
            end
          end

          # Move the node to root nodes
          def move_to_root
            move_to self, :root
          end

          # Order children in a nested set by an attribute
          # Can order by any attribute class that uses the Comparable mixin, for example a string or integer
          # Usage example when sorting categories alphabetically: @new_category.move_to_ordered_child_of(@root, "name")
          def move_to_ordered_child_of(parent, order_attribute, ascending = true)
            self.move_to_root and return unless parent

            left_neighbor = find_left_neighbor(parent, order_attribute, ascending)
            self.move_to_child_of(parent)

            return unless parent.children.many?

            if left_neighbor
              self.move_to_right_of(left_neighbor)
            else # Self is the left most node.
              self.move_to_left_of(parent.children[0])
            end
          end

          # Find the node immediately to the left of this node.
          def find_left_neighbor(parent, order_attribute, ascending)
            left = nil
            parent.children.each do |n|
              if ascending
                left = n if n.send(order_attribute) < self.send(order_attribute)
              else
                left = n if n.send(order_attribute) > self.send(order_attribute)
              end
            end
            left
          end

          def move_to(target, position)
            prevent_unpersisted_move

            run_callbacks :move do
              in_tenacious_transaction do
                target = reload_target(target, position)
                self.reload_nested_set

                Move.new(target, position, self).move
                update_counter_cache
              end
              after_move_to(target, position)
            end
          end

          protected

          def after_move_to(target, position)
            target.reload_nested_set if target
            self.set_depth_for_self_and_descendants!
            self.reload_nested_set
          end

          def move_to_new_parent
            if @move_to_new_parent_id.nil?
              move_to_root
            elsif @move_to_new_parent_id
              move_to_child_of(@move_to_new_parent_id)
            end
          end

          def out_of_bounds?(left_bound, right_bound)
            left <= left_bound && right >= right_bound
          end

          def prevent_unpersisted_move
            if self.new_record?
              raise ActiveRecord::ActiveRecordError, "You cannot move a new node"
            end
          end

          def within_bounds?(left_bound, right_bound)
            !out_of_bounds?(left_bound, right_bound)
          end
        end
      end
    end
  end
end