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.

Files changed (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1165 -1591
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +84 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +87 -30
  18. data/lib/active_record/associations/collection_proxy.rb +33 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -12
  26. data/lib/active_record/associations/preloader/association.rb +14 -10
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +37 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +16 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +20 -12
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +57 -95
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +30 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -40
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +10 -12
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +62 -74
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +79 -47
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +18 -8
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +48 -27
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +19 -14
  126. data/lib/active_record/railties/databases.rake +55 -56
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +281 -117
  129. data/lib/active_record/relation/batches.rb +0 -1
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/delegation.rb +1 -1
  132. data/lib/active_record/relation/finder_methods.rb +71 -48
  133. data/lib/active_record/relation/merger.rb +39 -29
  134. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  135. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  136. data/lib/active_record/relation/predicate_builder.rb +42 -12
  137. data/lib/active_record/relation/query_methods.rb +130 -73
  138. data/lib/active_record/relation/spawn_methods.rb +10 -3
  139. data/lib/active_record/relation.rb +57 -25
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -8
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +54 -28
  155. data/lib/active_record/type/big_integer.rb +13 -0
  156. data/lib/active_record/type/binary.rb +50 -0
  157. data/lib/active_record/type/boolean.rb +31 -0
  158. data/lib/active_record/type/date.rb +50 -0
  159. data/lib/active_record/type/date_time.rb +54 -0
  160. data/lib/active_record/type/decimal.rb +64 -0
  161. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  162. data/lib/active_record/type/decorator.rb +14 -0
  163. data/lib/active_record/type/float.rb +19 -0
  164. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  165. data/lib/active_record/type/integer.rb +59 -0
  166. data/lib/active_record/type/mutable.rb +16 -0
  167. data/lib/active_record/type/numeric.rb +36 -0
  168. data/lib/active_record/type/serialized.rb +62 -0
  169. data/lib/active_record/type/string.rb +40 -0
  170. data/lib/active_record/type/text.rb +11 -0
  171. data/lib/active_record/type/time.rb +26 -0
  172. data/lib/active_record/type/time_value.rb +38 -0
  173. data/lib/active_record/type/type_map.rb +64 -0
  174. data/lib/active_record/type/unsigned_integer.rb +15 -0
  175. data/lib/active_record/type/value.rb +110 -0
  176. data/lib/active_record/type.rb +23 -0
  177. data/lib/active_record/validations/associated.rb +5 -3
  178. data/lib/active_record/validations/presence.rb +5 -3
  179. data/lib/active_record/validations/uniqueness.rb +24 -20
  180. data/lib/active_record/validations.rb +25 -19
  181. data/lib/active_record.rb +5 -0
  182. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  183. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  184. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  185. metadata +66 -11
  186. 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 + [:order, :as]
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.add_before_destroy_callbacks(model, reflection)
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 + [:remote, :dependent, :primary_key, :inverse_of]
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
- @proxy ||= CollectionProxy.create(klass, self)
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.primary_key_column
59
- ids = Array(ids).reject { |id| id.blank? }
60
- ids.map! { |i| pk_column.type_cast(i) }
61
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
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 `:dependent` option. However
174
- # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
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.present? && ![:nullify, :delete_all].include?(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.present?
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
- delete(:all, dependent: dependent).tap do
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.first == :all
252
- if (loaded? || dependent == :destroy) && dependent != :delete_all
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
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
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
- transaction { replace_records(other_array, original_target) }
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 association_scope.distinct_value && index = @target.index(record)
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 = scope.to_a
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
- target = source.send(reflection.source_reflection.name)
564
- target.respond_to?(:include?) ? target.include?(record) : target == record
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. For +has_many+ associations,
359
- # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
360
- # option. Returns an array with the deleted records.
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
- # If no <tt>:dependent</tt> option is given, then it will follow the
363
- # default strategy. The default strategy is <tt>:nullify</tt>. This
364
- # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
365
- # the default strategy is +delete_all+.
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
- # If it is set to <tt>:destroy</tt> all the objects from the collection
397
- # are removed by calling their +destroy+ method. See +destroy+ for more
398
- # information.
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. It invokes +before_remove+,
452
- # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
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 and removes them from the collection. For
477
- # +has_many+ associations, the deletion is done according to the strategy
478
- # specified by the <tt>:dependent</tt> option. Returns an array with the
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
- # If no <tt>:dependent</tt> option is given, then it will follow the default
482
- # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
483
- # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
484
- # strategy is +delete_all+.
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 +Fixnum+ or +String+ values, it finds the records
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 +Fixnum+ or +String+ values, it finds the records
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 object is present in the collection.
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
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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.send(:read_attribute, cached_counter_attribute_name)
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
- owner.attribute_present?(cached_counter_attribute_name(reflection))
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] || "#{reflection.name}_count"
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.changed_attributes.delete(counter) # eww
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 inverse_updates_counter_cache?(reflection = reflection())
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
102
133
  counter_name = cached_counter_attribute_name(reflection)
103
- reflection.klass._reflections.values.any? { |inverse_reflection|
104
- :belongs_to == inverse_reflection.macro &&
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
- if records == :all || !reflection.klass.primary_key
116
- scope = self.scope
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 foreign_key_present?
130
- if reflection.klass.primary_key
131
- owner.attribute_present?(reflection.association_primary_key)
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
- false
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