activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -1,9 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
3
+ require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ActiveRecord
6
6
  module Delegation # :nodoc:
7
+ class << self
8
+ def delegated_classes
9
+ [
10
+ ActiveRecord::Relation,
11
+ ActiveRecord::Associations::CollectionProxy,
12
+ ActiveRecord::AssociationRelation,
13
+ ActiveRecord::DisableJoinsAssociationRelation,
14
+ ]
15
+ end
16
+
17
+ def uncacheable_methods
18
+ @uncacheable_methods ||= (
19
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
20
+ ).to_set.freeze
21
+ end
22
+ end
23
+
7
24
  module DelegateCache # :nodoc:
8
25
  def relation_delegate_class(klass)
9
26
  @relation_delegate_cache[klass]
@@ -11,11 +28,7 @@ module ActiveRecord
11
28
 
12
29
  def initialize_relation_delegate_cache
13
30
  @relation_delegate_cache = cache = {}
14
- [
15
- ActiveRecord::Relation,
16
- ActiveRecord::Associations::CollectionProxy,
17
- ActiveRecord::AssociationRelation
18
- ].each do |klass|
31
+ Delegation.delegated_classes.each do |klass|
19
32
  delegate = Class.new(klass) {
20
33
  include ClassSpecificRelation
21
34
  }
@@ -53,21 +66,21 @@ module ActiveRecord
53
66
  end
54
67
 
55
68
  class GeneratedRelationMethods < Module # :nodoc:
56
- include Mutex_m
69
+ MUTEX = Mutex.new
57
70
 
58
71
  def generate_method(method)
59
- synchronize do
72
+ MUTEX.synchronize do
60
73
  return if method_defined?(method)
61
74
 
62
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
75
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
63
76
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
64
- def #{method}(*args, &block)
65
- scoping { klass.#{method}(*args, &block) }
77
+ def #{method}(...)
78
+ scoping { klass.#{method}(...) }
66
79
  end
67
80
  RUBY
68
81
  else
69
- define_method(method) do |*args, &block|
70
- scoping { klass.public_send(method, *args, &block) }
82
+ define_method(method) do |*args, **kwargs, &block|
83
+ scoping { klass.public_send(method, *args, **kwargs, &block) }
71
84
  end
72
85
  end
73
86
  end
@@ -82,12 +95,12 @@ module ActiveRecord
82
95
  # may vary depending on the klass of a relation, so we create a subclass of Relation
83
96
  # for each different klass, and the delegations are compiled into that subclass only.
84
97
 
85
- delegate :to_xml, :encode_with, :length, :each, :join,
98
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
86
99
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
87
- :to_sentence, :to_formatted_s, :as_json,
100
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
88
101
  :shuffle, :split, :slice, :index, :rindex, to: :records
89
102
 
90
- delegate :primary_key, :connection, to: :klass
103
+ delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
91
104
 
92
105
  module ClassSpecificRelation # :nodoc:
93
106
  extend ActiveSupport::Concern
@@ -99,11 +112,12 @@ module ActiveRecord
99
112
  end
100
113
 
101
114
  private
102
-
103
- def method_missing(method, *args, &block)
115
+ def method_missing(method, ...)
104
116
  if @klass.respond_to?(method)
105
- @klass.generate_relation_method(method)
106
- scoping { @klass.public_send(method, *args, &block) }
117
+ unless Delegation.uncacheable_methods.include?(method)
118
+ @klass.generate_relation_method(method)
119
+ end
120
+ scoping { @klass.public_send(method, ...) }
107
121
  else
108
122
  super
109
123
  end
@@ -111,12 +125,11 @@ module ActiveRecord
111
125
  end
112
126
 
113
127
  module ClassMethods # :nodoc:
114
- def create(klass, *args)
115
- relation_class_for(klass).new(klass, *args)
128
+ def create(klass, *args, **kwargs)
129
+ relation_class_for(klass).new(klass, *args, **kwargs)
116
130
  end
117
131
 
118
132
  private
119
-
120
133
  def relation_class_for(klass)
121
134
  klass.relation_delegate_class(self)
122
135
  end
@@ -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
  #
@@ -14,10 +16,31 @@ module ActiveRecord
14
16
  # Person.find("1") # returns the object for ID = 1
15
17
  # Person.find("31-sarah") # returns the object for ID = 31
16
18
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
17
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
19
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17), or with composite primary key [7, 17]
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.
@@ -64,6 +87,14 @@ module ActiveRecord
64
87
  #
65
88
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
89
  # # returns an Array of the required fields.
90
+ #
91
+ # ==== Edge Cases
92
+ #
93
+ # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
94
+ # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
95
+ # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
96
+ # Person.find([]) # returns an empty array if the argument is an empty array.
97
+ # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
67
98
  def find(*args)
68
99
  return super if block_given?
69
100
  find_with_ids(*args)
@@ -104,6 +135,32 @@ module ActiveRecord
104
135
  take || raise_record_not_found_exception!
105
136
  end
106
137
 
138
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
139
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
140
+ # record is found.
141
+ #
142
+ # Product.where(["price = %?", price]).sole
143
+ def sole
144
+ found, undesired = first(2)
145
+
146
+ if found.nil?
147
+ raise_record_not_found_exception!
148
+ elsif undesired.present?
149
+ raise ActiveRecord::SoleRecordExceeded.new(self)
150
+ else
151
+ found
152
+ end
153
+ end
154
+
155
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
156
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
157
+ # record is found.
158
+ #
159
+ # Product.find_sole_by(["price = %?", price])
160
+ def find_sole_by(arg, *args)
161
+ where(arg, *args).sole
162
+ end
163
+
107
164
  # Find the first record (or first N records if a parameter is supplied).
108
165
  # If no order is defined it will order by primary key.
109
166
  #
@@ -275,9 +332,9 @@ module ActiveRecord
275
332
  # * Integer - Finds the record with this primary key.
276
333
  # * String - Finds the record with a primary key corresponding to this
277
334
  # string (such as <tt>'5'</tt>).
278
- # * Array - Finds the record that matches these +find+-style conditions
335
+ # * Array - Finds the record that matches these +where+-style conditions
279
336
  # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
280
- # * Hash - Finds the record that matches these +find+-style conditions
337
+ # * Hash - Finds the record that matches these +where+-style conditions
281
338
  # (such as <tt>{name: 'David'}</tt>).
282
339
  # * +false+ - Returns always +false+.
283
340
  # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
@@ -298,6 +355,8 @@ module ActiveRecord
298
355
  # Person.exists?
299
356
  # Person.where(name: 'Spartacus', rating: 4).exists?
300
357
  def exists?(conditions = :none)
358
+ return false if @none
359
+
301
360
  if Base === conditions
302
361
  raise ArgumentError, <<-MSG.squish
303
362
  You are passing an instance of ActiveRecord::Base to `exists?`.
@@ -313,10 +372,40 @@ module ActiveRecord
313
372
  end
314
373
 
315
374
  relation = construct_relation_for_exists(conditions)
375
+ return false if relation.where_clause.contradiction?
376
+
377
+ skip_query_cache_if_necessary do
378
+ with_connection do |c|
379
+ c.select_rows(relation.arel, "#{name} Exists?").size == 1
380
+ end
381
+ end
382
+ end
383
+
384
+ # Returns true if the relation contains the given record or false otherwise.
385
+ #
386
+ # No query is performed if the relation is loaded; the given record is
387
+ # compared to the records in memory. If the relation is unloaded, an
388
+ # efficient existence query is performed, as in #exists?.
389
+ def include?(record)
390
+ # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
391
+ # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
392
+ return false unless record.is_a?(klass)
393
+
394
+ if loaded? || offset_value || limit_value || having_clause.any?
395
+ records.include?(record)
396
+ else
397
+ id = if record.class.composite_primary_key?
398
+ record.class.primary_key.zip(record.id).to_h
399
+ else
400
+ record.id
401
+ end
316
402
 
317
- skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists?") } ? true : false
403
+ exists?(id)
404
+ end
318
405
  end
319
406
 
407
+ alias :member? :include?
408
+
320
409
  # This method is called whenever no records are found with either a single
321
410
  # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
322
411
  #
@@ -326,31 +415,27 @@ module ActiveRecord
326
415
  # the expected number of results should be provided in the +expected_size+
327
416
  # argument.
328
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
329
- conditions = arel.where_sql(@klass)
330
- conditions = " [#{conditions}]" if conditions
418
+ conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
419
+
331
420
  name = @klass.name
332
421
 
333
422
  if ids.nil?
334
423
  error = +"Couldn't find #{name}"
335
424
  error << " with#{conditions}" if conditions
336
425
  raise RecordNotFound.new(error, name, key)
337
- elsif Array(ids).size == 1
338
- error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
426
+ elsif Array.wrap(ids).size == 1
427
+ id = Array.wrap(ids)[0]
428
+ error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
339
429
  raise RecordNotFound.new(error, name, key, ids)
340
430
  else
341
431
  error = +"Couldn't find all #{name.pluralize} with '#{key}': "
342
- error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
343
- error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
432
+ error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
433
+ error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
344
434
  raise RecordNotFound.new(error, name, key, ids)
345
435
  end
346
436
  end
347
437
 
348
438
  private
349
-
350
- def offset_index
351
- offset_value || 0
352
- end
353
-
354
439
  def construct_relation_for_exists(conditions)
355
440
  conditions = sanitize_forbidden_attributes(conditions)
356
441
 
@@ -372,16 +457,25 @@ module ActiveRecord
372
457
 
373
458
  def apply_join_dependency(eager_loading: group_values.empty?)
374
459
  join_dependency = construct_join_dependency(
375
- eager_load_values + includes_values, Arel::Nodes::OuterJoin
460
+ eager_load_values | includes_values, Arel::Nodes::OuterJoin
376
461
  )
377
462
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
378
463
 
379
- if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
380
- if has_limit_or_offset?
381
- limited_ids = limited_ids_for(relation)
382
- limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
464
+ if eager_loading && has_limit_or_offset? && !(
465
+ using_limitable_reflections?(join_dependency.reflections) &&
466
+ using_limitable_reflections?(
467
+ construct_join_dependency(
468
+ select_association_list(joins_values).concat(
469
+ select_association_list(left_outer_joins_values)
470
+ ), nil
471
+ ).reflections
472
+ )
473
+ )
474
+ relation = skip_query_cache_if_necessary do
475
+ klass.with_connection do |c|
476
+ c.distinct_relation_for_primary_key(relation)
477
+ end
383
478
  end
384
- relation.limit_value = relation.offset_value = nil
385
479
  end
386
480
 
387
481
  if block_given?
@@ -391,18 +485,6 @@ module ActiveRecord
391
485
  end
392
486
  end
393
487
 
394
- def limited_ids_for(relation)
395
- values = @klass.connection.columns_for_distinct(
396
- connection.visitor.compile(arel_attribute(primary_key)),
397
- relation.order_values
398
- )
399
-
400
- relation = relation.except(:select).select(values).distinct!
401
-
402
- id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
403
- id_rows.map { |row| row[primary_key] }
404
- end
405
-
406
488
  def using_limitable_reflections?(reflections)
407
489
  reflections.none?(&:collection?)
408
490
  end
@@ -410,10 +492,17 @@ module ActiveRecord
410
492
  def find_with_ids(*ids)
411
493
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
412
494
 
413
- expects_array = ids.first.kind_of?(Array)
495
+ expects_array = if klass.composite_primary_key?
496
+ ids.first.first.is_a?(Array)
497
+ else
498
+ ids.first.is_a?(Array)
499
+ end
500
+
414
501
  return [] if expects_array && ids.first.empty?
415
502
 
416
- ids = ids.flatten.compact.uniq
503
+ ids = ids.first if expects_array
504
+
505
+ ids = ids.compact.uniq
417
506
 
418
507
  model_name = @klass.name
419
508
 
@@ -437,7 +526,12 @@ module ActiveRecord
437
526
  MSG
438
527
  end
439
528
 
440
- relation = where(primary_key => id)
529
+ relation = if klass.composite_primary_key?
530
+ where(primary_key.zip(id).to_h)
531
+ else
532
+ where(primary_key => id)
533
+ end
534
+
441
535
  record = relation.take
442
536
 
443
537
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -448,7 +542,9 @@ module ActiveRecord
448
542
  def find_some(ids)
449
543
  return find_some_ordered(ids) unless order_values.present?
450
544
 
451
- result = where(primary_key => ids).to_a
545
+ relation = where(primary_key => ids)
546
+ relation = relation.select(table[primary_key]) unless select_values.empty?
547
+ result = relation.to_a
452
548
 
453
549
  expected_size =
454
550
  if limit_value && ids.size > limit_value
@@ -472,13 +568,13 @@ module ActiveRecord
472
568
  def find_some_ordered(ids)
473
569
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
474
570
 
475
- result = except(:limit, :offset).where(primary_key => ids).records
571
+ relation = except(:limit, :offset)
572
+ relation = relation.where(primary_key => ids)
573
+ relation = relation.select(table[primary_key]) unless select_values.empty?
574
+ result = relation.records
476
575
 
477
576
  if result.size == ids.size
478
- pk_type = @klass.type_for_attribute(primary_key)
479
-
480
- records_by_id = result.index_by(&:id)
481
- ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
577
+ result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
482
578
  else
483
579
  raise_record_not_found_exception!(ids, result.size, ids.size)
484
580
  end
@@ -501,7 +597,8 @@ module ActiveRecord
501
597
  end
502
598
 
503
599
  def find_nth(index)
504
- @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
600
+ @offsets ||= {}
601
+ @offsets[index] ||= find_nth_with_limit(index, 1).first
505
602
  end
506
603
 
507
604
  def find_nth_with_limit(index, limit)
@@ -515,7 +612,7 @@ module ActiveRecord
515
612
  end
516
613
 
517
614
  if limit > 0
518
- relation = relation.offset(offset_index + index) unless index.zero?
615
+ relation = relation.offset((offset_value || 0) + index) unless index.zero?
519
616
  relation.limit(limit).to_a
520
617
  else
521
618
  []
@@ -529,10 +626,10 @@ module ActiveRecord
529
626
  else
530
627
  relation = ordered_relation
531
628
 
532
- if equal?(relation) || has_limit_or_offset?
629
+ if relation.order_values.empty? || relation.has_limit_or_offset?
533
630
  relation.records[-index]
534
631
  else
535
- relation.last(index)[-index]
632
+ relation.reverse_order.offset(index - 1).first
536
633
  end
537
634
  end
538
635
  end
@@ -542,11 +639,24 @@ module ActiveRecord
542
639
  end
543
640
 
544
641
  def ordered_relation
545
- if order_values.empty? && (implicit_order_column || primary_key)
546
- order(arel_attribute(implicit_order_column || primary_key).asc)
642
+ if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
643
+ order(_order_columns.map { |column| table[column].asc })
547
644
  else
548
645
  self
549
646
  end
550
647
  end
648
+
649
+ def _order_columns
650
+ oc = []
651
+
652
+ oc << implicit_order_column if implicit_order_column
653
+ oc << query_constraints_list if query_constraints_list
654
+
655
+ if primary_key && query_constraints_list.nil?
656
+ oc << primary_key
657
+ end
658
+
659
+ oc.flatten.uniq.compact
660
+ end
551
661
  end
552
662
  end
@@ -18,8 +18,12 @@ module ActiveRecord
18
18
  value.nil?
19
19
  end
20
20
 
21
+ def ==(other)
22
+ self.class == other.class && value == other.value && name == other.name
23
+ end
24
+
21
25
  def self.empty
22
- @empty ||= new(nil, nil)
26
+ @empty ||= new(nil, nil).freeze
23
27
  end
24
28
  end
25
29
  end
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  @hash = hash
15
15
  end
16
16
 
17
- def merge #:nodoc:
17
+ def merge
18
18
  Merger.new(relation, other).merge
19
19
  end
20
20
 
@@ -28,19 +28,14 @@ module ActiveRecord
28
28
  table: relation.table,
29
29
  predicate_builder: relation.predicate_builder
30
30
  )
31
- hash.each { |k, v|
32
- if k == :joins
33
- if Hash === v
34
- other.joins!(v)
35
- else
36
- other.joins!(*v)
37
- end
38
- elsif k == :select
39
- other._select!(v)
31
+ hash.each do |k, v|
32
+ k = :_select if k == :select
33
+ if Array === v
34
+ other.public_send("#{k}!", *v)
40
35
  else
41
- other.send("#{k}!", v)
36
+ other.public_send("#{k}!", v)
42
37
  end
43
- }
38
+ end
44
39
  other
45
40
  end
46
41
  end
@@ -54,30 +49,27 @@ module ActiveRecord
54
49
  @other = other
55
50
  end
56
51
 
57
- NORMAL_VALUES = Relation::VALUE_METHODS -
58
- Relation::CLAUSE_METHODS -
59
- [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
60
-
61
- def normal_values
62
- NORMAL_VALUES
63
- end
52
+ NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
53
+ [
54
+ :select, :includes, :preload, :joins, :left_outer_joins,
55
+ :order, :reverse_order, :lock, :create_with, :reordering
56
+ ]
64
57
 
65
58
  def merge
66
- normal_values.each do |name|
59
+ NORMAL_VALUES.each do |name|
67
60
  value = values[name]
68
61
  # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
69
62
  # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
70
63
  # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
71
64
  # don't fall through the cracks.
72
65
  unless value.nil? || (value.blank? && false != value)
73
- if name == :select
74
- relation._select!(*value)
75
- else
76
- relation.send("#{name}!", *value)
77
- end
66
+ relation.public_send(:"#{name}!", *value)
78
67
  end
79
68
  end
80
69
 
70
+ relation.none! if other.null_relation?
71
+
72
+ merge_select_values
81
73
  merge_multi_values
82
74
  merge_single_values
83
75
  merge_clauses
@@ -89,13 +81,24 @@ module ActiveRecord
89
81
  end
90
82
 
91
83
  private
84
+ def merge_select_values
85
+ return if other.select_values.empty?
86
+
87
+ if other.klass == relation.klass
88
+ relation.select_values |= other.select_values
89
+ else
90
+ relation.select_values |= other.instance_eval do
91
+ arel_columns(select_values)
92
+ end
93
+ end
94
+ end
92
95
 
93
96
  def merge_preloads
94
97
  return if other.preload_values.empty? && other.includes_values.empty?
95
98
 
96
99
  if other.klass == relation.klass
97
- relation.preload!(*other.preload_values) unless other.preload_values.empty?
98
- relation.includes!(other.includes_values) unless other.includes_values.empty?
100
+ relation.preload_values |= other.preload_values unless other.preload_values.empty?
101
+ relation.includes_values |= other.includes_values unless other.includes_values.empty?
99
102
  else
100
103
  reflection = relation.klass.reflect_on_all_associations.find do |r|
101
104
  r.class_name == other.klass.name
@@ -112,10 +115,10 @@ module ActiveRecord
112
115
  end
113
116
 
114
117
  def merge_joins
115
- return if other.joins_values.blank?
118
+ return if other.joins_values.empty?
116
119
 
117
120
  if other.klass == relation.klass
118
- relation.joins!(*other.joins_values)
121
+ relation.joins_values |= other.joins_values
119
122
  else
120
123
  associations, others = other.joins_values.partition do |join|
121
124
  case join
@@ -131,16 +134,21 @@ module ActiveRecord
131
134
  end
132
135
 
133
136
  def merge_outer_joins
134
- return if other.left_outer_joins_values.blank?
137
+ return if other.left_outer_joins_values.empty?
135
138
 
136
139
  if other.klass == relation.klass
137
- relation.left_outer_joins!(*other.left_outer_joins_values)
140
+ relation.left_outer_joins_values |= other.left_outer_joins_values
138
141
  else
139
- associations = other.left_outer_joins_values
142
+ associations, others = other.left_outer_joins_values.partition do |join|
143
+ case join
144
+ when Hash, Symbol, Array; true
145
+ end
146
+ end
147
+
140
148
  join_dependency = other.construct_join_dependency(
141
149
  associations, Arel::Nodes::OuterJoin
142
150
  )
143
- relation.joins!(join_dependency)
151
+ relation.left_outer_joins!(join_dependency, *others)
144
152
  end
145
153
  end
146
154
 
@@ -13,27 +13,26 @@ module ActiveRecord
13
13
  return attribute.in([]) if value.empty?
14
14
 
15
15
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
- nils = values.extract!(&:nil?)
16
+ nils = values.compact!
17
17
  ranges = values.extract! { |v| v.is_a?(Range) }
18
18
 
19
19
  values_predicate =
20
20
  case values.length
21
21
  when 0 then NullPredicate
22
22
  when 1 then predicate_builder.build(attribute, values.first)
23
- else
24
- values.map! do |v|
25
- predicate_builder.build_bind_attribute(attribute.name, v)
26
- end
27
- values.empty? ? NullPredicate : attribute.in(values)
23
+ else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
28
24
  end
29
25
 
30
- unless nils.empty?
31
- values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
26
+ if nils
27
+ values_predicate = values_predicate.or(attribute.eq(nil))
32
28
  end
33
29
 
34
- array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
35
- array_predicates.unshift(values_predicate)
36
- array_predicates.inject(&:or)
30
+ if ranges.empty?
31
+ values_predicate
32
+ else
33
+ array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
+ array_predicates.inject(values_predicate, &:or)
35
+ end
37
36
  end
38
37
 
39
38
  private