aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/mixins/list_mixin.rb
blob: 9f964f11af96a6a72381b89463245c61b37c5df8 (plain) (blame)
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
153
154
module ActiveRecord
  # This mixin provides the capabilities for sorting and reordering a number of objects in list.
  # The class that has this mixin included needs to have a "position" column defined as an integer on
  # the mapped database table. Further more, you need to implement the <tt>scope_condition</tt> that
  # defines how to separate one list from another.
  #
  # Todo list example:
  #
  #   class TodoList < ActiveRecord::Base
  #     has_many :todo_items, :order => "position"
  #   end
  #
  #   class TodoItem < ActiveRecord::Base
  #     include ListMixin
  #     belongs_to :todo_list
  #  
  #     private
  #       def scope_condition
  #         "todo_list_id = #{todo_list_id}"
  #       end
  #   end
  #
  #   todo_list.first.move_to_bottom
  #   todo_list.last.move_higher
  #
  module ListMixin
    def self.append_features(base)
      super
      base.class_eval do
        before_destroy :remove_from_list
        after_create   :add_to_list_bottom
      end
    end

    # Moving around on the list

    def move_lower
      return unless lower_item
      lower_item.decrement_position
      increment_position
    end

    def move_higher
      return unless higher_item
      higher_item.increment_position
      decrement_position
    end
    
    def move_to_bottom
      decrement_positions_on_lower_items
      assume_bottom_position
    end

    def move_to_top
      increment_positions_on_higher_items
      assume_top_position
    end
    

    # Entering or existing the list
    
    def add_to_list_top
      increment_positions_on_all_items
    end

    def add_to_list_bottom
      assume_bottom_position
    end

    def remove_from_list
      decrement_positions_on_lower_items
    end


    # Changing the position

    def increment_position
      self.position = position.to_i + 1
      save
    end
    
    def decrement_position
      self.position = position.to_i - 1
      save
    end
    
    
    # Querying the position
    
    def first?
      self.position == 1
    end
    
    def last?
      self.position == bottom_position_in_list
    end

    private
      # Overwrite this method to define the scope of the list changes
      def scope_condition; end
    
      def higher_item
        self.class.find_first(
          "#{scope_condition} AND position = #{(position.to_i - 1).to_s}"
        )
      end
    
      def lower_item
        self.class.find_first(
          "#{scope_condition} AND position = #{(position.to_i + 1).to_s}"
        )
      end
    
      def bottom_position_in_list
        item = bottom_item
        item ? item.position : 0
      end
    
      def bottom_item
        self.class.find_first(
          "#{scope_condition} ",
          "position DESC"
        )
      end

      def assume_bottom_position
        self.position = bottom_position_in_list.to_i + 1
        save
      end
      
      def assume_top_position
        self.position = 1
        save
      end
      
      def decrement_positions_on_lower_items
        self.class.update_all(
          "position = (position - 1)",  "#{scope_condition} AND position > #{position.to_i}"
        )
      end
      
      def increment_positions_on_higher_items
        self.class.update_all(
          "position = (position + 1)",  "#{scope_condition} AND position < #{position.to_i}"
        )
      end

      def increment_positions_on_all_items
        self.class.update_all(
          "position = (position + 1)",  "#{scope_condition}"
        )
      end
  end
end