square-activerecord 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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