suhovius-acts_as_list 0.7.2.s

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "sqlite3", :platforms => [:ruby]
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rake"
8
+ gem "appraisal"
9
+ gem "activerecord", "4.1.10"
10
+
11
+ group :test do
12
+ gem "minitest"
13
+ end
14
+
15
+ platforms :rbx do
16
+ gem "rubysl", "~> 2.0"
17
+ gem "rubinius-developer_tools"
18
+ gem "rubysl-test-unit"
19
+ end
20
+
21
+ gemspec :path => "../"
@@ -0,0 +1,262 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ acts_as_list (0.7.2)
5
+ activerecord (>= 3.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (4.1.10)
11
+ activesupport (= 4.1.10)
12
+ builder (~> 3.1)
13
+ activerecord (4.1.10)
14
+ activemodel (= 4.1.10)
15
+ activesupport (= 4.1.10)
16
+ arel (~> 5.0.0)
17
+ activesupport (4.1.10)
18
+ i18n (~> 0.6, >= 0.6.9)
19
+ json (~> 1.7, >= 1.7.7)
20
+ minitest (~> 5.1)
21
+ thread_safe (~> 0.1)
22
+ tzinfo (~> 1.1)
23
+ appraisal (1.0.3)
24
+ bundler
25
+ rake
26
+ thor (>= 0.14.0)
27
+ arel (5.0.1.20140414130214)
28
+ builder (3.2.2)
29
+ ffi2-generators (0.1.1)
30
+ i18n (0.7.0)
31
+ json (1.8.2)
32
+ minitest (5.5.1)
33
+ rake (10.4.2)
34
+ rubinius-coverage (2.0.3)
35
+ rubinius-debugger (2.2.1)
36
+ rubinius-developer_tools (2.0.0)
37
+ rubinius-coverage (~> 2.0)
38
+ rubinius-debugger (~> 2.0)
39
+ rubinius-profiler (~> 2.0)
40
+ rubinius-profiler (2.0.1)
41
+ rubysl (2.1.0)
42
+ rubysl-abbrev (~> 2.0)
43
+ rubysl-base64 (~> 2.0)
44
+ rubysl-benchmark (~> 2.0)
45
+ rubysl-bigdecimal (~> 2.0)
46
+ rubysl-cgi (~> 2.0)
47
+ rubysl-cgi-session (~> 2.0)
48
+ rubysl-cmath (~> 2.0)
49
+ rubysl-complex (~> 2.0)
50
+ rubysl-continuation (~> 2.0)
51
+ rubysl-coverage (~> 2.0)
52
+ rubysl-csv (~> 2.0)
53
+ rubysl-curses (~> 2.0)
54
+ rubysl-date (~> 2.0)
55
+ rubysl-delegate (~> 2.0)
56
+ rubysl-digest (~> 2.0)
57
+ rubysl-drb (~> 2.0)
58
+ rubysl-e2mmap (~> 2.0)
59
+ rubysl-english (~> 2.0)
60
+ rubysl-enumerator (~> 2.0)
61
+ rubysl-erb (~> 2.0)
62
+ rubysl-etc (~> 2.0)
63
+ rubysl-expect (~> 2.0)
64
+ rubysl-fcntl (~> 2.0)
65
+ rubysl-fiber (~> 2.0)
66
+ rubysl-fileutils (~> 2.0)
67
+ rubysl-find (~> 2.0)
68
+ rubysl-forwardable (~> 2.0)
69
+ rubysl-getoptlong (~> 2.0)
70
+ rubysl-gserver (~> 2.0)
71
+ rubysl-io-console (~> 2.0)
72
+ rubysl-io-nonblock (~> 2.0)
73
+ rubysl-io-wait (~> 2.0)
74
+ rubysl-ipaddr (~> 2.0)
75
+ rubysl-irb (~> 2.1)
76
+ rubysl-logger (~> 2.0)
77
+ rubysl-mathn (~> 2.0)
78
+ rubysl-matrix (~> 2.0)
79
+ rubysl-mkmf (~> 2.0)
80
+ rubysl-monitor (~> 2.0)
81
+ rubysl-mutex_m (~> 2.0)
82
+ rubysl-net-ftp (~> 2.0)
83
+ rubysl-net-http (~> 2.0)
84
+ rubysl-net-imap (~> 2.0)
85
+ rubysl-net-pop (~> 2.0)
86
+ rubysl-net-protocol (~> 2.0)
87
+ rubysl-net-smtp (~> 2.0)
88
+ rubysl-net-telnet (~> 2.0)
89
+ rubysl-nkf (~> 2.0)
90
+ rubysl-observer (~> 2.0)
91
+ rubysl-open-uri (~> 2.0)
92
+ rubysl-open3 (~> 2.0)
93
+ rubysl-openssl (~> 2.0)
94
+ rubysl-optparse (~> 2.0)
95
+ rubysl-ostruct (~> 2.0)
96
+ rubysl-pathname (~> 2.0)
97
+ rubysl-prettyprint (~> 2.0)
98
+ rubysl-prime (~> 2.0)
99
+ rubysl-profile (~> 2.0)
100
+ rubysl-profiler (~> 2.0)
101
+ rubysl-pstore (~> 2.0)
102
+ rubysl-pty (~> 2.0)
103
+ rubysl-rational (~> 2.0)
104
+ rubysl-resolv (~> 2.0)
105
+ rubysl-rexml (~> 2.0)
106
+ rubysl-rinda (~> 2.0)
107
+ rubysl-rss (~> 2.0)
108
+ rubysl-scanf (~> 2.0)
109
+ rubysl-securerandom (~> 2.0)
110
+ rubysl-set (~> 2.0)
111
+ rubysl-shellwords (~> 2.0)
112
+ rubysl-singleton (~> 2.0)
113
+ rubysl-socket (~> 2.0)
114
+ rubysl-stringio (~> 2.0)
115
+ rubysl-strscan (~> 2.0)
116
+ rubysl-sync (~> 2.0)
117
+ rubysl-syslog (~> 2.0)
118
+ rubysl-tempfile (~> 2.0)
119
+ rubysl-thread (~> 2.0)
120
+ rubysl-thwait (~> 2.0)
121
+ rubysl-time (~> 2.0)
122
+ rubysl-timeout (~> 2.0)
123
+ rubysl-tmpdir (~> 2.0)
124
+ rubysl-tsort (~> 2.0)
125
+ rubysl-un (~> 2.0)
126
+ rubysl-uri (~> 2.0)
127
+ rubysl-weakref (~> 2.0)
128
+ rubysl-webrick (~> 2.0)
129
+ rubysl-xmlrpc (~> 2.0)
130
+ rubysl-yaml (~> 2.0)
131
+ rubysl-zlib (~> 2.0)
132
+ rubysl-abbrev (2.0.4)
133
+ rubysl-base64 (2.0.0)
134
+ rubysl-benchmark (2.0.1)
135
+ rubysl-bigdecimal (2.0.2)
136
+ rubysl-cgi (2.0.1)
137
+ rubysl-cgi-session (2.0.1)
138
+ rubysl-cmath (2.0.0)
139
+ rubysl-complex (2.0.0)
140
+ rubysl-continuation (2.0.0)
141
+ rubysl-coverage (2.0.3)
142
+ rubysl-csv (2.0.2)
143
+ rubysl-english (~> 2.0)
144
+ rubysl-curses (2.0.1)
145
+ rubysl-date (2.0.9)
146
+ rubysl-delegate (2.0.1)
147
+ rubysl-digest (2.0.3)
148
+ rubysl-drb (2.0.1)
149
+ rubysl-e2mmap (2.0.0)
150
+ rubysl-english (2.0.0)
151
+ rubysl-enumerator (2.0.0)
152
+ rubysl-erb (2.0.2)
153
+ rubysl-etc (2.0.3)
154
+ ffi2-generators (~> 0.1)
155
+ rubysl-expect (2.0.0)
156
+ rubysl-fcntl (2.0.4)
157
+ ffi2-generators (~> 0.1)
158
+ rubysl-fiber (2.0.0)
159
+ rubysl-fileutils (2.0.3)
160
+ rubysl-find (2.0.1)
161
+ rubysl-forwardable (2.0.1)
162
+ rubysl-getoptlong (2.0.0)
163
+ rubysl-gserver (2.0.0)
164
+ rubysl-socket (~> 2.0)
165
+ rubysl-thread (~> 2.0)
166
+ rubysl-io-console (2.0.0)
167
+ rubysl-io-nonblock (2.0.0)
168
+ rubysl-io-wait (2.0.0)
169
+ rubysl-ipaddr (2.0.0)
170
+ rubysl-irb (2.1.1)
171
+ rubysl-e2mmap (~> 2.0)
172
+ rubysl-mathn (~> 2.0)
173
+ rubysl-thread (~> 2.0)
174
+ rubysl-logger (2.1.0)
175
+ rubysl-mathn (2.0.0)
176
+ rubysl-matrix (2.1.0)
177
+ rubysl-e2mmap (~> 2.0)
178
+ rubysl-mkmf (2.0.1)
179
+ rubysl-fileutils (~> 2.0)
180
+ rubysl-shellwords (~> 2.0)
181
+ rubysl-monitor (2.0.0)
182
+ rubysl-mutex_m (2.0.0)
183
+ rubysl-net-ftp (2.0.1)
184
+ rubysl-net-http (2.0.4)
185
+ rubysl-cgi (~> 2.0)
186
+ rubysl-erb (~> 2.0)
187
+ rubysl-singleton (~> 2.0)
188
+ rubysl-net-imap (2.0.1)
189
+ rubysl-net-pop (2.0.1)
190
+ rubysl-net-protocol (2.0.1)
191
+ rubysl-net-smtp (2.0.1)
192
+ rubysl-net-telnet (2.0.0)
193
+ rubysl-nkf (2.0.1)
194
+ rubysl-observer (2.0.0)
195
+ rubysl-open-uri (2.0.0)
196
+ rubysl-open3 (2.0.0)
197
+ rubysl-openssl (2.2.1)
198
+ rubysl-optparse (2.0.1)
199
+ rubysl-shellwords (~> 2.0)
200
+ rubysl-ostruct (2.0.4)
201
+ rubysl-pathname (2.1.0)
202
+ rubysl-prettyprint (2.0.3)
203
+ rubysl-prime (2.0.1)
204
+ rubysl-profile (2.0.0)
205
+ rubysl-profiler (2.0.1)
206
+ rubysl-pstore (2.0.0)
207
+ rubysl-pty (2.0.3)
208
+ rubysl-rational (2.0.1)
209
+ rubysl-resolv (2.1.2)
210
+ rubysl-rexml (2.0.4)
211
+ rubysl-rinda (2.0.1)
212
+ rubysl-rss (2.0.0)
213
+ rubysl-scanf (2.0.0)
214
+ rubysl-securerandom (2.0.0)
215
+ rubysl-set (2.0.1)
216
+ rubysl-shellwords (2.0.0)
217
+ rubysl-singleton (2.0.0)
218
+ rubysl-socket (2.0.1)
219
+ rubysl-stringio (2.0.0)
220
+ rubysl-strscan (2.0.0)
221
+ rubysl-sync (2.0.0)
222
+ rubysl-syslog (2.1.0)
223
+ ffi2-generators (~> 0.1)
224
+ rubysl-tempfile (2.0.1)
225
+ rubysl-test-unit (2.0.2)
226
+ minitest
227
+ rubysl-thread (2.0.3)
228
+ rubysl-thwait (2.0.0)
229
+ rubysl-time (2.0.3)
230
+ rubysl-timeout (2.0.0)
231
+ rubysl-tmpdir (2.0.1)
232
+ rubysl-tsort (2.0.1)
233
+ rubysl-un (2.0.0)
234
+ rubysl-fileutils (~> 2.0)
235
+ rubysl-optparse (~> 2.0)
236
+ rubysl-uri (2.0.0)
237
+ rubysl-weakref (2.0.0)
238
+ rubysl-webrick (2.0.0)
239
+ rubysl-xmlrpc (2.0.0)
240
+ rubysl-yaml (2.1.0)
241
+ rubysl-zlib (2.0.1)
242
+ sqlite3 (1.3.10)
243
+ thor (0.19.1)
244
+ thread_safe (0.3.5)
245
+ tzinfo (1.2.2)
246
+ thread_safe (~> 0.1)
247
+
248
+ PLATFORMS
249
+ ruby
250
+
251
+ DEPENDENCIES
252
+ activerecord (= 4.1.10)
253
+ activerecord-jdbcsqlite3-adapter
254
+ acts_as_list!
255
+ appraisal
256
+ bundler (>= 1.0.0)
257
+ minitest
258
+ rake
259
+ rubinius-developer_tools
260
+ rubysl (~> 2.0)
261
+ rubysl-test-unit
262
+ sqlite3
@@ -0,0 +1,21 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "sqlite3", :platforms => [:ruby]
6
+ gem "activerecord-jdbcsqlite3-adapter", :platforms => [:jruby]
7
+ gem "rake"
8
+ gem "appraisal"
9
+ gem "activerecord", "4.2.1"
10
+
11
+ group :test do
12
+ gem "minitest"
13
+ end
14
+
15
+ platforms :rbx do
16
+ gem "rubysl", "~> 2.0"
17
+ gem "rubinius-developer_tools"
18
+ gem "rubysl-test-unit"
19
+ end
20
+
21
+ gemspec :path => "../"
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ acts_as_list (0.7.2)
5
+ activerecord (>= 3.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (4.2.1)
11
+ activesupport (= 4.2.1)
12
+ builder (~> 3.1)
13
+ activerecord (4.2.1)
14
+ activemodel (= 4.2.1)
15
+ activesupport (= 4.2.1)
16
+ arel (~> 6.0)
17
+ activesupport (4.2.1)
18
+ i18n (~> 0.7)
19
+ json (~> 1.7, >= 1.7.7)
20
+ minitest (~> 5.1)
21
+ thread_safe (~> 0.3, >= 0.3.4)
22
+ tzinfo (~> 1.1)
23
+ appraisal (1.0.3)
24
+ bundler
25
+ rake
26
+ thor (>= 0.14.0)
27
+ arel (6.0.0)
28
+ builder (3.2.2)
29
+ i18n (0.7.0)
30
+ json (1.8.2)
31
+ minitest (5.5.1)
32
+ rake (10.4.2)
33
+ sqlite3 (1.3.10)
34
+ thor (0.19.1)
35
+ thread_safe (0.3.5)
36
+ tzinfo (1.2.2)
37
+ thread_safe (~> 0.1)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ activerecord (= 4.2.1)
44
+ activerecord-jdbcsqlite3-adapter
45
+ acts_as_list!
46
+ appraisal
47
+ bundler (>= 1.0.0)
48
+ minitest
49
+ rake
50
+ rubinius-developer_tools
51
+ rubysl (~> 2.0)
52
+ rubysl-test-unit
53
+ sqlite3
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require "acts_as_list"
@@ -0,0 +1,15 @@
1
+ require 'acts_as_list/active_record/acts/list'
2
+
3
+ module ActsAsList
4
+ if defined?(Rails::Railtie)
5
+ class Railtie < Rails::Railtie
6
+ initializer 'acts_as_list.insert_into_active_record' do
7
+ ActiveSupport.on_load :active_record do
8
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::List)
9
+ end
10
+ end
11
+ end
12
+ else
13
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::List) if defined?(ActiveRecord)
14
+ end
15
+ end
@@ -0,0 +1,523 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
+ # The class that has this specified needs to have a +position+ column defined as an integer on
10
+ # the mapped database table.
11
+ #
12
+ # Todo list example:
13
+ #
14
+ # class TodoList < ActiveRecord::Base
15
+ # has_many :todo_items, order: "position"
16
+ # end
17
+ #
18
+ # class TodoItem < ActiveRecord::Base
19
+ # belongs_to :todo_list
20
+ # acts_as_list scope: :todo_list
21
+ # end
22
+ #
23
+ # todo_list.first.move_to_bottom
24
+ # todo_list.last.move_higher
25
+ module ClassMethods
26
+ # Configuration options are:
27
+ #
28
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
+ # Example: <tt>acts_as_list scope: 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ # * +top_of_list+ - defines the integer used for the top of the list. Defaults to 1. Use 0 to make the collection
34
+ # act more like an array in its indexing.
35
+ # * +add_new_at+ - specifies whether objects get added to the :top or :bottom of the list. (default: +bottom+)
36
+ # `nil` will result in new items not being added to the list on create
37
+ def acts_as_list(options = {})
38
+ configuration = { column: "position", scope: "1 = 1", top_of_list: 1, add_new_at: :bottom}
39
+ configuration.update(options) if options.is_a?(Hash)
40
+
41
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
42
+
43
+ if configuration[:scope].is_a?(Symbol)
44
+ scope_methods = %(
45
+ def scope_condition
46
+ { :#{configuration[:scope].to_s} => send(:#{configuration[:scope].to_s}) }
47
+ end
48
+
49
+ def scope_changed?
50
+ changes.include?(scope_name.to_s)
51
+ end
52
+ )
53
+ elsif configuration[:scope].is_a?(Array)
54
+ scope_methods = %(
55
+ def attrs
56
+ %w(#{configuration[:scope].join(" ")}).inject({}) do |memo,column|
57
+ memo[column.intern] = read_attribute(column.intern); memo
58
+ end
59
+ end
60
+
61
+ def scope_changed?
62
+ (attrs.keys & changes.keys.map(&:to_sym)).any?
63
+ end
64
+
65
+ def scope_condition
66
+ attrs
67
+ end
68
+ )
69
+ else
70
+ scope_methods = %(
71
+ def scope_condition
72
+ "#{configuration[:scope]}"
73
+ end
74
+
75
+ def scope_changed?() false end
76
+ )
77
+ end
78
+
79
+ class_eval <<-EOV
80
+ include ::ActiveRecord::Acts::List::InstanceMethods
81
+
82
+ def acts_as_list_top
83
+ #{configuration[:top_of_list]}.to_i
84
+ end
85
+
86
+ def acts_as_list_class
87
+ ::#{self.name}
88
+ end
89
+
90
+ def position_column
91
+ '#{configuration[:column]}'
92
+ end
93
+
94
+ def scope_name
95
+ '#{configuration[:scope]}'
96
+ end
97
+
98
+ def add_new_at
99
+ '#{configuration[:add_new_at]}'
100
+ end
101
+
102
+ def #{configuration[:column]}=(position)
103
+ write_attribute(:#{configuration[:column]}, position)
104
+ @position_changed = true
105
+ end
106
+
107
+ #{scope_methods}
108
+
109
+ # only add to attr_accessible
110
+ # if the class has some mass_assignment_protection
111
+
112
+ if defined?(accessible_attributes) and !accessible_attributes.blank?
113
+ attr_accessible :#{configuration[:column]}
114
+ end
115
+
116
+ attr_accessor :skip_acts_as_list_reload_position_callback, :skip_acts_as_list_decrement_positions_on_lower_items_callback, :skip_acts_as_list_check_scope_callback, :skip_acts_as_list_update_positions_callback, :skip_acts_as_list_check_top_position_callback, :skip_acts_as_list_add_to_list_top_callback, :skip_acts_as_list_add_to_list_bottom_callback
117
+
118
+ before_destroy :reload_position, :unless => :skip_acts_as_list_reload_position_callback
119
+ after_destroy :decrement_positions_on_lower_items, :unless => :skip_acts_as_list_decrement_positions_on_lower_items_callback
120
+ before_update :check_scope, :unless => :skip_acts_as_list_check_scope_callback
121
+ after_update :update_positions, :unless => :skip_acts_as_list_update_positions_callback
122
+ before_validation :check_top_position, :unless => :skip_acts_as_list_check_top_position_callback
123
+
124
+ scope :in_list, lambda { where("#{table_name}.#{configuration[:column]} IS NOT NULL") }
125
+ EOV
126
+
127
+ if configuration[:add_new_at].present?
128
+ self.send(:before_create, "add_to_list_#{configuration[:add_new_at]}", :unless => "skip_acts_as_list_add_to_list_#{configuration[:add_new_at]}_callback" )
129
+ end
130
+
131
+ end
132
+
133
+ def destroy_dependent_association_without_acts_as_list_callbacks(associaton_name)
134
+ after_destroy do |record|
135
+ self.send(associaton_name).each do |associated_record|
136
+ associated_record.skip_acts_as_list_destroy_callbacks!
137
+ associated_record.destroy
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
144
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
145
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
146
+ # the first in the list of all chapters.
147
+ module InstanceMethods
148
+
149
+ def skip_acts_as_list_all_callbacks!
150
+ self.skip_acts_as_list_reload_position_callback = true
151
+ self.skip_acts_as_list_decrement_positions_on_lower_items_callback = true
152
+ self.skip_acts_as_list_check_scope_callback = true
153
+ self.skip_acts_as_list_update_positions_callback = true
154
+ self.skip_acts_as_list_check_top_position_callback = true
155
+ self.skip_acts_as_list_add_to_list_top_callback = true
156
+ self.skip_acts_as_list_add_to_list_bottom_callback = true
157
+ end
158
+
159
+ def skip_acts_as_list_add_to_list_callbacks!
160
+ self.skip_acts_as_list_add_to_list_top_callback = true
161
+ self.skip_acts_as_list_add_to_list_bottom_callback = true
162
+ end
163
+
164
+ def skip_acts_as_list_destroy_callbacks!
165
+ self.skip_acts_as_list_reload_position_callback = true
166
+ self.skip_acts_as_list_decrement_positions_on_lower_items_callback = true
167
+ end
168
+
169
+ # Insert the item at the given position (defaults to the top position of 1).
170
+ def insert_at(position = acts_as_list_top)
171
+ insert_at_position(position)
172
+ end
173
+
174
+ # Swap positions with the next lower item, if one exists.
175
+ def move_lower
176
+ return unless lower_item
177
+
178
+ acts_as_list_class.transaction do
179
+ lower_item.decrement_position
180
+ increment_position
181
+ end
182
+ end
183
+
184
+ # Swap positions with the next higher item, if one exists.
185
+ def move_higher
186
+ return unless higher_item
187
+
188
+ acts_as_list_class.transaction do
189
+ higher_item.increment_position
190
+ decrement_position
191
+ end
192
+ end
193
+
194
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
195
+ # position adjusted accordingly.
196
+ def move_to_bottom
197
+ return unless in_list?
198
+ acts_as_list_class.transaction do
199
+ decrement_positions_on_lower_items
200
+ assume_bottom_position
201
+ end
202
+ end
203
+
204
+ # Move to the top of the list. If the item is already in the list, the items above it have their
205
+ # position adjusted accordingly.
206
+ def move_to_top
207
+ return unless in_list?
208
+ acts_as_list_class.transaction do
209
+ increment_positions_on_higher_items
210
+ assume_top_position
211
+ end
212
+ end
213
+
214
+ # Removes the item from the list.
215
+ def remove_from_list
216
+ if in_list?
217
+ decrement_positions_on_lower_items
218
+ set_list_position(nil)
219
+ end
220
+ end
221
+
222
+ # Move the item within scope. If a position within the new scope isn't supplied, the item will
223
+ # be appended to the end of the list.
224
+ def move_within_scope(scope_id)
225
+ send("#{scope_name}=", scope_id)
226
+ save!
227
+ end
228
+
229
+ # Increase the position of this item without adjusting the rest of the list.
230
+ def increment_position
231
+ return unless in_list?
232
+ set_list_position(self.send(position_column).to_i + 1)
233
+ end
234
+
235
+ # Decrease the position of this item without adjusting the rest of the list.
236
+ def decrement_position
237
+ return unless in_list?
238
+ set_list_position(self.send(position_column).to_i - 1)
239
+ end
240
+
241
+ # Return +true+ if this object is the first in the list.
242
+ def first?
243
+ return false unless in_list?
244
+ self.send(position_column) == acts_as_list_top
245
+ end
246
+
247
+ # Return +true+ if this object is the last in the list.
248
+ def last?
249
+ return false unless in_list?
250
+ self.send(position_column) == bottom_position_in_list
251
+ end
252
+
253
+ # Return the next higher item in the list.
254
+ def higher_item
255
+ return nil unless in_list?
256
+ acts_as_list_class.unscoped do
257
+ acts_as_list_class.where(scope_condition).where("#{position_column} < #{(send(position_column).to_i).to_s}").
258
+ order("#{acts_as_list_class.table_name}.#{position_column} DESC").first
259
+ end
260
+ end
261
+
262
+ # Return the next n higher items in the list
263
+ # selects all higher items by default
264
+ def higher_items(limit=nil)
265
+ limit ||= acts_as_list_list.count
266
+ position_value = send(position_column)
267
+ acts_as_list_list.
268
+ where("#{position_column} < ?", position_value).
269
+ where("#{position_column} >= ?", position_value - limit).
270
+ limit(limit).
271
+ order("#{acts_as_list_class.table_name}.#{position_column} ASC")
272
+ end
273
+
274
+ # Return the next lower item in the list.
275
+ def lower_item
276
+ return nil unless in_list?
277
+ acts_as_list_class.unscoped do
278
+ acts_as_list_class.where(scope_condition).where("#{position_column} > #{(send(position_column).to_i).to_s}").
279
+ order("#{acts_as_list_class.table_name}.#{position_column} ASC").first
280
+ end
281
+ end
282
+
283
+ # Return the next n lower items in the list
284
+ # selects all lower items by default
285
+ def lower_items(limit=nil)
286
+ limit ||= acts_as_list_list.count
287
+ position_value = send(position_column)
288
+ acts_as_list_list.
289
+ where("#{position_column} > ?", position_value).
290
+ where("#{position_column} <= ?", position_value + limit).
291
+ limit(limit).
292
+ order("#{acts_as_list_class.table_name}.#{position_column} ASC")
293
+ end
294
+
295
+ # Test if this record is in a list
296
+ def in_list?
297
+ !not_in_list?
298
+ end
299
+
300
+ def not_in_list?
301
+ send(position_column).nil?
302
+ end
303
+
304
+ def default_position
305
+ acts_as_list_class.columns_hash[position_column.to_s].default
306
+ end
307
+
308
+ def default_position?
309
+ default_position && default_position.to_i == send(position_column)
310
+ end
311
+
312
+ # Sets the new position and saves it
313
+ def set_list_position(new_position)
314
+ write_attribute position_column, new_position
315
+ save(validate: false)
316
+ end
317
+
318
+ private
319
+ def acts_as_list_list
320
+ acts_as_list_class.unscoped do
321
+ acts_as_list_class.where(scope_condition)
322
+ end
323
+ end
324
+
325
+ def add_to_list_top
326
+ increment_positions_on_all_items
327
+ self[position_column] = acts_as_list_top
328
+ end
329
+
330
+ def add_to_list_bottom
331
+ if not_in_list? || scope_changed? && !@position_changed || default_position?
332
+ self[position_column] = bottom_position_in_list.to_i + 1
333
+ else
334
+ increment_positions_on_lower_items(self[position_column], id)
335
+ end
336
+ end
337
+
338
+ # Overwrite this method to define the scope of the list changes
339
+ def scope_condition() {} end
340
+
341
+ # Returns the bottom position number in the list.
342
+ # bottom_position_in_list # => 2
343
+ def bottom_position_in_list(except = nil)
344
+ item = bottom_item(except)
345
+ item ? item.send(position_column) : acts_as_list_top - 1
346
+ end
347
+
348
+ # Returns the bottom item
349
+ def bottom_item(except = nil)
350
+ conditions = scope_condition
351
+ conditions = except ? "#{self.class.primary_key} != #{self.class.connection.quote(except.id)}" : {}
352
+ acts_as_list_class.unscoped do
353
+ acts_as_list_class.in_list.where(scope_condition).where(conditions).order("#{acts_as_list_class.table_name}.#{position_column} DESC").first
354
+ end
355
+ end
356
+
357
+ # Forces item to assume the bottom position in the list.
358
+ def assume_bottom_position
359
+ set_list_position(bottom_position_in_list(self).to_i + 1)
360
+ end
361
+
362
+ # Forces item to assume the top position in the list.
363
+ def assume_top_position
364
+ set_list_position(acts_as_list_top)
365
+ end
366
+
367
+ # This has the effect of moving all the higher items up one.
368
+ def decrement_positions_on_higher_items(position)
369
+ acts_as_list_class.unscoped do
370
+ acts_as_list_class.where(scope_condition).where(
371
+ "#{position_column} <= #{position}"
372
+ ).update_all(
373
+ "#{position_column} = (#{position_column} - 1)"
374
+ )
375
+ end
376
+ end
377
+
378
+ # This has the effect of moving all the lower items up one.
379
+ def decrement_positions_on_lower_items(position=nil)
380
+ return unless in_list?
381
+ position ||= send(position_column).to_i
382
+ acts_as_list_class.unscoped do
383
+ acts_as_list_class.where(scope_condition).where(
384
+ "#{position_column} > #{position}"
385
+ ).update_all(
386
+ "#{position_column} = (#{position_column} - 1)"
387
+ )
388
+ end
389
+ end
390
+
391
+ # This has the effect of moving all the higher items down one.
392
+ def increment_positions_on_higher_items
393
+ return unless in_list?
394
+ acts_as_list_class.unscoped do
395
+ acts_as_list_class.where(scope_condition).where(
396
+ "#{position_column} < #{send(position_column).to_i}"
397
+ ).update_all(
398
+ "#{position_column} = (#{position_column} + 1)"
399
+ )
400
+ end
401
+ end
402
+
403
+ # This has the effect of moving all the lower items down one.
404
+ def increment_positions_on_lower_items(position, avoid_id = nil)
405
+ avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
406
+
407
+ acts_as_list_class.unscoped do
408
+ acts_as_list_class.where(scope_condition).where(
409
+ "#{position_column} >= #{position}#{avoid_id_condition}"
410
+ ).update_all(
411
+ "#{position_column} = (#{position_column} + 1)"
412
+ )
413
+ end
414
+ end
415
+
416
+ # Increments position (<tt>position_column</tt>) of all items in the list.
417
+ def increment_positions_on_all_items
418
+ acts_as_list_class.unscoped do
419
+ acts_as_list_class.where(
420
+ scope_condition
421
+ ).update_all(
422
+ "#{position_column} = (#{position_column} + 1)"
423
+ )
424
+ end
425
+ end
426
+
427
+ # Reorders intermediate items to support moving an item from old_position to new_position.
428
+ def shuffle_positions_on_intermediate_items(old_position, new_position, avoid_id = nil)
429
+ return if old_position == new_position
430
+ avoid_id_condition = avoid_id ? " AND #{self.class.primary_key} != #{self.class.connection.quote(avoid_id)}" : ''
431
+
432
+ if old_position < new_position
433
+ # Decrement position of intermediate items
434
+ #
435
+ # e.g., if moving an item from 2 to 5,
436
+ # move [3, 4, 5] to [2, 3, 4]
437
+ acts_as_list_class.unscoped do
438
+ acts_as_list_class.where(scope_condition).where(
439
+ "#{position_column} > #{old_position}"
440
+ ).where(
441
+ "#{position_column} <= #{new_position}#{avoid_id_condition}"
442
+ ).update_all(
443
+ "#{position_column} = (#{position_column} - 1)"
444
+ )
445
+ end
446
+ else
447
+ # Increment position of intermediate items
448
+ #
449
+ # e.g., if moving an item from 5 to 2,
450
+ # move [2, 3, 4] to [3, 4, 5]
451
+ acts_as_list_class.unscoped do
452
+ acts_as_list_class.where(scope_condition).where(
453
+ "#{position_column} >= #{new_position}"
454
+ ).where(
455
+ "#{position_column} < #{old_position}#{avoid_id_condition}"
456
+ ).update_all(
457
+ "#{position_column} = (#{position_column} + 1)"
458
+ )
459
+ end
460
+ end
461
+ end
462
+
463
+ def insert_at_position(position)
464
+ return set_list_position(position) if new_record?
465
+ if in_list?
466
+ old_position = send(position_column).to_i
467
+ return if position == old_position
468
+ shuffle_positions_on_intermediate_items(old_position, position)
469
+ else
470
+ increment_positions_on_lower_items(position)
471
+ end
472
+ set_list_position(position)
473
+ end
474
+
475
+ # used by insert_at_position instead of remove_from_list, as postgresql raises error if position_column has non-null constraint
476
+ def store_at_0
477
+ if in_list?
478
+ old_position = send(position_column).to_i
479
+ set_list_position(0)
480
+ decrement_positions_on_lower_items(old_position)
481
+ end
482
+ end
483
+
484
+ def update_positions
485
+ old_position = send("#{position_column}_was").to_i
486
+ new_position = send(position_column).to_i
487
+
488
+ return unless acts_as_list_class.unscoped do
489
+ acts_as_list_class.where(scope_condition).where("#{position_column} = #{new_position}").count > 1
490
+ end
491
+ shuffle_positions_on_intermediate_items old_position, new_position, id
492
+ end
493
+
494
+ # Temporarily swap changes attributes with current attributes
495
+ def swap_changed_attributes
496
+ @changed_attributes.each { |k, _| @changed_attributes[k], self[k] =
497
+ self[k], @changed_attributes[k] }
498
+ end
499
+
500
+ def check_scope
501
+ if scope_changed?
502
+ swap_changed_attributes
503
+ send('decrement_positions_on_lower_items') if lower_item
504
+ swap_changed_attributes
505
+ send("add_to_list_#{add_new_at}")
506
+ end
507
+ end
508
+
509
+ def reload_position
510
+ self.reload
511
+ end
512
+
513
+ # This check is skipped if the position is currently the default position from the table
514
+ # as modifying the default position on creation is handled elsewhere
515
+ def check_top_position
516
+ if send(position_column) && !default_position? && send(position_column) < acts_as_list_top
517
+ self[position_column] = acts_as_list_top
518
+ end
519
+ end
520
+ end
521
+ end
522
+ end
523
+ end