activerecord 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/class/attribute'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
3
|
# = Active Record Timestamp
|
@@ -37,14 +36,14 @@ module ActiveRecord
|
|
37
36
|
self.record_timestamps = true
|
38
37
|
end
|
39
38
|
|
40
|
-
def initialize_dup(other)
|
39
|
+
def initialize_dup(other) # :nodoc:
|
41
40
|
clear_timestamp_attributes
|
42
41
|
super
|
43
42
|
end
|
44
43
|
|
45
44
|
private
|
46
45
|
|
47
|
-
def
|
46
|
+
def create_record
|
48
47
|
if self.record_timestamps
|
49
48
|
current_time = current_time_from_proper_timezone
|
50
49
|
|
@@ -58,7 +57,7 @@ module ActiveRecord
|
|
58
57
|
super
|
59
58
|
end
|
60
59
|
|
61
|
-
def
|
60
|
+
def update_record(*args)
|
62
61
|
if should_record_timestamps?
|
63
62
|
current_time = current_time_from_proper_timezone
|
64
63
|
|
@@ -72,7 +71,7 @@ module ActiveRecord
|
|
72
71
|
end
|
73
72
|
|
74
73
|
def should_record_timestamps?
|
75
|
-
self.record_timestamps && (!
|
74
|
+
self.record_timestamps && (!partial_writes? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
|
76
75
|
end
|
77
76
|
|
78
77
|
def timestamp_attributes_for_create_in_model
|
@@ -87,19 +86,19 @@ module ActiveRecord
|
|
87
86
|
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
|
88
87
|
end
|
89
88
|
|
90
|
-
def timestamp_attributes_for_update
|
89
|
+
def timestamp_attributes_for_update
|
91
90
|
[:updated_at, :updated_on]
|
92
91
|
end
|
93
92
|
|
94
|
-
def timestamp_attributes_for_create
|
93
|
+
def timestamp_attributes_for_create
|
95
94
|
[:created_at, :created_on]
|
96
95
|
end
|
97
96
|
|
98
|
-
def all_timestamp_attributes
|
97
|
+
def all_timestamp_attributes
|
99
98
|
timestamp_attributes_for_create + timestamp_attributes_for_update
|
100
99
|
end
|
101
100
|
|
102
|
-
def current_time_from_proper_timezone
|
101
|
+
def current_time_from_proper_timezone
|
103
102
|
self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
104
103
|
end
|
105
104
|
|
@@ -4,6 +4,7 @@ module ActiveRecord
|
|
4
4
|
# See ActiveRecord::Transactions::ClassMethods for documentation.
|
5
5
|
module Transactions
|
6
6
|
extend ActiveSupport::Concern
|
7
|
+
ACTIONS = [:create, :destroy, :update]
|
7
8
|
|
8
9
|
class TransactionError < ActiveRecordError # :nodoc:
|
9
10
|
end
|
@@ -108,10 +109,10 @@ module ActiveRecord
|
|
108
109
|
#
|
109
110
|
# # Suppose that we have a Number model with a unique column called 'i'.
|
110
111
|
# Number.transaction do
|
111
|
-
# Number.create(:
|
112
|
+
# Number.create(i: 0)
|
112
113
|
# begin
|
113
114
|
# # This will raise a unique constraint error...
|
114
|
-
# Number.create(:
|
115
|
+
# Number.create(i: 0)
|
115
116
|
# rescue ActiveRecord::StatementInvalid
|
116
117
|
# # ...which we ignore.
|
117
118
|
# end
|
@@ -119,7 +120,7 @@ module ActiveRecord
|
|
119
120
|
# # On PostgreSQL, the transaction is now unusable. The following
|
120
121
|
# # statement will cause a PostgreSQL error, even though the unique
|
121
122
|
# # constraint is no longer violated:
|
122
|
-
# Number.create(:
|
123
|
+
# Number.create(i: 1)
|
123
124
|
# # => "PGError: ERROR: current transaction is aborted, commands
|
124
125
|
# # ignored until end of transaction block"
|
125
126
|
# end
|
@@ -134,9 +135,9 @@ module ActiveRecord
|
|
134
135
|
# transaction. For example, the following behavior may be surprising:
|
135
136
|
#
|
136
137
|
# User.transaction do
|
137
|
-
# User.create(:
|
138
|
+
# User.create(username: 'Kotori')
|
138
139
|
# User.transaction do
|
139
|
-
# User.create(:
|
140
|
+
# User.create(username: 'Nemu')
|
140
141
|
# raise ActiveRecord::Rollback
|
141
142
|
# end
|
142
143
|
# end
|
@@ -147,14 +148,14 @@ module ActiveRecord
|
|
147
148
|
# real transaction is committed.
|
148
149
|
#
|
149
150
|
# In order to get a ROLLBACK for the nested transaction you may ask for a real
|
150
|
-
# sub-transaction by passing <tt
|
151
|
+
# sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
|
151
152
|
# the database rolls back to the beginning of the sub-transaction without rolling
|
152
153
|
# back the parent transaction. If we add it to the previous example:
|
153
154
|
#
|
154
155
|
# User.transaction do
|
155
|
-
# User.create(:
|
156
|
-
# User.transaction(:
|
157
|
-
# User.create(:
|
156
|
+
# User.create(username: 'Kotori')
|
157
|
+
# User.transaction(requires_new: true) do
|
158
|
+
# User.create(username: 'Nemu')
|
158
159
|
# raise ActiveRecord::Rollback
|
159
160
|
# end
|
160
161
|
# end
|
@@ -165,7 +166,7 @@ module ActiveRecord
|
|
165
166
|
# writing, the only database that we're aware of that supports true nested
|
166
167
|
# transactions, is MS-SQL. Because of this, Active Record emulates nested
|
167
168
|
# transactions by using savepoints on MySQL and PostgreSQL. See
|
168
|
-
# http://dev.mysql.com/doc/refman/5.
|
169
|
+
# http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
|
169
170
|
# for more information about savepoints.
|
170
171
|
#
|
171
172
|
# === Callbacks
|
@@ -194,7 +195,7 @@ module ActiveRecord
|
|
194
195
|
# automatically released. The following example demonstrates the problem:
|
195
196
|
#
|
196
197
|
# Model.connection.transaction do # BEGIN
|
197
|
-
# Model.connection.transaction(:
|
198
|
+
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
198
199
|
# Model.connection.create_table(...) # active_record_1 now automatically released
|
199
200
|
# end # RELEASE savepoint active_record_1
|
200
201
|
# # ^^^^ BOOM! database error!
|
@@ -213,22 +214,17 @@ module ActiveRecord
|
|
213
214
|
# You can specify that the callback should only be fired by a certain action with
|
214
215
|
# the +:on+ option:
|
215
216
|
#
|
216
|
-
# after_commit :do_foo, :
|
217
|
-
# after_commit :do_bar, :
|
218
|
-
# after_commit :do_baz, :
|
217
|
+
# after_commit :do_foo, on: :create
|
218
|
+
# after_commit :do_bar, on: :update
|
219
|
+
# after_commit :do_baz, on: :destroy
|
219
220
|
#
|
220
|
-
#
|
221
|
-
#
|
222
|
-
# after_commit :do_zoo, :if => :persisted?
|
221
|
+
# after_commit :do_foo_bar, :on [:create, :update]
|
222
|
+
# after_commit :do_bar_baz, :on [:update, :destroy]
|
223
223
|
#
|
224
224
|
# Note that transactional fixtures do not play well with this feature. Please
|
225
225
|
# use the +test_after_commit+ gem to have these hooks fired in tests.
|
226
226
|
def after_commit(*args, &block)
|
227
|
-
|
228
|
-
if options.is_a?(Hash) && options[:on]
|
229
|
-
options[:if] = Array.wrap(options[:if])
|
230
|
-
options[:if] << "transaction_include_action?(:#{options[:on]})"
|
231
|
-
end
|
227
|
+
set_options_for_callbacks!(args)
|
232
228
|
set_callback(:commit, :after, *args, &block)
|
233
229
|
end
|
234
230
|
|
@@ -236,12 +232,27 @@ module ActiveRecord
|
|
236
232
|
#
|
237
233
|
# Please check the documentation of +after_commit+ for options.
|
238
234
|
def after_rollback(*args, &block)
|
235
|
+
set_options_for_callbacks!(args)
|
236
|
+
set_callback(:rollback, :after, *args, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def set_options_for_callbacks!(args)
|
239
242
|
options = args.last
|
240
243
|
if options.is_a?(Hash) && options[:on]
|
241
|
-
|
242
|
-
options[:if]
|
244
|
+
assert_valid_transaction_action(options[:on])
|
245
|
+
options[:if] = Array(options[:if])
|
246
|
+
fire_on = Array(options[:on]).map(&:to_sym)
|
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
|
|
@@ -269,7 +280,6 @@ module ActiveRecord
|
|
269
280
|
remember_transaction_record_state
|
270
281
|
yield
|
271
282
|
rescue Exception
|
272
|
-
IdentityMap.remove(self) if IdentityMap.enabled?
|
273
283
|
restore_transaction_record_state
|
274
284
|
raise
|
275
285
|
ensure
|
@@ -277,8 +287,11 @@ module ActiveRecord
|
|
277
287
|
end
|
278
288
|
|
279
289
|
# Call the after_commit callbacks
|
290
|
+
#
|
291
|
+
# Ensure that it is not called if the object was never persisted (failed create),
|
292
|
+
# but call it after the commit of a destroyed object
|
280
293
|
def committed! #:nodoc:
|
281
|
-
run_callbacks :commit
|
294
|
+
run_callbacks :commit if destroyed? || persisted?
|
282
295
|
ensure
|
283
296
|
clear_transaction_record_state
|
284
297
|
end
|
@@ -288,7 +301,6 @@ module ActiveRecord
|
|
288
301
|
def rolledback!(force_restore_state = false) #:nodoc:
|
289
302
|
run_callbacks :rollback
|
290
303
|
ensure
|
291
|
-
IdentityMap.remove(self) if IdentityMap.enabled?
|
292
304
|
restore_transaction_record_state(force_restore_state)
|
293
305
|
end
|
294
306
|
|
@@ -310,7 +322,13 @@ module ActiveRecord
|
|
310
322
|
status = nil
|
311
323
|
self.class.transaction do
|
312
324
|
add_to_transaction
|
313
|
-
|
325
|
+
begin
|
326
|
+
status = yield
|
327
|
+
rescue ActiveRecord::Rollback
|
328
|
+
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
329
|
+
status = nil
|
330
|
+
end
|
331
|
+
|
314
332
|
raise ActiveRecord::Rollback unless status
|
315
333
|
end
|
316
334
|
status
|
@@ -320,32 +338,25 @@ module ActiveRecord
|
|
320
338
|
|
321
339
|
# Save the new record state and id of a record so it can be restored later if a transaction fails.
|
322
340
|
def remember_transaction_record_state #:nodoc:
|
323
|
-
@_start_transaction_state ||= {}
|
324
341
|
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
|
325
|
-
|
326
|
-
|
327
|
-
end
|
328
|
-
unless @_start_transaction_state.include?(:destroyed)
|
329
|
-
@_start_transaction_state[:destroyed] = @destroyed
|
330
|
-
end
|
342
|
+
@_start_transaction_state[:new_record] = @new_record
|
343
|
+
@_start_transaction_state[:destroyed] = @destroyed
|
331
344
|
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
|
332
345
|
@_start_transaction_state[:frozen?] = @attributes.frozen?
|
333
346
|
end
|
334
347
|
|
335
348
|
# Clear the new record state and id of a record.
|
336
349
|
def clear_transaction_record_state #:nodoc:
|
337
|
-
|
338
|
-
|
339
|
-
remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
|
340
|
-
end
|
350
|
+
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
351
|
+
@_start_transaction_state.clear if @_start_transaction_state[:level] < 1
|
341
352
|
end
|
342
353
|
|
343
354
|
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
|
344
355
|
def restore_transaction_record_state(force = false) #:nodoc:
|
345
|
-
|
356
|
+
unless @_start_transaction_state.empty?
|
346
357
|
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
|
347
358
|
if @_start_transaction_state[:level] < 1 || force
|
348
|
-
restore_state =
|
359
|
+
restore_state = @_start_transaction_state
|
349
360
|
was_frozen = restore_state[:frozen?]
|
350
361
|
@attributes = @attributes.dup if @attributes.frozen?
|
351
362
|
@new_record = restore_state[:new_record]
|
@@ -357,24 +368,27 @@ module ActiveRecord
|
|
357
368
|
@attributes_cache.delete(self.class.primary_key)
|
358
369
|
end
|
359
370
|
@attributes.freeze if was_frozen
|
371
|
+
@_start_transaction_state.clear
|
360
372
|
end
|
361
373
|
end
|
362
374
|
end
|
363
375
|
|
364
376
|
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
|
365
377
|
def transaction_record_state(state) #:nodoc:
|
366
|
-
@_start_transaction_state[state]
|
378
|
+
@_start_transaction_state[state]
|
367
379
|
end
|
368
380
|
|
369
381
|
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
|
370
|
-
def
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
382
|
+
def transaction_include_any_action?(actions) #:nodoc:
|
383
|
+
actions.any? do |action|
|
384
|
+
case action
|
385
|
+
when :create
|
386
|
+
transaction_record_state(:new_record)
|
387
|
+
when :destroy
|
388
|
+
destroyed?
|
389
|
+
when :update
|
390
|
+
!(transaction_record_state(:new_record) || destroyed?)
|
391
|
+
end
|
378
392
|
end
|
379
393
|
end
|
380
394
|
end
|
@@ -10,11 +10,11 @@ module ActiveRecord
|
|
10
10
|
# puts invalid.record.errors
|
11
11
|
# end
|
12
12
|
class RecordInvalid < ActiveRecordError
|
13
|
-
attr_reader :record
|
14
|
-
def initialize(record)
|
13
|
+
attr_reader :record # :nodoc:
|
14
|
+
def initialize(record) # :nodoc:
|
15
15
|
@record = record
|
16
16
|
errors = @record.errors.full_messages.join(", ")
|
17
|
-
super(I18n.t("
|
17
|
+
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -32,11 +32,11 @@ module ActiveRecord
|
|
32
32
|
module ClassMethods
|
33
33
|
# Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
|
34
34
|
# so an exception is raised if the record is invalid.
|
35
|
-
def create!(attributes = nil,
|
35
|
+
def create!(attributes = nil, &block)
|
36
36
|
if attributes.is_a?(Array)
|
37
|
-
attributes.collect { |attr| create!(attr,
|
37
|
+
attributes.collect { |attr| create!(attr, &block) }
|
38
38
|
else
|
39
|
-
object = new(attributes
|
39
|
+
object = new(attributes)
|
40
40
|
yield(object) if block_given?
|
41
41
|
object.save!
|
42
42
|
object
|
@@ -44,23 +44,24 @@ module ActiveRecord
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
# The validation process on save can be skipped by passing <tt
|
48
|
-
# replaced with this when the validations
|
47
|
+
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
|
48
|
+
# The regular Base#save method is replaced with this when the validations
|
49
|
+
# module is mixed in, which it is by default.
|
49
50
|
def save(options={})
|
50
51
|
perform_validations(options) ? super : false
|
51
52
|
end
|
52
53
|
|
53
|
-
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
|
54
|
-
# if the record is not valid.
|
54
|
+
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
|
55
|
+
# exception instead of returning +false+ if the record is not valid.
|
55
56
|
def save!(options={})
|
56
57
|
perform_validations(options) ? super : raise(RecordInvalid.new(self))
|
57
58
|
end
|
58
59
|
|
59
|
-
# Runs all the validations within the specified context. Returns true if
|
60
|
-
# false otherwise.
|
60
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
61
|
+
# no errors are found, +false+ otherwise.
|
61
62
|
#
|
62
|
-
# If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
|
63
|
-
# <tt>new_record?</tt> is true
|
63
|
+
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
|
64
|
+
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
|
64
65
|
#
|
65
66
|
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
66
67
|
# some <tt>:on</tt> option will only run in the specified context.
|
@@ -72,7 +73,7 @@ module ActiveRecord
|
|
72
73
|
|
73
74
|
protected
|
74
75
|
|
75
|
-
def perform_validations(options={})
|
76
|
+
def perform_validations(options={}) # :nodoc:
|
76
77
|
perform_validation = options[:validate] != false
|
77
78
|
perform_validation ? valid?(options[:context]) : true
|
78
79
|
end
|
@@ -81,3 +82,4 @@ end
|
|
81
82
|
|
82
83
|
require "active_record/validations/associated"
|
83
84
|
require "active_record/validations/uniqueness"
|
85
|
+
require "active_record/validations/presence"
|
@@ -1,15 +1,16 @@
|
|
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
|
-
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
|
5
|
+
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
|
6
6
|
record.errors.add(attribute, :invalid, options.merge(:value => value))
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
# Validates whether the associated object or objects are all valid
|
12
|
+
# Validates whether the associated object or objects are all valid
|
13
|
+
# themselves. Works with any kind of association.
|
13
14
|
#
|
14
15
|
# class Book < ActiveRecord::Base
|
15
16
|
# has_many :pages
|
@@ -18,23 +19,28 @@ module ActiveRecord
|
|
18
19
|
# validates_associated :pages, :library
|
19
20
|
# end
|
20
21
|
#
|
21
|
-
# WARNING: This validation must not be used on both ends of an association.
|
22
|
+
# WARNING: This validation must not be used on both ends of an association.
|
23
|
+
# Doing so will lead to a circular dependency and cause infinite recursion.
|
22
24
|
#
|
23
|
-
# NOTE: This validation will not fail if the association hasn't been
|
24
|
-
# ensure that the association is both present and
|
25
|
-
# use +validates_presence_of+.
|
25
|
+
# NOTE: This validation will not fail if the association hasn't been
|
26
|
+
# assigned. If you want to ensure that the association is both present and
|
27
|
+
# guaranteed to be valid, you also need to use +validates_presence_of+.
|
26
28
|
#
|
27
29
|
# Configuration options:
|
28
|
-
#
|
30
|
+
#
|
31
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
|
29
32
|
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
|
30
33
|
# validation contexts by default (+nil+), other options are <tt>:create</tt>
|
31
34
|
# and <tt>:update</tt>.
|
32
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
33
|
-
# occur (e.g. <tt
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
35
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
36
|
+
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
37
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
38
|
+
# proc or string should return or evaluate to a +true+ or +false+ value.
|
39
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
40
|
+
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
41
|
+
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
42
|
+
# method, proc or string should return or evaluate to a +true+ or +false+
|
43
|
+
# value.
|
38
44
|
def validates_associated(*attr_names)
|
39
45
|
validates_with AssociatedValidator, _merge_attributes(attr_names)
|
40
46
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Validations
|
3
|
+
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
|
4
|
+
def validate(record)
|
5
|
+
super
|
6
|
+
attributes.each do |attribute|
|
7
|
+
next unless record.class.reflect_on_association(attribute)
|
8
|
+
associated_records = Array(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
|