activerecord 7.0.8.7 → 7.1.0.beta1

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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -5,6 +5,23 @@ require "active_support/core_ext/module/delegation"
5
5
 
6
6
  module ActiveRecord
7
7
  module Delegation # :nodoc:
8
+ class << self
9
+ def delegated_classes
10
+ [
11
+ ActiveRecord::Relation,
12
+ ActiveRecord::Associations::CollectionProxy,
13
+ ActiveRecord::AssociationRelation,
14
+ ActiveRecord::DisableJoinsAssociationRelation,
15
+ ]
16
+ end
17
+
18
+ def uncacheable_methods
19
+ @uncacheable_methods ||= (
20
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
21
+ ).to_set.freeze
22
+ end
23
+ end
24
+
8
25
  module DelegateCache # :nodoc:
9
26
  def relation_delegate_class(klass)
10
27
  @relation_delegate_cache[klass]
@@ -12,12 +29,7 @@ module ActiveRecord
12
29
 
13
30
  def initialize_relation_delegate_cache
14
31
  @relation_delegate_cache = cache = {}
15
- [
16
- ActiveRecord::Relation,
17
- ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation,
19
- ActiveRecord::DisableJoinsAssociationRelation
20
- ].each do |klass|
32
+ Delegation.delegated_classes.each do |klass|
21
33
  delegate = Class.new(klass) {
22
34
  include ClassSpecificRelation
23
35
  }
@@ -85,7 +97,7 @@ module ActiveRecord
85
97
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
98
  # for each different klass, and the delegations are compiled into that subclass only.
87
99
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
100
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
@@ -104,7 +116,9 @@ module ActiveRecord
104
116
  private
105
117
  def method_missing(method, *args, &block)
106
118
  if @klass.respond_to?(method)
107
- @klass.generate_relation_method(method)
119
+ unless Delegation.uncacheable_methods.include?(method)
120
+ @klass.generate_relation_method(method)
121
+ end
108
122
  scoping { @klass.public_send(method, *args, &block) }
109
123
  else
110
124
  super
@@ -6,7 +6,9 @@ module ActiveRecord
6
6
  module FinderMethods
7
7
  ONE_AS_ONE = "1 AS one"
8
8
 
9
- # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
9
+ # Find by id - This can either be a specific id (ID), a list of ids (ID, ID, ID), or an array of ids ([ID, ID, ID]).
10
+ # `ID` refers to an "identifier". For models with a single-column primary key, `ID` will be a single value,
11
+ # and for models with a composite primary key, it will be an array of values.
10
12
  # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
11
13
  # If the primary key is an integer, find by id coerces its arguments by using +to_i+.
12
14
  #
@@ -18,6 +20,27 @@ module ActiveRecord
18
20
  # Person.find([1]) # returns an array for the object with ID = 1
19
21
  # Person.where("administrator = 1").order("created_on DESC").find(1)
20
22
  #
23
+ # ==== Find a record for a composite primary key model
24
+ # TravelRoute.primary_key = [:origin, :destination]
25
+ #
26
+ # TravelRoute.find(["Ottawa", "London"])
27
+ # => #<TravelRoute origin: "Ottawa", destination: "London">
28
+ #
29
+ # TravelRoute.find([["Paris", "Montreal"]])
30
+ # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
+ #
32
+ # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
+ # => [
34
+ # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # ]
37
+ #
38
+ # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
+ # => [
40
+ # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # ]
43
+ #
21
44
  # NOTE: The returned records are in the same order as the ids you provide.
22
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
23
46
  # method and provide an explicit ActiveRecord::QueryMethods#order option.
@@ -324,6 +347,8 @@ module ActiveRecord
324
347
  # Person.exists?
325
348
  # Person.where(name: 'Spartacus', rating: 4).exists?
326
349
  def exists?(conditions = :none)
350
+ return false if @none
351
+
327
352
  if Base === conditions
328
353
  raise ArgumentError, <<-MSG.squish
329
354
  You are passing an instance of ActiveRecord::Base to `exists?`.
@@ -350,10 +375,20 @@ module ActiveRecord
350
375
  # compared to the records in memory. If the relation is unloaded, an
351
376
  # efficient existence query is performed, as in #exists?.
352
377
  def include?(record)
378
+ # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
379
+ # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
380
+ return false unless record.is_a?(klass)
381
+
353
382
  if loaded? || offset_value || limit_value || having_clause.any?
354
383
  records.include?(record)
355
384
  else
356
- record.is_a?(klass) && exists?(record.id)
385
+ id = if record.class.composite_primary_key?
386
+ record.class.primary_key.zip(record.id).to_h
387
+ else
388
+ record.id
389
+ end
390
+
391
+ exists?(id)
357
392
  end
358
393
  end
359
394
 
@@ -442,10 +477,17 @@ module ActiveRecord
442
477
  def find_with_ids(*ids)
443
478
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
444
479
 
445
- expects_array = ids.first.kind_of?(Array)
480
+ expects_array = if klass.composite_primary_key?
481
+ ids.first.first.is_a?(Array)
482
+ else
483
+ ids.first.is_a?(Array)
484
+ end
485
+
446
486
  return [] if expects_array && ids.first.empty?
447
487
 
448
- ids = ids.flatten.compact.uniq
488
+ ids = ids.first if expects_array
489
+
490
+ ids = ids.compact.uniq
449
491
 
450
492
  model_name = @klass.name
451
493
 
@@ -469,7 +511,12 @@ module ActiveRecord
469
511
  MSG
470
512
  end
471
513
 
472
- relation = where(primary_key => id)
514
+ relation = if klass.composite_primary_key?
515
+ where(primary_key.zip(id).to_h)
516
+ else
517
+ where(primary_key => id)
518
+ end
519
+
473
520
  record = relation.take
474
521
 
475
522
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -480,7 +527,14 @@ module ActiveRecord
480
527
  def find_some(ids)
481
528
  return find_some_ordered(ids) unless order_values.present?
482
529
 
483
- result = where(primary_key => ids).to_a
530
+ relation = if klass.composite_primary_key?
531
+ ids.map { |values_set| where(primary_key.zip(values_set).to_h) }.inject(&:or)
532
+ else
533
+ where(primary_key => ids)
534
+ end
535
+
536
+ relation = relation.select(table[primary_key]) unless select_values.empty?
537
+ result = relation.to_a
484
538
 
485
539
  expected_size =
486
540
  if limit_value && ids.size > limit_value
@@ -504,7 +558,14 @@ module ActiveRecord
504
558
  def find_some_ordered(ids)
505
559
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
506
560
 
507
- result = except(:limit, :offset).where(primary_key => ids).records
561
+ relation = except(:limit, :offset)
562
+ relation = if klass.composite_primary_key?
563
+ ids.map { |values_set| relation.where(primary_key.zip(values_set).to_h) }.inject(&:or)
564
+ else
565
+ relation.where(primary_key => ids)
566
+ end
567
+ relation = relation.select(table[primary_key]) unless select_values.empty?
568
+ result = relation.records
508
569
 
509
570
  if result.size == ids.size
510
571
  result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
@@ -559,10 +620,10 @@ module ActiveRecord
559
620
  else
560
621
  relation = ordered_relation
561
622
 
562
- if equal?(relation) || has_limit_or_offset?
623
+ if relation.order_values.empty? || relation.has_limit_or_offset?
563
624
  relation.records[-index]
564
625
  else
565
- relation.last(index)[-index]
626
+ relation.reverse_order.offset(index - 1).first
566
627
  end
567
628
  end
568
629
  end
@@ -572,15 +633,24 @@ module ActiveRecord
572
633
  end
573
634
 
574
635
  def ordered_relation
575
- if order_values.empty? && (implicit_order_column || primary_key)
576
- if implicit_order_column && primary_key && implicit_order_column != primary_key
577
- order(table[implicit_order_column].asc, table[primary_key].asc)
578
- else
579
- order(table[implicit_order_column || primary_key].asc)
580
- end
636
+ if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
637
+ order(_order_columns.map { |column| table[column].asc })
581
638
  else
582
639
  self
583
640
  end
584
641
  end
642
+
643
+ def _order_columns
644
+ oc = []
645
+
646
+ oc << implicit_order_column if implicit_order_column
647
+ oc << query_constraints_list if query_constraints_list
648
+
649
+ if primary_key && query_constraints_list.nil?
650
+ oc << primary_key
651
+ end
652
+
653
+ oc.flatten.uniq.compact
654
+ end
585
655
  end
586
656
  end
@@ -69,6 +69,8 @@ module ActiveRecord
69
69
  end
70
70
  end
71
71
 
72
+ relation.none! if other.null_relation?
73
+
72
74
  merge_select_values
73
75
  merge_multi_values
74
76
  merge_single_values
@@ -9,7 +9,14 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [ associated_table.join_foreign_key => ids ]
12
+ if associated_table.join_foreign_key.is_a?(Array)
13
+ id_list = ids
14
+ id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
+
16
+ id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
17
+ else
18
+ [ associated_table.join_foreign_key => ids ]
19
+ end
13
20
  end
14
21
 
15
22
  private
@@ -25,7 +32,7 @@ module ActiveRecord
25
32
  when Array
26
33
  value.map { |v| convert_to_id(v) }
27
34
  else
28
- convert_to_id(value)
35
+ [convert_to_id(value)]
29
36
  end
30
37
  end
31
38
 
@@ -50,6 +57,8 @@ module ActiveRecord
50
57
  end
51
58
 
52
59
  def convert_to_id(value)
60
+ return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
61
+
53
62
  if value.respond_to?(primary_key)
54
63
  value.public_send(primary_key)
55
64
  else
@@ -9,7 +9,11 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- value = value.select(value.table[value.klass.primary_key])
12
+ if value.klass.composite_primary_key?
13
+ raise ArgumentError, "Cannot map composite primary key #{value.klass.primary_key} to #{attribute.name}"
14
+ else
15
+ value = value.select(value.table[value.klass.primary_key])
16
+ end
13
17
  end
14
18
 
15
19
  attribute.in(value.arel)
@@ -29,8 +29,8 @@ module ActiveRecord
29
29
  attributes.each_with_object([]) do |(key, value), result|
30
30
  if value.is_a?(Hash)
31
31
  result << Arel.sql(key)
32
- elsif key.include?(".")
33
- result << Arel.sql(key.split(".").first)
32
+ elsif (idx = key.rindex("."))
33
+ result << Arel.sql(key[0, idx])
34
34
  end
35
35
  end
36
36
  end
@@ -77,7 +77,13 @@ module ActiveRecord
77
77
  return ["1=0"] if attributes.empty?
78
78
 
79
79
  attributes.flat_map do |key, value|
80
- if value.is_a?(Hash) && !table.has_column?(key)
80
+ if key.is_a?(Array)
81
+ queries = Array(value).map do |ids_set|
82
+ raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
83
+ expand_from_hash(key.zip(ids_set).to_h)
84
+ end
85
+ grouping_queries(queries)
86
+ elsif value.is_a?(Hash) && !table.has_column?(key)
81
87
  table.associated_table(key, &block)
82
88
  .predicate_builder.expand_from_hash(value.stringify_keys)
83
89
  elsif table.associated_with?(key)
@@ -142,19 +148,25 @@ module ActiveRecord
142
148
  end
143
149
 
144
150
  def convert_dot_notation_to_hash(attributes)
145
- dot_notation = attributes.select do |k, v|
146
- k.include?(".") && !v.is_a?(Hash)
147
- end
148
-
149
- dot_notation.each_key do |key|
150
- table_name, column_name = key.split(".")
151
- value = attributes.delete(key)
152
- attributes[table_name] ||= {}
151
+ attributes.each_with_object({}) do |(key, value), converted|
152
+ if value.is_a?(Hash)
153
+ if (existing = converted[key])
154
+ existing.merge!(value)
155
+ else
156
+ converted[key] = value.dup
157
+ end
158
+ elsif (idx = key.rindex("."))
159
+ table_name, column_name = key[0, idx], key[idx + 1, key.length]
153
160
 
154
- attributes[table_name] = attributes[table_name].merge(column_name => value)
161
+ if (existing = converted[table_name])
162
+ existing[column_name] = value
163
+ else
164
+ converted[table_name] = { column_name => value }
165
+ end
166
+ else
167
+ converted[key] = value
168
+ end
155
169
  end
156
-
157
- attributes
158
170
  end
159
171
 
160
172
  def handler_for(object)
@@ -24,7 +24,8 @@ module ActiveRecord
24
24
  end
25
25
 
26
26
  def value_for_database
27
- @value_for_database ||= super
27
+ @value_for_database = _value_for_database unless defined?(@value_for_database)
28
+ @value_for_database
28
29
  end
29
30
 
30
31
  def with_cast_value(value)