activerecord 4.1.0 → 4.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +776 -1330
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +12 -8
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +14 -13
- data/lib/active_record/associations/association.rb +2 -2
- data/lib/active_record/associations/association_scope.rb +83 -43
- data/lib/active_record/associations/belongs_to_association.rb +15 -5
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +66 -29
- data/lib/active_record/associations/collection_proxy.rb +22 -26
- data/lib/active_record/associations/has_many_association.rb +65 -18
- data/lib/active_record/associations/has_many_through_association.rb +55 -27
- data/lib/active_record/associations/has_one_association.rb +0 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +20 -12
- data/lib/active_record/associations/preloader/association.rb +34 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +49 -59
- data/lib/active_record/associations/singular_association.rb +25 -4
- data/lib/active_record/associations/through_association.rb +23 -14
- data/lib/active_record/associations.rb +171 -42
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +18 -10
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
- data/lib/active_record/attribute_methods/dirty.rb +98 -44
- data/lib/active_record/attribute_methods/primary_key.rb +14 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +37 -147
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
- data/lib/active_record/attribute_methods/write.rb +14 -21
- data/lib/active_record/attribute_methods.rb +67 -94
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +45 -38
- data/lib/active_record/base.rb +10 -20
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
- data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
- data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
- data/lib/active_record/connection_adapters/column.rb +28 -239
- data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
- data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
- data/lib/active_record/connection_handling.rb +3 -3
- data/lib/active_record/core.rb +143 -32
- data/lib/active_record/counter_cache.rb +60 -7
- data/lib/active_record/enum.rb +10 -11
- data/lib/active_record/errors.rb +49 -27
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/fixtures.rb +56 -70
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/locking/optimistic.rb +35 -17
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +52 -49
- data/lib/active_record/model_schema.rb +49 -57
- data/lib/active_record/nested_attributes.rb +7 -7
- data/lib/active_record/null_relation.rb +19 -5
- data/lib/active_record/persistence.rb +50 -31
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +14 -11
- data/lib/active_record/railties/databases.rake +56 -54
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +286 -102
- data/lib/active_record/relation/batches.rb +0 -1
- data/lib/active_record/relation/calculations.rb +39 -31
- data/lib/active_record/relation/delegation.rb +2 -2
- data/lib/active_record/relation/finder_methods.rb +80 -36
- data/lib/active_record/relation/merger.rb +25 -30
- data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +11 -10
- data/lib/active_record/relation/query_methods.rb +141 -55
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/relation.rb +69 -30
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +58 -26
- data/lib/active_record/schema_migration.rb +11 -0
- data/lib/active_record/scoping/default.rb +8 -7
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +19 -10
- data/lib/active_record/tasks/database_tasks.rb +73 -7
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +11 -9
- data/lib/active_record/transactions.rb +37 -21
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +6 -4
- data/lib/active_record/validations/uniqueness.rb +11 -17
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +3 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +65 -10
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -33,7 +33,13 @@ module ActiveRecord
|
|
33
33
|
reload
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
if owner.new_record?
|
37
|
+
# Cache the proxy separately before the owner has an id
|
38
|
+
# or else a post-save proxy will still lack the id
|
39
|
+
@new_record_proxy ||= CollectionProxy.create(klass, self)
|
40
|
+
else
|
41
|
+
@proxy ||= CollectionProxy.create(klass, self)
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -55,9 +61,9 @@ module ActiveRecord
|
|
55
61
|
|
56
62
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
57
63
|
def ids_writer(ids)
|
58
|
-
|
64
|
+
pk_type = reflection.primary_key_type
|
59
65
|
ids = Array(ids).reject { |id| id.blank? }
|
60
|
-
ids.map! { |i|
|
66
|
+
ids.map! { |i| pk_type.type_cast_from_user(i) }
|
61
67
|
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
|
62
68
|
end
|
63
69
|
|
@@ -134,20 +140,19 @@ module ActiveRecord
|
|
134
140
|
end
|
135
141
|
|
136
142
|
def create(attributes = {}, &block)
|
137
|
-
|
143
|
+
_create_record(attributes, &block)
|
138
144
|
end
|
139
145
|
|
140
146
|
def create!(attributes = {}, &block)
|
141
|
-
|
147
|
+
_create_record(attributes, true, &block)
|
142
148
|
end
|
143
149
|
|
144
150
|
# Add +records+ to this association. Returns +self+ so method calls may
|
145
151
|
# be chained. Since << flattens its argument list and inserts each record,
|
146
152
|
# +push+ and +concat+ behave identically.
|
147
153
|
def concat(*records)
|
148
|
-
load_target if owner.new_record?
|
149
|
-
|
150
154
|
if owner.new_record?
|
155
|
+
load_target
|
151
156
|
concat_records(records)
|
152
157
|
else
|
153
158
|
transaction { concat_records(records) }
|
@@ -170,8 +175,8 @@ module ActiveRecord
|
|
170
175
|
end
|
171
176
|
|
172
177
|
# Removes all records from the association without calling callbacks
|
173
|
-
# on the associated records. It honors the
|
174
|
-
# if the
|
178
|
+
# on the associated records. It honors the +:dependent+ option. However
|
179
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
175
180
|
# deletion strategy for the association is applied.
|
176
181
|
#
|
177
182
|
# You can force a particular deletion strategy by passing a parameter.
|
@@ -183,11 +188,11 @@ module ActiveRecord
|
|
183
188
|
#
|
184
189
|
# See delete for more info.
|
185
190
|
def delete_all(dependent = nil)
|
186
|
-
if dependent
|
191
|
+
if dependent && ![:nullify, :delete_all].include?(dependent)
|
187
192
|
raise ArgumentError, "Valid values are :nullify or :delete_all"
|
188
193
|
end
|
189
194
|
|
190
|
-
dependent = if dependent
|
195
|
+
dependent = if dependent
|
191
196
|
dependent
|
192
197
|
elsif options[:dependent] == :destroy
|
193
198
|
:delete_all
|
@@ -195,7 +200,7 @@ module ActiveRecord
|
|
195
200
|
options[:dependent]
|
196
201
|
end
|
197
202
|
|
198
|
-
|
203
|
+
delete_or_nullify_all_records(dependent).tap do
|
199
204
|
reset
|
200
205
|
loaded!
|
201
206
|
end
|
@@ -245,19 +250,12 @@ module ActiveRecord
|
|
245
250
|
# are actually removed from the database, that depends precisely on
|
246
251
|
# +delete_records+. They are in any case removed from the collection.
|
247
252
|
def delete(*records)
|
253
|
+
return if records.empty?
|
248
254
|
_options = records.extract_options!
|
249
255
|
dependent = _options[:dependent] || options[:dependent]
|
250
256
|
|
251
|
-
if records.
|
252
|
-
|
253
|
-
delete_or_destroy(load_target, dependent)
|
254
|
-
else
|
255
|
-
delete_records(:all, dependent)
|
256
|
-
end
|
257
|
-
else
|
258
|
-
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
|
259
|
-
delete_or_destroy(records, dependent)
|
260
|
-
end
|
257
|
+
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
|
258
|
+
delete_or_destroy(records, dependent)
|
261
259
|
end
|
262
260
|
|
263
261
|
# Deletes the +records+ and removes them from this association calling
|
@@ -266,6 +264,7 @@ module ActiveRecord
|
|
266
264
|
# Note that this method removes records from the database ignoring the
|
267
265
|
# +:dependent+ option.
|
268
266
|
def destroy(*records)
|
267
|
+
return if records.empty?
|
269
268
|
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
|
270
269
|
delete_or_destroy(records, :destroy)
|
271
270
|
end
|
@@ -359,7 +358,10 @@ module ActiveRecord
|
|
359
358
|
if owner.new_record?
|
360
359
|
replace_records(other_array, original_target)
|
361
360
|
else
|
362
|
-
|
361
|
+
replace_common_records_in_memory(other_array, original_target)
|
362
|
+
if other_array != original_target
|
363
|
+
transaction { replace_records(other_array, original_target) }
|
364
|
+
end
|
363
365
|
end
|
364
366
|
end
|
365
367
|
|
@@ -368,7 +370,7 @@ module ActiveRecord
|
|
368
370
|
if record.new_record?
|
369
371
|
include_in_memory?(record)
|
370
372
|
else
|
371
|
-
loaded? ? target.include?(record) : scope.exists?(record)
|
373
|
+
loaded? ? target.include?(record) : scope.exists?(record.id)
|
372
374
|
end
|
373
375
|
else
|
374
376
|
false
|
@@ -384,11 +386,18 @@ module ActiveRecord
|
|
384
386
|
target
|
385
387
|
end
|
386
388
|
|
387
|
-
def add_to_target(record, skip_callbacks = false)
|
389
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
390
|
+
if association_scope.distinct_value
|
391
|
+
index = @target.index(record)
|
392
|
+
end
|
393
|
+
replace_on_target(record, index, skip_callbacks, &block)
|
394
|
+
end
|
395
|
+
|
396
|
+
def replace_on_target(record, index, skip_callbacks)
|
388
397
|
callback(:before_add, record) unless skip_callbacks
|
389
398
|
yield(record) if block_given?
|
390
399
|
|
391
|
-
if
|
400
|
+
if index
|
392
401
|
@target[index] = record
|
393
402
|
else
|
394
403
|
@target << record
|
@@ -411,9 +420,29 @@ module ActiveRecord
|
|
411
420
|
end
|
412
421
|
|
413
422
|
private
|
423
|
+
def get_records
|
424
|
+
if reflection.scope_chain.any?(&:any?) ||
|
425
|
+
scope.eager_loading? ||
|
426
|
+
klass.current_scope ||
|
427
|
+
klass.default_scopes.any?
|
428
|
+
|
429
|
+
return scope.to_a
|
430
|
+
end
|
431
|
+
|
432
|
+
conn = klass.connection
|
433
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
434
|
+
StatementCache.create(conn) { |params|
|
435
|
+
as = AssociationScope.create { params.bind }
|
436
|
+
target_scope.merge as.scope(self, conn)
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
441
|
+
sc.execute binds, klass, klass.connection
|
442
|
+
end
|
414
443
|
|
415
444
|
def find_target
|
416
|
-
records =
|
445
|
+
records = get_records
|
417
446
|
records.each { |record| set_inverse_instance(record) }
|
418
447
|
records
|
419
448
|
end
|
@@ -448,13 +477,13 @@ module ActiveRecord
|
|
448
477
|
persisted + memory
|
449
478
|
end
|
450
479
|
|
451
|
-
def
|
480
|
+
def _create_record(attributes, raise = false, &block)
|
452
481
|
unless owner.persisted?
|
453
482
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
454
483
|
end
|
455
484
|
|
456
485
|
if attributes.is_a?(Array)
|
457
|
-
attributes.collect { |attr|
|
486
|
+
attributes.collect { |attr| _create_record(attr, raise, &block) }
|
458
487
|
else
|
459
488
|
transaction do
|
460
489
|
add_to_target(build_record(attributes)) do |record|
|
@@ -513,6 +542,14 @@ module ActiveRecord
|
|
513
542
|
target
|
514
543
|
end
|
515
544
|
|
545
|
+
def replace_common_records_in_memory(new_target, original_target)
|
546
|
+
common_records = new_target & original_target
|
547
|
+
common_records.each do |record|
|
548
|
+
skip_callbacks = true
|
549
|
+
replace_on_target(record, @target.index(record), skip_callbacks)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
516
553
|
def concat_records(records, should_raise = false)
|
517
554
|
result = true
|
518
555
|
|
@@ -355,14 +355,15 @@ module ActiveRecord
|
|
355
355
|
@association.replace(other_array)
|
356
356
|
end
|
357
357
|
|
358
|
-
# Deletes all the records from the collection
|
359
|
-
#
|
360
|
-
#
|
358
|
+
# Deletes all the records from the collection according to the strategy
|
359
|
+
# specified by the +:dependent+ option. If no +:dependent+ option is given,
|
360
|
+
# then it will follow the default strategy.
|
361
361
|
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
# the default strategy is
|
362
|
+
# For +has_many :through+ associations, the default deletion strategy is
|
363
|
+
# +:delete_all+.
|
364
|
+
#
|
365
|
+
# For +has_many+ associations, the default deletion strategy is +:nullify+.
|
366
|
+
# This sets the foreign keys to +NULL+.
|
366
367
|
#
|
367
368
|
# class Person < ActiveRecord::Base
|
368
369
|
# has_many :pets # dependent: :nullify option by default
|
@@ -393,9 +394,9 @@ module ActiveRecord
|
|
393
394
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
|
394
395
|
# # ]
|
395
396
|
#
|
396
|
-
#
|
397
|
-
#
|
398
|
-
#
|
397
|
+
# Both +has_many+ and +has_many :through+ dependencies default to the
|
398
|
+
# +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
|
399
|
+
# Records are not instantiated and callbacks will not be fired.
|
399
400
|
#
|
400
401
|
# class Person < ActiveRecord::Base
|
401
402
|
# has_many :pets, dependent: :destroy
|
@@ -410,11 +411,6 @@ module ActiveRecord
|
|
410
411
|
# # ]
|
411
412
|
#
|
412
413
|
# person.pets.delete_all
|
413
|
-
# # => [
|
414
|
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
415
|
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
416
|
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
417
|
-
# # ]
|
418
414
|
#
|
419
415
|
# Pet.find(1, 2, 3)
|
420
416
|
# # => ActiveRecord::RecordNotFound
|
@@ -435,11 +431,6 @@ module ActiveRecord
|
|
435
431
|
# # ]
|
436
432
|
#
|
437
433
|
# person.pets.delete_all
|
438
|
-
# # => [
|
439
|
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
440
|
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
441
|
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
442
|
-
# # ]
|
443
434
|
#
|
444
435
|
# Pet.find(1, 2, 3)
|
445
436
|
# # => ActiveRecord::RecordNotFound
|
@@ -448,8 +439,9 @@ module ActiveRecord
|
|
448
439
|
end
|
449
440
|
|
450
441
|
# Deletes the records of the collection directly from the database
|
451
|
-
# ignoring the +:dependent+ option.
|
452
|
-
# +after_remove+ , +before_destroy+ and
|
442
|
+
# ignoring the +:dependent+ option. Records are instantiated and it
|
443
|
+
# invokes +before_remove+, +after_remove+ , +before_destroy+ and
|
444
|
+
# +after_destroy+ callbacks.
|
453
445
|
#
|
454
446
|
# class Person < ActiveRecord::Base
|
455
447
|
# has_many :pets
|
@@ -788,7 +780,7 @@ module ActiveRecord
|
|
788
780
|
# person.pets.count # => 0
|
789
781
|
# person.pets.any? # => true
|
790
782
|
#
|
791
|
-
# You can also pass a block to define criteria. The behavior
|
783
|
+
# You can also pass a +block+ to define criteria. The behavior
|
792
784
|
# is the same, it returns true if the collection based on the
|
793
785
|
# criteria is not empty.
|
794
786
|
#
|
@@ -822,7 +814,7 @@ module ActiveRecord
|
|
822
814
|
# person.pets.count # => 2
|
823
815
|
# person.pets.many? # => true
|
824
816
|
#
|
825
|
-
# You can also pass a block to define criteria. The
|
817
|
+
# You can also pass a +block+ to define criteria. The
|
826
818
|
# behavior is the same, it returns true if the collection
|
827
819
|
# based on the criteria has more than one record.
|
828
820
|
#
|
@@ -846,7 +838,7 @@ module ActiveRecord
|
|
846
838
|
@association.many?(&block)
|
847
839
|
end
|
848
840
|
|
849
|
-
# Returns +true+ if the given
|
841
|
+
# Returns +true+ if the given +record+ is present in the collection.
|
850
842
|
#
|
851
843
|
# class Person < ActiveRecord::Base
|
852
844
|
# has_many :pets
|
@@ -860,6 +852,10 @@ module ActiveRecord
|
|
860
852
|
!!@association.include?(record)
|
861
853
|
end
|
862
854
|
|
855
|
+
def arel
|
856
|
+
scope.arel
|
857
|
+
end
|
858
|
+
|
863
859
|
def proxy_association
|
864
860
|
@association
|
865
861
|
end
|
@@ -880,7 +876,7 @@ module ActiveRecord
|
|
880
876
|
|
881
877
|
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
882
878
|
# contain the same number of elements and if each element is equal
|
883
|
-
# to the corresponding element in the other array, otherwise returns
|
879
|
+
# to the corresponding element in the +other+ array, otherwise returns
|
884
880
|
# +false+.
|
885
881
|
#
|
886
882
|
# class Person < ActiveRecord::Base
|
@@ -41,6 +41,14 @@ module ActiveRecord
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def empty?
|
45
|
+
if has_cached_counter?
|
46
|
+
size.zero?
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
44
52
|
private
|
45
53
|
|
46
54
|
# Returns the number of records in this collection.
|
@@ -58,7 +66,7 @@ module ActiveRecord
|
|
58
66
|
# the loaded flag is set to true as well.
|
59
67
|
def count_records
|
60
68
|
count = if has_cached_counter?
|
61
|
-
owner.
|
69
|
+
owner._read_attribute cached_counter_attribute_name
|
62
70
|
else
|
63
71
|
scope.count
|
64
72
|
end
|
@@ -71,20 +79,31 @@ module ActiveRecord
|
|
71
79
|
[association_scope.limit_value, count].compact.min
|
72
80
|
end
|
73
81
|
|
74
|
-
def has_cached_counter?(reflection = reflection)
|
82
|
+
def has_cached_counter?(reflection = reflection())
|
75
83
|
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
76
84
|
end
|
77
85
|
|
78
|
-
def cached_counter_attribute_name(reflection = reflection)
|
86
|
+
def cached_counter_attribute_name(reflection = reflection())
|
79
87
|
options[:counter_cache] || "#{reflection.name}_count"
|
80
88
|
end
|
81
89
|
|
82
|
-
def update_counter(difference, reflection = reflection)
|
90
|
+
def update_counter(difference, reflection = reflection())
|
91
|
+
update_counter_in_database(difference, reflection)
|
92
|
+
update_counter_in_memory(difference, reflection)
|
93
|
+
end
|
94
|
+
|
95
|
+
def update_counter_in_database(difference, reflection = reflection())
|
83
96
|
if has_cached_counter?(reflection)
|
84
97
|
counter = cached_counter_attribute_name(reflection)
|
85
98
|
owner.class.update_counters(owner.id, counter => difference)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def update_counter_in_memory(difference, reflection = reflection())
|
103
|
+
if has_cached_counter?(reflection)
|
104
|
+
counter = cached_counter_attribute_name(reflection)
|
86
105
|
owner[counter] += difference
|
87
|
-
owner.
|
106
|
+
owner.send(:clear_attribute_changes, counter) # eww
|
88
107
|
end
|
89
108
|
end
|
90
109
|
|
@@ -98,30 +117,39 @@ module ActiveRecord
|
|
98
117
|
# it will be decremented twice.
|
99
118
|
#
|
100
119
|
# Hence this method.
|
101
|
-
def inverse_updates_counter_cache?(reflection = reflection)
|
120
|
+
def inverse_updates_counter_cache?(reflection = reflection())
|
102
121
|
counter_name = cached_counter_attribute_name(reflection)
|
103
|
-
reflection
|
122
|
+
inverse_updates_counter_named?(counter_name, reflection)
|
123
|
+
end
|
124
|
+
|
125
|
+
def inverse_updates_counter_named?(counter_name, reflection = reflection())
|
126
|
+
reflection.klass._reflections.values.any? { |inverse_reflection|
|
127
|
+
inverse_reflection.belongs_to? &&
|
104
128
|
inverse_reflection.counter_cache_column == counter_name
|
105
129
|
}
|
106
130
|
end
|
107
131
|
|
132
|
+
def delete_count(method, scope)
|
133
|
+
if method == :delete_all
|
134
|
+
scope.delete_all
|
135
|
+
else
|
136
|
+
scope.update_all(reflection.foreign_key => nil)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_or_nullify_all_records(method)
|
141
|
+
count = delete_count(method, self.scope)
|
142
|
+
update_counter(-count)
|
143
|
+
end
|
144
|
+
|
108
145
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
109
146
|
def delete_records(records, method)
|
110
147
|
if method == :destroy
|
111
148
|
records.each(&:destroy!)
|
112
149
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
113
150
|
else
|
114
|
-
|
115
|
-
|
116
|
-
else
|
117
|
-
scope = self.scope.where(reflection.klass.primary_key => records)
|
118
|
-
end
|
119
|
-
|
120
|
-
if method == :delete_all
|
121
|
-
update_counter(-scope.delete_all)
|
122
|
-
else
|
123
|
-
update_counter(-scope.update_all(reflection.foreign_key => nil))
|
124
|
-
end
|
151
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
152
|
+
update_counter(-delete_count(method, scope))
|
125
153
|
end
|
126
154
|
end
|
127
155
|
|
@@ -132,6 +160,25 @@ module ActiveRecord
|
|
132
160
|
false
|
133
161
|
end
|
134
162
|
end
|
163
|
+
|
164
|
+
def concat_records(records, *)
|
165
|
+
update_counter_if_success(super, records.length)
|
166
|
+
end
|
167
|
+
|
168
|
+
def _create_record(attributes, *)
|
169
|
+
if attributes.is_a?(Array)
|
170
|
+
super
|
171
|
+
else
|
172
|
+
update_counter_if_success(super, 1)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def update_counter_if_success(saved_successfully, difference)
|
177
|
+
if saved_successfully
|
178
|
+
update_counter_in_memory(difference)
|
179
|
+
end
|
180
|
+
saved_successfully
|
181
|
+
end
|
135
182
|
end
|
136
183
|
end
|
137
184
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_support/core_ext/string/filters'
|
1
2
|
|
2
3
|
module ActiveRecord
|
3
4
|
# = Active Record Has Many Through Association
|
@@ -12,17 +13,18 @@ module ActiveRecord
|
|
12
13
|
@through_association = nil
|
13
14
|
end
|
14
15
|
|
15
|
-
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
16
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
17
|
+
# if the collection hasn't been loaded, and by calling collection.size if
|
18
|
+
# it has. If the collection will likely have a size greater than zero,
|
19
|
+
# and if fetching the collection will be needed afterwards, one less
|
20
|
+
# SELECT query will be generated by using #length instead.
|
19
21
|
def size
|
20
22
|
if has_cached_counter?
|
21
|
-
owner.
|
23
|
+
owner._read_attribute cached_counter_attribute_name(reflection)
|
22
24
|
elsif loaded?
|
23
25
|
target.size
|
24
26
|
else
|
25
|
-
|
27
|
+
super
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
@@ -62,7 +64,16 @@ module ActiveRecord
|
|
62
64
|
end
|
63
65
|
|
64
66
|
save_through_record(record)
|
65
|
-
|
67
|
+
if has_cached_counter? && !through_reflection_updates_counter_cache?
|
68
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
69
|
+
Automatic updating of counter caches on through associations has been
|
70
|
+
deprecated, and will be removed in Rails 5. Instead, please set the
|
71
|
+
appropriate `counter_cache` options on the `has_many` and `belongs_to`
|
72
|
+
for your associations to #{through_reflection.name}.
|
73
|
+
MSG
|
74
|
+
|
75
|
+
update_counter_in_database(1)
|
76
|
+
end
|
66
77
|
record
|
67
78
|
end
|
68
79
|
|
@@ -72,23 +83,31 @@ module ActiveRecord
|
|
72
83
|
@through_association ||= owner.association(through_reflection.name)
|
73
84
|
end
|
74
85
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# want to use the exact same object.
|
86
|
+
# The through record (built with build_record) is temporarily cached
|
87
|
+
# so that it may be reused if insert_record is subsequently called.
|
78
88
|
#
|
79
|
-
# However, after insert_record has been called,
|
80
|
-
#
|
81
|
-
# association
|
89
|
+
# However, after insert_record has been called, the cache is cleared in
|
90
|
+
# order to allow multiple instances of the same record in an association.
|
82
91
|
def build_through_record(record)
|
83
92
|
@through_records[record.object_id] ||= begin
|
84
93
|
ensure_mutable
|
85
94
|
|
86
|
-
through_record = through_association.build
|
95
|
+
through_record = through_association.build(*options_for_through_record)
|
87
96
|
through_record.send("#{source_reflection.name}=", record)
|
88
97
|
through_record
|
89
98
|
end
|
90
99
|
end
|
91
100
|
|
101
|
+
def options_for_through_record
|
102
|
+
[through_scope_attributes]
|
103
|
+
end
|
104
|
+
|
105
|
+
def through_scope_attributes
|
106
|
+
scope.where_values_hash(through_association.reflection.name.to_s).
|
107
|
+
except!(through_association.reflection.foreign_key,
|
108
|
+
through_association.reflection.klass.inheritance_column)
|
109
|
+
end
|
110
|
+
|
92
111
|
def save_through_record(record)
|
93
112
|
build_through_record(record).save!
|
94
113
|
ensure
|
@@ -102,9 +121,9 @@ module ActiveRecord
|
|
102
121
|
|
103
122
|
inverse = source_reflection.inverse_of
|
104
123
|
if inverse
|
105
|
-
if inverse.
|
124
|
+
if inverse.collection?
|
106
125
|
record.send(inverse.name) << build_through_record(record)
|
107
|
-
elsif inverse.
|
126
|
+
elsif inverse.has_one?
|
108
127
|
record.send("#{inverse.name}=", build_through_record(record))
|
109
128
|
end
|
110
129
|
end
|
@@ -113,7 +132,7 @@ module ActiveRecord
|
|
113
132
|
end
|
114
133
|
|
115
134
|
def target_reflection_has_associated_record?
|
116
|
-
!(through_reflection.
|
135
|
+
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
|
117
136
|
end
|
118
137
|
|
119
138
|
def update_through_counter?(method)
|
@@ -127,13 +146,13 @@ module ActiveRecord
|
|
127
146
|
end
|
128
147
|
end
|
129
148
|
|
149
|
+
def delete_or_nullify_all_records(method)
|
150
|
+
delete_records(load_target, method)
|
151
|
+
end
|
152
|
+
|
130
153
|
def delete_records(records, method)
|
131
154
|
ensure_not_nested
|
132
155
|
|
133
|
-
# This is unoptimised; it will load all the target records
|
134
|
-
# even when we just want to delete everything.
|
135
|
-
records = load_target if records == :all
|
136
|
-
|
137
156
|
scope = through_association.scope
|
138
157
|
scope.where! construct_join_attributes(*records)
|
139
158
|
|
@@ -142,8 +161,8 @@ module ActiveRecord
|
|
142
161
|
if scope.klass.primary_key
|
143
162
|
count = scope.destroy_all.length
|
144
163
|
else
|
145
|
-
scope.
|
146
|
-
record.
|
164
|
+
scope.each do |record|
|
165
|
+
record._run_destroy_callbacks
|
147
166
|
end
|
148
167
|
|
149
168
|
arel = scope.arel
|
@@ -167,7 +186,7 @@ module ActiveRecord
|
|
167
186
|
klass.decrement_counter counter, records.map(&:id)
|
168
187
|
end
|
169
188
|
|
170
|
-
if through_reflection.
|
189
|
+
if through_reflection.collection? && update_through_counter?(method)
|
171
190
|
update_counter(-count, through_reflection)
|
172
191
|
end
|
173
192
|
|
@@ -177,14 +196,18 @@ module ActiveRecord
|
|
177
196
|
def through_records_for(record)
|
178
197
|
attributes = construct_join_attributes(record)
|
179
198
|
candidates = Array.wrap(through_association.target)
|
180
|
-
candidates.find_all
|
199
|
+
candidates.find_all do |c|
|
200
|
+
attributes.all? do |key, value|
|
201
|
+
c.public_send(key) == value
|
202
|
+
end
|
203
|
+
end
|
181
204
|
end
|
182
205
|
|
183
206
|
def delete_through_records(records)
|
184
207
|
records.each do |record|
|
185
208
|
through_records = through_records_for(record)
|
186
209
|
|
187
|
-
if through_reflection.
|
210
|
+
if through_reflection.collection?
|
188
211
|
through_records.each { |r| through_association.target.delete(r) }
|
189
212
|
else
|
190
213
|
if through_records.include?(through_association.target)
|
@@ -198,13 +221,18 @@ module ActiveRecord
|
|
198
221
|
|
199
222
|
def find_target
|
200
223
|
return [] unless target_reflection_has_associated_record?
|
201
|
-
|
224
|
+
get_records
|
202
225
|
end
|
203
226
|
|
204
227
|
# NOTE - not sure that we can actually cope with inverses here
|
205
228
|
def invertible_for?(record)
|
206
229
|
false
|
207
230
|
end
|
231
|
+
|
232
|
+
def through_reflection_updates_counter_cache?
|
233
|
+
counter_name = cached_counter_attribute_name
|
234
|
+
inverse_updates_counter_named?(counter_name, through_reflection)
|
235
|
+
end
|
208
236
|
end
|
209
237
|
end
|
210
238
|
end
|