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,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/hash/slice"
3
4
  require "active_support/core_ext/object/deep_dup"
4
5
 
5
6
  module ActiveRecord
@@ -7,7 +8,7 @@ module ActiveRecord
7
8
  # but can be queried by name. Example:
8
9
  #
9
10
  # class Conversation < ActiveRecord::Base
10
- # enum status: [ :active, :archived ]
11
+ # enum :status, [ :active, :archived ]
11
12
  # end
12
13
  #
13
14
  # # conversation.update! status: 0
@@ -41,19 +42,33 @@ module ActiveRecord
41
42
  # Conversation.where(status: [:active, :archived])
42
43
  # Conversation.where.not(status: :active)
43
44
  #
44
- # You can set the default value from the database declaration, like:
45
+ # Defining scopes can be disabled by setting +:scopes+ to +false+.
45
46
  #
46
- # create_table :conversations do |t|
47
- # t.column :status, :integer, default: 0
47
+ # class Conversation < ActiveRecord::Base
48
+ # enum :status, [ :active, :archived ], scopes: false
48
49
  # end
49
50
  #
50
- # Good practice is to let the first declared status be the default.
51
+ # You can set the default enum value by setting +:default+, like:
52
+ #
53
+ # class Conversation < ActiveRecord::Base
54
+ # enum :status, [ :active, :archived ], default: :active
55
+ # end
51
56
  #
52
- # Finally, it's also possible to explicitly map the relation between attribute and
57
+ # conversation = Conversation.new
58
+ # conversation.status # => "active"
59
+ #
60
+ # It's possible to explicitly map the relation between attribute and
53
61
  # database integer with a hash:
54
62
  #
55
63
  # class Conversation < ActiveRecord::Base
56
- # enum status: { active: 0, archived: 1 }
64
+ # enum :status, active: 0, archived: 1
65
+ # end
66
+ #
67
+ # Finally it's also possible to use a string column to persist the enumerated value.
68
+ # Note that this will likely lead to slower database queries:
69
+ #
70
+ # class Conversation < ActiveRecord::Base
71
+ # enum :status, active: "active", archived: "archived"
57
72
  # end
58
73
  #
59
74
  # Note that when an array is used, the implicit mapping from the values to database
@@ -68,7 +83,7 @@ module ActiveRecord
68
83
  #
69
84
  # In rare circumstances you might need to access the mapping directly.
70
85
  # The mappings are exposed through a class method with the pluralized attribute
71
- # name, which return the mapping in a +HashWithIndifferentAccess+:
86
+ # name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess :
72
87
  #
73
88
  # Conversation.statuses[:active] # => 0
74
89
  # Conversation.statuses["archived"] # => 1
@@ -78,14 +93,14 @@ module ActiveRecord
78
93
  #
79
94
  # Conversation.where("status <> ?", Conversation.statuses[:archived])
80
95
  #
81
- # You can use the +:_prefix+ or +:_suffix+ options when you need to define
96
+ # You can use the +:prefix+ or +:suffix+ options when you need to define
82
97
  # multiple enums with same values. If the passed value is +true+, the methods
83
98
  # are prefixed/suffixed with the name of the enum. It is also possible to
84
99
  # supply a custom value:
85
100
  #
86
101
  # class Conversation < ActiveRecord::Base
87
- # enum status: [:active, :archived], _suffix: true
88
- # enum comments_status: [:active, :inactive], _prefix: :comments
102
+ # enum :status, [ :active, :archived ], suffix: true
103
+ # enum :comments_status, [ :active, :inactive ], prefix: :comments
89
104
  # end
90
105
  #
91
106
  # With the above example, the bang and predicate methods along with the
@@ -96,64 +111,137 @@ module ActiveRecord
96
111
  #
97
112
  # conversation.comments_inactive!
98
113
  # conversation.comments_active? # => false
99
-
114
+ #
115
+ # If you want to disable the auto-generated methods on the model, you can do
116
+ # so by setting the +:instance_methods+ option to false:
117
+ #
118
+ # class Conversation < ActiveRecord::Base
119
+ # enum :status, [ :active, :archived ], instance_methods: false
120
+ # end
121
+ #
122
+ # By default, an +ArgumentError+ will be raised when assigning an invalid value:
123
+ #
124
+ # class Conversation < ActiveRecord::Base
125
+ # enum :status, [ :active, :archived ]
126
+ # end
127
+ #
128
+ # conversation = Conversation.new
129
+ #
130
+ # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
131
+ #
132
+ # If, instead, you want the enum value to be validated before saving, use the
133
+ # +:validate+ option:
134
+ #
135
+ # class Conversation < ActiveRecord::Base
136
+ # enum :status, [ :active, :archived ], validate: true
137
+ # end
138
+ #
139
+ # conversation = Conversation.new
140
+ #
141
+ # conversation.status = :unknown
142
+ # conversation.valid? # => false
143
+ #
144
+ # conversation.status = nil
145
+ # conversation.valid? # => false
146
+ #
147
+ # conversation.status = :active
148
+ # conversation.valid? # => true
149
+ #
150
+ # You may also pass additional validation options:
151
+ #
152
+ # class Conversation < ActiveRecord::Base
153
+ # enum :status, [ :active, :archived ], validate: { allow_nil: true }
154
+ # end
155
+ #
156
+ # conversation = Conversation.new
157
+ #
158
+ # conversation.status = :unknown
159
+ # conversation.valid? # => false
160
+ #
161
+ # conversation.status = nil
162
+ # conversation.valid? # => true
163
+ #
164
+ # conversation.status = :active
165
+ # conversation.valid? # => true
100
166
  module Enum
101
167
  def self.extended(base) # :nodoc:
102
168
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
103
169
  end
104
170
 
105
- def inherited(base) # :nodoc:
106
- base.defined_enums = defined_enums.deep_dup
107
- super
108
- end
109
-
110
171
  class EnumType < Type::Value # :nodoc:
111
172
  delegate :type, to: :subtype
112
173
 
113
- def initialize(name, mapping, subtype)
174
+ def initialize(name, mapping, subtype, raise_on_invalid_values: true)
114
175
  @name = name
115
176
  @mapping = mapping
116
177
  @subtype = subtype
178
+ @_raise_on_invalid_values = raise_on_invalid_values
117
179
  end
118
180
 
119
181
  def cast(value)
120
- return if value.blank?
121
-
122
182
  if mapping.has_key?(value)
123
183
  value.to_s
124
184
  elsif mapping.has_value?(value)
125
185
  mapping.key(value)
126
186
  else
127
- assert_valid_value(value)
187
+ value.presence
128
188
  end
129
189
  end
130
190
 
131
191
  def deserialize(value)
132
- return if value.nil?
133
192
  mapping.key(subtype.deserialize(value))
134
193
  end
135
194
 
136
195
  def serialize(value)
137
- mapping.fetch(value, value)
196
+ subtype.serialize(mapping.fetch(value, value))
197
+ end
198
+
199
+ def serializable?(value, &block)
200
+ subtype.serializable?(mapping.fetch(value, value), &block)
138
201
  end
139
202
 
140
203
  def assert_valid_value(value)
204
+ return unless @_raise_on_invalid_values
205
+
141
206
  unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
142
207
  raise ArgumentError, "'#{value}' is not a valid #{name}"
143
208
  end
144
209
  end
145
210
 
211
+ attr_reader :subtype
212
+
146
213
  private
147
- attr_reader :name, :mapping, :subtype
214
+ attr_reader :name, :mapping
215
+ end
216
+
217
+ def enum(name = nil, values = nil, **options)
218
+ if name
219
+ values, options = options, {} unless values
220
+ return _enum(name, values, **options)
221
+ end
222
+
223
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
224
+ options.transform_keys! { |key| :"#{key[1..-1]}" }
225
+
226
+ definitions.each { |name, values| _enum(name, values, **options) }
227
+
228
+ ActiveRecord.deprecator.warn(<<~MSG)
229
+ Defining enums with keyword arguments is deprecated and will be removed
230
+ in Rails 8.0. Positional arguments should be used instead:
231
+
232
+ #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
233
+ MSG
148
234
  end
149
235
 
150
- def enum(definitions)
151
- klass = self
152
- enum_prefix = definitions.delete(:_prefix)
153
- enum_suffix = definitions.delete(:_suffix)
154
- enum_scopes = definitions.delete(:_scopes)
155
- definitions.each do |name, values|
236
+ private
237
+ def inherited(base)
238
+ base.defined_enums = defined_enums.deep_dup
239
+ super
240
+ end
241
+
242
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
156
243
  assert_valid_enum_definition_values(values)
244
+ assert_valid_enum_options(options)
157
245
  # statuses = { }
158
246
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
159
247
  name = name.to_s
@@ -166,73 +254,128 @@ module ActiveRecord
166
254
  detect_enum_conflict!(name, name)
167
255
  detect_enum_conflict!(name, "#{name}=")
168
256
 
169
- attr = attribute_alias?(name) ? attribute_alias(name) : name
170
- decorate_attribute_type(attr, :enum) do |subtype|
171
- EnumType.new(attr, enum_values, subtype)
257
+ attribute(name, **options)
258
+
259
+ decorate_attributes([name]) do |_name, subtype|
260
+ if subtype == ActiveModel::Type.default_value
261
+ raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
262
+ " backed by a database column or declared with an explicit type" \
263
+ " via `attribute`."
264
+ end
265
+
266
+ subtype = subtype.subtype if EnumType === subtype
267
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
172
268
  end
173
269
 
270
+ value_method_names = []
174
271
  _enum_methods_module.module_eval do
272
+ prefix = if prefix
273
+ prefix == true ? "#{name}_" : "#{prefix}_"
274
+ end
275
+
276
+ suffix = if suffix
277
+ suffix == true ? "_#{name}" : "_#{suffix}"
278
+ end
279
+
175
280
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
176
281
  pairs.each do |label, value|
177
- if enum_prefix == true
178
- prefix = "#{name}_"
179
- elsif enum_prefix
180
- prefix = "#{enum_prefix}_"
181
- end
182
- if enum_suffix == true
183
- suffix = "_#{name}"
184
- elsif enum_suffix
185
- suffix = "_#{enum_suffix}"
186
- end
187
-
188
- value_method_name = "#{prefix}#{label}#{suffix}"
189
282
  enum_values[label] = value
190
283
  label = label.to_s
191
284
 
192
- # def active?() status == "active" end
193
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
194
- define_method("#{value_method_name}?") { self[attr] == label }
285
+ value_method_name = "#{prefix}#{label}#{suffix}"
286
+ value_method_names << value_method_name
287
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
288
+
289
+ method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
290
+ value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
195
291
 
196
- # def active!() update!(status: 0) end
197
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
198
- define_method("#{value_method_name}!") { update!(attr => value) }
292
+ if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
293
+ value_method_names << value_method_alias
294
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
295
+ end
296
+ end
297
+ end
298
+ detect_negative_enum_conditions!(value_method_names) if scopes
199
299
 
200
- # scope :active, -> { where(status: 0) }
201
- # scope :not_active, -> { where.not(status: 0) }
202
- if enum_scopes != false
203
- klass.send(:detect_negative_condition!, value_method_name)
300
+ if validate
301
+ validate = {} unless Hash === validate
302
+ validates_inclusion_of name, in: enum_values.keys, **validate
303
+ end
204
304
 
305
+ enum_values.freeze
306
+ end
307
+
308
+ class EnumMethods < Module # :nodoc:
309
+ def initialize(klass)
310
+ @klass = klass
311
+ end
312
+
313
+ private
314
+ attr_reader :klass
315
+
316
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
317
+ if instance_methods
318
+ # def active?() status_for_database == 0 end
319
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
320
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
321
+
322
+ # def active!() update!(status: 0) end
323
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
324
+ define_method("#{value_method_name}!") { update!(name => value) }
325
+ end
326
+
327
+ if scopes
328
+ # scope :active, -> { where(status: 0) }
205
329
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
206
- klass.scope value_method_name, -> { where(attr => value) }
330
+ klass.scope value_method_name, -> { where(name => value) }
207
331
 
332
+ # scope :not_active, -> { where.not(status: 0) }
208
333
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
209
- klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
334
+ klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
210
335
  end
211
336
  end
212
- end
213
- enum_values.freeze
214
337
  end
215
- end
338
+ private_constant :EnumMethods
216
339
 
217
- private
218
340
  def _enum_methods_module
219
341
  @_enum_methods_module ||= begin
220
- mod = Module.new
342
+ mod = EnumMethods.new(self)
221
343
  include mod
222
344
  mod
223
345
  end
224
346
  end
225
347
 
226
348
  def assert_valid_enum_definition_values(values)
227
- unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
228
- error_message = <<~MSG
229
- Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
230
- MSG
231
- raise ArgumentError, error_message
349
+ case values
350
+ when Hash
351
+ if values.empty?
352
+ raise ArgumentError, "Enum values #{values} must not be empty."
353
+ end
354
+
355
+ if values.keys.any?(&:blank?)
356
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
357
+ end
358
+ when Array
359
+ if values.empty?
360
+ raise ArgumentError, "Enum values #{values} must not be empty."
361
+ end
362
+
363
+ unless values.all?(Symbol) || values.all?(String)
364
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
365
+ end
366
+
367
+ if values.any?(&:blank?)
368
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
369
+ end
370
+ else
371
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
232
372
  end
373
+ end
233
374
 
234
- if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
235
- raise ArgumentError, "Enum label name must not be blank."
375
+ def assert_valid_enum_options(options)
376
+ invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods]
377
+ unless invalid_keys.empty?
378
+ raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate."
236
379
  end
237
380
  end
238
381
 
@@ -247,6 +390,8 @@ module ActiveRecord
247
390
  raise_conflict_error(enum_name, method_name, type: "class")
248
391
  elsif klass_method && method_defined_within?(method_name, Relation)
249
392
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
393
+ elsif klass_method && method_name.to_sym == :id
394
+ raise_conflict_error(enum_name, method_name)
250
395
  elsif !klass_method && dangerous_attribute_method?(method_name)
251
396
  raise_conflict_error(enum_name, method_name)
252
397
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
@@ -264,10 +409,16 @@ module ActiveRecord
264
409
  }
265
410
  end
266
411
 
267
- def detect_negative_condition!(method_name)
268
- if method_name.start_with?("not_") && logger
269
- logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
270
- " This will cause a conflict with auto generated negative scopes."
412
+ def detect_negative_enum_conditions!(method_names)
413
+ return unless logger
414
+
415
+ method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
416
+ inverted_form = potential_not.sub("not_", "")
417
+ if method_names.include?(inverted_form)
418
+ logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
419
+ " This has caused a conflict with auto generated negative scopes." \
420
+ " Avoid using enum elements starting with 'not' where the positive form is also an element."
421
+ end
271
422
  end
272
423
  end
273
424
  end