activerecord 3.0.0

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

Potentially problematic release.


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

Files changed (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,356 @@
1
+ require 'thread'
2
+
3
+ module ActiveRecord
4
+ # See ActiveRecord::Transactions::ClassMethods for documentation.
5
+ module Transactions
6
+ extend ActiveSupport::Concern
7
+
8
+ class TransactionError < ActiveRecordError # :nodoc:
9
+ end
10
+
11
+ included do
12
+ define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
13
+ end
14
+ # = Active Record Transactions
15
+ #
16
+ # Transactions are protective blocks where SQL statements are only permanent
17
+ # if they can all succeed as one atomic action. The classic example is a
18
+ # transfer between two accounts where you can only have a deposit if the
19
+ # withdrawal succeeded and vice versa. Transactions enforce the integrity of
20
+ # the database and guard the data against program errors or database
21
+ # break-downs. So basically you should use transaction blocks whenever you
22
+ # have a number of statements that must be executed together or not at all.
23
+ #
24
+ # For example:
25
+ #
26
+ # ActiveRecord::Base.transaction do
27
+ # david.withdrawal(100)
28
+ # mary.deposit(100)
29
+ # end
30
+ #
31
+ # This example will only take money from David and give it to Mary if neither
32
+ # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
33
+ # ROLLBACK that returns the database to the state before the transaction
34
+ # began. Be aware, though, that the objects will _not_ have their instance
35
+ # data returned to their pre-transactional state.
36
+ #
37
+ # == Different Active Record classes in a single transaction
38
+ #
39
+ # Though the transaction class method is called on some Active Record class,
40
+ # the objects within the transaction block need not all be instances of
41
+ # that class. This is because transactions are per-database connection, not
42
+ # per-model.
43
+ #
44
+ # In this example a +balance+ record is transactionally saved even
45
+ # though +transaction+ is called on the +Account+ class:
46
+ #
47
+ # Account.transaction do
48
+ # balance.save!
49
+ # account.save!
50
+ # end
51
+ #
52
+ # The +transaction+ method is also available as a model instance method.
53
+ # For example, you can also do this:
54
+ #
55
+ # balance.transaction do
56
+ # balance.save!
57
+ # account.save!
58
+ # end
59
+ #
60
+ # == Transactions are not distributed across database connections
61
+ #
62
+ # A transaction acts on a single database connection. If you have
63
+ # multiple class-specific databases, the transaction will not protect
64
+ # interaction among them. One workaround is to begin a transaction
65
+ # on each class whose models you alter:
66
+ #
67
+ # Student.transaction do
68
+ # Course.transaction do
69
+ # course.enroll(student)
70
+ # student.units += course.units
71
+ # end
72
+ # end
73
+ #
74
+ # This is a poor solution, but fully distributed transactions are beyond
75
+ # the scope of Active Record.
76
+ #
77
+ # == +save+ and +destroy+ are automatically wrapped in a transaction
78
+ #
79
+ # Both +save+ and +destroy+ come wrapped in a transaction that ensures
80
+ # that whatever you do in validations or callbacks will happen under its
81
+ # protected cover. So you can use validations to check for values that
82
+ # the transaction depends on or you can raise exceptions in the callbacks
83
+ # to rollback, including <tt>after_*</tt> callbacks.
84
+ #
85
+ # As a consequence changes to the database are not seen outside your connection
86
+ # until the operation is complete. For example, if you try to update the index
87
+ # of a search engine in +after_save+ the indexer won't see the updated record.
88
+ # The +after_commit+ callback is the only one that is triggered once the update
89
+ # is committed. See below.
90
+ #
91
+ # == Exception handling and rolling back
92
+ #
93
+ # Also have in mind that exceptions thrown within a transaction block will
94
+ # be propagated (after triggering the ROLLBACK), so you should be ready to
95
+ # catch those in your application code.
96
+ #
97
+ # One exception is the <tt>ActiveRecord::Rollback</tt> exception, which will trigger
98
+ # a ROLLBACK when raised, but not be re-raised by the transaction block.
99
+ #
100
+ # *Warning*: one should not catch <tt>ActiveRecord::StatementInvalid</tt> exceptions
101
+ # inside a transaction block. <tt>ActiveRecord::StatementInvalid</tt> exceptions indicate that an
102
+ # error occurred at the database level, for example when a unique constraint
103
+ # is violated. On some database systems, such as PostgreSQL, database errors
104
+ # inside a transaction cause the entire transaction to become unusable
105
+ # until it's restarted from the beginning. Here is an example which
106
+ # demonstrates the problem:
107
+ #
108
+ # # Suppose that we have a Number model with a unique column called 'i'.
109
+ # Number.transaction do
110
+ # Number.create(:i => 0)
111
+ # begin
112
+ # # This will raise a unique constraint error...
113
+ # Number.create(:i => 0)
114
+ # rescue ActiveRecord::StatementInvalid
115
+ # # ...which we ignore.
116
+ # end
117
+ #
118
+ # # On PostgreSQL, the transaction is now unusable. The following
119
+ # # statement will cause a PostgreSQL error, even though the unique
120
+ # # constraint is no longer violated:
121
+ # Number.create(:i => 1)
122
+ # # => "PGError: ERROR: current transaction is aborted, commands
123
+ # # ignored until end of transaction block"
124
+ # end
125
+ #
126
+ # One should restart the entire transaction if an
127
+ # <tt>ActiveRecord::StatementInvalid</tt> occurred.
128
+ #
129
+ # == Nested transactions
130
+ #
131
+ # +transaction+ calls can be nested. By default, this makes all database
132
+ # statements in the nested transaction block become part of the parent
133
+ # transaction. For example:
134
+ #
135
+ # User.transaction do
136
+ # User.create(:username => 'Kotori')
137
+ # User.transaction do
138
+ # User.create(:username => 'Nemu')
139
+ # raise ActiveRecord::Rollback
140
+ # end
141
+ # end
142
+ #
143
+ # User.find(:all) # => empty
144
+ #
145
+ # It is also possible to requires a sub-transaction by passing
146
+ # <tt>:requires_new => true</tt>. If anything goes wrong, the
147
+ # database rolls back to the beginning of the sub-transaction
148
+ # without rolling back the parent transaction. For example:
149
+ #
150
+ # User.transaction do
151
+ # User.create(:username => 'Kotori')
152
+ # User.transaction(:requires_new => true) do
153
+ # User.create(:username => 'Nemu')
154
+ # raise ActiveRecord::Rollback
155
+ # end
156
+ # end
157
+ #
158
+ # User.find(:all) # => Returns only Kotori
159
+ #
160
+ # Most databases don't support true nested transactions. At the time of
161
+ # writing, the only database that we're aware of that supports true nested
162
+ # transactions, is MS-SQL. Because of this, Active Record emulates nested
163
+ # transactions by using savepoints. See
164
+ # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
165
+ # for more information about savepoints.
166
+ #
167
+ # === Callbacks
168
+ #
169
+ # There are two types of callbacks associated with committing and rolling back transactions:
170
+ # +after_commit+ and +after_rollback+.
171
+ #
172
+ # +after_commit+ callbacks are called on every record saved or destroyed within a
173
+ # transaction immediately after the transaction is committed. +after_rollback+ callbacks
174
+ # are called on every record saved or destroyed within a transaction immediately after the
175
+ # transaction or savepoint is rolled back.
176
+ #
177
+ # These callbacks are useful for interacting with other systems since you will be guaranteed
178
+ # that the callback is only executed when the database is in a permanent state. For example,
179
+ # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
180
+ # within a transaction could trigger the cache to be regenerated before the database is updated.
181
+ #
182
+ # === Caveats
183
+ #
184
+ # If you're on MySQL, then do not use DDL operations in nested transactions
185
+ # blocks that are emulated with savepoints. That is, do not execute statements
186
+ # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
187
+ # releases all savepoints upon executing a DDL operation. When +transaction+
188
+ # is finished and tries to release the savepoint it created earlier, a
189
+ # database error will occur because the savepoint has already been
190
+ # automatically released. The following example demonstrates the problem:
191
+ #
192
+ # Model.connection.transaction do # BEGIN
193
+ # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
194
+ # Model.connection.create_table(...) # active_record_1 now automatically released
195
+ # end # RELEASE savepoint active_record_1
196
+ # # ^^^^ BOOM! database error!
197
+ # end
198
+ #
199
+ # Note that "TRUNCATE" is also a MySQL DDL statement!
200
+ module ClassMethods
201
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
202
+ def transaction(options = {}, &block)
203
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
204
+ connection.transaction(options, &block)
205
+ end
206
+
207
+ def after_commit(*args, &block)
208
+ options = args.last
209
+ if options.is_a?(Hash) && options[:on]
210
+ options[:if] = Array.wrap(options[:if])
211
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
212
+ end
213
+ set_callback(:commit, :after, *args, &block)
214
+ end
215
+
216
+ def after_rollback(*args, &block)
217
+ options = args.last
218
+ if options.is_a?(Hash) && options[:on]
219
+ options[:if] = Array.wrap(options[:if])
220
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
221
+ end
222
+ set_callback(:rollback, :after, *args, &block)
223
+ end
224
+ end
225
+
226
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
227
+ def transaction(&block)
228
+ self.class.transaction(&block)
229
+ end
230
+
231
+ def destroy #:nodoc:
232
+ with_transaction_returning_status { super }
233
+ end
234
+
235
+ def save(*) #:nodoc:
236
+ rollback_active_record_state! do
237
+ with_transaction_returning_status { super }
238
+ end
239
+ end
240
+
241
+ def save!(*) #:nodoc:
242
+ with_transaction_returning_status { super }
243
+ end
244
+
245
+ # Reset id and @new_record if the transaction rolls back.
246
+ def rollback_active_record_state!
247
+ remember_transaction_record_state
248
+ yield
249
+ rescue Exception
250
+ restore_transaction_record_state
251
+ raise
252
+ ensure
253
+ clear_transaction_record_state
254
+ end
255
+
256
+ # Call the after_commit callbacks
257
+ def committed! #:nodoc:
258
+ _run_commit_callbacks
259
+ ensure
260
+ clear_transaction_record_state
261
+ end
262
+
263
+ # Call the after rollback callbacks. The restore_state argument indicates if the record
264
+ # state should be rolled back to the beginning or just to the last savepoint.
265
+ def rolledback!(force_restore_state = false) #:nodoc:
266
+ _run_rollback_callbacks
267
+ ensure
268
+ restore_transaction_record_state(force_restore_state)
269
+ end
270
+
271
+ # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
272
+ # can be called.
273
+ def add_to_transaction
274
+ if self.class.connection.add_transaction_record(self)
275
+ remember_transaction_record_state
276
+ end
277
+ end
278
+
279
+ # Executes +method+ within a transaction and captures its return value as a
280
+ # status flag. If the status is true the transaction is committed, otherwise
281
+ # a ROLLBACK is issued. In any case the status flag is returned.
282
+ #
283
+ # This method is available within the context of an ActiveRecord::Base
284
+ # instance.
285
+ def with_transaction_returning_status
286
+ status = nil
287
+ self.class.transaction do
288
+ add_to_transaction
289
+ status = yield
290
+ raise ActiveRecord::Rollback unless status
291
+ end
292
+ status
293
+ end
294
+
295
+ protected
296
+
297
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
298
+ def remember_transaction_record_state #:nodoc
299
+ @_start_transaction_state ||= {}
300
+ unless @_start_transaction_state.include?(:new_record)
301
+ @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
302
+ @_start_transaction_state[:new_record] = @new_record
303
+ end
304
+ unless @_start_transaction_state.include?(:destroyed)
305
+ @_start_transaction_state[:destroyed] = @destroyed
306
+ end
307
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
308
+ end
309
+
310
+ # Clear the new record state and id of a record.
311
+ def clear_transaction_record_state #:nodoc
312
+ if defined?(@_start_transaction_state)
313
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
314
+ remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
315
+ end
316
+ end
317
+
318
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
319
+ def restore_transaction_record_state(force = false) #:nodoc
320
+ if defined?(@_start_transaction_state)
321
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
322
+ if @_start_transaction_state[:level] < 1
323
+ restore_state = remove_instance_variable(:@_start_transaction_state)
324
+ if restore_state
325
+ @attributes = @attributes.dup if @attributes.frozen?
326
+ @new_record = restore_state[:new_record]
327
+ @destroyed = restore_state[:destroyed]
328
+ if restore_state[:id]
329
+ self.id = restore_state[:id]
330
+ else
331
+ @attributes.delete(self.class.primary_key)
332
+ @attributes_cache.delete(self.class.primary_key)
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+ # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
340
+ def transaction_record_state(state) #:nodoc
341
+ @_start_transaction_state[state] if defined?(@_start_transaction_state)
342
+ end
343
+
344
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
345
+ def transaction_include_action?(action) #:nodoc
346
+ case action
347
+ when :create
348
+ transaction_record_state(:new_record)
349
+ when :destroy
350
+ destroyed?
351
+ when :update
352
+ !(transaction_record_state(:new_record) || destroyed?)
353
+ end
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,84 @@
1
+ module ActiveRecord
2
+ # = Active Record Validations
3
+ #
4
+ # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
5
+ # +record+ method to retrieve the record which did not validate.
6
+ #
7
+ # begin
8
+ # complex_operation_that_calls_save!_internally
9
+ # rescue ActiveRecord::RecordInvalid => invalid
10
+ # puts invalid.record.errors
11
+ # end
12
+ class RecordInvalid < ActiveRecordError
13
+ attr_reader :record
14
+ def initialize(record)
15
+ @record = record
16
+ errors = @record.errors.full_messages.join(", ")
17
+ super(I18n.t("activerecord.errors.messages.record_invalid", :errors => errors))
18
+ end
19
+ end
20
+
21
+ module Validations
22
+ extend ActiveSupport::Concern
23
+ include ActiveModel::Validations
24
+
25
+ module ClassMethods
26
+ # Creates an object just like Base.create but calls save! instead of save
27
+ # so an exception is raised if the record is invalid.
28
+ def create!(attributes = nil, &block)
29
+ if attributes.is_a?(Array)
30
+ attributes.collect { |attr| create!(attr, &block) }
31
+ else
32
+ object = new(attributes)
33
+ yield(object) if block_given?
34
+ object.save!
35
+ object
36
+ end
37
+ end
38
+ end
39
+
40
+ # The validation process on save can be skipped by passing false. The regular Base#save method is
41
+ # replaced with this when the validations module is mixed in, which it is by default.
42
+ def save(options={})
43
+ perform_validations(options) ? super : false
44
+ end
45
+
46
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
47
+ # if the record is not valid.
48
+ def save!(options={})
49
+ perform_validations(options) ? super : raise(RecordInvalid.new(self))
50
+ end
51
+
52
+ # Runs all the specified validations and returns true if no errors were added otherwise false.
53
+ def valid?(context = nil)
54
+ context ||= (new_record? ? :create : :update)
55
+ output = super(context)
56
+
57
+ deprecated_callback_method(:validate)
58
+ deprecated_callback_method(:"validate_on_#{context}")
59
+
60
+ errors.empty? && output
61
+ end
62
+
63
+ protected
64
+
65
+ def perform_validations(options={})
66
+ perform_validation = case options
67
+ when Hash
68
+ options[:validate] != false
69
+ else
70
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
71
+ options
72
+ end
73
+
74
+ if perform_validation
75
+ valid?(options.is_a?(Hash) ? options[:context] : nil)
76
+ else
77
+ true
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ require "active_record/validations/associated"
84
+ require "active_record/validations/uniqueness"
@@ -0,0 +1,48 @@
1
+ module ActiveRecord
2
+ module Validations
3
+ class AssociatedValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
6
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
12
+ #
13
+ # class Book < ActiveRecord::Base
14
+ # has_many :pages
15
+ # belongs_to :library
16
+ #
17
+ # validates_associated :pages, :library
18
+ # end
19
+ #
20
+ # Warning: If, after the above definition, you then wrote:
21
+ #
22
+ # class Page < ActiveRecord::Base
23
+ # belongs_to :book
24
+ #
25
+ # validates_associated :book
26
+ # end
27
+ #
28
+ # this would specify a circular dependency and cause infinite recursion.
29
+ #
30
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
31
+ # ensure that the association is both present and guaranteed to be valid, you also need to
32
+ # use +validates_presence_of+.
33
+ #
34
+ # Configuration options:
35
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid")
36
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
37
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
38
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
39
+ # method, proc or string should return or evaluate to a true or false value.
40
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
41
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, 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 value.
43
+ def validates_associated(*attr_names)
44
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
45
+ end
46
+ end
47
+ end
48
+ end