activerecord 5.0.0.beta2 → 5.0.0.beta3

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -20
  3. data/lib/active_record/association_relation.rb +1 -1
  4. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -0
  5. data/lib/active_record/associations/collection_association.rb +12 -1
  6. data/lib/active_record/associations/collection_proxy.rb +14 -0
  7. data/lib/active_record/associations/join_dependency/join_association.rb +13 -7
  8. data/lib/active_record/associations/preloader/association.rb +1 -1
  9. data/lib/active_record/associations/singular_association.rb +1 -1
  10. data/lib/active_record/attribute_assignment.rb +0 -8
  11. data/lib/active_record/attribute_methods.rb +0 -24
  12. data/lib/active_record/attribute_methods/read.rb +5 -17
  13. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -2
  14. data/lib/active_record/attribute_methods/write.rb +0 -13
  15. data/lib/active_record/base.rb +0 -1
  16. data/lib/active_record/callbacks.rb +1 -1
  17. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  18. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -2
  20. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  21. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -6
  22. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -2
  23. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -16
  24. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  25. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +2 -2
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -3
  27. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  28. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -3
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +3 -4
  30. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +0 -1
  31. data/lib/active_record/core.rb +5 -0
  32. data/lib/active_record/gem_version.rb +1 -1
  33. data/lib/active_record/inheritance.rb +1 -1
  34. data/lib/active_record/migration/compatibility.rb +1 -1
  35. data/lib/active_record/nested_attributes.rb +14 -6
  36. data/lib/active_record/null_relation.rb +1 -1
  37. data/lib/active_record/querying.rb +3 -3
  38. data/lib/active_record/reflection.rb +53 -36
  39. data/lib/active_record/relation.rb +26 -18
  40. data/lib/active_record/relation/batches.rb +4 -4
  41. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  42. data/lib/active_record/relation/calculations.rb +2 -10
  43. data/lib/active_record/relation/delegation.rb +2 -1
  44. data/lib/active_record/relation/finder_methods.rb +55 -26
  45. data/lib/active_record/relation/predicate_builder.rb +3 -4
  46. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +10 -0
  47. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  48. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  49. data/lib/active_record/relation/query_methods.rb +11 -7
  50. data/lib/active_record/relation/spawn_methods.rb +1 -1
  51. data/lib/active_record/sanitization.rb +1 -1
  52. data/lib/active_record/schema_dumper.rb +6 -4
  53. data/lib/active_record/scoping/named.rb +10 -0
  54. data/lib/active_record/statement_cache.rb +1 -1
  55. data/lib/active_record/table_metadata.rb +5 -1
  56. data/lib/active_record/tasks/database_tasks.rb +4 -0
  57. data/lib/active_record/validations.rb +1 -1
  58. data/lib/active_record/validations/absence.rb +0 -1
  59. data/lib/active_record/validations/length.rb +0 -12
  60. data/lib/active_record/validations/presence.rb +0 -1
  61. data/lib/active_record/validations/uniqueness.rb +7 -9
  62. data/lib/rails/generators/active_record/model/model_generator.rb +9 -0
  63. data/lib/rails/generators/active_record/model/templates/application_record.rb +3 -0
  64. metadata +9 -8
@@ -182,6 +182,7 @@ module ActiveRecord
182
182
  end
183
183
  CODE
184
184
  end
185
+ alias_method :numeric, :decimal
185
186
  end
186
187
 
187
188
  # Represents the schema of an SQL table in an abstract way. This class
@@ -211,7 +212,7 @@ module ActiveRecord
211
212
  def initialize(name, temporary, options, as = nil)
212
213
  @columns_hash = {}
213
214
  @indexes = {}
214
- @foreign_keys = {}
215
+ @foreign_keys = []
215
216
  @primary_keys = nil
216
217
  @temporary = temporary
217
218
  @options = options
@@ -329,7 +330,7 @@ module ActiveRecord
329
330
  end
330
331
 
331
332
  def foreign_key(table_name, options = {}) # :nodoc:
332
- foreign_keys[table_name] = options
333
+ foreign_keys.push([table_name, options])
333
334
  end
334
335
 
335
336
  # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
@@ -436,6 +437,7 @@ module ActiveRecord
436
437
  # t.bigint
437
438
  # t.float
438
439
  # t.decimal
440
+ # t.numeric
439
441
  # t.datetime
440
442
  # t.timestamp
441
443
  # t.time
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
 
15
15
  def column_spec_for_primary_key(column)
16
16
  return if column.type == :integer
17
- spec = { id: column.type.inspect }
17
+ spec = { id: schema_type(column).inspect }
18
18
  spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  def prepare_column_options(column)
25
25
  spec = {}
26
26
  spec[:name] = column.name.inspect
27
- spec[:type] = schema_type(column)
27
+ spec[:type] = schema_type(column).to_s
28
28
  spec[:null] = 'false' unless column.null
29
29
 
30
30
  if limit = schema_limit(column)
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  private
58
58
 
59
59
  def schema_type(column)
60
- column.type.to_s
60
+ column.type
61
61
  end
62
62
 
63
63
  def schema_limit(column)
@@ -459,7 +459,7 @@ module ActiveRecord
459
459
  # The +type+ parameter is normally one of the migrations native types,
460
460
  # which is one of the following:
461
461
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
462
- # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
462
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
463
463
  # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
464
464
  # <tt>:binary</tt>, <tt>:boolean</tt>.
465
465
  #
@@ -477,9 +477,9 @@ module ActiveRecord
477
477
  # Allows or disallows +NULL+ values in the column. This option could
478
478
  # have been named <tt>:null_allowed</tt>.
479
479
  # * <tt>:precision</tt> -
480
- # Specifies the precision for a <tt>:decimal</tt> column.
480
+ # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
481
481
  # * <tt>:scale</tt> -
482
- # Specifies the scale for a <tt>:decimal</tt> column.
482
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
483
483
  #
484
484
  # Note: The precision is the total number of significant digits
485
485
  # and the scale is the number of digits that can be stored following
@@ -496,8 +496,6 @@ module ActiveRecord
496
496
  # Default is (10,0).
497
497
  # * PostgreSQL: <tt>:precision</tt> [1..infinity],
498
498
  # <tt>:scale</tt> [0..infinity]. No default.
499
- # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
500
- # Internal storage as strings. No default.
501
499
  # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
502
500
  # but the maximum supported <tt>:precision</tt> is 16. No default.
503
501
  # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
@@ -700,7 +698,7 @@ module ActiveRecord
700
698
  #
701
699
  # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
702
700
  #
703
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
701
+ # Note: only supported by MySQL.
704
702
  def add_index(table_name, column_name, options = {})
705
703
  index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
706
704
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -1,5 +1,4 @@
1
1
  require 'active_record/type'
2
- require 'active_support/core_ext/benchmark'
3
2
  require 'active_record/connection_adapters/determine_if_preparable_visitor'
4
3
  require 'active_record/connection_adapters/schema_cache'
5
4
  require 'active_record/connection_adapters/sql_type_metadata'
@@ -398,7 +397,7 @@ module ActiveRecord
398
397
  if can_perform_case_insensitive_comparison_for?(column)
399
398
  table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
400
399
  else
401
- case_sensitive_comparison(table, attribute, column, value)
400
+ table[attribute].eq(Arel::Nodes::BindParam.new)
402
401
  end
403
402
  end
404
403
 
@@ -52,7 +52,6 @@ module ActiveRecord
52
52
  INDEX_TYPES = [:fulltext, :spatial]
53
53
  INDEX_USINGS = [:btree, :hash]
54
54
 
55
- # FIXME: Make the first parameter more similar for the two adapters
56
55
  def initialize(connection, logger, connection_options, config)
57
56
  super(connection, logger, config)
58
57
  @quoted_column_names, @quoted_table_names = {}, {}
@@ -65,6 +64,10 @@ module ActiveRecord
65
64
  else
66
65
  @prepared_statements = false
67
66
  end
67
+
68
+ if version < '5.0.0'
69
+ raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
70
+ end
68
71
  end
69
72
 
70
73
  CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
@@ -98,12 +101,8 @@ module ActiveRecord
98
101
  true
99
102
  end
100
103
 
101
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
102
- # where the transaction level gets persisted for the whole session:
103
- #
104
- # http://bugs.mysql.com/bug.php?id=39170
105
104
  def supports_transaction_isolation?
106
- version >= '5.0.0'
105
+ true
107
106
  end
108
107
 
109
108
  def supports_explain?
@@ -119,17 +118,15 @@ module ActiveRecord
119
118
  end
120
119
 
121
120
  def supports_views?
122
- version >= '5.0.0'
121
+ true
123
122
  end
124
123
 
125
124
  def supports_datetime_with_precision?
126
125
  version >= '5.6.4'
127
126
  end
128
127
 
129
- # 5.0.0 definitely supports it, possibly supported by earlier versions but
130
- # not sure
131
128
  def supports_advisory_locks?
132
- version >= '5.0.0'
129
+ true
133
130
  end
134
131
 
135
132
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
@@ -618,13 +615,10 @@ module ActiveRecord
618
615
  end
619
616
  end
620
617
 
621
- def case_insensitive_comparison(table, attribute, column, value)
622
- if column.case_sensitive?
623
- super
624
- else
625
- table[attribute].eq(Arel::Nodes::BindParam.new)
626
- end
618
+ def can_perform_case_insensitive_comparison_for?(column)
619
+ column.case_sensitive?
627
620
  end
621
+ private :can_perform_case_insensitive_comparison_for?
628
622
 
629
623
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
630
624
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  def initialize(url)
34
34
  raise "Database URL cannot be empty" if url.blank?
35
35
  @uri = uri_parser.parse(url)
36
- @adapter = @uri.scheme.tr('-', '_')
36
+ @adapter = @uri.scheme && @uri.scheme.tr('-', '_')
37
37
  @adapter = "postgresql" if @adapter == "postgres"
38
38
 
39
39
  if @uri.opaque
@@ -12,7 +12,7 @@ module ActiveRecord
12
12
  spec[:unsigned] = 'true' if column.unsigned?
13
13
  return if spec.empty?
14
14
  else
15
- spec[:id] = column.type.inspect
15
+ spec[:id] = schema_type(column).inspect
16
16
  spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
17
17
  end
18
18
  spec
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
 
33
33
  def schema_type(column)
34
34
  if column.sql_type == 'tinyblob'
35
- 'blob'
35
+ :blob
36
36
  else
37
37
  super
38
38
  end
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  end
43
43
 
44
44
  def supports_json?
45
- version >= '5.7.8'
45
+ !mariadb? && version >= '5.7.8'
46
46
  end
47
47
 
48
48
  # HELPER METHODS ===========================================
@@ -134,8 +134,6 @@ module ActiveRecord
134
134
  ActiveRecord::Result.new(result.fields, result.to_a)
135
135
  end
136
136
 
137
- alias exec_without_stmt exec_query
138
-
139
137
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
140
138
  execute to_sql(sql, binds), name
141
139
  end
@@ -118,7 +118,7 @@ module ActiveRecord
118
118
  alias :exec_update :exec_delete
119
119
 
120
120
  def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
121
- unless pk
121
+ if pk.nil?
122
122
  # Extract the table from the insert sql. Yuck.
123
123
  table_ref = extract_table_ref_from_insert_sql(sql)
124
124
  pk = primary_key(table_ref) if table_ref
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  spec[:id] = ':uuid'
12
12
  spec[:default] = schema_default(column) || 'nil'
13
13
  else
14
- spec[:id] = column.type.inspect
14
+ spec[:id] = schema_type(column).inspect
15
15
  spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
16
16
  end
17
17
  spec
@@ -35,9 +35,9 @@ module ActiveRecord
35
35
  return super unless column.serial?
36
36
 
37
37
  if column.bigint?
38
- 'bigserial'
38
+ :bigserial
39
39
  else
40
- 'serial'
40
+ :serial
41
41
  end
42
42
  end
43
43
 
@@ -214,8 +214,8 @@ module ActiveRecord
214
214
  @statements = StatementPool.new @connection,
215
215
  self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
216
216
 
217
- if postgresql_version < 80200
218
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
217
+ if postgresql_version < 90100
218
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
219
219
  end
220
220
 
221
221
  add_pg_decoders
@@ -297,9 +297,8 @@ module ActiveRecord
297
297
  true
298
298
  end
299
299
 
300
- # Returns true if pg > 9.1
301
300
  def supports_extensions?
302
- postgresql_version >= 90100
301
+ true
303
302
  end
304
303
 
305
304
  # Range datatypes weren't introduced until PostgreSQL 9.2
@@ -8,7 +8,6 @@ require 'sqlite3'
8
8
 
9
9
  module ActiveRecord
10
10
  module ConnectionHandling # :nodoc:
11
- # sqlite3 adapter reuses sqlite_connection.
12
11
  def sqlite3_connection(config)
13
12
  # Require database.
14
13
  unless config[:database]
@@ -256,6 +256,11 @@ module ActiveRecord
256
256
  end
257
257
  end
258
258
 
259
+ def arel_attribute(name, table = arel_table) # :nodoc:
260
+ name = attribute_alias(name) if attribute_alias?(name)
261
+ table[name]
262
+ end
263
+
259
264
  def predicate_builder # :nodoc:
260
265
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
261
266
  end
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  MAJOR = 5
9
9
  MINOR = 0
10
10
  TINY = 0
11
- PRE = "beta2"
11
+ PRE = "beta3"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -192,7 +192,7 @@ module ActiveRecord
192
192
  end
193
193
 
194
194
  def type_condition(table = arel_table)
195
- sti_column = table[inheritance_column]
195
+ sti_column = arel_attribute(inheritance_column, table)
196
196
  sti_names = ([self] + descendants).map(&:sti_name)
197
197
 
198
198
  sti_column.in(sti_names)
@@ -102,7 +102,7 @@ module ActiveRecord
102
102
  module Legacy
103
103
  include FourTwoShared
104
104
 
105
- def run(*)
105
+ def migrate(*)
106
106
  ActiveSupport::Deprecation.warn \
107
107
  "Directly inheriting from ActiveRecord::Migration is deprecated. " \
108
108
  "Please specify the Rails release the migration was written for:\n" \
@@ -195,15 +195,23 @@ module ActiveRecord
195
195
  # Nested attributes for an associated collection can also be passed in
196
196
  # the form of a hash of hashes instead of an array of hashes:
197
197
  #
198
- # Member.create(name: 'joe',
199
- # posts_attributes: { first: { title: 'Foo' },
200
- # second: { title: 'Bar' } })
198
+ # Member.create(
199
+ # name: 'joe',
200
+ # posts_attributes: {
201
+ # first: { title: 'Foo' },
202
+ # second: { title: 'Bar' }
203
+ # }
204
+ # )
201
205
  #
202
206
  # has the same effect as
203
207
  #
204
- # Member.create(name: 'joe',
205
- # posts_attributes: [ { title: 'Foo' },
206
- # { title: 'Bar' } ])
208
+ # Member.create(
209
+ # name: 'joe',
210
+ # posts_attributes: [
211
+ # { title: 'Foo' },
212
+ # { title: 'Bar' }
213
+ # ]
214
+ # )
207
215
  #
208
216
  # The keys of the hash which is the value for +:posts_attributes+ are
209
217
  # ignored in this case.
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module NullRelation # :nodoc:
3
3
  def exec_queries
4
- @records = []
4
+ @records = [].freeze
5
5
  end
6
6
 
7
7
  def pluck(*column_names)
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Querying
3
3
  delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
4
- delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
4
+ delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
5
5
  delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
6
6
  delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
7
7
  delegate :find_by, :find_by!, to: :all
@@ -35,8 +35,8 @@ module ActiveRecord
35
35
  #
36
36
  # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
37
37
  # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
38
- def find_by_sql(sql, binds = [])
39
- result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
38
+ def find_by_sql(sql, binds = [], preparable: nil)
39
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
40
40
  column_types = result_set.column_types.dup
41
41
  columns_hash.each_key { |k| column_types.delete k }
42
42
  message_bus = ActiveSupport::Notifications.instrumenter
@@ -124,8 +124,19 @@ module ActiveRecord
124
124
  end
125
125
  end
126
126
 
127
- # Holds all the methods that are shared between MacroReflection, AssociationReflection
128
- # and ThroughReflection
127
+ # Holds all the methods that are shared between MacroReflection and ThroughReflection.
128
+ #
129
+ # AbstractReflection
130
+ # MacroReflection
131
+ # AggregateReflection
132
+ # AssociationReflection
133
+ # HasManyReflection
134
+ # HasOneReflection
135
+ # BelongsToReflection
136
+ # HasAndBelongsToManyReflection
137
+ # ThroughReflection
138
+ # PolymorphicReflection
139
+ # RuntimeReflection
129
140
  class AbstractReflection # :nodoc:
130
141
  def table_name
131
142
  klass.table_name
@@ -228,18 +239,14 @@ module ActiveRecord
228
239
  def alias_candidate(name)
229
240
  "#{plural_name}_#{name}"
230
241
  end
242
+
243
+ def chain
244
+ collect_join_chain
245
+ end
231
246
  end
232
247
 
233
248
  # Base class for AggregateReflection and AssociationReflection. Objects of
234
249
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
235
- #
236
- # MacroReflection
237
- # AggregateReflection
238
- # AssociationReflection
239
- # HasManyReflection
240
- # HasOneReflection
241
- # BelongsToReflection
242
- # ThroughReflection
243
250
  class MacroReflection < AbstractReflection
244
251
  # Returns the name of the macro.
245
252
  #
@@ -418,7 +425,7 @@ module ActiveRecord
418
425
 
419
426
  # A chain of reflections from this one back to the owner. For more see the explanation in
420
427
  # ThroughReflection.
421
- def chain
428
+ def collect_join_chain
422
429
  [self]
423
430
  end
424
431
 
@@ -492,6 +499,18 @@ module ActiveRecord
492
499
  VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
493
500
  INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
494
501
 
502
+ def add_as_source(seed)
503
+ seed
504
+ end
505
+
506
+ def add_as_polymorphic_through(reflection, seed)
507
+ seed + [PolymorphicReflection.new(self, reflection)]
508
+ end
509
+
510
+ def add_as_through(seed)
511
+ seed + [self]
512
+ end
513
+
495
514
  protected
496
515
 
497
516
  def actual_source_reflection # FIXME: this is a horrible name
@@ -594,10 +613,6 @@ module ActiveRecord
594
613
  end
595
614
 
596
615
  class HasManyReflection < AssociationReflection # :nodoc:
597
- def initialize(name, scope, options, active_record)
598
- super(name, scope, options, active_record)
599
- end
600
-
601
616
  def macro; :has_many; end
602
617
 
603
618
  def collection?; true; end
@@ -612,10 +627,6 @@ module ActiveRecord
612
627
  end
613
628
 
614
629
  class HasOneReflection < AssociationReflection # :nodoc:
615
- def initialize(name, scope, options, active_record)
616
- super(name, scope, options, active_record)
617
- end
618
-
619
630
  def macro; :has_one; end
620
631
 
621
632
  def has_one?; true; end
@@ -636,10 +647,6 @@ module ActiveRecord
636
647
  end
637
648
 
638
649
  class BelongsToReflection < AssociationReflection # :nodoc:
639
- def initialize(name, scope, options, active_record)
640
- super(name, scope, options, active_record)
641
- end
642
-
643
650
  def macro; :belongs_to; end
644
651
 
645
652
  def belongs_to?; true; end
@@ -751,19 +758,8 @@ module ActiveRecord
751
758
  # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
752
759
  # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
753
760
  #
754
- def chain
755
- @chain ||= begin
756
- a = source_reflection.chain
757
- b = through_reflection.chain.map(&:dup)
758
-
759
- if options[:source_type]
760
- b[0] = PolymorphicReflection.new(b[0], self)
761
- end
762
-
763
- chain = a + b
764
- chain[0] = self # Use self so we don't lose the information from :source_type
765
- chain
766
- end
761
+ def collect_join_chain
762
+ collect_join_reflections [self]
767
763
  end
768
764
 
769
765
  # This is for clearing cache on the reflection. Useful for tests that need to compare
@@ -922,6 +918,27 @@ module ActiveRecord
922
918
  scope_chain
923
919
  end
924
920
 
921
+ def add_as_source(seed)
922
+ collect_join_reflections seed
923
+ end
924
+
925
+ def add_as_polymorphic_through(reflection, seed)
926
+ collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
927
+ end
928
+
929
+ def add_as_through(seed)
930
+ collect_join_reflections(seed + [self])
931
+ end
932
+
933
+ def collect_join_reflections(seed)
934
+ a = source_reflection.add_as_source seed
935
+ if options[:source_type]
936
+ through_reflection.add_as_polymorphic_through self, a
937
+ else
938
+ through_reflection.add_as_through a
939
+ end
940
+ end
941
+
925
942
  protected
926
943
 
927
944
  def actual_source_reflection # FIXME: this is a horrible name