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.

Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. 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(:i => 0)
112
+ # Number.create(i: 0)
112
113
  # begin
113
114
  # # This will raise a unique constraint error...
114
- # Number.create(:i => 0)
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(:i => 1)
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(:username => 'Kotori')
138
+ # User.create(username: 'Kotori')
138
139
  # User.transaction do
139
- # User.create(:username => 'Nemu')
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>:requires_new => true</tt>. If anything goes wrong,
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(:username => 'Kotori')
156
- # User.transaction(:requires_new => true) do
157
- # User.create(:username => 'Nemu')
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. (This works on MySQL and PostgreSQL, but not on SQLite3.)
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.0/en/savepoint.html
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(:requires_new => true) do # CREATE SAVEPOINT active_record_1
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, :on => :create
217
- # after_commit :do_bar, :on => :update
218
- # after_commit :do_baz, :on => :destroy
217
+ # after_commit :do_foo, on: :create
218
+ # after_commit :do_bar, on: :update
219
+ # after_commit :do_baz, on: :destroy
219
220
  #
220
- # Also, to have the callback fired on create and update, but not on destroy:
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
- options = args.last
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
- options[:if] = Array.wrap(options[:if])
242
- options[:if] << "transaction_include_action?(:#{options[:on]})"
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
- clear_transaction_record_state
300
+ @_start_transaction_state.clear
284
301
  end
285
302
 
286
- # Call the after rollback callbacks. The restore_state argument indicates if the record
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 :after_rollback and :after_commit callbacks
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
- status = yield
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
- if defined?(@_start_transaction_state)
338
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
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
- if defined?(@_start_transaction_state)
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 = remove_instance_variable(:@_start_transaction_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.id = restore_state[:id]
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] if defined?(@_start_transaction_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 transaction_include_action?(action) #:nodoc:
371
- case action
372
- when :create
373
- transaction_record_state(:new_record)
374
- when :destroy
375
- destroyed?
376
- when :update
377
- !(transaction_record_state(:new_record) || destroyed?)
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 themselves. Works with any kind of association.
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. Doing so will lead to a circular dependency and cause infinite recursion.
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 assigned. If you want to
24
- # ensure that the association is both present and guaranteed to be valid, you also need to
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
- # * <tt>:message</tt> - A custom error message (default is: "is invalid")
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 if the validation should
33
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
34
- # method, proc or string should return or evaluate to a true or false value.
35
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
36
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
37
- # method, proc or string should return or evaluate to a true or false value.
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
- super(options.reverse_merge(:case_sensitive => true))
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.send(:id))) if record.persisted?
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
- Array.wrap(options[:scope]).each do |scope_item|
29
- scope_value = record.read_attribute(scope_item)
30
- relation = relation.and(table[scope_item].eq(scope_value))
31
- end
28
+ if relation.exists?
29
+ error_options = options.except(:case_sensitive, :scope, :conditions)
30
+ error_options[:value] = value
32
31
 
33
- if finder_class.unscoped.where(relation).exists?
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.insert(0, class_hierarchy.first.superclass)
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
- column = klass.columns_hash[attribute.to_s]
57
- value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if value && column.text?
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
- relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
73
+ klass.connection.case_insensitive_comparison(table, attribute, column, value)
62
74
  else
63
- value = klass.connection.case_sensitive_modifier(value) if value
64
- relation = table[attribute].eq(value)
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 across the system.
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 unique based on a scope parameter:
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, :scope => :account_id
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 teacher can only be on the schedule once
87
- # per semester for a particular class.
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, :scope => [:semester_id, :class_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 record exists in the database
94
- # with the given value for the specified attribute (that maps to a column). When the record is updated,
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
- # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
99
- # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
100
- # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
101
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
102
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
103
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
104
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
105
- # The method, proc or string should return or evaluate to a true or false value.
106
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
107
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
108
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, proc or string should
109
- # return or evaluate to a true or false value.
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 his title is unique.
182
+ # | # infers that their title is unique.
130
183
  # | SELECT * FROM comments
131
184
  # | WHERE title = 'My Post'
132
185
  # |
133
- # # User 1 inserts his comment. |
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 him to re-enter the title).
160
- # This technique is also known as optimistic concurrency control:
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
- # For other adapters you will have to parse the (database-specific) exception
167
- # message to detect such a case.
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