activerecord 6.1.7 → 7.1.5

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class BelongsTo < SingularAssociation #:nodoc:
4
+ class BelongsTo < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :belongs_to
7
7
  end
@@ -30,17 +30,17 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  model.after_update lambda { |record|
31
31
  association = association(reflection.name)
32
32
 
33
- if association.target_changed?
33
+ if association.saved_change_to_target?
34
34
  association.increment_counters
35
35
  association.decrement_counters_before_last_save
36
36
  end
37
37
  }
38
38
 
39
39
  klass = reflection.class_name.safe_constantize
40
- klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
40
+ klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
41
41
  end
42
42
 
43
- def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
43
+ def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
44
44
  old_foreign_id = changes[foreign_key] && changes[foreign_key].first
45
45
 
46
46
  if old_foreign_id
@@ -49,7 +49,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
49
49
  if reflection.polymorphic?
50
50
  foreign_type = reflection.foreign_type
51
51
  klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
52
- klass = klass.constantize
52
+ klass = o.class.polymorphic_class_for(klass)
53
53
  else
54
54
  klass = association.klass
55
55
  end
@@ -58,9 +58,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
58
58
 
59
59
  if old_record
60
60
  if touch != true
61
- old_record.public_send(touch_method, touch)
61
+ old_record.touch_later(touch)
62
62
  else
63
- old_record.public_send(touch_method)
63
+ old_record.touch_later
64
64
  end
65
65
  end
66
66
  end
@@ -68,9 +68,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
68
68
  record = o.public_send name
69
69
  if record && record.persisted?
70
70
  if touch != true
71
- record.public_send(touch_method, touch)
71
+ record.touch_later(touch)
72
72
  else
73
- record.public_send(touch_method)
73
+ record.touch_later
74
74
  end
75
75
  end
76
76
  end
@@ -81,13 +81,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
81
81
  touch = reflection.options[:touch]
82
82
 
83
83
  callback = lambda { |changes_method| lambda { |record|
84
- BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
84
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch)
85
85
  }}
86
86
 
87
87
  if reflection.counter_cache_column
88
88
  touch_callback = callback.(:saved_changes)
89
89
  update_callback = lambda { |record|
90
- instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
90
+ instance_exec(record, &touch_callback) unless association(reflection.name).saved_change_to_target?
91
91
  }
92
92
  model.after_update update_callback, if: :saved_changes?
93
93
  else
@@ -123,11 +123,37 @@ module ActiveRecord::Associations::Builder # :nodoc:
123
123
  super
124
124
 
125
125
  if required
126
- model.validates_presence_of reflection.name, message: :required
126
+ if ActiveRecord.belongs_to_required_validates_foreign_key
127
+ model.validates_presence_of reflection.name, message: :required
128
+ else
129
+ condition = lambda { |record|
130
+ foreign_key = reflection.foreign_key
131
+ foreign_type = reflection.foreign_type
132
+
133
+ record.read_attribute(foreign_key).nil? ||
134
+ record.attribute_changed?(foreign_key) ||
135
+ (reflection.polymorphic? && (record.read_attribute(foreign_type).nil? || record.attribute_changed?(foreign_type)))
136
+ }
137
+
138
+ model.validates_presence_of reflection.name, message: :required, if: condition
139
+ end
127
140
  end
128
141
  end
129
142
 
130
- private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
131
- :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
143
+ def self.define_change_tracking_methods(model, reflection)
144
+ model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
145
+ def #{reflection.name}_changed?
146
+ association(:#{reflection.name}).target_changed?
147
+ end
148
+
149
+ def #{reflection.name}_previously_changed?
150
+ association(:#{reflection.name}).target_previously_changed?
151
+ end
152
+ CODE
153
+ end
154
+
155
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks,
156
+ :define_validations, :define_change_tracking_methods, :add_counter_cache_callbacks,
157
+ :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
132
158
  end
133
159
  end
@@ -3,7 +3,7 @@
3
3
  require "active_record/associations"
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class CollectionAssociation < Association #:nodoc:
6
+ class CollectionAssociation < Association # :nodoc:
7
7
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
8
8
 
9
9
  def self.valid_options(options)
@@ -30,11 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  def self.define_callback(model, callback_name, name, options)
31
31
  full_callback_name = "#{callback_name}_for_#{name}"
32
32
 
33
- unless model.method_defined?(full_callback_name)
33
+ callback_values = Array(options[callback_name.to_sym])
34
+ method_defined = model.respond_to?(full_callback_name)
35
+
36
+ # If there are no callbacks, we must also check if a superclass had
37
+ # previously defined this association
38
+ return if callback_values.empty? && !method_defined
39
+
40
+ unless method_defined
34
41
  model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
35
42
  end
36
43
 
37
- callbacks = Array(options[callback_name.to_sym]).map do |callback|
44
+ callbacks = callback_values.map do |callback|
38
45
  case callback
39
46
  when Symbol
40
47
  ->(method, owner, record) { owner.send(callback, record) }
@@ -20,6 +20,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  attr_accessor :right_reflection
21
21
  end
22
22
 
23
+ @table_name = nil
23
24
  def self.table_name
24
25
  # Table name needs to be resolved lazily
25
26
  # because RHS class might not have been loaded
@@ -44,11 +45,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
44
45
  def self.retrieve_connection
45
46
  left_model.retrieve_connection
46
47
  end
47
-
48
- private
49
- def self.suppress_composite_primary_key(pk)
50
- pk unless pk.is_a?(Array)
51
- end
52
48
  }
53
49
 
54
50
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasMany < CollectionAssociation #:nodoc:
4
+ class HasMany < CollectionAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_many
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
10
+ valid = super + [:counter_cache, :join_table, :index_errors]
11
11
  valid += [:as, :foreign_type] if options[:as]
12
12
  valid += [:through, :source, :source_type] if options[:through]
13
13
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
14
15
  valid
15
16
  end
16
17
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasOne < SingularAssociation #:nodoc:
4
+ class HasOne < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_one
7
7
  end
@@ -11,6 +11,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
11
11
  valid += [:as, :foreign_type] if options[:as]
12
12
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
13
  valid += [:through, :source, :source_type] if options[:through]
14
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
14
15
  valid
15
16
  end
16
17
 
@@ -3,7 +3,7 @@
3
3
  # This class is inherited by the has_one and belongs_to association classes
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class SingularAssociation < Association #:nodoc:
6
+ class SingularAssociation < Association # :nodoc:
7
7
  def self.valid_options(options)
8
8
  super + [:required, :touch]
9
9
  end
@@ -13,12 +13,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
13
13
  mixin = model.generated_association_methods
14
14
  name = reflection.name
15
15
 
16
- define_constructors(mixin, name) if reflection.constructable?
16
+ define_constructors(mixin, name) unless reflection.polymorphic?
17
17
 
18
18
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
19
19
  def reload_#{name}
20
20
  association(:#{name}).force_reload_reader
21
21
  end
22
+
23
+ def reset_#{name}
24
+ association(:#{name}).reset
25
+ end
22
26
  CODE
23
27
  end
24
28
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  # = Active Record Association Collection
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  #
15
17
  # The CollectionAssociation class provides common methods to the collections
16
18
  # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
17
- # the +:through association+ option.
19
+ # the <tt>:through association</tt> option.
18
20
  #
19
21
  # You need to be careful with assumptions regarding the target: The proxy
20
22
  # does not fetch records from the database until it needs them, but new
@@ -25,9 +27,11 @@ module ActiveRecord
25
27
  #
26
28
  # If you need to work on all current children, new and existing records,
27
29
  # +load_target+ and the +loaded+ flag are your friends.
28
- class CollectionAssociation < Association #:nodoc:
30
+ class CollectionAssociation < Association # :nodoc:
29
31
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
30
32
  def reader
33
+ ensure_klass_exists!
34
+
31
35
  if stale_target?
32
36
  reload
33
37
  end
@@ -44,11 +48,11 @@ module ActiveRecord
44
48
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
49
  def ids_reader
46
50
  if loaded?
47
- target.pluck(reflection.association_primary_key)
51
+ target.pluck(*reflection.association_primary_key)
48
52
  elsif !target.empty?
49
- load_target.pluck(reflection.association_primary_key)
53
+ load_target.pluck(*reflection.association_primary_key)
50
54
  else
51
- @association_ids ||= scope.pluck(reflection.association_primary_key)
55
+ @association_ids ||= scope.pluck(*reflection.association_primary_key)
52
56
  end
53
57
  end
54
58
 
@@ -57,14 +61,20 @@ module ActiveRecord
57
61
  primary_key = reflection.association_primary_key
58
62
  pk_type = klass.type_for_attribute(primary_key)
59
63
  ids = Array(ids).compact_blank
60
- ids.map! { |i| pk_type.cast(i) }
64
+ ids.map! { |id| pk_type.cast(id) }
61
65
 
62
- records = klass.where(primary_key => ids).index_by do |r|
63
- r.public_send(primary_key)
66
+ records = if klass.composite_primary_key?
67
+ klass.where(primary_key => ids).index_by do |record|
68
+ primary_key.map { |primary_key| record._read_attribute(primary_key) }
69
+ end
70
+ else
71
+ klass.where(primary_key => ids).index_by do |record|
72
+ record._read_attribute(primary_key)
73
+ end
64
74
  end.values_at(*ids).compact
65
75
 
66
76
  if records.size != ids.size
67
- found_ids = records.map { |record| record.public_send(primary_key) }
77
+ found_ids = records.map { |record| record._read_attribute(primary_key) }
68
78
  not_found_ids = ids - found_ids
69
79
  klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
70
80
  else
@@ -75,7 +85,7 @@ module ActiveRecord
75
85
  def reset
76
86
  super
77
87
  @target = []
78
- @replaced_or_added_targets = Set.new
88
+ @replaced_or_added_targets = Set.new.compare_by_identity
79
89
  @association_ids = nil
80
90
  end
81
91
 
@@ -115,28 +125,13 @@ module ActiveRecord
115
125
  def concat(*records)
116
126
  records = records.flatten
117
127
  if owner.new_record?
118
- load_target
128
+ skip_strict_loading { load_target }
119
129
  concat_records(records)
120
130
  else
121
131
  transaction { concat_records(records) }
122
132
  end
123
133
  end
124
134
 
125
- # Starts a transaction in the association class's database connection.
126
- #
127
- # class Author < ActiveRecord::Base
128
- # has_many :books
129
- # end
130
- #
131
- # Author.first.books.transaction do
132
- # # same effect as calling Book.transaction
133
- # end
134
- def transaction(*args)
135
- reflection.klass.transaction(*args) do
136
- yield
137
- end
138
- end
139
-
140
135
  # Removes all records from the association without calling callbacks
141
136
  # on the associated records. It honors the +:dependent+ option. However
142
137
  # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
@@ -191,7 +186,7 @@ module ActiveRecord
191
186
  end
192
187
 
193
188
  # Deletes the +records+ and removes them from this association calling
194
- # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
189
+ # +before_remove+, +after_remove+, +before_destroy+ and +after_destroy+ callbacks.
195
190
  #
196
191
  # Note that this method removes records from the database ignoring the
197
192
  # +:dependent+ option.
@@ -244,7 +239,7 @@ module ActiveRecord
244
239
  # and delete/add only records that have changed.
245
240
  def replace(other_array)
246
241
  other_array.each { |val| raise_on_type_mismatch!(val) }
247
- original_target = load_target.dup
242
+ original_target = skip_strict_loading { load_target }.dup
248
243
 
249
244
  if owner.new_record?
250
245
  replace_records(other_array, original_target)
@@ -284,9 +279,11 @@ module ActiveRecord
284
279
  end
285
280
 
286
281
  def target=(record)
287
- return super unless ActiveRecord::Base.has_many_inversing
282
+ return super unless reflection.klass.has_many_inversing
288
283
 
289
284
  case record
285
+ when nil
286
+ # It's not possible to remove the record from the inverse association.
290
287
  when Array
291
288
  super
292
289
  else
@@ -306,13 +303,17 @@ module ActiveRecord
306
303
 
307
304
  def find_from_target?
308
305
  loaded? ||
309
- owner.strict_loading? ||
306
+ (owner.strict_loading? && owner.strict_loading_all?) ||
310
307
  reflection.strict_loading? ||
311
308
  owner.new_record? ||
312
309
  target.any? { |record| record.new_record? || record.changed? }
313
310
  end
314
311
 
315
312
  private
313
+ def transaction(&block)
314
+ reflection.klass.transaction(&block)
315
+ end
316
+
316
317
  # We have some records loaded from the database (persisted) and some that are
317
318
  # in-memory (memory). The same record may be represented in the persisted array
318
319
  # and in the memory array.
@@ -325,13 +326,12 @@ module ActiveRecord
325
326
  # * Otherwise, attributes should have the value found in the database
326
327
  def merge_target_lists(persisted, memory)
327
328
  return persisted if memory.empty?
328
- return memory if persisted.empty?
329
329
 
330
330
  persisted.map! do |record|
331
331
  if mem_record = memory.delete(record)
332
332
 
333
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
334
- mem_record[name] = record[name]
333
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name|
334
+ mem_record._write_attribute(name, record[name])
335
335
  end
336
336
 
337
337
  mem_record
@@ -345,7 +345,7 @@ module ActiveRecord
345
345
 
346
346
  def _create_record(attributes, raise = false, &block)
347
347
  unless owner.persisted?
348
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
348
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
349
349
  end
350
350
 
351
351
  if attributes.is_a?(Array)
@@ -489,7 +489,11 @@ module ActiveRecord
489
489
 
490
490
  def callbacks_for(callback_name)
491
491
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
492
- owner.class.send(full_callback_name)
492
+ if owner.class.respond_to?(full_callback_name)
493
+ owner.class.send(full_callback_name)
494
+ else
495
+ []
496
+ end
493
497
  end
494
498
 
495
499
  def include_in_memory?(record)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
+ # = Active Record Collection Proxy
6
+ #
5
7
  # Collection proxies in Active Record are middlemen between an
6
8
  # <tt>association</tt>, and its <tt>target</tt> result set.
7
9
  #
@@ -27,7 +29,7 @@ module ActiveRecord
27
29
  # is computed directly through SQL and does not trigger by itself the
28
30
  # instantiation of the actual post records.
29
31
  class CollectionProxy < Relation
30
- def initialize(klass, association, **) #:nodoc:
32
+ def initialize(klass, association, **) # :nodoc:
31
33
  @association = association
32
34
  super klass
33
35
 
@@ -46,7 +48,7 @@ module ActiveRecord
46
48
  # Returns +true+ if the association has been loaded, otherwise +false+.
47
49
  #
48
50
  # person.pets.loaded? # => false
49
- # person.pets
51
+ # person.pets.records
50
52
  # person.pets.loaded? # => true
51
53
  def loaded?
52
54
  @association.loaded?
@@ -94,12 +96,12 @@ module ActiveRecord
94
96
  # receive:
95
97
  #
96
98
  # person.pets.select(:name).first.person_id
97
- # # => ActiveModel::MissingAttributeError: missing attribute: person_id
99
+ # # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet
98
100
  #
99
- # *Second:* You can pass a block so it can be used just like Array#select.
101
+ # *Second:* You can pass a block so it can be used just like <tt>Array#select</tt>.
100
102
  # This builds an array of objects from the database for the scope,
101
103
  # converting them into an array and iterating through them using
102
- # Array#select.
104
+ # <tt>Array#select</tt>.
103
105
  #
104
106
  # person.pets.select { |pet| /oo/.match?(pet.name) }
105
107
  # # => [
@@ -108,7 +110,7 @@ module ActiveRecord
108
110
  # # ]
109
111
 
110
112
  # Finds an object in the collection responding to the +id+. Uses the same
111
- # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
113
+ # rules as ActiveRecord::FinderMethods.find. Returns ActiveRecord::RecordNotFound
112
114
  # error if the object cannot be found.
113
115
  #
114
116
  # class Person < ActiveRecord::Base
@@ -218,7 +220,7 @@ module ActiveRecord
218
220
  # :call-seq:
219
221
  # third_to_last()
220
222
  #
221
- # Same as #first except returns only the third-to-last record.
223
+ # Same as #last except returns only the third-to-last record.
222
224
 
223
225
  ##
224
226
  # :method: second_to_last
@@ -226,7 +228,7 @@ module ActiveRecord
226
228
  # :call-seq:
227
229
  # second_to_last()
228
230
  #
229
- # Same as #first except returns only the second-to-last record.
231
+ # Same as #last except returns only the second-to-last record.
230
232
 
231
233
  # Returns the last record, or the last +n+ records, from the collection.
232
234
  # If the collection is empty, the first form returns +nil+, and the second
@@ -260,7 +262,7 @@ module ActiveRecord
260
262
  end
261
263
 
262
264
  # Gives a record (or N records if a parameter is supplied) from the collection
263
- # using the same rules as <tt>ActiveRecord::Base.take</tt>.
265
+ # using the same rules as ActiveRecord::FinderMethods.take.
264
266
  #
265
267
  # class Person < ActiveRecord::Base
266
268
  # has_many :pets
@@ -382,7 +384,7 @@ module ActiveRecord
382
384
  # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
383
385
  #
384
386
  # If the supplied array has an incorrect association type, it raises
385
- # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
387
+ # an ActiveRecord::AssociationTypeMismatch error:
386
388
  #
387
389
  # person.pets.replace(["doo", "ggie", "gaga"])
388
390
  # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
@@ -475,7 +477,7 @@ module ActiveRecord
475
477
 
476
478
  # Deletes the records of the collection directly from the database
477
479
  # ignoring the +:dependent+ option. Records are instantiated and it
478
- # invokes +before_remove+, +after_remove+ , +before_destroy+ and
480
+ # invokes +before_remove+, +after_remove+, +before_destroy+, and
479
481
  # +after_destroy+ callbacks.
480
482
  #
481
483
  # class Person < ActiveRecord::Base
@@ -813,7 +815,7 @@ module ActiveRecord
813
815
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
814
816
  # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
815
817
  # not already been loaded and you are going to fetch the records anyway it
816
- # is better to check <tt>collection.length.zero?</tt>.
818
+ # is better to check <tt>collection.load.empty?</tt>.
817
819
  #
818
820
  # class Person < ActiveRecord::Base
819
821
  # has_many :pets
@@ -849,6 +851,11 @@ module ActiveRecord
849
851
  # person.pets.count # => 1
850
852
  # person.pets.any? # => true
851
853
  #
854
+ # Calling it without a block when the collection is not yet
855
+ # loaded is equivalent to <tt>collection.exists?</tt>.
856
+ # If you're going to load the collection anyway, it is better
857
+ # to call <tt>collection.load.any?</tt> to avoid an extra query.
858
+ #
852
859
  # You can also pass a +block+ to define criteria. The behavior
853
860
  # is the same, it returns true if the collection based on the
854
861
  # criteria is not empty.
@@ -925,7 +932,7 @@ module ActiveRecord
925
932
  @association
926
933
  end
927
934
 
928
- # Returns a <tt>Relation</tt> object for the records in this association
935
+ # Returns a Relation object for the records in this association
929
936
  def scope
930
937
  @scope ||= @association.scope
931
938
  end
@@ -950,10 +957,13 @@ module ActiveRecord
950
957
  # person.pets == other
951
958
  # # => true
952
959
  #
960
+ #
961
+ # Note that unpersisted records can still be seen as equal:
962
+ #
953
963
  # other = [Pet.new(id: 1), Pet.new(id: 2)]
954
964
  #
955
965
  # person.pets == other
956
- # # => false
966
+ # # => true
957
967
  def ==(other)
958
968
  load_target == other
959
969
  end
@@ -1097,13 +1107,18 @@ module ActiveRecord
1097
1107
  super
1098
1108
  end
1099
1109
 
1110
+ def pretty_print(pp) # :nodoc:
1111
+ load_target if find_from_target?
1112
+ super
1113
+ end
1114
+
1100
1115
  delegate_methods = [
1101
1116
  QueryMethods,
1102
1117
  SpawnMethods,
1103
1118
  ].flat_map { |klass|
1104
1119
  klass.public_instance_methods(false)
1105
1120
  } - self.public_instance_methods(false) - [:select] + [
1106
- :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
1121
+ :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async
1107
1122
  ]
1108
1123
 
1109
1124
  delegate(*delegate_methods, to: :scope)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class DisableJoinsAssociationScope < AssociationScope # :nodoc:
6
+ def scope(association)
7
+ source_reflection = association.reflection
8
+ owner = association.owner
9
+ unscoped = association.klass.unscoped
10
+ reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse
11
+
12
+ last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner)
13
+
14
+ add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered)
15
+ end
16
+
17
+ private
18
+ def last_scope_chain(reverse_chain, owner)
19
+ first_item = reverse_chain.shift
20
+ first_scope = [first_item, false, [owner._read_attribute(first_item.join_foreign_key)]]
21
+
22
+ reverse_chain.inject(first_scope) do |(reflection, ordered, join_ids), next_reflection|
23
+ key = reflection.join_primary_key
24
+ records = add_constraints(reflection, key, join_ids, owner, ordered)
25
+ foreign_key = next_reflection.join_foreign_key
26
+ record_ids = records.pluck(foreign_key)
27
+ records_ordered = records && records.order_values.any?
28
+
29
+ [next_reflection, records_ordered, record_ids]
30
+ end
31
+ end
32
+
33
+ def add_constraints(reflection, key, join_ids, owner, ordered)
34
+ scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids)
35
+
36
+ relation = reflection.klass.scope_for_association
37
+ scope.merge!(
38
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
39
+ )
40
+
41
+ scope = reflection.constraints.inject(scope) do |memo, scope_chain_item|
42
+ item = eval_scope(reflection, scope_chain_item, owner)
43
+ scope.unscope!(*item.unscope_values)
44
+ scope.where_clause += item.where_clause
45
+ scope.order_values = item.order_values | scope.order_values
46
+ scope
47
+ end
48
+
49
+ if scope.order_values.empty? && ordered
50
+ split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
51
+ split_scope.where_clause += scope.where_clause
52
+ split_scope
53
+ else
54
+ scope
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -12,7 +12,7 @@ module ActiveRecord::Associations
12
12
 
13
13
  def nullified_owner_attributes
14
14
  Hash.new.tap do |attrs|
15
- attrs[reflection.foreign_key] = nil
15
+ Array(reflection.foreign_key).each { |foreign_key| attrs[foreign_key] = nil }
16
16
  attrs[reflection.type] = nil if reflection.type.present?
17
17
  end
18
18
  end
@@ -22,8 +22,15 @@ module ActiveRecord::Associations
22
22
  def set_owner_attributes(record)
23
23
  return if options[:through]
24
24
 
25
- key = owner._read_attribute(reflection.join_foreign_key)
26
- record._write_attribute(reflection.join_primary_key, key)
25
+ primary_key_attribute_names = Array(reflection.join_primary_key)
26
+ foreign_key_attribute_names = Array(reflection.join_foreign_key)
27
+
28
+ primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names)
29
+
30
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
31
+ value = owner._read_attribute(foreign_key)
32
+ record._write_attribute(primary_key, value)
33
+ end
27
34
 
28
35
  if reflection.type
29
36
  record._write_attribute(reflection.type, owner.class.polymorphic_name)