activerecord 6.1.7 → 7.1.0

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 (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -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 +50 -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 +35 -31
  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.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module"
4
+ require "active_support/core_ext/array"
5
+
6
+ module ActiveRecord
7
+ module Encryption
8
+ extend ActiveSupport::Autoload
9
+
10
+ eager_autoload do
11
+ autoload :AutoFilteredParameters
12
+ autoload :Cipher
13
+ autoload :Config
14
+ autoload :Configurable
15
+ autoload :Context
16
+ autoload :Contexts
17
+ autoload :DerivedSecretKeyProvider
18
+ autoload :EncryptableRecord
19
+ autoload :EncryptedAttributeType
20
+ autoload :EncryptedFixtures
21
+ autoload :EncryptingOnlyEncryptor
22
+ autoload :DeterministicKeyProvider
23
+ autoload :Encryptor
24
+ autoload :EnvelopeEncryptionKeyProvider
25
+ autoload :Errors
26
+ autoload :ExtendedDeterministicQueries
27
+ autoload :ExtendedDeterministicUniquenessValidator
28
+ autoload :Key
29
+ autoload :KeyGenerator
30
+ autoload :KeyProvider
31
+ autoload :Message
32
+ autoload :MessageSerializer
33
+ autoload :NullEncryptor
34
+ autoload :Properties
35
+ autoload :ReadOnlyNullEncryptor
36
+ autoload :Scheme
37
+ end
38
+
39
+ class Cipher
40
+ extend ActiveSupport::Autoload
41
+
42
+ eager_autoload do
43
+ autoload :Aes256Gcm
44
+ end
45
+ end
46
+
47
+ include Configurable
48
+ include Contexts
49
+
50
+ def self.eager_load!
51
+ super
52
+
53
+ Cipher.eager_load!
54
+ end
55
+ end
56
+ end
@@ -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,26 +42,33 @@ module ActiveRecord
41
42
  # Conversation.where(status: [:active, :archived])
42
43
  # Conversation.where.not(status: :active)
43
44
  #
44
- # Defining scopes can be disabled by setting +:_scopes+ to +false+.
45
+ # Defining scopes can be disabled by setting +:scopes+ to +false+.
45
46
  #
46
47
  # class Conversation < ActiveRecord::Base
47
- # enum status: [ :active, :archived ], _scopes: false
48
+ # enum :status, [ :active, :archived ], scopes: false
48
49
  # end
49
50
  #
50
- # You can set the default enum value by setting +:_default+, like:
51
+ # You can set the default enum value by setting +:default+, like:
51
52
  #
52
53
  # class Conversation < ActiveRecord::Base
53
- # enum status: [ :active, :archived ], _default: "active"
54
+ # enum :status, [ :active, :archived ], default: :active
54
55
  # end
55
56
  #
56
57
  # conversation = Conversation.new
57
58
  # conversation.status # => "active"
58
59
  #
59
- # Finally, it's also possible to explicitly map the relation between attribute and
60
+ # It's possible to explicitly map the relation between attribute and
60
61
  # database integer with a hash:
61
62
  #
62
63
  # class Conversation < ActiveRecord::Base
63
- # 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"
64
72
  # end
65
73
  #
66
74
  # Note that when an array is used, the implicit mapping from the values to database
@@ -75,7 +83,7 @@ module ActiveRecord
75
83
  #
76
84
  # In rare circumstances you might need to access the mapping directly.
77
85
  # The mappings are exposed through a class method with the pluralized attribute
78
- # name, which return the mapping in a +HashWithIndifferentAccess+:
86
+ # name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess :
79
87
  #
80
88
  # Conversation.statuses[:active] # => 0
81
89
  # Conversation.statuses["archived"] # => 1
@@ -85,14 +93,14 @@ module ActiveRecord
85
93
  #
86
94
  # Conversation.where("status <> ?", Conversation.statuses[:archived])
87
95
  #
88
- # 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
89
97
  # multiple enums with same values. If the passed value is +true+, the methods
90
98
  # are prefixed/suffixed with the name of the enum. It is also possible to
91
99
  # supply a custom value:
92
100
  #
93
101
  # class Conversation < ActiveRecord::Base
94
- # enum status: [:active, :archived], _suffix: true
95
- # enum comments_status: [:active, :inactive], _prefix: :comments
102
+ # enum :status, [ :active, :archived ], suffix: true
103
+ # enum :comments_status, [ :active, :inactive ], prefix: :comments
96
104
  # end
97
105
  #
98
106
  # With the above example, the bang and predicate methods along with the
@@ -103,24 +111,79 @@ module ActiveRecord
103
111
  #
104
112
  # conversation.comments_inactive!
105
113
  # conversation.comments_active? # => false
106
-
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
+ # If you want the enum value to be validated before saving, use the option +:validate+:
123
+ #
124
+ # class Conversation < ActiveRecord::Base
125
+ # enum :status, [ :active, :archived ], validate: true
126
+ # end
127
+ #
128
+ # conversation = Conversation.new
129
+ #
130
+ # conversation.status = :unknown
131
+ # conversation.valid? # => false
132
+ #
133
+ # conversation.status = nil
134
+ # conversation.valid? # => false
135
+ #
136
+ # conversation.status = :active
137
+ # conversation.valid? # => true
138
+ #
139
+ # It is also possible to pass additional validation options:
140
+ #
141
+ # class Conversation < ActiveRecord::Base
142
+ # enum :status, [ :active, :archived ], validate: { allow_nil: true }
143
+ # end
144
+ #
145
+ # conversation = Conversation.new
146
+ #
147
+ # conversation.status = :unknown
148
+ # conversation.valid? # => false
149
+ #
150
+ # conversation.status = nil
151
+ # conversation.valid? # => true
152
+ #
153
+ # conversation.status = :active
154
+ # conversation.valid? # => true
155
+ #
156
+ # Otherwise +ArgumentError+ will raise:
157
+ #
158
+ # class Conversation < ActiveRecord::Base
159
+ # enum :status, [ :active, :archived ]
160
+ # end
161
+ #
162
+ # conversation = Conversation.new
163
+ #
164
+ # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
107
165
  module Enum
108
166
  def self.extended(base) # :nodoc:
109
167
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
110
168
  end
111
169
 
112
- def inherited(base) # :nodoc:
113
- base.defined_enums = defined_enums.deep_dup
114
- super
170
+ def load_schema! # :nodoc:
171
+ attributes_to_define_after_schema_loads.each do |name, (cast_type, _default)|
172
+ unless columns_hash.key?(name)
173
+ cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
174
+ raise "Unknown enum attribute '#{name}' for #{self.name}" if Enum::EnumType === cast_type
175
+ end
176
+ end
115
177
  end
116
178
 
117
179
  class EnumType < Type::Value # :nodoc:
118
180
  delegate :type, to: :subtype
119
181
 
120
- def initialize(name, mapping, subtype)
182
+ def initialize(name, mapping, subtype, raise_on_invalid_values: true)
121
183
  @name = name
122
184
  @mapping = mapping
123
185
  @subtype = subtype
186
+ @_raise_on_invalid_values = raise_on_invalid_values
124
187
  end
125
188
 
126
189
  def cast(value)
@@ -128,10 +191,8 @@ module ActiveRecord
128
191
  value.to_s
129
192
  elsif mapping.has_value?(value)
130
193
  mapping.key(value)
131
- elsif value.blank?
132
- nil
133
194
  else
134
- assert_valid_value(value)
195
+ value.presence
135
196
  end
136
197
  end
137
198
 
@@ -140,10 +201,16 @@ module ActiveRecord
140
201
  end
141
202
 
142
203
  def serialize(value)
143
- mapping.fetch(value, value)
204
+ subtype.serialize(mapping.fetch(value, value))
205
+ end
206
+
207
+ def serializable?(value, &block)
208
+ subtype.serializable?(mapping.fetch(value, value), &block)
144
209
  end
145
210
 
146
211
  def assert_valid_value(value)
212
+ return unless @_raise_on_invalid_values
213
+
147
214
  unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
148
215
  raise ArgumentError, "'#{value}' is not a valid #{name}"
149
216
  end
@@ -155,15 +222,25 @@ module ActiveRecord
155
222
  attr_reader :name, :mapping
156
223
  end
157
224
 
158
- def enum(definitions)
159
- enum_prefix = definitions.delete(:_prefix)
160
- enum_suffix = definitions.delete(:_suffix)
161
- enum_scopes = definitions.delete(:_scopes)
225
+ def enum(name = nil, values = nil, **options)
226
+ if name
227
+ values, options = options, {} unless values
228
+ return _enum(name, values, **options)
229
+ end
230
+
231
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
232
+ options.transform_keys! { |key| :"#{key[1..-1]}" }
233
+
234
+ definitions.each { |name, values| _enum(name, values, **options) }
235
+ end
162
236
 
163
- default = {}
164
- default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
237
+ private
238
+ def inherited(base)
239
+ base.defined_enums = defined_enums.deep_dup
240
+ super
241
+ end
165
242
 
166
- definitions.each do |name, values|
243
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
167
244
  assert_valid_enum_definition_values(values)
168
245
  # statuses = { }
169
246
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -177,24 +254,19 @@ module ActiveRecord
177
254
  detect_enum_conflict!(name, name)
178
255
  detect_enum_conflict!(name, "#{name}=")
179
256
 
180
- attr = attribute_alias?(name) ? attribute_alias(name) : name
181
-
182
- decorate_attribute_type(attr, **default) do |subtype|
183
- EnumType.new(attr, enum_values, subtype)
257
+ attribute(name, **options) do |subtype|
258
+ subtype = subtype.subtype if EnumType === subtype
259
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
184
260
  end
185
261
 
186
262
  value_method_names = []
187
263
  _enum_methods_module.module_eval do
188
- prefix = if enum_prefix == true
189
- "#{name}_"
190
- elsif enum_prefix
191
- "#{enum_prefix}_"
264
+ prefix = if prefix
265
+ prefix == true ? "#{name}_" : "#{prefix}_"
192
266
  end
193
267
 
194
- suffix = if enum_suffix == true
195
- "_#{name}"
196
- elsif enum_suffix
197
- "_#{enum_suffix}"
268
+ suffix = if suffix
269
+ suffix == true ? "_#{name}" : "_#{suffix}"
198
270
  end
199
271
 
200
272
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
@@ -204,23 +276,27 @@ module ActiveRecord
204
276
 
205
277
  value_method_name = "#{prefix}#{label}#{suffix}"
206
278
  value_method_names << value_method_name
207
- define_enum_methods(name, value_method_name, value, enum_scopes)
279
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
208
280
 
209
281
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
210
282
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
211
283
 
212
284
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
213
285
  value_method_names << value_method_alias
214
- define_enum_methods(name, value_method_alias, value, enum_scopes)
286
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
215
287
  end
216
288
  end
217
289
  end
218
- detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
290
+ detect_negative_enum_conditions!(value_method_names) if scopes
291
+
292
+ if validate
293
+ validate = {} unless Hash === validate
294
+ validates_inclusion_of name, in: enum_values.keys, **validate
295
+ end
296
+
219
297
  enum_values.freeze
220
298
  end
221
- end
222
299
 
223
- private
224
300
  class EnumMethods < Module # :nodoc:
225
301
  def initialize(klass)
226
302
  @klass = klass
@@ -229,21 +305,23 @@ module ActiveRecord
229
305
  private
230
306
  attr_reader :klass
231
307
 
232
- def define_enum_methods(name, value_method_name, value, enum_scopes)
233
- # def active?() status_for_database == 0 end
234
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
235
- define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
308
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
309
+ if instance_methods
310
+ # def active?() status_for_database == 0 end
311
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
312
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
236
313
 
237
- # def active!() update!(status: 0) end
238
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
239
- define_method("#{value_method_name}!") { update!(name => value) }
314
+ # def active!() update!(status: 0) end
315
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
316
+ define_method("#{value_method_name}!") { update!(name => value) }
317
+ end
240
318
 
241
- # scope :active, -> { where(status: 0) }
242
- # scope :not_active, -> { where.not(status: 0) }
243
- if enum_scopes != false
319
+ if scopes
320
+ # scope :active, -> { where(status: 0) }
244
321
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
245
322
  klass.scope value_method_name, -> { where(name => value) }
246
323
 
324
+ # scope :not_active, -> { where.not(status: 0) }
247
325
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
248
326
  klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
249
327
  end
@@ -260,15 +338,29 @@ module ActiveRecord
260
338
  end
261
339
 
262
340
  def assert_valid_enum_definition_values(values)
263
- unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
264
- error_message = <<~MSG
265
- Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
266
- MSG
267
- raise ArgumentError, error_message
268
- end
341
+ case values
342
+ when Hash
343
+ if values.empty?
344
+ raise ArgumentError, "Enum values #{values} must not be empty."
345
+ end
269
346
 
270
- if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
271
- raise ArgumentError, "Enum label name must not be blank."
347
+ if values.keys.any?(&:blank?)
348
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
349
+ end
350
+ when Array
351
+ if values.empty?
352
+ raise ArgumentError, "Enum values #{values} must not be empty."
353
+ end
354
+
355
+ unless values.all?(Symbol) || values.all?(String)
356
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
357
+ end
358
+
359
+ if values.any?(&:blank?)
360
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
361
+ end
362
+ else
363
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
272
364
  end
273
365
  end
274
366
 
@@ -283,6 +375,8 @@ module ActiveRecord
283
375
  raise_conflict_error(enum_name, method_name, type: "class")
284
376
  elsif klass_method && method_defined_within?(method_name, Relation)
285
377
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
378
+ elsif klass_method && method_name.to_sym == :id
379
+ raise_conflict_error(enum_name, method_name)
286
380
  elsif !klass_method && dangerous_attribute_method?(method_name)
287
381
  raise_conflict_error(enum_name, method_name)
288
382
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)