activerecord 4.2.0 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -1
  3. data/lib/active_record.rb +3 -0
  4. data/lib/active_record/aggregations.rb +6 -3
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations.rb +5 -4
  7. data/lib/active_record/associations/association.rb +15 -3
  8. data/lib/active_record/associations/association_scope.rb +1 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +13 -5
  10. data/lib/active_record/associations/builder/association.rb +1 -1
  11. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -4
  13. data/lib/active_record/associations/collection_association.rb +35 -15
  14. data/lib/active_record/associations/collection_proxy.rb +15 -9
  15. data/lib/active_record/associations/foreign_association.rb +11 -0
  16. data/lib/active_record/associations/has_many_association.rb +30 -15
  17. data/lib/active_record/associations/has_many_through_association.rb +11 -2
  18. data/lib/active_record/associations/has_one_association.rb +1 -0
  19. data/lib/active_record/associations/join_dependency.rb +8 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +7 -1
  21. data/lib/active_record/associations/preloader.rb +4 -4
  22. data/lib/active_record/associations/preloader/association.rb +5 -1
  23. data/lib/active_record/associations/singular_association.rb +2 -8
  24. data/lib/active_record/associations/through_association.rb +11 -6
  25. data/lib/active_record/attribute.rb +15 -1
  26. data/lib/active_record/attribute_assignment.rb +2 -2
  27. data/lib/active_record/attribute_methods.rb +4 -8
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +14 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  31. data/lib/active_record/attribute_methods/write.rb +1 -1
  32. data/lib/active_record/attribute_set.rb +4 -0
  33. data/lib/active_record/attribute_set/builder.rb +32 -12
  34. data/lib/active_record/attributes.rb +8 -0
  35. data/lib/active_record/autosave_association.rb +24 -9
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +12 -6
  39. data/lib/active_record/connection_adapters/abstract/database_statements.rb +23 -3
  40. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -16
  44. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +87 -24
  45. data/lib/active_record/connection_adapters/abstract/transaction.rb +2 -6
  46. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -7
  47. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -10
  48. data/lib/active_record/connection_adapters/column.rb +2 -2
  49. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -21
  50. data/lib/active_record/connection_adapters/mysql_adapter.rb +10 -3
  51. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  54. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  55. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  56. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  58. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +21 -13
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -12
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -28
  63. data/lib/active_record/connection_handling.rb +1 -1
  64. data/lib/active_record/core.rb +28 -15
  65. data/lib/active_record/counter_cache.rb +1 -1
  66. data/lib/active_record/enum.rb +2 -3
  67. data/lib/active_record/errors.rb +6 -5
  68. data/lib/active_record/explain_subscriber.rb +1 -1
  69. data/lib/active_record/fixtures.rb +9 -7
  70. data/lib/active_record/gem_version.rb +1 -1
  71. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  72. data/lib/active_record/locking/optimistic.rb +16 -14
  73. data/lib/active_record/migration.rb +38 -10
  74. data/lib/active_record/model_schema.rb +4 -2
  75. data/lib/active_record/nested_attributes.rb +13 -3
  76. data/lib/active_record/no_touching.rb +1 -1
  77. data/lib/active_record/persistence.rb +7 -4
  78. data/lib/active_record/railtie.rb +5 -3
  79. data/lib/active_record/railties/databases.rake +17 -24
  80. data/lib/active_record/reflection.rb +40 -28
  81. data/lib/active_record/relation.rb +3 -2
  82. data/lib/active_record/relation/calculations.rb +10 -3
  83. data/lib/active_record/relation/delegation.rb +1 -1
  84. data/lib/active_record/relation/finder_methods.rb +4 -16
  85. data/lib/active_record/relation/merger.rb +24 -1
  86. data/lib/active_record/relation/predicate_builder.rb +32 -3
  87. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -2
  88. data/lib/active_record/relation/query_methods.rb +29 -27
  89. data/lib/active_record/relation/spawn_methods.rb +7 -3
  90. data/lib/active_record/schema_dumper.rb +1 -1
  91. data/lib/active_record/schema_migration.rb +1 -4
  92. data/lib/active_record/scoping/default.rb +1 -0
  93. data/lib/active_record/tasks/database_tasks.rb +5 -2
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +30 -16
  95. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -8
  96. data/lib/active_record/transactions.rb +21 -11
  97. data/lib/active_record/type/boolean.rb +1 -0
  98. data/lib/active_record/type/date.rb +4 -0
  99. data/lib/active_record/type/date_time.rb +14 -3
  100. data/lib/active_record/type/decimal.rb +27 -3
  101. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  102. data/lib/active_record/type/integer.rb +9 -5
  103. data/lib/active_record/type/numeric.rb +1 -1
  104. data/lib/active_record/type/serialized.rb +7 -1
  105. data/lib/active_record/type/string.rb +4 -0
  106. data/lib/active_record/type/value.rb +9 -0
  107. data/lib/active_record/validations/uniqueness.rb +16 -6
  108. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  110. metadata +9 -7
@@ -42,15 +42,18 @@ db_namespace = namespace :db do
42
42
  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
43
43
  task :migrate => [:environment, :load_config] do
44
44
  ActiveRecord::Tasks::DatabaseTasks.migrate
45
- db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
45
+ db_namespace['_dump'].invoke
46
46
  end
47
47
 
48
+ # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false
48
49
  task :_dump do
49
- case ActiveRecord::Base.schema_format
50
- when :ruby then db_namespace["schema:dump"].invoke
51
- when :sql then db_namespace["structure:dump"].invoke
52
- else
53
- raise "unknown schema format #{ActiveRecord::Base.schema_format}"
50
+ if ActiveRecord::Base.dump_schema_after_migration
51
+ case ActiveRecord::Base.schema_format
52
+ when :ruby then db_namespace["schema:dump"].invoke
53
+ when :sql then db_namespace["structure:dump"].invoke
54
+ else
55
+ raise "unknown schema format #{ActiveRecord::Base.schema_format}"
56
+ end
54
57
  end
55
58
  # Allow this task to be called as many times as required. An example is the
56
59
  # migrate:redo task, which calls other two internally that depend on this one.
@@ -60,6 +63,8 @@ db_namespace = namespace :db do
60
63
  namespace :migrate do
61
64
  # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
62
65
  task :redo => [:environment, :load_config] do
66
+ raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
67
+
63
68
  if ENV['VERSION']
64
69
  db_namespace['migrate:down'].invoke
65
70
  db_namespace['migrate:up'].invoke
@@ -74,16 +79,17 @@ db_namespace = namespace :db do
74
79
 
75
80
  # desc 'Runs the "up" for a given migration VERSION.'
76
81
  task :up => [:environment, :load_config] do
82
+ raise "VERSION is required" if ENV["VERSION"] && ENV["VERSION"].empty?
83
+
77
84
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
78
- raise 'VERSION is required' unless version
79
85
  ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
80
86
  db_namespace['_dump'].invoke
81
87
  end
82
88
 
83
89
  # desc 'Runs the "down" for a given migration VERSION.'
84
90
  task :down => [:environment, :load_config] do
91
+ raise "VERSION is required - To go down one migration, use db:rollback" if ENV["VERSION"] && ENV["VERSION"].empty?
85
92
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
86
- raise 'VERSION is required - To go down one migration, run db:rollback' unless version
87
93
  ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
88
94
  db_namespace['_dump'].invoke
89
95
  end
@@ -93,26 +99,13 @@ db_namespace = namespace :db do
93
99
  unless ActiveRecord::SchemaMigration.table_exists?
94
100
  abort 'Schema migrations table does not exist yet.'
95
101
  end
96
- db_list = ActiveRecord::SchemaMigration.normalized_versions
97
-
98
- file_list =
99
- ActiveRecord::Migrator.migrations_paths.flat_map do |path|
100
- # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
101
- Dir.foreach(path).grep(/^(\d{3,})_(.+)\.rb$/) do
102
- version = ActiveRecord::SchemaMigration.normalize_migration_number($1)
103
- status = db_list.delete(version) ? 'up' : 'down'
104
- [status, version, $2.humanize]
105
- end
106
- end
107
102
 
108
- db_list.map! do |version|
109
- ['up', version, '********** NO FILE **********']
110
- end
111
103
  # output
112
104
  puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
113
105
  puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
114
106
  puts "-" * 50
115
- (db_list + file_list).sort_by { |_, version, _| version }.each do |status, version, name|
107
+ paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
108
+ ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
116
109
  puts "#{status.center(8)} #{version.ljust(14)} #{name}"
117
110
  end
118
111
  puts
@@ -286,7 +279,7 @@ db_namespace = namespace :db do
286
279
  end
287
280
 
288
281
  desc "Recreate the databases from the structure.sql file"
289
- task :load => [:environment, :load_config] do
282
+ task :load => [:load_config] do
290
283
  ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
291
284
  end
292
285
 
@@ -7,8 +7,8 @@ module ActiveRecord
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- class_attribute :_reflections
11
- class_attribute :aggregate_reflections
10
+ class_attribute :_reflections, instance_writer: false
11
+ class_attribute :aggregate_reflections, instance_writer: false
12
12
  self._reflections = {}
13
13
  self.aggregate_reflections = {}
14
14
  end
@@ -32,6 +32,7 @@ module ActiveRecord
32
32
  end
33
33
 
34
34
  def self.add_reflection(ar, name, reflection)
35
+ ar.clear_reflections_cache
35
36
  ar._reflections = ar._reflections.merge(name.to_s => reflection)
36
37
  end
37
38
 
@@ -67,16 +68,21 @@ module ActiveRecord
67
68
  #
68
69
  # @api public
69
70
  def reflections
70
- ref = {}
71
- _reflections.each do |name, reflection|
72
- parent_name, parent_reflection = reflection.parent_reflection
73
- if parent_name
74
- ref[parent_name] = parent_reflection
75
- else
76
- ref[name] = reflection
71
+ @__reflections ||= begin
72
+ ref = {}
73
+
74
+ _reflections.each do |name, reflection|
75
+ parent_name, parent_reflection = reflection.parent_reflection
76
+
77
+ if parent_name
78
+ ref[parent_name] = parent_reflection
79
+ else
80
+ ref[name] = reflection
81
+ end
77
82
  end
83
+
84
+ ref
78
85
  end
79
- ref
80
86
  end
81
87
 
82
88
  # Returns an array of AssociationReflection objects for all the
@@ -116,6 +122,10 @@ module ActiveRecord
116
122
  def reflect_on_all_autosave_associations
117
123
  reflections.values.select { |reflection| reflection.options[:autosave] }
118
124
  end
125
+
126
+ def clear_reflections_cache #:nodoc:
127
+ @__reflections = nil
128
+ end
119
129
  end
120
130
 
121
131
  # Holds all the methods that are shared between MacroReflection, AssociationReflection
@@ -161,6 +171,20 @@ module ActiveRecord
161
171
 
162
172
  macro
163
173
  end
174
+
175
+ def inverse_of
176
+ return unless inverse_name
177
+
178
+ @inverse_of ||= klass._reflect_on_association inverse_name
179
+ end
180
+
181
+ def check_validity_of_inverse!
182
+ unless polymorphic?
183
+ if has_inverse? && inverse_of.nil?
184
+ raise InverseOfAssociationNotFoundError.new(self)
185
+ end
186
+ end
187
+ end
164
188
  end
165
189
  # Base class for AggregateReflection and AssociationReflection. Objects of
166
190
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
@@ -196,7 +220,7 @@ module ActiveRecord
196
220
  @scope = scope
197
221
  @options = options
198
222
  @active_record = active_record
199
- @klass = options[:class]
223
+ @klass = options[:anonymous_class]
200
224
  @plural_name = active_record.pluralize_table_names ?
201
225
  name.to_s.pluralize : name.to_s
202
226
  end
@@ -303,7 +327,7 @@ module ActiveRecord
303
327
  end
304
328
 
305
329
  def foreign_key
306
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
330
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
307
331
  end
308
332
 
309
333
  def association_foreign_key
@@ -331,14 +355,6 @@ module ActiveRecord
331
355
  check_validity_of_inverse!
332
356
  end
333
357
 
334
- def check_validity_of_inverse!
335
- unless polymorphic?
336
- if has_inverse? && inverse_of.nil?
337
- raise InverseOfAssociationNotFoundError.new(self)
338
- end
339
- end
340
- end
341
-
342
358
  def check_preloadable!
343
359
  return unless scope
344
360
 
@@ -387,12 +403,6 @@ module ActiveRecord
387
403
  inverse_name
388
404
  end
389
405
 
390
- def inverse_of
391
- return unless inverse_name
392
-
393
- @inverse_of ||= klass._reflect_on_association inverse_name
394
- end
395
-
396
406
  def polymorphic_inverse_of(associated_class)
397
407
  if has_inverse?
398
408
  if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
@@ -499,7 +509,7 @@ module ActiveRecord
499
509
  # returns either nil or the inverse association name that it finds.
500
510
  def automatic_inverse_of
501
511
  if can_find_inverse_of_automatically?(self)
502
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
512
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
503
513
 
504
514
  begin
505
515
  reflection = klass._reflect_on_association(inverse_name)
@@ -632,7 +642,7 @@ module ActiveRecord
632
642
 
633
643
  def initialize(delegate_reflection)
634
644
  @delegate_reflection = delegate_reflection
635
- @klass = delegate_reflection.options[:class]
645
+ @klass = delegate_reflection.options[:anonymous_class]
636
646
  @source_reflection_name = delegate_reflection.options[:source]
637
647
  end
638
648
 
@@ -865,6 +875,8 @@ module ActiveRecord
865
875
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
866
876
  end
867
877
 
878
+ def inverse_name; delegate_reflection.send(:inverse_name); end
879
+
868
880
  private
869
881
  def derive_class_name
870
882
  # get the class_name of the belongs_to association of the through reflection
@@ -474,7 +474,8 @@ module ActiveRecord
474
474
  stmt.wheres = arel.constraints
475
475
  end
476
476
 
477
- affected = @klass.connection.delete(stmt, 'SQL', bind_values)
477
+ bvs = arel.bind_values + bind_values
478
+ affected = @klass.connection.delete(stmt, 'SQL', bvs)
478
479
 
479
480
  reset
480
481
  affected
@@ -568,7 +569,7 @@ module ActiveRecord
568
569
  [name, binds.fetch(name.to_s) {
569
570
  case where.right
570
571
  when Array then where.right.map(&:val)
571
- else
572
+ when Arel::Nodes::Casted
572
573
  where.right.val
573
574
  end
574
575
  }]
@@ -36,9 +36,15 @@ module ActiveRecord
36
36
  # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
37
37
  # between databases. In invalid cases, an error from the database is thrown.
38
38
  def count(column_name = nil, options = {})
39
+ if options.present? && !ActiveRecord.const_defined?(:DeprecatedFinders)
40
+ raise ArgumentError, "Relation#count does not support finder options anymore. " \
41
+ "Please build a scope and then call count on it or use the " \
42
+ "activerecord-deprecated_finders gem to enable this functionality."
43
+
44
+ end
45
+
39
46
  # TODO: Remove options argument as soon we remove support to
40
47
  # activerecord-deprecated_finders.
41
- column_name, options = nil, column_name if column_name.is_a?(Hash)
42
48
  calculate(:count, column_name, options)
43
49
  end
44
50
 
@@ -88,7 +94,7 @@ module ActiveRecord
88
94
  #
89
95
  # There are two basic forms of output:
90
96
  #
91
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
97
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
92
98
  # for AVG, and the given column's type for everything else.
93
99
  #
94
100
  # * Grouped values: This returns an ordered hash of the values and groups them. It
@@ -281,6 +287,7 @@ module ActiveRecord
281
287
  else
282
288
  group_fields = group_attrs
283
289
  end
290
+ group_fields = arel_columns(group_fields)
284
291
 
285
292
  group_aliases = group_fields.map { |field|
286
293
  column_alias_for(field)
@@ -303,7 +310,7 @@ module ActiveRecord
303
310
  operation,
304
311
  distinct).as(aggregate_alias)
305
312
  ]
306
- select_values += select_values unless having_values.empty?
313
+ select_values += self.select_values unless having_values.empty?
307
314
 
308
315
  select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
309
316
  if field.respond_to?(:as)
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  BLACKLISTED_ARRAY_METHODS = [
41
41
  :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
42
42
  :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
43
- :keep_if, :pop, :shift, :delete_at, :compact, :select!
43
+ :keep_if, :pop, :shift, :delete_at, :select!
44
44
  ].to_set # :nodoc:
45
45
 
46
46
  delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
@@ -114,23 +114,11 @@ module ActiveRecord
114
114
  # Find the first record (or first N records if a parameter is supplied).
115
115
  # If no order is defined it will order by primary key.
116
116
  #
117
- # Person.first # returns the first object fetched by SELECT * FROM people
117
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
118
118
  # Person.where(["user_name = ?", user_name]).first
119
119
  # Person.where(["user_name = :u", { u: user_name }]).first
120
120
  # Person.order("created_on DESC").offset(5).first
121
- # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
122
- #
123
- # ==== Rails 3
124
- #
125
- # Person.first # SELECT "people".* FROM "people" LIMIT 1
126
- #
127
- # NOTE: Rails 3 may not order this query by the primary key and the order
128
- # will depend on the database implementation. In order to ensure that behavior,
129
- # use <tt>User.order(:id).first</tt> instead.
130
- #
131
- # ==== Rails 4
132
- #
133
- # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
121
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
134
122
  #
135
123
  def first(limit = nil)
136
124
  if limit
@@ -307,7 +295,7 @@ module ActiveRecord
307
295
  relation = relation.where(conditions)
308
296
  else
309
297
  unless conditions == :none
310
- relation = where(primary_key => conditions)
298
+ relation = relation.where(primary_key => conditions)
311
299
  end
312
300
  end
313
301
 
@@ -379,7 +367,7 @@ module ActiveRecord
379
367
  def construct_relation_for_association_calculations
380
368
  from = arel.froms.first
381
369
  if Arel::Table === from
382
- apply_join_dependency(self, construct_join_dependency)
370
+ apply_join_dependency(self, construct_join_dependency(joins_values))
383
371
  else
384
372
  # FIXME: as far as I can tell, `from` will always be an Arel::Table.
385
373
  # There are no tests that test this branch, but presumably it's
@@ -51,7 +51,8 @@ module ActiveRecord
51
51
 
52
52
  NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
53
53
  Relation::MULTI_VALUE_METHODS -
54
- [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
54
+ [:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
55
+
55
56
 
56
57
  def normal_values
57
58
  NORMAL_VALUES
@@ -75,6 +76,7 @@ module ActiveRecord
75
76
 
76
77
  merge_multi_values
77
78
  merge_single_values
79
+ merge_preloads
78
80
  merge_joins
79
81
 
80
82
  relation
@@ -82,6 +84,27 @@ module ActiveRecord
82
84
 
83
85
  private
84
86
 
87
+ def merge_preloads
88
+ return if other.preload_values.empty? && other.includes_values.empty?
89
+
90
+ if other.klass == relation.klass
91
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
92
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
93
+ else
94
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
95
+ r.class_name == other.klass.name
96
+ end || return
97
+
98
+ unless other.preload_values.empty?
99
+ relation.preload! reflection.name => other.preload_values
100
+ end
101
+
102
+ unless other.includes_values.empty?
103
+ relation.includes! reflection.name => other.includes_values
104
+ end
105
+ end
106
+ end
107
+
85
108
  def merge_joins
86
109
  return if other.joins_values.blank?
87
110
 
@@ -6,7 +6,9 @@ module ActiveRecord
6
6
  autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
7
7
 
8
8
  def self.resolve_column_aliases(klass, hash)
9
- hash = hash.dup
9
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
10
+ # https://bugs.ruby-lang.org/issues/7166
11
+ hash = Hash[hash]
10
12
  hash.keys.grep(Symbol) do |key|
11
13
  if klass.attribute_alias? key
12
14
  hash[klass.attribute_alias(key)] = hash.delete key
@@ -56,11 +58,18 @@ module ActiveRecord
56
58
  # For polymorphic relationships, find the foreign key and type:
57
59
  # PriceEstimate.where(estimate_of: treasure)
58
60
  if klass && reflection = klass._reflect_on_association(column)
59
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
61
+ base_class = polymorphic_base_class_from_value(value)
62
+
63
+ if reflection.polymorphic? && base_class
60
64
  queries << build(table[reflection.foreign_type], base_class)
61
65
  end
62
66
 
63
67
  column = reflection.foreign_key
68
+
69
+ if base_class
70
+ primary_key = reflection.association_primary_key(base_class)
71
+ value = convert_value_to_association_ids(value, primary_key)
72
+ end
64
73
  end
65
74
 
66
75
  queries << build(table[column], value)
@@ -105,7 +114,8 @@ module ActiveRecord
105
114
  @handlers.unshift([klass, handler])
106
115
  end
107
116
 
108
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
117
+ BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc:
118
+ register_handler(BasicObject, BASIC_OBJECT_HANDLER)
109
119
  # FIXME: I think we need to deprecate this behavior
110
120
  register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
111
121
  register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
@@ -122,5 +132,24 @@ module ActiveRecord
122
132
  @handlers.detect { |klass, _| klass === object }.last
123
133
  end
124
134
  private_class_method :handler_for
135
+
136
+ def self.convert_value_to_association_ids(value, primary_key)
137
+ case value
138
+ when Relation
139
+ value.select(primary_key)
140
+ when Array
141
+ value.map { |v| convert_value_to_association_ids(v, primary_key) }
142
+ when Base
143
+ value._read_attribute(primary_key)
144
+ else
145
+ value
146
+ end
147
+ end
148
+
149
+ def self.can_be_bound?(value) # :nodoc:
150
+ !value.nil? &&
151
+ !value.is_a?(Hash) &&
152
+ handler_for(value) == BASIC_OBJECT_HANDLER
153
+ end
125
154
  end
126
155
  end