activerecord 4.1.8 → 4.2.11.3
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 +5 -5
- data/CHANGELOG.md +1165 -1591
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +84 -43
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
- 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 +87 -30
- data/lib/active_record/associations/collection_proxy.rb +33 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +26 -12
- data/lib/active_record/associations/preloader/association.rb +14 -10
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +37 -26
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +16 -12
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +20 -12
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -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 +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_methods.rb +57 -95
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +30 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
- data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
- 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 +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -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 +15 -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 +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -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 +19 -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 +109 -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 -385
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
- 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 +134 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -40
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +10 -12
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +62 -74
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- 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 +79 -47
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +18 -8
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +48 -27
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +19 -14
- data/lib/active_record/railties/databases.rake +55 -56
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +281 -117
- data/lib/active_record/relation/batches.rb +0 -1
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +71 -48
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +42 -12
- data/lib/active_record/relation/query_methods.rb +130 -73
- data/lib/active_record/relation/spawn_methods.rb +10 -3
- data/lib/active_record/relation.rb +57 -25
- 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 +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- 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 +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -8
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +54 -28
- 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 +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -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 +23 -0
- data/lib/active_record/type/integer.rb +59 -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 +62 -0
- data/lib/active_record/type/string.rb +40 -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 +110 -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 +5 -3
- data/lib/active_record/validations/uniqueness.rb +24 -20
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +5 -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 +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def valid_options
|
8
|
-
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
|
8
|
+
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.valid_dependent_options
|
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def valid_options
|
8
|
-
valid = super + [:
|
8
|
+
valid = super + [:as, :foreign_type]
|
9
9
|
valid += [:through, :source, :source_type] if options[:through]
|
10
10
|
valid
|
11
11
|
end
|
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
|
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
def self.
|
19
|
+
def self.add_destroy_callbacks(model, reflection)
|
20
20
|
super unless reflection.options[:through]
|
21
21
|
end
|
22
22
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveRecord::Associations::Builder
|
4
4
|
class SingularAssociation < Association #:nodoc:
|
5
5
|
def valid_options
|
6
|
-
super + [:
|
6
|
+
super + [:dependent, :primary_key, :inverse_of, :required]
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.define_accessors(model, reflection)
|
@@ -27,5 +27,12 @@ module ActiveRecord::Associations::Builder
|
|
27
27
|
end
|
28
28
|
CODE
|
29
29
|
end
|
30
|
+
|
31
|
+
def self.define_validations(model, reflection)
|
32
|
+
super
|
33
|
+
if reflection.options[:required]
|
34
|
+
model.validates_presence_of reflection.name
|
35
|
+
end
|
36
|
+
end
|
30
37
|
end
|
31
38
|
end
|
@@ -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,10 +61,21 @@ 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
|
-
pk_column = reflection.
|
59
|
-
|
60
|
-
ids.map
|
61
|
-
|
64
|
+
pk_column = reflection.association_primary_key
|
65
|
+
pk_type = klass.type_for_attribute(pk_column)
|
66
|
+
ids = Array(ids).reject(&:blank?).map do |i|
|
67
|
+
pk_type.type_cast_from_user(i)
|
68
|
+
end
|
69
|
+
|
70
|
+
objs = klass.where(pk_column => ids).index_by do |r|
|
71
|
+
r.send(pk_column)
|
72
|
+
end.values_at(*ids).compact
|
73
|
+
|
74
|
+
if objs.size == ids.size
|
75
|
+
replace(objs.index_by { |r| r.send(pk_column) }.values_at(*ids))
|
76
|
+
else
|
77
|
+
klass.all.raise_record_not_found_exception!(ids, objs.size, ids.size)
|
78
|
+
end
|
62
79
|
end
|
63
80
|
|
64
81
|
def reset
|
@@ -123,6 +140,16 @@ module ActiveRecord
|
|
123
140
|
first_nth_or_last(:last, *args)
|
124
141
|
end
|
125
142
|
|
143
|
+
def take(n = nil)
|
144
|
+
if loaded?
|
145
|
+
n ? target.take(n) : target.first
|
146
|
+
else
|
147
|
+
scope.take(n).tap do |record|
|
148
|
+
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
126
153
|
def build(attributes = {}, &block)
|
127
154
|
if attributes.is_a?(Array)
|
128
155
|
attributes.collect { |attr| build(attr, &block) }
|
@@ -145,9 +172,8 @@ module ActiveRecord
|
|
145
172
|
# be chained. Since << flattens its argument list and inserts each record,
|
146
173
|
# +push+ and +concat+ behave identically.
|
147
174
|
def concat(*records)
|
148
|
-
load_target if owner.new_record?
|
149
|
-
|
150
175
|
if owner.new_record?
|
176
|
+
load_target
|
151
177
|
concat_records(records)
|
152
178
|
else
|
153
179
|
transaction { concat_records(records) }
|
@@ -170,8 +196,8 @@ module ActiveRecord
|
|
170
196
|
end
|
171
197
|
|
172
198
|
# Removes all records from the association without calling callbacks
|
173
|
-
# on the associated records. It honors the
|
174
|
-
# if the
|
199
|
+
# on the associated records. It honors the +:dependent+ option. However
|
200
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
175
201
|
# deletion strategy for the association is applied.
|
176
202
|
#
|
177
203
|
# You can force a particular deletion strategy by passing a parameter.
|
@@ -183,11 +209,11 @@ module ActiveRecord
|
|
183
209
|
#
|
184
210
|
# See delete for more info.
|
185
211
|
def delete_all(dependent = nil)
|
186
|
-
if dependent
|
212
|
+
if dependent && ![:nullify, :delete_all].include?(dependent)
|
187
213
|
raise ArgumentError, "Valid values are :nullify or :delete_all"
|
188
214
|
end
|
189
215
|
|
190
|
-
dependent = if dependent
|
216
|
+
dependent = if dependent
|
191
217
|
dependent
|
192
218
|
elsif options[:dependent] == :destroy
|
193
219
|
:delete_all
|
@@ -195,7 +221,7 @@ module ActiveRecord
|
|
195
221
|
options[:dependent]
|
196
222
|
end
|
197
223
|
|
198
|
-
|
224
|
+
delete_or_nullify_all_records(dependent).tap do
|
199
225
|
reset
|
200
226
|
loaded!
|
201
227
|
end
|
@@ -245,19 +271,12 @@ module ActiveRecord
|
|
245
271
|
# are actually removed from the database, that depends precisely on
|
246
272
|
# +delete_records+. They are in any case removed from the collection.
|
247
273
|
def delete(*records)
|
274
|
+
return if records.empty?
|
248
275
|
_options = records.extract_options!
|
249
276
|
dependent = _options[:dependent] || options[:dependent]
|
250
277
|
|
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
|
278
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
279
|
+
delete_or_destroy(records, dependent)
|
261
280
|
end
|
262
281
|
|
263
282
|
# Deletes the +records+ and removes them from this association calling
|
@@ -266,7 +285,8 @@ module ActiveRecord
|
|
266
285
|
# Note that this method removes records from the database ignoring the
|
267
286
|
# +:dependent+ option.
|
268
287
|
def destroy(*records)
|
269
|
-
|
288
|
+
return if records.empty?
|
289
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
270
290
|
delete_or_destroy(records, :destroy)
|
271
291
|
end
|
272
292
|
|
@@ -359,7 +379,10 @@ module ActiveRecord
|
|
359
379
|
if owner.new_record?
|
360
380
|
replace_records(other_array, original_target)
|
361
381
|
else
|
362
|
-
|
382
|
+
replace_common_records_in_memory(other_array, original_target)
|
383
|
+
if other_array != original_target
|
384
|
+
transaction { replace_records(other_array, original_target) }
|
385
|
+
end
|
363
386
|
end
|
364
387
|
end
|
365
388
|
|
@@ -368,7 +391,7 @@ module ActiveRecord
|
|
368
391
|
if record.new_record?
|
369
392
|
include_in_memory?(record)
|
370
393
|
else
|
371
|
-
loaded? ? target.include?(record) : scope.exists?(record)
|
394
|
+
loaded? ? target.include?(record) : scope.exists?(record.id)
|
372
395
|
end
|
373
396
|
else
|
374
397
|
false
|
@@ -384,11 +407,18 @@ module ActiveRecord
|
|
384
407
|
target
|
385
408
|
end
|
386
409
|
|
387
|
-
def add_to_target(record, skip_callbacks = false)
|
410
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
411
|
+
if association_scope.distinct_value
|
412
|
+
index = @target.index(record)
|
413
|
+
end
|
414
|
+
replace_on_target(record, index, skip_callbacks, &block)
|
415
|
+
end
|
416
|
+
|
417
|
+
def replace_on_target(record, index, skip_callbacks)
|
388
418
|
callback(:before_add, record) unless skip_callbacks
|
389
419
|
yield(record) if block_given?
|
390
420
|
|
391
|
-
if
|
421
|
+
if index
|
392
422
|
@target[index] = record
|
393
423
|
else
|
394
424
|
@target << record
|
@@ -411,9 +441,23 @@ module ActiveRecord
|
|
411
441
|
end
|
412
442
|
|
413
443
|
private
|
444
|
+
def get_records
|
445
|
+
return scope.to_a if skip_statement_cache?
|
446
|
+
|
447
|
+
conn = klass.connection
|
448
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
449
|
+
StatementCache.create(conn) { |params|
|
450
|
+
as = AssociationScope.create { params.bind }
|
451
|
+
target_scope.merge as.scope(self, conn)
|
452
|
+
}
|
453
|
+
end
|
454
|
+
|
455
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
456
|
+
sc.execute binds, klass, klass.connection
|
457
|
+
end
|
414
458
|
|
415
459
|
def find_target
|
416
|
-
records =
|
460
|
+
records = get_records
|
417
461
|
records.each { |record| set_inverse_instance(record) }
|
418
462
|
records
|
419
463
|
end
|
@@ -513,6 +557,14 @@ module ActiveRecord
|
|
513
557
|
target
|
514
558
|
end
|
515
559
|
|
560
|
+
def replace_common_records_in_memory(new_target, original_target)
|
561
|
+
common_records = new_target & original_target
|
562
|
+
common_records.each do |record|
|
563
|
+
skip_callbacks = true
|
564
|
+
replace_on_target(record, @target.index(record), skip_callbacks)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
516
568
|
def concat_records(records, should_raise = false)
|
517
569
|
result = true
|
518
570
|
|
@@ -560,8 +612,13 @@ module ActiveRecord
|
|
560
612
|
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
561
613
|
assoc = owner.association(reflection.through_reflection.name)
|
562
614
|
assoc.reader.any? { |source|
|
563
|
-
|
564
|
-
|
615
|
+
target_association = source.send(reflection.source_reflection.name)
|
616
|
+
|
617
|
+
if target_association.respond_to?(:include?)
|
618
|
+
target_association.include?(record)
|
619
|
+
else
|
620
|
+
target_association == record
|
621
|
+
end
|
565
622
|
} || target.include?(record)
|
566
623
|
else
|
567
624
|
target.include?(record)
|
@@ -29,6 +29,7 @@ module ActiveRecord
|
|
29
29
|
# instantiation of the actual post records.
|
30
30
|
class CollectionProxy < Relation
|
31
31
|
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
|
32
|
+
delegate :find_nth, to: :scope
|
32
33
|
|
33
34
|
def initialize(klass, association) #:nodoc:
|
34
35
|
@association = association
|
@@ -226,6 +227,10 @@ module ActiveRecord
|
|
226
227
|
@association.last(*args)
|
227
228
|
end
|
228
229
|
|
230
|
+
def take(n = nil)
|
231
|
+
@association.take(n)
|
232
|
+
end
|
233
|
+
|
229
234
|
# Returns a new object of the collection type that has been instantiated
|
230
235
|
# with +attributes+ and linked to this object, but have not yet been saved.
|
231
236
|
# You can pass an array of attributes hashes, this will return an array
|
@@ -355,14 +360,15 @@ module ActiveRecord
|
|
355
360
|
@association.replace(other_array)
|
356
361
|
end
|
357
362
|
|
358
|
-
# Deletes all the records from the collection
|
359
|
-
#
|
360
|
-
#
|
363
|
+
# Deletes all the records from the collection according to the strategy
|
364
|
+
# specified by the +:dependent+ option. If no +:dependent+ option is given,
|
365
|
+
# then it will follow the default strategy.
|
361
366
|
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
# the default strategy is
|
367
|
+
# For +has_many :through+ associations, the default deletion strategy is
|
368
|
+
# +:delete_all+.
|
369
|
+
#
|
370
|
+
# For +has_many+ associations, the default deletion strategy is +:nullify+.
|
371
|
+
# This sets the foreign keys to +NULL+.
|
366
372
|
#
|
367
373
|
# class Person < ActiveRecord::Base
|
368
374
|
# has_many :pets # dependent: :nullify option by default
|
@@ -393,9 +399,9 @@ module ActiveRecord
|
|
393
399
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
|
394
400
|
# # ]
|
395
401
|
#
|
396
|
-
#
|
397
|
-
#
|
398
|
-
#
|
402
|
+
# Both +has_many+ and +has_many :through+ dependencies default to the
|
403
|
+
# +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
|
404
|
+
# Records are not instantiated and callbacks will not be fired.
|
399
405
|
#
|
400
406
|
# class Person < ActiveRecord::Base
|
401
407
|
# has_many :pets, dependent: :destroy
|
@@ -410,11 +416,6 @@ module ActiveRecord
|
|
410
416
|
# # ]
|
411
417
|
#
|
412
418
|
# 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
419
|
#
|
419
420
|
# Pet.find(1, 2, 3)
|
420
421
|
# # => ActiveRecord::RecordNotFound
|
@@ -435,11 +436,6 @@ module ActiveRecord
|
|
435
436
|
# # ]
|
436
437
|
#
|
437
438
|
# 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
439
|
#
|
444
440
|
# Pet.find(1, 2, 3)
|
445
441
|
# # => ActiveRecord::RecordNotFound
|
@@ -448,8 +444,9 @@ module ActiveRecord
|
|
448
444
|
end
|
449
445
|
|
450
446
|
# Deletes the records of the collection directly from the database
|
451
|
-
# ignoring the +:dependent+ option.
|
452
|
-
# +after_remove+ , +before_destroy+ and
|
447
|
+
# ignoring the +:dependent+ option. Records are instantiated and it
|
448
|
+
# invokes +before_remove+, +after_remove+ , +before_destroy+ and
|
449
|
+
# +after_destroy+ callbacks.
|
453
450
|
#
|
454
451
|
# class Person < ActiveRecord::Base
|
455
452
|
# has_many :pets
|
@@ -473,15 +470,16 @@ module ActiveRecord
|
|
473
470
|
@association.destroy_all
|
474
471
|
end
|
475
472
|
|
476
|
-
# Deletes the +records+ supplied
|
477
|
-
#
|
478
|
-
#
|
473
|
+
# Deletes the +records+ supplied from the collection according to the strategy
|
474
|
+
# specified by the +:dependent+ option. If no +:dependent+ option is given,
|
475
|
+
# then it will follow the default strategy. Returns an array with the
|
479
476
|
# deleted records.
|
480
477
|
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
484
|
-
# strategy is
|
478
|
+
# For +has_many :through+ associations, the default deletion strategy is
|
479
|
+
# +:delete_all+.
|
480
|
+
#
|
481
|
+
# For +has_many+ associations, the default deletion strategy is +:nullify+.
|
482
|
+
# This sets the foreign keys to +NULL+.
|
485
483
|
#
|
486
484
|
# class Person < ActiveRecord::Base
|
487
485
|
# has_many :pets # dependent: :nullify option by default
|
@@ -564,7 +562,7 @@ module ActiveRecord
|
|
564
562
|
# Pet.find(1)
|
565
563
|
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
|
566
564
|
#
|
567
|
-
# You can pass +
|
565
|
+
# You can pass +Integer+ or +String+ values, it finds the records
|
568
566
|
# responding to the +id+ and executes delete on them.
|
569
567
|
#
|
570
568
|
# class Person < ActiveRecord::Base
|
@@ -628,7 +626,7 @@ module ActiveRecord
|
|
628
626
|
#
|
629
627
|
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
|
630
628
|
#
|
631
|
-
# You can pass +
|
629
|
+
# You can pass +Integer+ or +String+ values, it finds the records
|
632
630
|
# responding to the +id+ and then deletes them from the database.
|
633
631
|
#
|
634
632
|
# person.pets.size # => 3
|
@@ -788,7 +786,7 @@ module ActiveRecord
|
|
788
786
|
# person.pets.count # => 0
|
789
787
|
# person.pets.any? # => true
|
790
788
|
#
|
791
|
-
# You can also pass a block to define criteria. The behavior
|
789
|
+
# You can also pass a +block+ to define criteria. The behavior
|
792
790
|
# is the same, it returns true if the collection based on the
|
793
791
|
# criteria is not empty.
|
794
792
|
#
|
@@ -822,7 +820,7 @@ module ActiveRecord
|
|
822
820
|
# person.pets.count # => 2
|
823
821
|
# person.pets.many? # => true
|
824
822
|
#
|
825
|
-
# You can also pass a block to define criteria. The
|
823
|
+
# You can also pass a +block+ to define criteria. The
|
826
824
|
# behavior is the same, it returns true if the collection
|
827
825
|
# based on the criteria has more than one record.
|
828
826
|
#
|
@@ -846,7 +844,7 @@ module ActiveRecord
|
|
846
844
|
@association.many?(&block)
|
847
845
|
end
|
848
846
|
|
849
|
-
# Returns +true+ if the given
|
847
|
+
# Returns +true+ if the given +record+ is present in the collection.
|
850
848
|
#
|
851
849
|
# class Person < ActiveRecord::Base
|
852
850
|
# has_many :pets
|
@@ -884,7 +882,7 @@ module ActiveRecord
|
|
884
882
|
|
885
883
|
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
886
884
|
# contain the same number of elements and if each element is equal
|
887
|
-
# to the corresponding element in the other array, otherwise returns
|
885
|
+
# to the corresponding element in the +other+ array, otherwise returns
|
888
886
|
# +false+.
|
889
887
|
#
|
890
888
|
# class Person < ActiveRecord::Base
|
@@ -6,6 +6,7 @@ module ActiveRecord
|
|
6
6
|
# If the association has a <tt>:through</tt> option further specialization
|
7
7
|
# is provided by its child HasManyThroughAssociation.
|
8
8
|
class HasManyAssociation < CollectionAssociation #:nodoc:
|
9
|
+
include ForeignAssociation
|
9
10
|
|
10
11
|
def handle_dependency
|
11
12
|
case options[:dependent]
|
@@ -41,6 +42,14 @@ module ActiveRecord
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
45
|
+
def empty?
|
46
|
+
if has_cached_counter?
|
47
|
+
size.zero?
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
private
|
45
54
|
|
46
55
|
# Returns the number of records in this collection.
|
@@ -58,7 +67,7 @@ module ActiveRecord
|
|
58
67
|
# the loaded flag is set to true as well.
|
59
68
|
def count_records
|
60
69
|
count = if has_cached_counter?
|
61
|
-
owner.
|
70
|
+
owner._read_attribute cached_counter_attribute_name
|
62
71
|
else
|
63
72
|
scope.count
|
64
73
|
end
|
@@ -71,20 +80,42 @@ module ActiveRecord
|
|
71
80
|
[association_scope.limit_value, count].compact.min
|
72
81
|
end
|
73
82
|
|
83
|
+
|
84
|
+
# Returns whether a counter cache should be used for this association.
|
85
|
+
#
|
86
|
+
# The counter_cache option must be given on either the owner or inverse
|
87
|
+
# association, and the column must be present on the owner.
|
74
88
|
def has_cached_counter?(reflection = reflection())
|
75
|
-
|
89
|
+
if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
|
90
|
+
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
91
|
+
end
|
76
92
|
end
|
77
93
|
|
78
94
|
def cached_counter_attribute_name(reflection = reflection())
|
79
|
-
options[:counter_cache]
|
95
|
+
if reflection.options[:counter_cache]
|
96
|
+
reflection.options[:counter_cache].to_s
|
97
|
+
else
|
98
|
+
"#{reflection.name}_count"
|
99
|
+
end
|
80
100
|
end
|
81
101
|
|
82
102
|
def update_counter(difference, reflection = reflection())
|
103
|
+
update_counter_in_database(difference, reflection)
|
104
|
+
update_counter_in_memory(difference, reflection)
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_counter_in_database(difference, reflection = reflection())
|
83
108
|
if has_cached_counter?(reflection)
|
84
109
|
counter = cached_counter_attribute_name(reflection)
|
85
110
|
owner.class.update_counters(owner.id, counter => difference)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_counter_in_memory(difference, reflection = reflection())
|
115
|
+
if counter_must_be_updated_by_has_many?(reflection)
|
116
|
+
counter = cached_counter_attribute_name(reflection)
|
86
117
|
owner[counter] += difference
|
87
|
-
owner.
|
118
|
+
owner.send(:clear_attribute_changes, counter) # eww
|
88
119
|
end
|
89
120
|
end
|
90
121
|
|
@@ -98,13 +129,41 @@ module ActiveRecord
|
|
98
129
|
# it will be decremented twice.
|
99
130
|
#
|
100
131
|
# Hence this method.
|
101
|
-
def
|
132
|
+
def inverse_which_updates_counter_cache(reflection = reflection())
|
102
133
|
counter_name = cached_counter_attribute_name(reflection)
|
103
|
-
reflection
|
104
|
-
|
134
|
+
inverse_which_updates_counter_named(counter_name, reflection)
|
135
|
+
end
|
136
|
+
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
137
|
+
|
138
|
+
def inverse_which_updates_counter_named(counter_name, reflection)
|
139
|
+
reflection.klass._reflections.values.find { |inverse_reflection|
|
140
|
+
inverse_reflection.belongs_to? &&
|
105
141
|
inverse_reflection.counter_cache_column == counter_name
|
106
142
|
}
|
107
143
|
end
|
144
|
+
alias inverse_updates_counter_named? inverse_which_updates_counter_named
|
145
|
+
|
146
|
+
def inverse_updates_counter_in_memory?(reflection)
|
147
|
+
inverse = inverse_which_updates_counter_cache(reflection)
|
148
|
+
inverse && inverse == reflection.inverse_of
|
149
|
+
end
|
150
|
+
|
151
|
+
def counter_must_be_updated_by_has_many?(reflection)
|
152
|
+
!inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
|
153
|
+
end
|
154
|
+
|
155
|
+
def delete_count(method, scope)
|
156
|
+
if method == :delete_all
|
157
|
+
scope.delete_all
|
158
|
+
else
|
159
|
+
scope.update_all(reflection.foreign_key => nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def delete_or_nullify_all_records(method)
|
164
|
+
count = delete_count(method, self.scope)
|
165
|
+
update_counter(-count)
|
166
|
+
end
|
108
167
|
|
109
168
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
110
169
|
def delete_records(records, method)
|
@@ -112,26 +171,28 @@ module ActiveRecord
|
|
112
171
|
records.each(&:destroy!)
|
113
172
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
114
173
|
else
|
115
|
-
|
116
|
-
|
117
|
-
else
|
118
|
-
scope = self.scope.where(reflection.klass.primary_key => records)
|
119
|
-
end
|
120
|
-
|
121
|
-
if method == :delete_all
|
122
|
-
update_counter(-scope.delete_all)
|
123
|
-
else
|
124
|
-
update_counter(-scope.update_all(reflection.foreign_key => nil))
|
125
|
-
end
|
174
|
+
scope = self.scope.where(reflection.klass.primary_key => records)
|
175
|
+
update_counter(-delete_count(method, scope))
|
126
176
|
end
|
127
177
|
end
|
128
178
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
179
|
+
def concat_records(records, *)
|
180
|
+
update_counter_if_success(super, records.length)
|
181
|
+
end
|
182
|
+
|
183
|
+
def _create_record(attributes, *)
|
184
|
+
if attributes.is_a?(Array)
|
185
|
+
super
|
132
186
|
else
|
133
|
-
|
187
|
+
update_counter_if_success(super, 1)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def update_counter_if_success(saved_successfully, difference)
|
192
|
+
if saved_successfully
|
193
|
+
update_counter_in_memory(difference)
|
134
194
|
end
|
195
|
+
saved_successfully
|
135
196
|
end
|
136
197
|
end
|
137
198
|
end
|