activerecord 3.2.22.4 → 4.0.13
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2799 -617
- data/MIT-LICENSE +1 -1
- data/README.rdoc +23 -32
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations/alias_tracker.rb +4 -2
- data/lib/active_record/associations/association.rb +60 -46
- data/lib/active_record/associations/association_scope.rb +46 -40
- data/lib/active_record/associations/belongs_to_association.rb +17 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +73 -56
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +130 -96
- data/lib/active_record/associations/collection_proxy.rb +916 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
- data/lib/active_record/associations/has_many_association.rb +35 -8
- data/lib/active_record/associations/has_many_through_association.rb +37 -17
- data/lib/active_record/associations/has_one_association.rb +42 -19
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
- data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
- data/lib/active_record/associations/join_dependency.rb +30 -9
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/preloader.rb +20 -43
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +223 -282
- data/lib/active_record/attribute_assignment.rb +134 -154
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +36 -29
- data/lib/active_record/attribute_methods/primary_key.rb +45 -31
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +67 -90
- data/lib/active_record/attribute_methods/serialization.rb +133 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
- data/lib/active_record/attribute_methods/write.rb +34 -39
- data/lib/active_record/attribute_methods.rb +268 -108
- data/lib/active_record/autosave_association.rb +80 -73
- data/lib/active_record/base.rb +54 -451
- data/lib/active_record/callbacks.rb +60 -22
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
- data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
- data/lib/active_record/connection_adapters/column.rb +67 -36
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
- data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
- data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +472 -0
- data/lib/active_record/counter_cache.rb +107 -108
- data/lib/active_record/dynamic_matchers.rb +115 -63
- data/lib/active_record/errors.rb +36 -18
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +8 -4
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +159 -155
- data/lib/active_record/inheritance.rb +93 -59
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +39 -43
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration/command_recorder.rb +102 -33
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +411 -173
- data/lib/active_record/model_schema.rb +81 -94
- data/lib/active_record/nested_attributes.rb +173 -131
- data/lib/active_record/null_relation.rb +67 -0
- data/lib/active_record/persistence.rb +254 -106
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +113 -38
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +115 -368
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +110 -61
- data/lib/active_record/relation/batches.rb +29 -29
- data/lib/active_record/relation/calculations.rb +155 -125
- data/lib/active_record/relation/delegation.rb +94 -18
- data/lib/active_record/relation/finder_methods.rb +151 -203
- data/lib/active_record/relation/merger.rb +188 -0
- data/lib/active_record/relation/predicate_builder.rb +85 -42
- data/lib/active_record/relation/query_methods.rb +793 -146
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/relation.rb +293 -173
- data/lib/active_record/result.rb +48 -7
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +41 -54
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +41 -41
- data/lib/active_record/schema_migration.rb +46 -0
- data/lib/active_record/scoping/default.rb +56 -52
- data/lib/active_record/scoping/named.rb +78 -103
- data/lib/active_record/scoping.rb +54 -124
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +131 -15
- data/lib/active_record/tasks/database_tasks.rb +204 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +67 -38
- data/lib/active_record/timestamp.rb +16 -11
- data/lib/active_record/transactions.rb +73 -51
- data/lib/active_record/validations/associated.rb +19 -13
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +110 -57
- data/lib/active_record/validations.rb +18 -17
- data/lib/active_record/version.rb +7 -6
- data/lib/active_record.rb +63 -45
- data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -5
- metadata +43 -29
- data/examples/associations.png +0 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -4,6 +4,7 @@ module ActiveRecord
|
|
4
4
|
# See ActiveRecord::Transactions::ClassMethods for documentation.
|
5
5
|
module Transactions
|
6
6
|
extend ActiveSupport::Concern
|
7
|
+
ACTIONS = [:create, :destroy, :update]
|
7
8
|
|
8
9
|
class TransactionError < ActiveRecordError # :nodoc:
|
9
10
|
end
|
@@ -108,10 +109,10 @@ module ActiveRecord
|
|
108
109
|
#
|
109
110
|
# # Suppose that we have a Number model with a unique column called 'i'.
|
110
111
|
# Number.transaction do
|
111
|
-
# Number.create(:
|
112
|
+
# Number.create(i: 0)
|
112
113
|
# begin
|
113
114
|
# # This will raise a unique constraint error...
|
114
|
-
# Number.create(:
|
115
|
+
# Number.create(i: 0)
|
115
116
|
# rescue ActiveRecord::StatementInvalid
|
116
117
|
# # ...which we ignore.
|
117
118
|
# end
|
@@ -119,7 +120,7 @@ module ActiveRecord
|
|
119
120
|
# # On PostgreSQL, the transaction is now unusable. The following
|
120
121
|
# # statement will cause a PostgreSQL error, even though the unique
|
121
122
|
# # constraint is no longer violated:
|
122
|
-
# Number.create(:
|
123
|
+
# Number.create(i: 1)
|
123
124
|
# # => "PGError: ERROR: current transaction is aborted, commands
|
124
125
|
# # ignored until end of transaction block"
|
125
126
|
# end
|
@@ -134,9 +135,9 @@ module ActiveRecord
|
|
134
135
|
# transaction. For example, the following behavior may be surprising:
|
135
136
|
#
|
136
137
|
# User.transaction do
|
137
|
-
# User.create(:
|
138
|
+
# User.create(username: 'Kotori')
|
138
139
|
# User.transaction do
|
139
|
-
# User.create(:
|
140
|
+
# User.create(username: 'Nemu')
|
140
141
|
# raise ActiveRecord::Rollback
|
141
142
|
# end
|
142
143
|
# end
|
@@ -147,25 +148,25 @@ module ActiveRecord
|
|
147
148
|
# real transaction is committed.
|
148
149
|
#
|
149
150
|
# In order to get a ROLLBACK for the nested transaction you may ask for a real
|
150
|
-
# sub-transaction by passing <tt
|
151
|
+
# sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
|
151
152
|
# the database rolls back to the beginning of the sub-transaction without rolling
|
152
153
|
# back the parent transaction. If we add it to the previous example:
|
153
154
|
#
|
154
155
|
# User.transaction do
|
155
|
-
# User.create(:
|
156
|
-
# User.transaction(:
|
157
|
-
# User.create(:
|
156
|
+
# User.create(username: 'Kotori')
|
157
|
+
# User.transaction(requires_new: true) do
|
158
|
+
# User.create(username: 'Nemu')
|
158
159
|
# raise ActiveRecord::Rollback
|
159
160
|
# end
|
160
161
|
# end
|
161
162
|
#
|
162
|
-
# only "Kotori" is created.
|
163
|
+
# only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
|
163
164
|
#
|
164
165
|
# Most databases don't support true nested transactions. At the time of
|
165
166
|
# writing, the only database that we're aware of that supports true nested
|
166
167
|
# transactions, is MS-SQL. Because of this, Active Record emulates nested
|
167
168
|
# transactions by using savepoints on MySQL and PostgreSQL. See
|
168
|
-
# http://dev.mysql.com/doc/refman/5.
|
169
|
+
# http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
|
169
170
|
# for more information about savepoints.
|
170
171
|
#
|
171
172
|
# === Callbacks
|
@@ -194,7 +195,7 @@ module ActiveRecord
|
|
194
195
|
# automatically released. The following example demonstrates the problem:
|
195
196
|
#
|
196
197
|
# Model.connection.transaction do # BEGIN
|
197
|
-
# Model.connection.transaction(:
|
198
|
+
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
198
199
|
# Model.connection.create_table(...) # active_record_1 now automatically released
|
199
200
|
# end # RELEASE savepoint active_record_1
|
200
201
|
# # ^^^^ BOOM! database error!
|
@@ -213,22 +214,17 @@ module ActiveRecord
|
|
213
214
|
# You can specify that the callback should only be fired by a certain action with
|
214
215
|
# the +:on+ option:
|
215
216
|
#
|
216
|
-
# after_commit :do_foo, :
|
217
|
-
# after_commit :do_bar, :
|
218
|
-
# after_commit :do_baz, :
|
217
|
+
# after_commit :do_foo, on: :create
|
218
|
+
# after_commit :do_bar, on: :update
|
219
|
+
# after_commit :do_baz, on: :destroy
|
219
220
|
#
|
220
|
-
#
|
221
|
-
#
|
222
|
-
# after_commit :do_zoo, :if => :persisted?
|
221
|
+
# after_commit :do_foo_bar, on: [:create, :update]
|
222
|
+
# after_commit :do_bar_baz, on: [:update, :destroy]
|
223
223
|
#
|
224
224
|
# Note that transactional fixtures do not play well with this feature. Please
|
225
225
|
# use the +test_after_commit+ gem to have these hooks fired in tests.
|
226
226
|
def after_commit(*args, &block)
|
227
|
-
|
228
|
-
if options.is_a?(Hash) && options[:on]
|
229
|
-
options[:if] = Array.wrap(options[:if])
|
230
|
-
options[:if] << "transaction_include_action?(:#{options[:on]})"
|
231
|
-
end
|
227
|
+
set_options_for_callbacks!(args)
|
232
228
|
set_callback(:commit, :after, *args, &block)
|
233
229
|
end
|
234
230
|
|
@@ -236,12 +232,27 @@ module ActiveRecord
|
|
236
232
|
#
|
237
233
|
# Please check the documentation of +after_commit+ for options.
|
238
234
|
def after_rollback(*args, &block)
|
235
|
+
set_options_for_callbacks!(args)
|
236
|
+
set_callback(:rollback, :after, *args, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def set_options_for_callbacks!(args)
|
239
242
|
options = args.last
|
240
243
|
if options.is_a?(Hash) && options[:on]
|
241
|
-
|
242
|
-
options[:if]
|
244
|
+
assert_valid_transaction_action(options[:on])
|
245
|
+
options[:if] = Array(options[:if])
|
246
|
+
fire_on = Array(options[:on])
|
247
|
+
options[:if] << "transaction_include_any_action?(#{fire_on})"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def assert_valid_transaction_action(actions)
|
252
|
+
actions = Array(actions)
|
253
|
+
if (actions - ACTIONS).any?
|
254
|
+
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
|
243
255
|
end
|
244
|
-
set_callback(:rollback, :after, *args, &block)
|
245
256
|
end
|
246
257
|
end
|
247
258
|
|
@@ -264,35 +275,40 @@ module ActiveRecord
|
|
264
275
|
with_transaction_returning_status { super }
|
265
276
|
end
|
266
277
|
|
278
|
+
def touch(*) #:nodoc:
|
279
|
+
with_transaction_returning_status { super }
|
280
|
+
end
|
281
|
+
|
267
282
|
# Reset id and @new_record if the transaction rolls back.
|
268
283
|
def rollback_active_record_state!
|
269
284
|
remember_transaction_record_state
|
270
285
|
yield
|
271
286
|
rescue Exception
|
272
|
-
IdentityMap.remove(self) if IdentityMap.enabled?
|
273
287
|
restore_transaction_record_state
|
274
288
|
raise
|
275
289
|
ensure
|
276
290
|
clear_transaction_record_state
|
277
291
|
end
|
278
292
|
|
279
|
-
# Call the after_commit callbacks
|
293
|
+
# Call the +after_commit+ callbacks.
|
294
|
+
#
|
295
|
+
# Ensure that it is not called if the object was never persisted (failed create),
|
296
|
+
# but call it after the commit of a destroyed object.
|
280
297
|
def committed! #:nodoc:
|
281
|
-
run_callbacks :commit
|
298
|
+
run_callbacks :commit if destroyed? || persisted?
|
282
299
|
ensure
|
283
|
-
|
300
|
+
@_start_transaction_state.clear
|
284
301
|
end
|
285
302
|
|
286
|
-
# Call the
|
303
|
+
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
|
287
304
|
# state should be rolled back to the beginning or just to the last savepoint.
|
288
305
|
def rolledback!(force_restore_state = false) #:nodoc:
|
289
306
|
run_callbacks :rollback
|
290
307
|
ensure
|
291
|
-
IdentityMap.remove(self) if IdentityMap.enabled?
|
292
308
|
restore_transaction_record_state(force_restore_state)
|
293
309
|
end
|
294
310
|
|
295
|
-
# Add the record to the current transaction so that the
|
311
|
+
# Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
|
296
312
|
# can be called.
|
297
313
|
def add_to_transaction
|
298
314
|
if self.class.connection.add_transaction_record(self)
|
@@ -310,7 +326,13 @@ module ActiveRecord
|
|
310
326
|
status = nil
|
311
327
|
self.class.transaction do
|
312
328
|
add_to_transaction
|
313
|
-
|
329
|
+
begin
|
330
|
+
status = yield
|
331
|
+
rescue ActiveRecord::Rollback
|
332
|
+
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
333
|
+
status = nil
|
334
|
+
end
|
335
|
+
|
314
336
|
raise ActiveRecord::Rollback unless status
|
315
337
|
end
|
316
338
|
status
|
@@ -320,7 +342,6 @@ module ActiveRecord
|
|
320
342
|
|
321
343
|
# Save the new record state and id of a record so it can be restored later if a transaction fails.
|
322
344
|
def remember_transaction_record_state #:nodoc:
|
323
|
-
@_start_transaction_state ||= {}
|
324
345
|
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
|
325
346
|
unless @_start_transaction_state.include?(:new_record)
|
326
347
|
@_start_transaction_state[:new_record] = @new_record
|
@@ -334,47 +355,48 @@ module ActiveRecord
|
|
334
355
|
|
335
356
|
# Clear the new record state and id of a record.
|
336
357
|
def clear_transaction_record_state #:nodoc:
|
337
|
-
|
338
|
-
|
339
|
-
remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
|
340
|
-
end
|
358
|
+
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
359
|
+
@_start_transaction_state.clear if @_start_transaction_state[:level] < 1
|
341
360
|
end
|
342
361
|
|
343
362
|
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
|
344
363
|
def restore_transaction_record_state(force = false) #:nodoc:
|
345
|
-
|
364
|
+
unless @_start_transaction_state.empty?
|
346
365
|
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
347
366
|
if @_start_transaction_state[:level] < 1 || force
|
348
|
-
restore_state =
|
367
|
+
restore_state = @_start_transaction_state
|
349
368
|
was_frozen = restore_state[:frozen?]
|
350
369
|
@attributes = @attributes.dup if @attributes.frozen?
|
351
370
|
@new_record = restore_state[:new_record]
|
352
371
|
@destroyed = restore_state[:destroyed]
|
353
372
|
if restore_state.has_key?(:id)
|
354
|
-
self.
|
373
|
+
write_attribute(self.class.primary_key, restore_state[:id])
|
355
374
|
else
|
356
375
|
@attributes.delete(self.class.primary_key)
|
357
376
|
@attributes_cache.delete(self.class.primary_key)
|
358
377
|
end
|
359
378
|
@attributes.freeze if was_frozen
|
379
|
+
@_start_transaction_state.clear
|
360
380
|
end
|
361
381
|
end
|
362
382
|
end
|
363
383
|
|
364
384
|
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
|
365
385
|
def transaction_record_state(state) #:nodoc:
|
366
|
-
@_start_transaction_state[state]
|
386
|
+
@_start_transaction_state[state]
|
367
387
|
end
|
368
388
|
|
369
389
|
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
|
370
|
-
def
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
390
|
+
def transaction_include_any_action?(actions) #:nodoc:
|
391
|
+
actions.any? do |action|
|
392
|
+
case action
|
393
|
+
when :create
|
394
|
+
transaction_record_state(:new_record)
|
395
|
+
when :destroy
|
396
|
+
destroyed?
|
397
|
+
when :update
|
398
|
+
!(transaction_record_state(:new_record) || destroyed?)
|
399
|
+
end
|
378
400
|
end
|
379
401
|
end
|
380
402
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Validations
|
3
|
-
class AssociatedValidator < ActiveModel::EachValidator
|
3
|
+
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
|
4
4
|
def validate_each(record, attribute, value)
|
5
5
|
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
|
6
6
|
record.errors.add(attribute, :invalid, options.merge(:value => value))
|
@@ -9,7 +9,8 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
# Validates whether the associated object or objects are all valid
|
12
|
+
# Validates whether the associated object or objects are all valid.
|
13
|
+
# Works with any kind of association.
|
13
14
|
#
|
14
15
|
# class Book < ActiveRecord::Base
|
15
16
|
# has_many :pages
|
@@ -18,23 +19,28 @@ module ActiveRecord
|
|
18
19
|
# validates_associated :pages, :library
|
19
20
|
# end
|
20
21
|
#
|
21
|
-
# WARNING: This validation must not be used on both ends of an association.
|
22
|
+
# WARNING: This validation must not be used on both ends of an association.
|
23
|
+
# Doing so will lead to a circular dependency and cause infinite recursion.
|
22
24
|
#
|
23
|
-
# NOTE: This validation will not fail if the association hasn't been
|
24
|
-
# ensure that the association is both present and
|
25
|
-
# use +validates_presence_of+.
|
25
|
+
# NOTE: This validation will not fail if the association hasn't been
|
26
|
+
# assigned. If you want to ensure that the association is both present and
|
27
|
+
# guaranteed to be valid, you also need to use +validates_presence_of+.
|
26
28
|
#
|
27
29
|
# Configuration options:
|
28
|
-
#
|
30
|
+
#
|
31
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
|
29
32
|
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
|
30
33
|
# validation contexts by default (+nil+), other options are <tt>:create</tt>
|
31
34
|
# and <tt>:update</tt>.
|
32
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
33
|
-
# occur (e.g. <tt
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
35
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
36
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
37
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
38
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
39
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
40
|
+
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
41
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
42
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
43
|
+
# value.
|
38
44
|
def validates_associated(*attr_names)
|
39
45
|
validates_with AssociatedValidator, _merge_attributes(attr_names)
|
40
46
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Validations
|
3
|
+
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
|
4
|
+
def validate(record)
|
5
|
+
super
|
6
|
+
attributes.each do |attribute|
|
7
|
+
next unless record.class.reflect_on_association(attribute)
|
8
|
+
associated_records = Array.wrap(record.send(attribute))
|
9
|
+
|
10
|
+
# Superclass validates presence. Ensure present records aren't about to be destroyed.
|
11
|
+
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
|
12
|
+
record.errors.add(attribute, :blank, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# Validates that the specified attributes are not blank (as defined by
|
20
|
+
# Object#blank?), and, if the attribute is an association, that the
|
21
|
+
# associated object is not marked for destruction. Happens by default
|
22
|
+
# on save.
|
23
|
+
#
|
24
|
+
# class Person < ActiveRecord::Base
|
25
|
+
# has_one :face
|
26
|
+
# validates_presence_of :face
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# The face attribute must be in the object and it cannot be blank or marked
|
30
|
+
# for destruction.
|
31
|
+
#
|
32
|
+
# If you want to validate the presence of a boolean field (where the real values
|
33
|
+
# are true and false), you will want to use
|
34
|
+
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
|
35
|
+
#
|
36
|
+
# This is due to the way Object#blank? handles boolean values:
|
37
|
+
# <tt>false.blank? # => true</tt>.
|
38
|
+
#
|
39
|
+
# This validator defers to the ActiveModel validation for presence, adding the
|
40
|
+
# check to see that an associated object is not marked for destruction. This
|
41
|
+
# prevents the parent object from validating successfully and saving, which then
|
42
|
+
# deletes the associated object, thus putting the parent object into an invalid
|
43
|
+
# state.
|
44
|
+
#
|
45
|
+
# Configuration options:
|
46
|
+
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
|
47
|
+
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
|
48
|
+
# validation contexts by default (+nil+), other options are <tt>:create</tt>
|
49
|
+
# and <tt>:update</tt>.
|
50
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
|
51
|
+
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
|
52
|
+
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
|
53
|
+
# or string should return or evaluate to a +true+ or +false+ value.
|
54
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
55
|
+
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
56
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
|
57
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
58
|
+
# * <tt>:strict</tt> - Specifies whether validation should be strict.
|
59
|
+
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
|
60
|
+
def validates_presence_of(*attr_names)
|
61
|
+
validates_with PresenceValidator, _merge_attributes(attr_names)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Validations
|
5
|
-
class UniquenessValidator < ActiveModel::EachValidator
|
3
|
+
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
|
6
4
|
def initialize(options)
|
7
|
-
|
5
|
+
if options[:conditions] && !options[:conditions].respond_to?(:call)
|
6
|
+
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
|
7
|
+
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
|
8
|
+
end
|
9
|
+
super({ case_sensitive: true }.merge!(options))
|
8
10
|
end
|
9
11
|
|
10
12
|
# Unfortunately, we have to tie Uniqueness validators to a class.
|
@@ -15,23 +17,19 @@ module ActiveRecord
|
|
15
17
|
def validate_each(record, attribute, value)
|
16
18
|
finder_class = find_finder_class_for(record)
|
17
19
|
table = finder_class.arel_table
|
18
|
-
|
19
|
-
coder = record.class.serialized_attributes[attribute.to_s]
|
20
|
-
|
21
|
-
if value && coder
|
22
|
-
value = coder.dump value
|
23
|
-
end
|
20
|
+
value = deserialize_attribute(record, attribute, value)
|
24
21
|
|
25
22
|
relation = build_relation(finder_class, table, attribute, value)
|
26
|
-
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.
|
23
|
+
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
|
24
|
+
relation = scope_relation(record, table, relation)
|
25
|
+
relation = finder_class.unscoped.where(relation)
|
26
|
+
relation = relation.merge(options[:conditions]) if options[:conditions]
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
28
|
+
if relation.exists?
|
29
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
30
|
+
error_options[:value] = value
|
32
31
|
|
33
|
-
|
34
|
-
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
32
|
+
record.errors.add(attribute, :taken, error_options)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
@@ -46,67 +44,122 @@ module ActiveRecord
|
|
46
44
|
class_hierarchy = [record.class]
|
47
45
|
|
48
46
|
while class_hierarchy.first != @klass
|
49
|
-
class_hierarchy.
|
47
|
+
class_hierarchy.unshift(class_hierarchy.first.superclass)
|
50
48
|
end
|
51
49
|
|
52
50
|
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
53
51
|
end
|
54
52
|
|
55
53
|
def build_relation(klass, table, attribute, value) #:nodoc:
|
56
|
-
|
57
|
-
|
54
|
+
if reflection = klass.reflect_on_association(attribute)
|
55
|
+
attribute = reflection.foreign_key
|
56
|
+
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
attribute_name = attribute.to_s
|
60
|
+
|
61
|
+
# the attribute may be an aliased attribute
|
62
|
+
if klass.attribute_aliases[attribute_name]
|
63
|
+
attribute = klass.attribute_aliases[attribute_name]
|
64
|
+
attribute_name = attribute.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
column = klass.columns_hash[attribute_name]
|
68
|
+
value = klass.connection.type_cast(value, column)
|
69
|
+
value = value.to_s[0, column.limit] if value && column.limit && column.text?
|
58
70
|
|
59
71
|
if !options[:case_sensitive] && value && column.text?
|
60
72
|
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
|
61
|
-
|
73
|
+
klass.connection.case_insensitive_comparison(table, attribute, column, value)
|
62
74
|
else
|
63
|
-
value
|
64
|
-
|
75
|
+
value = klass.connection.case_sensitive_modifier(value) unless value.nil?
|
76
|
+
table[attribute].eq(value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def scope_relation(record, table, relation)
|
81
|
+
Array(options[:scope]).each do |scope_item|
|
82
|
+
if reflection = record.class.reflect_on_association(scope_item)
|
83
|
+
scope_value = record.send(reflection.foreign_key)
|
84
|
+
scope_item = reflection.foreign_key
|
85
|
+
else
|
86
|
+
scope_value = record.read_attribute(scope_item)
|
87
|
+
end
|
88
|
+
relation = relation.and(table[scope_item].eq(scope_value))
|
65
89
|
end
|
66
90
|
|
67
91
|
relation
|
68
92
|
end
|
93
|
+
|
94
|
+
def deserialize_attribute(record, attribute, value)
|
95
|
+
coder = record.class.serialized_attributes[attribute.to_s]
|
96
|
+
value = coder.dump value if value && coder
|
97
|
+
value
|
98
|
+
end
|
69
99
|
end
|
70
100
|
|
71
101
|
module ClassMethods
|
72
|
-
# Validates whether the value of the specified attributes are unique
|
73
|
-
# Useful for making sure that only one user
|
102
|
+
# Validates whether the value of the specified attributes are unique
|
103
|
+
# across the system. Useful for making sure that only one user
|
74
104
|
# can be named "davidhh".
|
75
105
|
#
|
76
106
|
# class Person < ActiveRecord::Base
|
77
107
|
# validates_uniqueness_of :user_name
|
78
108
|
# end
|
79
109
|
#
|
80
|
-
# It can also validate whether the value of the specified attributes are
|
110
|
+
# It can also validate whether the value of the specified attributes are
|
111
|
+
# unique based on a <tt>:scope</tt> parameter:
|
81
112
|
#
|
82
113
|
# class Person < ActiveRecord::Base
|
83
|
-
# validates_uniqueness_of :user_name, :
|
114
|
+
# validates_uniqueness_of :user_name, scope: :account_id
|
84
115
|
# end
|
85
116
|
#
|
86
|
-
# Or even multiple scope parameters. For example, making sure that a
|
87
|
-
# per semester for a particular
|
117
|
+
# Or even multiple scope parameters. For example, making sure that a
|
118
|
+
# teacher can only be on the schedule once per semester for a particular
|
119
|
+
# class.
|
88
120
|
#
|
89
121
|
# class TeacherSchedule < ActiveRecord::Base
|
90
|
-
# validates_uniqueness_of :teacher_id, :
|
122
|
+
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# It is also possible to limit the uniqueness constraint to a set of
|
126
|
+
# records matching certain conditions. In this example archived articles
|
127
|
+
# are not being taken into consideration when validating uniqueness
|
128
|
+
# of the title attribute:
|
129
|
+
#
|
130
|
+
# class Article < ActiveRecord::Base
|
131
|
+
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
|
91
132
|
# end
|
92
133
|
#
|
93
|
-
# When the record is created, a check is performed to make sure that no
|
94
|
-
# with the given value for the specified
|
134
|
+
# When the record is created, a check is performed to make sure that no
|
135
|
+
# record exists in the database with the given value for the specified
|
136
|
+
# attribute (that maps to a column). When the record is updated,
|
95
137
|
# the same check is made but disregarding the record itself.
|
96
138
|
#
|
97
139
|
# Configuration options:
|
98
|
-
#
|
99
|
-
# * <tt>:
|
100
|
-
#
|
101
|
-
# * <tt>:
|
102
|
-
#
|
103
|
-
# * <tt>:
|
104
|
-
#
|
105
|
-
#
|
106
|
-
# * <tt>:
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
140
|
+
#
|
141
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is:
|
142
|
+
# "has already been taken").
|
143
|
+
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
|
144
|
+
# the uniqueness constraint.
|
145
|
+
# * <tt>:conditions</tt> - Specify the conditions to be included as a
|
146
|
+
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
|
147
|
+
# (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
|
148
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
|
149
|
+
# non-text columns (+true+ by default).
|
150
|
+
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
|
151
|
+
# attribute is +nil+ (default is +false+).
|
152
|
+
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
|
153
|
+
# attribute is blank (default is +false+).
|
154
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
155
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
156
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
157
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
158
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
159
|
+
# determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
|
160
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
161
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
162
|
+
# value.
|
110
163
|
#
|
111
164
|
# === Concurrency and integrity
|
112
165
|
#
|
@@ -126,11 +179,11 @@ module ActiveRecord
|
|
126
179
|
# WHERE title = 'My Post' |
|
127
180
|
# |
|
128
181
|
# | # User 2 does the same thing and also
|
129
|
-
# | # infers that
|
182
|
+
# | # infers that their title is unique.
|
130
183
|
# | SELECT * FROM comments
|
131
184
|
# | WHERE title = 'My Post'
|
132
185
|
# |
|
133
|
-
# # User 1 inserts
|
186
|
+
# # User 1 inserts their comment. |
|
134
187
|
# INSERT INTO comments |
|
135
188
|
# (title, content) VALUES |
|
136
189
|
# ('My Post', 'hi!') |
|
@@ -156,22 +209,22 @@ module ActiveRecord
|
|
156
209
|
# exception. You can either choose to let this error propagate (which
|
157
210
|
# will result in the default Rails exception page being shown), or you
|
158
211
|
# can catch it and restart the transaction (e.g. by telling the user
|
159
|
-
# that the title already exists, and asking
|
160
|
-
# This technique is also known as
|
161
|
-
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
|
212
|
+
# that the title already exists, and asking them to re-enter the title).
|
213
|
+
# This technique is also known as
|
214
|
+
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
|
162
215
|
#
|
163
216
|
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
|
164
217
|
# constraint errors from other types of database errors by throwing an
|
165
|
-
# ActiveRecord::RecordNotUnique exception.
|
166
|
-
#
|
167
|
-
#
|
218
|
+
# ActiveRecord::RecordNotUnique exception. For other adapters you will
|
219
|
+
# have to parse the (database-specific) exception message to detect such
|
220
|
+
# a case.
|
221
|
+
#
|
168
222
|
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
|
169
|
-
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
|
170
|
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
171
|
-
# * ActiveRecord::ConnectionAdapters::SQLiteAdapter
|
172
|
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
|
173
|
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
174
223
|
#
|
224
|
+
# * ActiveRecord::ConnectionAdapters::MysqlAdapter.
|
225
|
+
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
|
226
|
+
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
|
227
|
+
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
|
175
228
|
def validates_uniqueness_of(*attr_names)
|
176
229
|
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
177
230
|
end
|