activerecord 4.0.4 → 4.1.16

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1632 -1797
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/examples/performance.rb +30 -18
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +4 -0
  9. data/lib/active_record/associations/alias_tracker.rb +49 -29
  10. data/lib/active_record/associations/association.rb +9 -17
  11. data/lib/active_record/associations/association_scope.rb +59 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
  14. data/lib/active_record/associations/builder/association.rb +84 -54
  15. data/lib/active_record/associations/builder/belongs_to.rb +90 -58
  16. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
  18. data/lib/active_record/associations/builder/has_many.rb +3 -3
  19. data/lib/active_record/associations/builder/has_one.rb +5 -7
  20. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  21. data/lib/active_record/associations/collection_association.rb +121 -111
  22. data/lib/active_record/associations/collection_proxy.rb +73 -18
  23. data/lib/active_record/associations/has_many_association.rb +14 -11
  24. data/lib/active_record/associations/has_many_through_association.rb +33 -6
  25. data/lib/active_record/associations/has_one_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
  27. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  28. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  29. data/lib/active_record/associations/join_dependency.rb +208 -168
  30. data/lib/active_record/associations/preloader/association.rb +69 -27
  31. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  35. data/lib/active_record/associations/preloader.rb +63 -49
  36. data/lib/active_record/associations/singular_association.rb +6 -5
  37. data/lib/active_record/associations/through_association.rb +30 -9
  38. data/lib/active_record/associations.rb +116 -42
  39. data/lib/active_record/attribute_assignment.rb +6 -3
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  41. data/lib/active_record/attribute_methods/dirty.rb +35 -26
  42. data/lib/active_record/attribute_methods/primary_key.rb +8 -1
  43. data/lib/active_record/attribute_methods/read.rb +56 -29
  44. data/lib/active_record/attribute_methods/serialization.rb +44 -12
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
  46. data/lib/active_record/attribute_methods/write.rb +59 -26
  47. data/lib/active_record/attribute_methods.rb +82 -43
  48. data/lib/active_record/autosave_association.rb +209 -194
  49. data/lib/active_record/base.rb +6 -2
  50. data/lib/active_record/callbacks.rb +2 -2
  51. data/lib/active_record/coders/json.rb +13 -0
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
  76. data/lib/active_record/connection_handling.rb +39 -5
  77. data/lib/active_record/core.rb +38 -54
  78. data/lib/active_record/counter_cache.rb +9 -10
  79. data/lib/active_record/dynamic_matchers.rb +6 -2
  80. data/lib/active_record/enum.rb +199 -0
  81. data/lib/active_record/errors.rb +22 -5
  82. data/lib/active_record/fixture_set/file.rb +2 -1
  83. data/lib/active_record/fixtures.rb +173 -76
  84. data/lib/active_record/gem_version.rb +15 -0
  85. data/lib/active_record/inheritance.rb +23 -9
  86. data/lib/active_record/integration.rb +54 -1
  87. data/lib/active_record/locking/optimistic.rb +7 -2
  88. data/lib/active_record/locking/pessimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +6 -13
  90. data/lib/active_record/migration/command_recorder.rb +8 -2
  91. data/lib/active_record/migration.rb +91 -56
  92. data/lib/active_record/model_schema.rb +7 -14
  93. data/lib/active_record/nested_attributes.rb +25 -13
  94. data/lib/active_record/no_touching.rb +52 -0
  95. data/lib/active_record/null_relation.rb +26 -6
  96. data/lib/active_record/persistence.rb +23 -29
  97. data/lib/active_record/querying.rb +15 -12
  98. data/lib/active_record/railtie.rb +12 -61
  99. data/lib/active_record/railties/databases.rake +37 -56
  100. data/lib/active_record/readonly_attributes.rb +0 -6
  101. data/lib/active_record/reflection.rb +230 -79
  102. data/lib/active_record/relation/batches.rb +74 -24
  103. data/lib/active_record/relation/calculations.rb +52 -48
  104. data/lib/active_record/relation/delegation.rb +54 -39
  105. data/lib/active_record/relation/finder_methods.rb +210 -67
  106. data/lib/active_record/relation/merger.rb +15 -12
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
  109. data/lib/active_record/relation/predicate_builder.rb +81 -40
  110. data/lib/active_record/relation/query_methods.rb +185 -108
  111. data/lib/active_record/relation/spawn_methods.rb +8 -5
  112. data/lib/active_record/relation.rb +79 -84
  113. data/lib/active_record/result.rb +45 -6
  114. data/lib/active_record/runtime_registry.rb +5 -0
  115. data/lib/active_record/sanitization.rb +4 -4
  116. data/lib/active_record/schema_dumper.rb +18 -6
  117. data/lib/active_record/schema_migration.rb +31 -18
  118. data/lib/active_record/scoping/default.rb +5 -18
  119. data/lib/active_record/scoping/named.rb +14 -29
  120. data/lib/active_record/scoping.rb +5 -0
  121. data/lib/active_record/store.rb +67 -18
  122. data/lib/active_record/tasks/database_tasks.rb +66 -26
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
  124. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  125. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  126. data/lib/active_record/timestamp.rb +6 -6
  127. data/lib/active_record/transactions.rb +10 -12
  128. data/lib/active_record/validations/presence.rb +1 -1
  129. data/lib/active_record/validations/uniqueness.rb +19 -9
  130. data/lib/active_record/version.rb +4 -7
  131. data/lib/active_record.rb +5 -7
  132. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  133. data/lib/rails/generators/active_record/migration.rb +18 -0
  134. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  135. data/lib/rails/generators/active_record.rb +2 -8
  136. metadata +18 -30
  137. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  138. data/lib/active_record/associations/join_helper.rb +0 -45
  139. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  141. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  142. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  143. data/lib/active_record/test_case.rb +0 -96
@@ -4,11 +4,32 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- class_attribute :reflections
8
- self.reflections = {}
7
+ class_attribute :_reflections, instance_writer: false
8
+ class_attribute :aggregate_reflections, instance_writer: false
9
+ self._reflections = {}
10
+ self.aggregate_reflections = {}
9
11
  end
10
12
 
11
- # Reflection enables to interrogate Active Record classes and objects
13
+ def self.create(macro, name, scope, options, ar)
14
+ case macro
15
+ when :has_many, :belongs_to, :has_one
16
+ klass = options[:through] ? ThroughReflection : AssociationReflection
17
+ when :composed_of
18
+ klass = AggregateReflection
19
+ end
20
+
21
+ klass.new(macro, name, scope, options, ar)
22
+ end
23
+
24
+ def self.add_reflection(ar, name, reflection)
25
+ ar._reflections = ar._reflections.merge(name.to_sym => reflection)
26
+ end
27
+
28
+ def self.add_aggregate_reflection(ar, name, reflection)
29
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
30
+ end
31
+
32
+ # \Reflection enables to interrogate Active Record classes and objects
12
33
  # about their associations and aggregations. This information can,
13
34
  # for example, be used in a form builder that takes an Active Record object
14
35
  # and creates input fields for all of the attributes depending on their type
@@ -17,22 +38,9 @@ module ActiveRecord
17
38
  # MacroReflection class has info for AggregateReflection and AssociationReflection
18
39
  # classes.
19
40
  module ClassMethods
20
- def create_reflection(macro, name, scope, options, active_record)
21
- case macro
22
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
23
- klass = options[:through] ? ThroughReflection : AssociationReflection
24
- reflection = klass.new(macro, name, scope, options, active_record)
25
- when :composed_of
26
- reflection = AggregateReflection.new(macro, name, scope, options, active_record)
27
- end
28
-
29
- self.reflections = self.reflections.merge(name => reflection)
30
- reflection
31
- end
32
-
33
41
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
34
42
  def reflect_on_all_aggregations
35
- reflections.values.grep(AggregateReflection)
43
+ aggregate_reflections.values
36
44
  end
37
45
 
38
46
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -40,8 +48,25 @@ module ActiveRecord
40
48
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
41
49
  #
42
50
  def reflect_on_aggregation(aggregation)
43
- reflection = reflections[aggregation]
44
- reflection if reflection.is_a?(AggregateReflection)
51
+ aggregate_reflections[aggregation.to_sym]
52
+ end
53
+
54
+ # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
55
+ #
56
+ # Account.reflections # => {balance: AggregateReflection}
57
+ #
58
+ # @api public
59
+ def reflections
60
+ ref = {}
61
+ _reflections.each do |name, reflection|
62
+ parent_name, parent_reflection = reflection.parent_reflection
63
+ if parent_name
64
+ ref[parent_name] = parent_reflection
65
+ else
66
+ ref[name] = reflection
67
+ end
68
+ end
69
+ ref
45
70
  end
46
71
 
47
72
  # Returns an array of AssociationReflection objects for all the
@@ -54,8 +79,9 @@ module ActiveRecord
54
79
  # Account.reflect_on_all_associations # returns an array of all associations
55
80
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
56
81
  #
82
+ # @api public
57
83
  def reflect_on_all_associations(macro = nil)
58
- association_reflections = reflections.values.grep(AssociationReflection)
84
+ association_reflections = reflections.values
59
85
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
60
86
  end
61
87
 
@@ -64,12 +90,19 @@ module ActiveRecord
64
90
  # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
65
91
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
66
92
  #
93
+ # @api public
67
94
  def reflect_on_association(association)
68
- reflection = reflections[association]
69
- reflection if reflection.is_a?(AssociationReflection)
95
+ reflections[association.to_sym]
96
+ end
97
+
98
+ # @api private
99
+ def _reflect_on_association(association) #:nodoc:
100
+ _reflections[association.to_sym]
70
101
  end
71
102
 
72
103
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
104
+ #
105
+ # @api public
73
106
  def reflect_on_all_autosave_associations
74
107
  reflections.values.select { |reflection| reflection.options[:autosave] }
75
108
  end
@@ -100,7 +133,7 @@ module ActiveRecord
100
133
  # Returns the hash of options used for the macro.
101
134
  #
102
135
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
103
- # <tt>has_many :clients</tt> returns +{}+
136
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
104
137
  attr_reader :options
105
138
 
106
139
  attr_reader :active_record
@@ -113,10 +146,20 @@ module ActiveRecord
113
146
  @scope = scope
114
147
  @options = options
115
148
  @active_record = active_record
149
+ @klass = options[:anonymous_class]
116
150
  @plural_name = active_record.pluralize_table_names ?
117
151
  name.to_s.pluralize : name.to_s
118
152
  end
119
153
 
154
+ def autosave=(autosave)
155
+ @automatic_inverse_of = false
156
+ @options[:autosave] = autosave
157
+ _, parent_reflection = self.parent_reflection
158
+ if parent_reflection
159
+ parent_reflection.autosave = autosave
160
+ end
161
+ end
162
+
120
163
  # Returns the class for the macro.
121
164
  #
122
165
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
@@ -139,7 +182,7 @@ module ActiveRecord
139
182
  super ||
140
183
  other_aggregation.kind_of?(self.class) &&
141
184
  name == other_aggregation.name &&
142
- other_aggregation.options &&
185
+ !other_aggregation.options.nil? &&
143
186
  active_record == other_aggregation.active_record
144
187
  end
145
188
 
@@ -178,9 +221,16 @@ module ActiveRecord
178
221
  @klass ||= active_record.send(:compute_type, class_name)
179
222
  end
180
223
 
181
- def initialize(*args)
224
+ attr_reader :type, :foreign_type
225
+ attr_accessor :parent_reflection # [:name, Reflection]
226
+
227
+ def initialize(macro, name, scope, options, active_record)
182
228
  super
183
229
  @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
230
+ @automatic_inverse_of = nil
231
+ @type = options[:as] && "#{options[:as]}_type"
232
+ @foreign_type = options[:foreign_type] || "#{name}_type"
233
+ @constructable = calculate_constructable(macro, options)
184
234
  end
185
235
 
186
236
  # Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -189,12 +239,16 @@ module ActiveRecord
189
239
  klass.new(attributes, &block)
190
240
  end
191
241
 
242
+ def constructable? # :nodoc:
243
+ @constructable
244
+ end
245
+
192
246
  def table_name
193
- @table_name ||= klass.table_name
247
+ klass.table_name
194
248
  end
195
249
 
196
250
  def quoted_table_name
197
- @quoted_table_name ||= klass.quoted_table_name
251
+ klass.quoted_table_name
198
252
  end
199
253
 
200
254
  def join_table
@@ -205,16 +259,8 @@ module ActiveRecord
205
259
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
206
260
  end
207
261
 
208
- def foreign_type
209
- @foreign_type ||= options[:foreign_type] || "#{name}_type"
210
- end
211
-
212
- def type
213
- @type ||= options[:as] && "#{options[:as]}_type"
214
- end
215
-
216
262
  def primary_key_column
217
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
263
+ klass.columns_hash[klass.primary_key]
218
264
  end
219
265
 
220
266
  def association_foreign_key
@@ -238,20 +284,8 @@ module ActiveRecord
238
284
  end
239
285
  end
240
286
 
241
- def columns(tbl_name)
242
- @columns ||= klass.connection.columns(tbl_name)
243
- end
244
-
245
- def reset_column_information
246
- @columns = nil
247
- end
248
-
249
287
  def check_validity!
250
288
  check_validity_of_inverse!
251
-
252
- if has_and_belongs_to_many? && association_foreign_key == foreign_key
253
- raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
254
- end
255
289
  end
256
290
 
257
291
  def check_validity_of_inverse!
@@ -267,7 +301,7 @@ module ActiveRecord
267
301
  end
268
302
 
269
303
  def source_reflection
270
- nil
304
+ self
271
305
  end
272
306
 
273
307
  # A chain of reflections from this one back to the owner. For more see the explanation in
@@ -289,18 +323,18 @@ module ActiveRecord
289
323
  alias :source_macro :macro
290
324
 
291
325
  def has_inverse?
292
- @options[:inverse_of]
326
+ inverse_name
293
327
  end
294
328
 
295
329
  def inverse_of
296
- if has_inverse?
297
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
298
- end
330
+ return unless inverse_name
331
+
332
+ @inverse_of ||= klass._reflect_on_association inverse_name
299
333
  end
300
334
 
301
335
  def polymorphic_inverse_of(associated_class)
302
336
  if has_inverse?
303
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
337
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
304
338
  inverse_relationship
305
339
  else
306
340
  raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -333,10 +367,6 @@ module ActiveRecord
333
367
  macro == :belongs_to
334
368
  end
335
369
 
336
- def has_and_belongs_to_many?
337
- macro == :has_and_belongs_to_many
338
- end
339
-
340
370
  def association_class
341
371
  case macro
342
372
  when :belongs_to
@@ -345,8 +375,6 @@ module ActiveRecord
345
375
  else
346
376
  Associations::BelongsToAssociation
347
377
  end
348
- when :has_and_belongs_to_many
349
- Associations::HasAndBelongsToManyAssociation
350
378
  when :has_many
351
379
  if options[:through]
352
380
  Associations::HasManyThroughAssociation
@@ -366,11 +394,96 @@ module ActiveRecord
366
394
  options.key? :polymorphic
367
395
  end
368
396
 
397
+ VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
398
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
399
+
400
+ protected
401
+
402
+ def actual_source_reflection # FIXME: this is a horrible name
403
+ self
404
+ end
405
+
369
406
  private
407
+
408
+ def calculate_constructable(macro, options)
409
+ case macro
410
+ when :belongs_to
411
+ !options[:polymorphic]
412
+ when :has_one
413
+ !options[:through]
414
+ else
415
+ true
416
+ end
417
+ end
418
+
419
+ # Attempts to find the inverse association name automatically.
420
+ # If it cannot find a suitable inverse association name, it returns
421
+ # nil.
422
+ def inverse_name
423
+ options.fetch(:inverse_of) do
424
+ if @automatic_inverse_of == false
425
+ nil
426
+ else
427
+ @automatic_inverse_of ||= automatic_inverse_of
428
+ end
429
+ end
430
+ end
431
+
432
+ # returns either nil or the inverse association name that it finds.
433
+ def automatic_inverse_of
434
+ if can_find_inverse_of_automatically?(self)
435
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
436
+
437
+ begin
438
+ reflection = klass._reflect_on_association(inverse_name)
439
+ rescue NameError
440
+ # Give up: we couldn't compute the klass type so we won't be able
441
+ # to find any associations either.
442
+ reflection = false
443
+ end
444
+
445
+ if valid_inverse_reflection?(reflection)
446
+ return inverse_name
447
+ end
448
+ end
449
+
450
+ false
451
+ end
452
+
453
+ # Checks if the inverse reflection that is returned from the
454
+ # +automatic_inverse_of+ method is a valid reflection. We must
455
+ # make sure that the reflection's active_record name matches up
456
+ # with the current reflection's klass name.
457
+ #
458
+ # Note: klass will always be valid because when there's a NameError
459
+ # from calling +klass+, +reflection+ will already be set to false.
460
+ def valid_inverse_reflection?(reflection)
461
+ reflection &&
462
+ klass.name == reflection.active_record.name &&
463
+ can_find_inverse_of_automatically?(reflection)
464
+ end
465
+
466
+ # Checks to see if the reflection doesn't have any options that prevent
467
+ # us from being able to guess the inverse automatically. First, the
468
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
469
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
470
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
471
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
472
+ # inverse association.
473
+ #
474
+ # Anything with a scope can additionally ruin our attempt at finding an
475
+ # inverse, so we exclude reflections with scopes.
476
+ def can_find_inverse_of_automatically?(reflection)
477
+ reflection.options[:inverse_of] != false &&
478
+ VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
479
+ !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
480
+ !reflection.scope
481
+ end
482
+
370
483
  def derive_class_name
371
- class_name = name.to_s.camelize
484
+ class_name = name.to_s
372
485
  class_name = class_name.singularize if collection?
373
- class_name
486
+ class_name.camelize
374
487
  end
375
488
 
376
489
  def derive_foreign_key
@@ -398,7 +511,12 @@ module ActiveRecord
398
511
  delegate :foreign_key, :foreign_type, :association_foreign_key,
399
512
  :active_record_primary_key, :type, :to => :source_reflection
400
513
 
401
- # Gets the source of the through reflection. It checks both a singularized
514
+ def initialize(macro, name, scope, options, active_record)
515
+ super
516
+ @source_reflection_name = options[:source]
517
+ end
518
+
519
+ # Returns the source of the through reflection. It checks both a singularized
402
520
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
403
521
  #
404
522
  # class Post < ActiveRecord::Base
@@ -412,12 +530,13 @@ module ActiveRecord
412
530
  # end
413
531
  #
414
532
  # tags_reflection = Post.reflect_on_association(:tags)
415
- #
416
- # taggings_reflection = tags_reflection.source_reflection
533
+ # tags_reflection.source_reflection
417
534
  # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
418
535
  #
419
536
  def source_reflection
420
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
537
+ if source_reflection_name
538
+ through_reflection.klass._reflect_on_association(source_reflection_name)
539
+ end
421
540
  end
422
541
 
423
542
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -429,10 +548,11 @@ module ActiveRecord
429
548
  # end
430
549
  #
431
550
  # tags_reflection = Post.reflect_on_association(:tags)
432
- # taggings_reflection = tags_reflection.through_reflection
551
+ # tags_reflection.through_reflection
552
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
433
553
  #
434
554
  def through_reflection
435
- @through_reflection ||= active_record.reflect_on_association(options[:through])
555
+ active_record._reflect_on_association(options[:through])
436
556
  end
437
557
 
438
558
  # Returns an array of reflections which are involved in this association. Each item in the
@@ -454,7 +574,9 @@ module ActiveRecord
454
574
  #
455
575
  def chain
456
576
  @chain ||= begin
457
- chain = source_reflection.chain + through_reflection.chain
577
+ a = source_reflection.chain
578
+ b = through_reflection.chain
579
+ chain = a + b
458
580
  chain[0] = self # Use self so we don't lose the information from :source_type
459
581
  chain
460
582
  end
@@ -489,8 +611,11 @@ module ActiveRecord
489
611
  through_scope_chain = through_reflection.scope_chain.map(&:dup)
490
612
 
491
613
  if options[:source_type]
492
- through_scope_chain.first <<
493
- through_reflection.klass.where(foreign_type => options[:source_type])
614
+ type = foreign_type
615
+ source_type = options[:source_type]
616
+ through_scope_chain.first << lambda { |object|
617
+ where(type => source_type)
618
+ }
494
619
  end
495
620
 
496
621
  # Recursively fill out the rest of the array from the through reflection
@@ -505,7 +630,7 @@ module ActiveRecord
505
630
 
506
631
  # A through association is nested if there would be more than one join table
507
632
  def nested?
508
- chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
633
+ chain.length > 2
509
634
  end
510
635
 
511
636
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -514,12 +639,7 @@ module ActiveRecord
514
639
  def association_primary_key(klass = nil)
515
640
  # Get the "actual" source reflection if the immediate source reflection has a
516
641
  # source reflection itself
517
- source_reflection = self.source_reflection
518
- while source_reflection.source_reflection
519
- source_reflection = source_reflection.source_reflection
520
- end
521
-
522
- source_reflection.options[:primary_key] || primary_key(klass || self.klass)
642
+ actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
523
643
  end
524
644
 
525
645
  # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
@@ -534,7 +654,32 @@ module ActiveRecord
534
654
  # # => [:tag, :tags]
535
655
  #
536
656
  def source_reflection_names
537
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
657
+ (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
658
+ end
659
+
660
+ def source_reflection_name # :nodoc:
661
+ return @source_reflection_name.to_sym if @source_reflection_name
662
+
663
+ names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
664
+ names = names.find_all { |n|
665
+ through_reflection.klass._reflect_on_association(n)
666
+ }
667
+
668
+ if names.length > 1
669
+ example_options = options.dup
670
+ example_options[:source] = source_reflection_names.first
671
+ ActiveSupport::Deprecation.warn <<-eowarn
672
+ Ambiguous source reflection for through association. Please specify a :source
673
+ directive on your declaration like:
674
+
675
+ class #{active_record.name} < ActiveRecord::Base
676
+ #{macro} :#{name}, #{example_options}
677
+ end
678
+
679
+ eowarn
680
+ end
681
+
682
+ @source_reflection_name = names.first
538
683
  end
539
684
 
540
685
  def source_options
@@ -573,6 +718,12 @@ module ActiveRecord
573
718
  check_validity_of_inverse!
574
719
  end
575
720
 
721
+ protected
722
+
723
+ def actual_source_reflection # FIXME: this is a horrible name
724
+ source_reflection.actual_source_reflection
725
+ end
726
+
576
727
  private
577
728
  def derive_class_name
578
729
  # get the class_name of the belongs_to association of the through reflection
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
12
12
  # specified by the +:batch_size+ option).
13
13
  #
14
- # Person.all.find_each do |person|
14
+ # Person.find_each do |person|
15
15
  # person.do_awesome_stuff
16
16
  # end
17
17
  #
@@ -19,53 +19,103 @@ module ActiveRecord
19
19
  # person.party_all_night!
20
20
  # end
21
21
  #
22
- # You can also pass the +:start+ option to specify
23
- # an offset to control the starting point.
24
- def find_each(options = {})
25
- find_in_batches(options) do |records|
26
- records.each { |record| yield record }
27
- end
28
- end
29
-
30
- # Yields each batch of records that was found by the find +options+ as
31
- # an array. The size of each batch is set by the +:batch_size+
32
- # option; the default is 1000.
22
+ # If you do not provide a block to #find_each, it will return an Enumerator
23
+ # for chaining with other methods:
24
+ #
25
+ # Person.find_each.with_index do |person, index|
26
+ # person.award_trophy(index + 1)
27
+ # end
33
28
  #
34
- # You can control the starting point for the batch processing by
35
- # supplying the +:start+ option. This is especially useful if you
36
- # want multiple workers dealing with the same processing queue. You can
37
- # make worker 1 handle all the records between id 0 and 10,000 and
38
- # worker 2 handle from 10,000 and beyond (by setting the +:start+
39
- # option on that worker).
29
+ # ==== Options
30
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
31
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
32
+ # This is especially useful if you want multiple workers dealing with
33
+ # the same processing queue. You can make worker 1 handle all the records
34
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
35
+ # (by setting the +:start+ option on that worker).
40
36
  #
41
- # It's not possible to set the order. That is automatically set to
37
+ # # Let's process for a batch of 2000 records, skipping the first 2000 rows
38
+ # Person.find_each(start: 2000, batch_size: 2000) do |person|
39
+ # person.party_all_night!
40
+ # end
41
+ #
42
+ # NOTE: It's not possible to set the order. That is automatically set to
42
43
  # ascending on the primary key ("id ASC") to make the batch ordering
43
44
  # work. This also means that this method only works with integer-based
44
- # primary keys. You can't set the limit either, that's used to control
45
+ # primary keys.
46
+ #
47
+ # NOTE: You can't set the limit either, that's used to control
45
48
  # the batch sizes.
49
+ def find_each(options = {})
50
+ if block_given?
51
+ find_in_batches(options) do |records|
52
+ records.each { |record| yield record }
53
+ end
54
+ else
55
+ enum_for :find_each, options do
56
+ options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
57
+ end
58
+ end
59
+ end
60
+
61
+ # Yields each batch of records that was found by the find +options+ as
62
+ # an array.
46
63
  #
47
64
  # Person.where("age > 21").find_in_batches do |group|
48
65
  # sleep(50) # Make sure it doesn't get too crowded in there!
49
66
  # group.each { |person| person.party_all_night! }
50
67
  # end
51
68
  #
69
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
70
+ # for chaining with other methods:
71
+ #
72
+ # Person.find_in_batches.with_index do |group, batch|
73
+ # puts "Processing group ##{batch}"
74
+ # group.each(&:recover_from_last_night!)
75
+ # end
76
+ #
77
+ # To be yielded each record one by one, use #find_each instead.
78
+ #
79
+ # ==== Options
80
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
81
+ # * <tt>:start</tt> - Specifies the starting point for the batch processing.
82
+ # This is especially useful if you want multiple workers dealing with
83
+ # the same processing queue. You can make worker 1 handle all the records
84
+ # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
85
+ # (by setting the +:start+ option on that worker).
86
+ #
52
87
  # # Let's process the next 2000 records
53
- # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
88
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
54
89
  # group.each { |person| person.party_all_night! }
55
90
  # end
91
+ #
92
+ # NOTE: It's not possible to set the order. That is automatically set to
93
+ # ascending on the primary key ("id ASC") to make the batch ordering
94
+ # work. This also means that this method only works with integer-based
95
+ # primary keys.
96
+ #
97
+ # NOTE: You can't set the limit either, that's used to control
98
+ # the batch sizes.
56
99
  def find_in_batches(options = {})
57
100
  options.assert_valid_keys(:start, :batch_size)
58
101
 
59
102
  relation = self
103
+ start = options[:start]
104
+ batch_size = options[:batch_size] || 1000
105
+
106
+ unless block_given?
107
+ return to_enum(:find_in_batches, options) do
108
+ total = start ? where(table[primary_key].gteq(start)).size : size
109
+ (total - 1).div(batch_size) + 1
110
+ end
111
+ end
60
112
 
61
113
  if logger && (arel.orders.present? || arel.taken.present?)
62
114
  logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
63
115
  end
64
116
 
65
- start = options.delete(:start)
66
- batch_size = options.delete(:batch_size) || 1000
67
-
68
117
  relation = relation.reorder(batch_order).limit(batch_size)
118
+ relation.reverse_order_value = false
69
119
  records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
70
120
 
71
121
  while records.any?