square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,359 @@
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, the following behavior may be surprising:
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
+ # creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
144
+ # exception in the nested block does not issue a ROLLBACK. Since these exceptions
145
+ # are captured in transaction blocks, the parent block does not see it and the
146
+ # real transaction is committed.
147
+ #
148
+ # In order to get a ROLLBACK for the nested transaction you may ask for a real
149
+ # sub-transaction by passing <tt>:requires_new => true</tt>. If anything goes wrong,
150
+ # the database rolls back to the beginning of the sub-transaction without rolling
151
+ # back the parent transaction. If we add it to the previous example:
152
+ #
153
+ # User.transaction do
154
+ # User.create(:username => 'Kotori')
155
+ # User.transaction(:requires_new => true) do
156
+ # User.create(:username => 'Nemu')
157
+ # raise ActiveRecord::Rollback
158
+ # end
159
+ # end
160
+ #
161
+ # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)
162
+ #
163
+ # Most databases don't support true nested transactions. At the time of
164
+ # writing, the only database that we're aware of that supports true nested
165
+ # transactions, is MS-SQL. Because of this, Active Record emulates nested
166
+ # transactions by using savepoints on MySQL and PostgreSQL. See
167
+ # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
168
+ # for more information about savepoints.
169
+ #
170
+ # === Callbacks
171
+ #
172
+ # There are two types of callbacks associated with committing and rolling back transactions:
173
+ # +after_commit+ and +after_rollback+.
174
+ #
175
+ # +after_commit+ callbacks are called on every record saved or destroyed within a
176
+ # transaction immediately after the transaction is committed. +after_rollback+ callbacks
177
+ # are called on every record saved or destroyed within a transaction immediately after the
178
+ # transaction or savepoint is rolled back.
179
+ #
180
+ # These callbacks are useful for interacting with other systems since you will be guaranteed
181
+ # that the callback is only executed when the database is in a permanent state. For example,
182
+ # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
183
+ # within a transaction could trigger the cache to be regenerated before the database is updated.
184
+ #
185
+ # === Caveats
186
+ #
187
+ # If you're on MySQL, then do not use DDL operations in nested transactions
188
+ # blocks that are emulated with savepoints. That is, do not execute statements
189
+ # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
190
+ # releases all savepoints upon executing a DDL operation. When +transaction+
191
+ # is finished and tries to release the savepoint it created earlier, a
192
+ # database error will occur because the savepoint has already been
193
+ # automatically released. The following example demonstrates the problem:
194
+ #
195
+ # Model.connection.transaction do # BEGIN
196
+ # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
197
+ # Model.connection.create_table(...) # active_record_1 now automatically released
198
+ # end # RELEASE savepoint active_record_1
199
+ # # ^^^^ BOOM! database error!
200
+ # end
201
+ #
202
+ # Note that "TRUNCATE" is also a MySQL DDL statement!
203
+ module ClassMethods
204
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
205
+ def transaction(options = {}, &block)
206
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
207
+ connection.transaction(options, &block)
208
+ end
209
+
210
+ def after_commit(*args, &block)
211
+ options = args.last
212
+ if options.is_a?(Hash) && options[:on]
213
+ options[:if] = Array.wrap(options[:if])
214
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
215
+ end
216
+ set_callback(:commit, :after, *args, &block)
217
+ end
218
+
219
+ def after_rollback(*args, &block)
220
+ options = args.last
221
+ if options.is_a?(Hash) && options[:on]
222
+ options[:if] = Array.wrap(options[:if])
223
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
224
+ end
225
+ set_callback(:rollback, :after, *args, &block)
226
+ end
227
+ end
228
+
229
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
230
+ def transaction(&block)
231
+ self.class.transaction(&block)
232
+ end
233
+
234
+ def destroy #:nodoc:
235
+ with_transaction_returning_status { super }
236
+ end
237
+
238
+ def save(*) #:nodoc:
239
+ rollback_active_record_state! do
240
+ with_transaction_returning_status { super }
241
+ end
242
+ end
243
+
244
+ def save!(*) #:nodoc:
245
+ with_transaction_returning_status { super }
246
+ end
247
+
248
+ # Reset id and @new_record if the transaction rolls back.
249
+ def rollback_active_record_state!
250
+ remember_transaction_record_state
251
+ yield
252
+ rescue Exception
253
+ restore_transaction_record_state
254
+ raise
255
+ ensure
256
+ clear_transaction_record_state
257
+ end
258
+
259
+ # Call the after_commit callbacks
260
+ def committed! #:nodoc:
261
+ _run_commit_callbacks
262
+ ensure
263
+ clear_transaction_record_state
264
+ end
265
+
266
+ # Call the after rollback callbacks. The restore_state argument indicates if the record
267
+ # state should be rolled back to the beginning or just to the last savepoint.
268
+ def rolledback!(force_restore_state = false) #:nodoc:
269
+ _run_rollback_callbacks
270
+ ensure
271
+ restore_transaction_record_state(force_restore_state)
272
+ end
273
+
274
+ # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
275
+ # can be called.
276
+ def add_to_transaction
277
+ if self.class.connection.add_transaction_record(self)
278
+ remember_transaction_record_state
279
+ end
280
+ end
281
+
282
+ # Executes +method+ within a transaction and captures its return value as a
283
+ # status flag. If the status is true the transaction is committed, otherwise
284
+ # a ROLLBACK is issued. In any case the status flag is returned.
285
+ #
286
+ # This method is available within the context of an ActiveRecord::Base
287
+ # instance.
288
+ def with_transaction_returning_status
289
+ status = nil
290
+ self.class.transaction do
291
+ add_to_transaction
292
+ status = yield
293
+ raise ActiveRecord::Rollback unless status
294
+ end
295
+ status
296
+ end
297
+
298
+ protected
299
+
300
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
301
+ def remember_transaction_record_state #:nodoc
302
+ @_start_transaction_state ||= {}
303
+ unless @_start_transaction_state.include?(:new_record)
304
+ @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
305
+ @_start_transaction_state[:new_record] = @new_record
306
+ end
307
+ unless @_start_transaction_state.include?(:destroyed)
308
+ @_start_transaction_state[:destroyed] = @destroyed
309
+ end
310
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
311
+ end
312
+
313
+ # Clear the new record state and id of a record.
314
+ def clear_transaction_record_state #:nodoc
315
+ if defined?(@_start_transaction_state)
316
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
317
+ remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
318
+ end
319
+ end
320
+
321
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
322
+ def restore_transaction_record_state(force = false) #:nodoc
323
+ if defined?(@_start_transaction_state)
324
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
325
+ if @_start_transaction_state[:level] < 1
326
+ restore_state = remove_instance_variable(:@_start_transaction_state)
327
+ if restore_state
328
+ @attributes = @attributes.dup if @attributes.frozen?
329
+ @new_record = restore_state[:new_record]
330
+ @destroyed = restore_state[:destroyed]
331
+ if restore_state[:id]
332
+ self.id = restore_state[:id]
333
+ else
334
+ @attributes.delete(self.class.primary_key)
335
+ @attributes_cache.delete(self.class.primary_key)
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
343
+ def transaction_record_state(state) #:nodoc
344
+ @_start_transaction_state[state] if defined?(@_start_transaction_state)
345
+ end
346
+
347
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
348
+ def transaction_include_action?(action) #:nodoc
349
+ case action
350
+ when :create
351
+ transaction_record_state(:new_record)
352
+ when :destroy
353
+ destroyed?
354
+ when :update
355
+ !(transaction_record_state(:new_record) || destroyed?)
356
+ end
357
+ end
358
+ end
359
+ 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