activerecord 4.0.13 → 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +745 -2700
  3. data/README.rdoc +2 -2
  4. data/examples/performance.rb +30 -18
  5. data/examples/simple.rb +4 -4
  6. data/lib/active_record.rb +2 -6
  7. data/lib/active_record/aggregations.rb +2 -1
  8. data/lib/active_record/association_relation.rb +0 -4
  9. data/lib/active_record/associations.rb +87 -43
  10. data/lib/active_record/associations/alias_tracker.rb +1 -3
  11. data/lib/active_record/associations/association.rb +8 -16
  12. data/lib/active_record/associations/association_scope.rb +5 -16
  13. data/lib/active_record/associations/belongs_to_association.rb +34 -25
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  15. data/lib/active_record/associations/builder/association.rb +78 -54
  16. data/lib/active_record/associations/builder/belongs_to.rb +91 -58
  17. data/lib/active_record/associations/builder/collection_association.rb +47 -45
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
  19. data/lib/active_record/associations/builder/has_many.rb +2 -2
  20. data/lib/active_record/associations/builder/has_one.rb +5 -7
  21. data/lib/active_record/associations/builder/singular_association.rb +6 -7
  22. data/lib/active_record/associations/collection_association.rb +68 -105
  23. data/lib/active_record/associations/collection_proxy.rb +12 -15
  24. data/lib/active_record/associations/has_many_association.rb +11 -9
  25. data/lib/active_record/associations/has_many_through_association.rb +16 -12
  26. data/lib/active_record/associations/has_one_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +204 -165
  28. data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
  29. data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
  31. data/lib/active_record/associations/join_helper.rb +2 -11
  32. data/lib/active_record/associations/preloader.rb +89 -34
  33. data/lib/active_record/associations/preloader/association.rb +43 -25
  34. data/lib/active_record/associations/preloader/collection_association.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +58 -26
  38. data/lib/active_record/associations/singular_association.rb +6 -5
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +5 -2
  41. data/lib/active_record/attribute_methods.rb +45 -40
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +8 -22
  44. data/lib/active_record/attribute_methods/primary_key.rb +1 -7
  45. data/lib/active_record/attribute_methods/read.rb +55 -28
  46. data/lib/active_record/attribute_methods/serialization.rb +12 -33
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
  48. data/lib/active_record/attribute_methods/write.rb +37 -12
  49. data/lib/active_record/autosave_association.rb +207 -207
  50. data/lib/active_record/base.rb +5 -1
  51. data/lib/active_record/callbacks.rb +2 -2
  52. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -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 +52 -83
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
  63. data/lib/active_record/connection_adapters/column.rb +1 -35
  64. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
  68. data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
  71. data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
  74. data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
  76. data/lib/active_record/connection_handling.rb +2 -2
  77. data/lib/active_record/core.rb +22 -43
  78. data/lib/active_record/counter_cache.rb +7 -7
  79. data/lib/active_record/enum.rb +100 -0
  80. data/lib/active_record/errors.rb +10 -5
  81. data/lib/active_record/fixture_set/file.rb +2 -1
  82. data/lib/active_record/fixtures.rb +171 -74
  83. data/lib/active_record/inheritance.rb +16 -22
  84. data/lib/active_record/integration.rb +52 -1
  85. data/lib/active_record/locking/optimistic.rb +7 -2
  86. data/lib/active_record/locking/pessimistic.rb +1 -1
  87. data/lib/active_record/log_subscriber.rb +5 -12
  88. data/lib/active_record/migration.rb +62 -46
  89. data/lib/active_record/migration/command_recorder.rb +7 -13
  90. data/lib/active_record/model_schema.rb +7 -14
  91. data/lib/active_record/nested_attributes.rb +10 -8
  92. data/lib/active_record/no_touching.rb +52 -0
  93. data/lib/active_record/null_relation.rb +3 -3
  94. data/lib/active_record/persistence.rb +16 -34
  95. data/lib/active_record/querying.rb +14 -12
  96. data/lib/active_record/railtie.rb +0 -50
  97. data/lib/active_record/railties/databases.rake +12 -15
  98. data/lib/active_record/readonly_attributes.rb +0 -6
  99. data/lib/active_record/reflection.rb +189 -75
  100. data/lib/active_record/relation.rb +69 -94
  101. data/lib/active_record/relation/batches.rb +57 -23
  102. data/lib/active_record/relation/calculations.rb +36 -43
  103. data/lib/active_record/relation/delegation.rb +54 -39
  104. data/lib/active_record/relation/finder_methods.rb +107 -62
  105. data/lib/active_record/relation/merger.rb +7 -20
  106. data/lib/active_record/relation/predicate_builder.rb +57 -38
  107. data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
  108. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  109. data/lib/active_record/relation/query_methods.rb +110 -98
  110. data/lib/active_record/relation/spawn_methods.rb +1 -2
  111. data/lib/active_record/result.rb +45 -6
  112. data/lib/active_record/runtime_registry.rb +5 -0
  113. data/lib/active_record/sanitization.rb +6 -8
  114. data/lib/active_record/schema_dumper.rb +16 -5
  115. data/lib/active_record/schema_migration.rb +24 -25
  116. data/lib/active_record/scoping/default.rb +5 -18
  117. data/lib/active_record/scoping/named.rb +8 -29
  118. data/lib/active_record/store.rb +56 -28
  119. data/lib/active_record/tasks/database_tasks.rb +8 -4
  120. data/lib/active_record/timestamp.rb +4 -4
  121. data/lib/active_record/transactions.rb +8 -10
  122. data/lib/active_record/validations/presence.rb +1 -1
  123. data/lib/active_record/validations/uniqueness.rb +1 -6
  124. data/lib/active_record/version.rb +1 -1
  125. data/lib/rails/generators/active_record.rb +2 -8
  126. data/lib/rails/generators/active_record/migration.rb +18 -0
  127. data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
  128. data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
  129. metadata +32 -45
  130. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
  131. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  132. data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
  133. data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
  134. data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
  135. data/lib/active_record/test_case.rb +0 -102
@@ -20,11 +20,5 @@ module ActiveRecord
20
20
  self._attr_readonly
21
21
  end
22
22
  end
23
-
24
- def _attr_readonly
25
- message = "Instance level _attr_readonly method is deprecated, please use class level method."
26
- ActiveSupport::Deprecation.warn message
27
- defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
28
- end
29
23
  end
30
24
  end
@@ -5,10 +5,31 @@ module ActiveRecord
5
5
 
6
6
  included do
7
7
  class_attribute :reflections
8
+ class_attribute :aggregate_reflections
8
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 => reflection)
26
+ end
27
+
28
+ def self.add_aggregate_reflection(ar, name, reflection)
29
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name => 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,7 @@ 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]
45
52
  end
46
53
 
47
54
  # Returns an array of AssociationReflection objects for all the
@@ -55,7 +62,7 @@ module ActiveRecord
55
62
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
56
63
  #
57
64
  def reflect_on_all_associations(macro = nil)
58
- association_reflections = reflections.values.grep(AssociationReflection)
65
+ association_reflections = reflections.values
59
66
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
60
67
  end
61
68
 
@@ -65,8 +72,7 @@ module ActiveRecord
65
72
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
66
73
  #
67
74
  def reflect_on_association(association)
68
- reflection = reflections[association]
69
- reflection if reflection.is_a?(AssociationReflection)
75
+ reflections[association]
70
76
  end
71
77
 
72
78
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -100,7 +106,7 @@ module ActiveRecord
100
106
  # Returns the hash of options used for the macro.
101
107
  #
102
108
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
103
- # <tt>has_many :clients</tt> returns +{}+
109
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
104
110
  attr_reader :options
105
111
 
106
112
  attr_reader :active_record
@@ -113,10 +119,16 @@ module ActiveRecord
113
119
  @scope = scope
114
120
  @options = options
115
121
  @active_record = active_record
122
+ @klass = options[:class]
116
123
  @plural_name = active_record.pluralize_table_names ?
117
124
  name.to_s.pluralize : name.to_s
118
125
  end
119
126
 
127
+ def autosave=(autosave)
128
+ @automatic_inverse_of = false
129
+ @options[:autosave] = autosave
130
+ end
131
+
120
132
  # Returns the class for the macro.
121
133
  #
122
134
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
@@ -178,9 +190,15 @@ module ActiveRecord
178
190
  @klass ||= active_record.send(:compute_type, class_name)
179
191
  end
180
192
 
181
- def initialize(*args)
193
+ attr_reader :type, :foreign_type
194
+
195
+ def initialize(macro, name, scope, options, active_record)
182
196
  super
183
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
197
+ @collection = :has_many == macro
198
+ @automatic_inverse_of = nil
199
+ @type = options[:as] && "#{options[:as]}_type"
200
+ @foreign_type = options[:foreign_type] || "#{name}_type"
201
+ @constructable = calculate_constructable(macro, options)
184
202
  end
185
203
 
186
204
  # Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -189,12 +207,16 @@ module ActiveRecord
189
207
  klass.new(attributes, &block)
190
208
  end
191
209
 
210
+ def constructable? # :nodoc:
211
+ @constructable
212
+ end
213
+
192
214
  def table_name
193
- @table_name ||= klass.table_name
215
+ klass.table_name
194
216
  end
195
217
 
196
218
  def quoted_table_name
197
- @quoted_table_name ||= klass.quoted_table_name
219
+ klass.quoted_table_name
198
220
  end
199
221
 
200
222
  def join_table
@@ -205,16 +227,8 @@ module ActiveRecord
205
227
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
206
228
  end
207
229
 
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
230
  def primary_key_column
217
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
231
+ klass.columns_hash[klass.primary_key]
218
232
  end
219
233
 
220
234
  def association_foreign_key
@@ -238,20 +252,8 @@ module ActiveRecord
238
252
  end
239
253
  end
240
254
 
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
255
  def check_validity!
250
256
  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
257
  end
256
258
 
257
259
  def check_validity_of_inverse!
@@ -267,7 +269,7 @@ module ActiveRecord
267
269
  end
268
270
 
269
271
  def source_reflection
270
- nil
272
+ self
271
273
  end
272
274
 
273
275
  # A chain of reflections from this one back to the owner. For more see the explanation in
@@ -289,13 +291,13 @@ module ActiveRecord
289
291
  alias :source_macro :macro
290
292
 
291
293
  def has_inverse?
292
- @options[:inverse_of]
294
+ inverse_name
293
295
  end
294
296
 
295
297
  def inverse_of
296
- if has_inverse?
297
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
298
- end
298
+ return unless inverse_name
299
+
300
+ @inverse_of ||= klass.reflect_on_association inverse_name
299
301
  end
300
302
 
301
303
  def polymorphic_inverse_of(associated_class)
@@ -333,10 +335,6 @@ module ActiveRecord
333
335
  macro == :belongs_to
334
336
  end
335
337
 
336
- def has_and_belongs_to_many?
337
- macro == :has_and_belongs_to_many
338
- end
339
-
340
338
  def association_class
341
339
  case macro
342
340
  when :belongs_to
@@ -345,8 +343,6 @@ module ActiveRecord
345
343
  else
346
344
  Associations::BelongsToAssociation
347
345
  end
348
- when :has_and_belongs_to_many
349
- Associations::HasAndBelongsToManyAssociation
350
346
  when :has_many
351
347
  if options[:through]
352
348
  Associations::HasManyThroughAssociation
@@ -366,11 +362,96 @@ module ActiveRecord
366
362
  options.key? :polymorphic
367
363
  end
368
364
 
365
+ VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
366
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
367
+
368
+ protected
369
+
370
+ def actual_source_reflection # FIXME: this is a horrible name
371
+ self
372
+ end
373
+
369
374
  private
375
+
376
+ def calculate_constructable(macro, options)
377
+ case macro
378
+ when :belongs_to
379
+ !options[:polymorphic]
380
+ when :has_one
381
+ !options[:through]
382
+ else
383
+ true
384
+ end
385
+ end
386
+
387
+ # Attempts to find the inverse association name automatically.
388
+ # If it cannot find a suitable inverse association name, it returns
389
+ # nil.
390
+ def inverse_name
391
+ options.fetch(:inverse_of) do
392
+ if @automatic_inverse_of == false
393
+ nil
394
+ else
395
+ @automatic_inverse_of ||= automatic_inverse_of
396
+ end
397
+ end
398
+ end
399
+
400
+ # returns either nil or the inverse association name that it finds.
401
+ def automatic_inverse_of
402
+ if can_find_inverse_of_automatically?(self)
403
+ inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
404
+
405
+ begin
406
+ reflection = klass.reflect_on_association(inverse_name)
407
+ rescue NameError
408
+ # Give up: we couldn't compute the klass type so we won't be able
409
+ # to find any associations either.
410
+ reflection = false
411
+ end
412
+
413
+ if valid_inverse_reflection?(reflection)
414
+ return inverse_name
415
+ end
416
+ end
417
+
418
+ false
419
+ end
420
+
421
+ # Checks if the inverse reflection that is returned from the
422
+ # +automatic_inverse_of+ method is a valid reflection. We must
423
+ # make sure that the reflection's active_record name matches up
424
+ # with the current reflection's klass name.
425
+ #
426
+ # Note: klass will always be valid because when there's a NameError
427
+ # from calling +klass+, +reflection+ will already be set to false.
428
+ def valid_inverse_reflection?(reflection)
429
+ reflection &&
430
+ klass.name == reflection.active_record.name &&
431
+ can_find_inverse_of_automatically?(reflection)
432
+ end
433
+
434
+ # Checks to see if the reflection doesn't have any options that prevent
435
+ # us from being able to guess the inverse automatically. First, the
436
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
437
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
438
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
439
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
440
+ # inverse association.
441
+ #
442
+ # Anything with a scope can additionally ruin our attempt at finding an
443
+ # inverse, so we exclude reflections with scopes.
444
+ def can_find_inverse_of_automatically?(reflection)
445
+ reflection.options[:inverse_of] != false &&
446
+ VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
447
+ !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
448
+ !reflection.scope
449
+ end
450
+
370
451
  def derive_class_name
371
- class_name = name.to_s
452
+ class_name = name.to_s.camelize
372
453
  class_name = class_name.singularize if collection?
373
- class_name.camelize
454
+ class_name
374
455
  end
375
456
 
376
457
  def derive_foreign_key
@@ -384,7 +465,7 @@ module ActiveRecord
384
465
  end
385
466
 
386
467
  def derive_join_table
387
- [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
468
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
388
469
  end
389
470
 
390
471
  def primary_key(klass)
@@ -398,7 +479,12 @@ module ActiveRecord
398
479
  delegate :foreign_key, :foreign_type, :association_foreign_key,
399
480
  :active_record_primary_key, :type, :to => :source_reflection
400
481
 
401
- # Gets the source of the through reflection. It checks both a singularized
482
+ def initialize(macro, name, scope, options, active_record)
483
+ super
484
+ @source_reflection_name = options[:source]
485
+ end
486
+
487
+ # Returns the source of the through reflection. It checks both a singularized
402
488
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
403
489
  #
404
490
  # class Post < ActiveRecord::Base
@@ -412,12 +498,11 @@ module ActiveRecord
412
498
  # end
413
499
  #
414
500
  # tags_reflection = Post.reflect_on_association(:tags)
415
- #
416
- # taggings_reflection = tags_reflection.source_reflection
501
+ # tags_reflection.source_reflection
417
502
  # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
418
503
  #
419
504
  def source_reflection
420
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
505
+ through_reflection.klass.reflect_on_association(source_reflection_name)
421
506
  end
422
507
 
423
508
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -429,10 +514,11 @@ module ActiveRecord
429
514
  # end
430
515
  #
431
516
  # tags_reflection = Post.reflect_on_association(:tags)
432
- # taggings_reflection = tags_reflection.through_reflection
517
+ # tags_reflection.through_reflection
518
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
433
519
  #
434
520
  def through_reflection
435
- @through_reflection ||= active_record.reflect_on_association(options[:through])
521
+ active_record.reflect_on_association(options[:through])
436
522
  end
437
523
 
438
524
  # Returns an array of reflections which are involved in this association. Each item in the
@@ -454,7 +540,9 @@ module ActiveRecord
454
540
  #
455
541
  def chain
456
542
  @chain ||= begin
457
- chain = source_reflection.chain + through_reflection.chain
543
+ a = source_reflection.chain
544
+ b = through_reflection.chain
545
+ chain = a + b
458
546
  chain[0] = self # Use self so we don't lose the information from :source_type
459
547
  chain
460
548
  end
@@ -505,7 +593,7 @@ module ActiveRecord
505
593
 
506
594
  # A through association is nested if there would be more than one join table
507
595
  def nested?
508
- chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
596
+ chain.length > 2
509
597
  end
510
598
 
511
599
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -514,12 +602,7 @@ module ActiveRecord
514
602
  def association_primary_key(klass = nil)
515
603
  # Get the "actual" source reflection if the immediate source reflection has a
516
604
  # 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)
605
+ actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
523
606
  end
524
607
 
525
608
  # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
@@ -534,7 +617,32 @@ module ActiveRecord
534
617
  # # => [:tag, :tags]
535
618
  #
536
619
  def source_reflection_names
537
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
620
+ (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
621
+ end
622
+
623
+ def source_reflection_name # :nodoc:
624
+ return @source_reflection_name if @source_reflection_name
625
+
626
+ names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
627
+ names = names.find_all { |n|
628
+ through_reflection.klass.reflect_on_association(n)
629
+ }
630
+
631
+ if names.length > 1
632
+ example_options = options.dup
633
+ example_options[:source] = source_reflection_names.first
634
+ ActiveSupport::Deprecation.warn <<-eowarn
635
+ Ambiguous source reflection for through association. Please specify a :source
636
+ directive on your declaration like:
637
+
638
+ class #{active_record.name} < ActiveRecord::Base
639
+ #{macro} :#{name}, #{example_options}
640
+ end
641
+
642
+ eowarn
643
+ end
644
+
645
+ @source_reflection_name = names.first
538
646
  end
539
647
 
540
648
  def source_options
@@ -573,6 +681,12 @@ module ActiveRecord
573
681
  check_validity_of_inverse!
574
682
  end
575
683
 
684
+ protected
685
+
686
+ def actual_source_reflection # FIXME: this is a horrible name
687
+ source_reflection.actual_source_reflection
688
+ end
689
+
576
690
  private
577
691
  def derive_class_name
578
692
  # get the class_name of the belongs_to association of the through reflection