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.
- data/CHANGELOG +250 -0
- data/README +17 -9
- data/dev-utils/eval_debugger.rb +1 -1
- data/install.rb +3 -1
- data/lib/active_record.rb +9 -2
- data/lib/active_record/acts/list.rb +178 -0
- data/lib/active_record/acts/tree.rb +44 -0
- data/lib/active_record/associations.rb +45 -8
- data/lib/active_record/associations/association_collection.rb +18 -9
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +14 -13
- data/lib/active_record/associations/has_many_association.rb +21 -12
- data/lib/active_record/base.rb +137 -37
- data/lib/active_record/callbacks.rb +30 -25
- data/lib/active_record/connection_adapters/abstract_adapter.rb +57 -33
- data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +298 -0
- data/lib/active_record/fixtures.rb +241 -147
- data/lib/active_record/support/class_inheritable_attributes.rb +5 -2
- data/lib/active_record/support/inflector.rb +13 -12
- data/lib/active_record/support/misc.rb +6 -0
- data/lib/active_record/timestamp.rb +33 -0
- data/lib/active_record/transactions.rb +1 -1
- data/lib/active_record/validations.rb +294 -16
- data/rakefile +3 -7
- data/test/abstract_unit.rb +1 -4
- data/test/associations_test.rb +17 -4
- data/test/base_test.rb +37 -5
- data/test/connections/native_sqlserver/connection.rb +15 -0
- data/test/deprecated_associations_test.rb +40 -38
- data/test/finder_test.rb +82 -4
- data/test/fixtures/accounts.yml +8 -0
- data/test/fixtures/company.rb +6 -0
- data/test/fixtures/company_in_module.rb +1 -1
- data/test/fixtures/db_definitions/mysql.sql +13 -0
- data/test/fixtures/db_definitions/postgresql.sql +13 -0
- data/test/fixtures/db_definitions/sqlite.sql +14 -0
- data/test/fixtures/db_definitions/sqlserver.sql +110 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +4 -0
- data/test/fixtures/developer.rb +2 -2
- data/test/fixtures/developers.yml +13 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/mixin.rb +17 -0
- data/test/fixtures/mixins.yml +14 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/project.rb +6 -0
- data/test/fixtures/reply.rb +14 -1
- data/test/fixtures/topic.rb +2 -2
- data/test/fixtures/topics/first +1 -0
- data/test/fixtures_test.rb +42 -12
- data/test/inflector_test.rb +2 -1
- data/test/inheritance_test.rb +22 -12
- data/test/mixin_test.rb +138 -0
- data/test/pk_test.rb +4 -2
- data/test/reflection_test.rb +3 -3
- data/test/transactions_test.rb +15 -0
- data/test/validations_test.rb +229 -4
- metadata +24 -10
- data/lib/active_record/associations.rb.orig +0 -555
- data/test/deprecated_associations_test.rb.orig +0 -334
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/developers/david +0 -2
- 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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
|
data/dev-utils/eval_debugger.rb
CHANGED
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
|
data/lib/active_record.rb
CHANGED
@@ -25,7 +25,7 @@
|
|
25
25
|
$:.unshift(File.dirname(__FILE__))
|
26
26
|
|
27
27
|
require 'active_record/support/clean_logger'
|
28
|
-
|
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
|