activerecord 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (68) hide show
  1. data/CHANGELOG +250 -0
  2. data/README +17 -9
  3. data/dev-utils/eval_debugger.rb +1 -1
  4. data/install.rb +3 -1
  5. data/lib/active_record.rb +9 -2
  6. data/lib/active_record/acts/list.rb +178 -0
  7. data/lib/active_record/acts/tree.rb +44 -0
  8. data/lib/active_record/associations.rb +45 -8
  9. data/lib/active_record/associations/association_collection.rb +18 -9
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +14 -13
  11. data/lib/active_record/associations/has_many_association.rb +21 -12
  12. data/lib/active_record/base.rb +137 -37
  13. data/lib/active_record/callbacks.rb +30 -25
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +57 -33
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
  16. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -2
  17. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +298 -0
  18. data/lib/active_record/fixtures.rb +241 -147
  19. data/lib/active_record/support/class_inheritable_attributes.rb +5 -2
  20. data/lib/active_record/support/inflector.rb +13 -12
  21. data/lib/active_record/support/misc.rb +6 -0
  22. data/lib/active_record/timestamp.rb +33 -0
  23. data/lib/active_record/transactions.rb +1 -1
  24. data/lib/active_record/validations.rb +294 -16
  25. data/rakefile +3 -7
  26. data/test/abstract_unit.rb +1 -4
  27. data/test/associations_test.rb +17 -4
  28. data/test/base_test.rb +37 -5
  29. data/test/connections/native_sqlserver/connection.rb +15 -0
  30. data/test/deprecated_associations_test.rb +40 -38
  31. data/test/finder_test.rb +82 -4
  32. data/test/fixtures/accounts.yml +8 -0
  33. data/test/fixtures/company.rb +6 -0
  34. data/test/fixtures/company_in_module.rb +1 -1
  35. data/test/fixtures/db_definitions/mysql.sql +13 -0
  36. data/test/fixtures/db_definitions/postgresql.sql +13 -0
  37. data/test/fixtures/db_definitions/sqlite.sql +14 -0
  38. data/test/fixtures/db_definitions/sqlserver.sql +110 -0
  39. data/test/fixtures/db_definitions/sqlserver2.sql +4 -0
  40. data/test/fixtures/developer.rb +2 -2
  41. data/test/fixtures/developers.yml +13 -0
  42. data/test/fixtures/fixture_database.sqlite +0 -0
  43. data/test/fixtures/fixture_database_2.sqlite +0 -0
  44. data/test/fixtures/mixin.rb +17 -0
  45. data/test/fixtures/mixins.yml +14 -0
  46. data/test/fixtures/naked/csv/accounts.csv +1 -0
  47. data/test/fixtures/naked/yml/accounts.yml +1 -0
  48. data/test/fixtures/naked/yml/companies.yml +1 -0
  49. data/test/fixtures/naked/yml/courses.yml +1 -0
  50. data/test/fixtures/project.rb +6 -0
  51. data/test/fixtures/reply.rb +14 -1
  52. data/test/fixtures/topic.rb +2 -2
  53. data/test/fixtures/topics/first +1 -0
  54. data/test/fixtures_test.rb +42 -12
  55. data/test/inflector_test.rb +2 -1
  56. data/test/inheritance_test.rb +22 -12
  57. data/test/mixin_test.rb +138 -0
  58. data/test/pk_test.rb +4 -2
  59. data/test/reflection_test.rb +3 -3
  60. data/test/transactions_test.rb +15 -0
  61. data/test/validations_test.rb +229 -4
  62. metadata +24 -10
  63. data/lib/active_record/associations.rb.orig +0 -555
  64. data/test/deprecated_associations_test.rb.orig +0 -334
  65. data/test/fixtures/accounts/signals37 +0 -3
  66. data/test/fixtures/accounts/unknown +0 -2
  67. data/test/fixtures/developers/david +0 -2
  68. data/test/fixtures/developers/jamis +0 -2
data/CHANGELOG CHANGED
@@ -1,3 +1,253 @@
1
+ *1.2.0*
2
+
3
+ * Added Base.validates_inclusion_of that validates whether the value of the specified attribute is available in a particular enumerable
4
+ object. [what-a-day]
5
+
6
+ class Person < ActiveRecord::Base
7
+ validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
8
+ validates_inclusion_of :age, :in=>0..99
9
+ end
10
+
11
+ * Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. [Tobias Luetke] Example:
12
+
13
+ class TodoItem < ActiveRecord::Base
14
+ acts_as_list :scope => :todo_list_id
15
+ belongs_to :todo_list
16
+ end
17
+
18
+ * Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
19
+ categories and the likes. [Tobias Luetke]
20
+
21
+ * Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
22
+ created_at/created_on or updated_at/updated_on are present. [Tobias Luetke]
23
+
24
+ * Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Luetke]
25
+
26
+ * Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all
27
+
28
+ * Fixed that Base#find will return an array if given an array -- regardless of the number of elements #270 [Marten]
29
+
30
+ * Fixed that has_and_belongs_to_many would generate bad sql when naming conventions differed from using vanilla "id" everywhere [RedTerror]
31
+
32
+ * Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example:
33
+
34
+ ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'.
35
+ This error is raised because the column 'type' is reserved for storing the class in case of inheritance.
36
+ Please rename this column if you didn't intend it to be used for storing the inheritance class or
37
+ overwrite Company.inheritance_column to use another column for that information.
38
+
39
+ * Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't
40
+ have any magic side effects.
41
+
42
+ * Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin]
43
+
44
+ * Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples:
45
+
46
+ Before:
47
+ errors.add(:name, "must be shorter") if name.size > 10
48
+ errors.on(:name) # => "must be shorter"
49
+ errors.on("name") # => nil
50
+
51
+ After:
52
+ errors.add(:name, "must be shorter") if name.size > 10
53
+ errors.on(:name) # => "must be shorter"
54
+ errors.on("name") # => "must be shorter"
55
+
56
+ * Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
57
+ it against the regular expression provided. [Marcel]
58
+
59
+ class Person < ActiveRecord::Base
60
+ validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create
61
+ end
62
+
63
+ * Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Luetke]. Example:
64
+
65
+ Validates that the specified attribute matches the length restrictions supplied in either:
66
+
67
+ - configuration[:minimum]
68
+ - configuration[:maximum]
69
+ - configuration[:is]
70
+ - configuration[:within] (aka. configuration[:in])
71
+
72
+ Only one option can be used at a time.
73
+
74
+ class Person < ActiveRecord::Base
75
+ validates_length_of :first_name, :maximum=>30
76
+ validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
77
+ validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
78
+ validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
79
+ validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
80
+ end
81
+
82
+ * Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
83
+
84
+ * Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
85
+ Useful for making sure that only one user can be named "davidhh".
86
+
87
+ class Person < ActiveRecord::Base
88
+ validates_uniqueness_of :user_name
89
+ end
90
+
91
+ When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
92
+ attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
93
+
94
+
95
+ * Added Base.validates_confirmation_of that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
96
+
97
+ Model:
98
+ class Person < ActiveRecord::Base
99
+ validates_confirmation_of :password
100
+ end
101
+
102
+ View:
103
+ <%= password_field "person", "password" %>
104
+ <%= password_field "person", "password_confirmation" %>
105
+
106
+ The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
107
+ It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
108
+
109
+
110
+ * Added Base.validates_acceptance_of that encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
111
+
112
+ class Person < ActiveRecord::Base
113
+ validates_acceptance_of :terms_of_service
114
+ end
115
+
116
+ The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
117
+
118
+ NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
119
+
120
+
121
+ * Added validation macros to make the stackable just like the lifecycle callbacks. Examples:
122
+
123
+ class Person < ActiveRecord::Base
124
+ validate { |record| record.errors.add("name", "too short") unless name.size > 10 }
125
+ validate { |record| record.errors.add("name", "too long") unless name.size < 20 }
126
+ validate_on_create :validate_password
127
+
128
+ private
129
+ def validate_password
130
+ errors.add("password", "too short") unless password.size > 6
131
+ end
132
+ end
133
+
134
+ * Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples:
135
+
136
+ Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20]
137
+ Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
138
+
139
+ * Fixed value quoting in all generated SQL statements, so that integers are not surrounded in quotes and that all sanitation are happening
140
+ through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer
141
+ columns.
142
+
143
+ * Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
144
+ [Florian Weber]
145
+
146
+ * Added counter_sql option for has_many associations [bitsweat]. Documentation:
147
+
148
+ <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
149
+ specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
150
+
151
+ * Fixed that methods wrapped in callbacks still return their original result #260 [bitsweat]
152
+
153
+ * Fixed the Inflector to handle the movie/movies pair correctly #261 [Scott Baron]
154
+
155
+ * Added named bind-style variable interpolation #281 [Michael Koziarski]. Example:
156
+
157
+ Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }])
158
+
159
+ * Added bind-style variable interpolation for the condition arrays that uses the adapter's quote method [Michael Koziarski]
160
+
161
+ Before:
162
+ find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])]
163
+ find_first([ "firm_id = %s", firm_id ])] # unsafe!
164
+
165
+ After:
166
+ find_first([ "user_name = ? AND password = ?", user_name, password ])]
167
+ find_first([ "firm_id = ?", firm_id ])]
168
+
169
+ * Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information)
170
+
171
+ * Fixed fixtures using primary key fields called something else than "id" [dave]
172
+
173
+ * Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 [HariSeldon]
174
+
175
+ * Added reverse order of deleting fixtures, so referential keys can be maintained #247 [Tim Bates]
176
+
177
+ * Added relative path search for sqlite dbfiles in database.yml (if RAILS_ROOT is defined) #233 [bitsweat]
178
+
179
+ * Added option to establish_connection where you'll be able to leave out the parameter to have it use the RAILS_ENV environment variable
180
+
181
+ * Fixed problems with primary keys and postgresql sequences (#230) [Tim Bates]
182
+
183
+ * Added reloading for associations under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
184
+ This is turned on by default, but can be turned off with ActiveRecord::Base.reload_dependencies = false in production environments.
185
+
186
+ NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through
187
+ require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the
188
+ auto-loading associations.
189
+
190
+ * Added ERB capabilities to the fixture files for dynamic fixture generation. You don't need to do anything, just include ERB blocks like:
191
+
192
+ david:
193
+ id: 1
194
+ name: David
195
+
196
+ jamis:
197
+ id: 2
198
+ name: Jamis
199
+
200
+ <% for digit in 3..10 %>
201
+ dev_<%= digit %>:
202
+ id: <%= digit %>
203
+ name: fixture_<%= digit %>
204
+ <% end %>
205
+
206
+ * Changed the yaml fixture searcher to look in the root of the fixtures directory, so when you before could have something like:
207
+
208
+ fixtures/developers/fixtures.yaml
209
+ fixtures/accounts/fixtures.yaml
210
+
211
+ ...you now need to do:
212
+
213
+ fixtures/developers.yaml
214
+ fixtures/accounts.yaml
215
+
216
+ * Changed the fixture format from:
217
+
218
+ name: david
219
+ data:
220
+ id: 1
221
+ name: David Heinemeier Hansson
222
+ birthday: 1979-10-15
223
+ profession: Systems development
224
+ ---
225
+ name: steve
226
+ data:
227
+ id: 2
228
+ name: Steve Ross Kellock
229
+ birthday: 1974-09-27
230
+ profession: guy with keyboard
231
+
232
+ ...to:
233
+
234
+ david:
235
+ id: 1
236
+ name: David Heinemeier Hansson
237
+ birthday: 1979-10-15
238
+ profession: Systems development
239
+
240
+ steve:
241
+ id: 2
242
+ name: Steve Ross Kellock
243
+ birthday: 1974-09-27
244
+ profession: guy with keyboard
245
+
246
+ The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
247
+
248
+ * All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
249
+
250
+
1
251
  *1.1.0* (34)
2
252
 
3
253
  * Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
data/README CHANGED
@@ -57,18 +57,26 @@ A short rundown of the major features:
57
57
 
58
58
  * Validation rules that can differ for new or existing objects.
59
59
 
60
- class Post < ActiveRecord::Base
61
- def validate # validates on both creates and updates
62
- errors.add_on_empty "title"
63
- end
64
-
65
- def validate_on_update
66
- errors.add_on_empty "password"
67
- end
68
- end
60
+ class Account < ActiveRecord::Base
61
+ validates_presence_of :subdomain, :name, :email_address, :password
62
+ validates_uniqueness_of :subdomain
63
+ validates_acceptance_of :terms_of_service, :on => :create
64
+ validates_confirmation_of :password, :email_address, :on => :create
65
+ end
69
66
 
70
67
  Learn more in link:classes/ActiveRecord/Validations.html
71
68
 
69
+
70
+ * Acts that can make records work as lists or trees:
71
+
72
+ class Item < ActiveRecord::Base
73
+ belongs_to :list
74
+ acts_as_list :scope => :list
75
+ end
76
+
77
+ item.move_higher
78
+ item.move_to_bottom
79
+
72
80
 
73
81
  * Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
74
82
 
@@ -1,5 +1,5 @@
1
1
  # Require this file to see the methods Active Record generates as they are added.
2
- class Module
2
+ class Module #:nodoc:
3
3
  alias :old_module_eval :module_eval
4
4
  def module_eval(*args, &block)
5
5
  if args[0]
data/install.rb CHANGED
@@ -18,7 +18,7 @@ unless $sitedir
18
18
  end
19
19
  end
20
20
 
21
- makedirs = %w{ active_record/associations active_record/connection_adapters active_record/support active_record/vendor }
21
+ makedirs = %w{ active_record/associations active_record/connection_adapters active_record/support active_record/vendor active_record/acts }
22
22
  makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
23
23
 
24
24
  # deprecated files that should be removed
@@ -42,6 +42,8 @@ files = %w-
42
42
  active_record/fixtures.rb
43
43
  active_record/observer.rb
44
44
  active_record/reflection.rb
45
+ active_record/mixins/list.rb
46
+ active_record/mixins/touch.rb
45
47
  active_record/support/class_attribute_accessors.rb
46
48
  active_record/support/class_inheritable_attributes.rb
47
49
  active_record/support/clean_logger.rb
@@ -25,7 +25,7 @@
25
25
  $:.unshift(File.dirname(__FILE__))
26
26
 
27
27
  require 'active_record/support/clean_logger'
28
- # require 'active_record/support/array_ext'
28
+ require 'active_record/support/misc'
29
29
 
30
30
  require 'active_record/base'
31
31
  require 'active_record/observer'
@@ -35,6 +35,9 @@ require 'active_record/associations'
35
35
  require 'active_record/aggregations'
36
36
  require 'active_record/transactions'
37
37
  require 'active_record/reflection'
38
+ require 'active_record/timestamp'
39
+ require 'active_record/acts/list'
40
+ require 'active_record/acts/tree'
38
41
 
39
42
  ActiveRecord::Base.class_eval do
40
43
  include ActiveRecord::Validations
@@ -43,8 +46,12 @@ ActiveRecord::Base.class_eval do
43
46
  include ActiveRecord::Aggregations
44
47
  include ActiveRecord::Transactions
45
48
  include ActiveRecord::Reflection
49
+ include ActiveRecord::Timestamp
50
+ include ActiveRecord::Acts::Tree
51
+ include ActiveRecord::Acts::List
46
52
  end
47
53
 
48
54
  require 'active_record/connection_adapters/mysql_adapter'
49
55
  require 'active_record/connection_adapters/postgresql_adapter'
50
- require 'active_record/connection_adapters/sqlite_adapter'
56
+ require 'active_record/connection_adapters/sqlite_adapter'
57
+ require 'active_record/connection_adapters/sqlserver_adapter'
@@ -0,0 +1,178 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module List #:nodoc:
4
+ def self.append_features(base)
5
+ super
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ # This act provides the capabilities for sorting and reordering a number of objects in list.
10
+ # The class that has this specified needs to have a "position" column defined as an integer on
11
+ # the mapped database table.
12
+ #
13
+ # Todo list example:
14
+ #
15
+ # class TodoList < ActiveRecord::Base
16
+ # has_many :todo_items, :order => "position"
17
+ # end
18
+ #
19
+ # class TodoItem < ActiveRecord::Base
20
+ # belongs_to :todo_list
21
+ # acts_as_list :scope => :todo_list
22
+ # end
23
+ #
24
+ # todo_list.first.move_to_bottom
25
+ # todo_list.last.move_higher
26
+ module ClassMethods
27
+ # Configuration options are:
28
+ #
29
+ # * +column+ - specifies the column name to use for keeping the position integer (default: position)
30
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" (if that hasn't been already) and use that
31
+ # as the foreign key restriction. It's also possible to give it an entire string that is interpolated if you need a tighter scope than
32
+ # just a foreign key. Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ def acts_as_list(options = {})
34
+ configuration = { :column => "position", :scope => "1" }
35
+ configuration.update(options) if options.is_a?(Hash)
36
+
37
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
38
+
39
+ class_eval <<-EOV
40
+ include ActiveRecord::Acts::List::InstanceMethods
41
+
42
+ def position_column
43
+ '#{configuration[:column]}'
44
+ end
45
+
46
+ def scope_condition
47
+ "#{configuration[:scope].is_a?(Symbol) ? configuration[:scope].to_s + " = \#{" + configuration[:scope].to_s + "}" : configuration[:scope]}"
48
+ end
49
+
50
+ before_destroy :remove_from_list
51
+ before_create :add_to_list_bottom
52
+ EOV
53
+ end
54
+ end
55
+
56
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified.
57
+ module InstanceMethods
58
+ def move_lower
59
+ return unless lower_item
60
+
61
+ self.class.transaction do
62
+ lower_item.decrement_position
63
+ increment_position
64
+ end
65
+ end
66
+
67
+ def move_higher
68
+ return unless higher_item
69
+
70
+ self.class.transaction do
71
+ higher_item.increment_position
72
+ decrement_position
73
+ end
74
+ end
75
+
76
+ def move_to_bottom
77
+ self.class.transaction do
78
+ decrement_positions_on_lower_items
79
+ assume_bottom_position
80
+ end
81
+ end
82
+
83
+ def move_to_top
84
+ self.class.transaction do
85
+ increment_positions_on_higher_items
86
+ assume_top_position
87
+ end
88
+ end
89
+
90
+
91
+ def remove_from_list
92
+ decrement_positions_on_lower_items
93
+ end
94
+
95
+
96
+ def increment_position
97
+ update_attribute position_column, self.send(position_column).to_i + 1
98
+ end
99
+
100
+ def decrement_position
101
+ update_attribute position_column, self.send(position_column).to_i - 1
102
+ end
103
+
104
+
105
+ def first?
106
+ self.send(position_column) == 1
107
+ end
108
+
109
+ def last?
110
+ self.send(position_column) == bottom_position_in_list
111
+ end
112
+
113
+ private
114
+
115
+ def add_to_list_top
116
+ increment_positions_on_all_items
117
+ end
118
+
119
+ def add_to_list_bottom
120
+ write_attribute(position_column, bottom_position_in_list.to_i + 1)
121
+ end
122
+
123
+ # Overwrite this method to define the scope of the list changes
124
+ def scope_condition() "1" end
125
+
126
+ def higher_item
127
+ self.class.find_first(
128
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
129
+ )
130
+ end
131
+
132
+ def lower_item
133
+ self.class.find_first(
134
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
135
+ )
136
+ end
137
+
138
+ def bottom_position_in_list
139
+ item = bottom_item
140
+ item ? item.send(position_column) : 0
141
+ end
142
+
143
+ def bottom_item
144
+ self.class.find_first(
145
+ "#{scope_condition} ",
146
+ "#{position_column} DESC"
147
+ )
148
+ end
149
+
150
+ def assume_bottom_position
151
+ update_attribute position_column, bottom_position_in_list.to_i + 1
152
+ end
153
+
154
+ def assume_top_position
155
+ update_attribute position_column, 1
156
+ end
157
+
158
+ def decrement_positions_on_lower_items
159
+ self.class.update_all(
160
+ "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
161
+ )
162
+ end
163
+
164
+ def increment_positions_on_higher_items
165
+ self.class.update_all(
166
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column)}"
167
+ )
168
+ end
169
+
170
+ def increment_positions_on_all_items
171
+ self.class.update_all(
172
+ "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
173
+ )
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end