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
155
156
157
158
159
160
161
162
|
module ActiveRecord
# Mixins are a way of decorating existing Active Record models with additional behavior. If you for example
# want to keep a number of Documents in order, you can include Mixins::List, and all of the sudden be able to
# call <tt>document.move_to_bottom</tt>.
module Mixins
# 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> if you want
# 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 ActiveRecord::Mixins::List
# 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 List
def self.append_features(base)
super
base.before_destroy :remove_from_list
base.after_create :add_to_list_bottom
end
# Moving around on the list
def move_lower
return unless lower_item
self.class.transaction do
lower_item.decrement_position
increment_position
end
end
def move_higher
return unless higher_item
self.class.transaction do
higher_item.increment_position
decrement_position
end
end
def move_to_bottom
self.class.transaction do
decrement_positions_on_lower_items
assume_bottom_position
end
end
def move_to_top
self.class.transaction do
increment_positions_on_higher_items
assume_top_position
end
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
update_attribute "position", position.to_i + 1
end
def decrement_position
update_attribute "position", position.to_i - 1
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() "1" 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
update_attribute "position", bottom_position_in_list.to_i + 1
end
def assume_top_position
update_attribute "position", 1
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
end
|