activerecord 7.0.8.6 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1939
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +56 -14
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+
3
5
  module ActiveRecord
4
6
  module Encryption
5
7
  # A message serializer that serializes +Messages+ with JSON.
@@ -31,6 +33,10 @@ module ActiveRecord
31
33
  JSON.dump message_to_json(message)
32
34
  end
33
35
 
36
+ def binary?
37
+ false
38
+ end
39
+
34
40
  private
35
41
  def parse_message(data, level)
36
42
  validate_message_data_format(data, level)
@@ -16,6 +16,10 @@ module ActiveRecord
16
16
  def encrypted?(text)
17
17
  false
18
18
  end
19
+
20
+ def binary?
21
+ false
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -14,10 +14,10 @@ module ActiveRecord
14
14
  #
15
15
  # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
16
  class Properties
17
- ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
17
+ ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
18
18
 
19
19
  delegate_missing_to :data
20
- delegate :==, to: :data
20
+ delegate :==, :[], :each, :key?, to: :data
21
21
 
22
22
  # For each entry it generates an accessor exposing the full name
23
23
  DEFAULT_PROPERTIES = {
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  end
55
55
 
56
56
  def validate_value_type(value)
57
- unless ALLOWED_VALUE_CLASSES.find { |klass| value.is_a?(klass) }
57
+ unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
58
58
  raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
59
59
  end
60
60
  end
@@ -19,6 +19,10 @@ module ActiveRecord
19
19
  def encrypted?(text)
20
20
  false
21
21
  end
22
+
23
+ def binary?
24
+ false
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  class Scheme
11
11
  attr_accessor :previous_schemes
12
12
 
13
- def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
13
+ def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
14
14
  previous_schemes: nil, **context_properties)
15
15
  # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
16
  # can merge schemes without overriding values with defaults. See +#merge+
@@ -18,6 +18,7 @@ module ActiveRecord
18
18
  @key_provider_param = key_provider
19
19
  @key = key
20
20
  @deterministic = deterministic
21
+ @support_unencrypted_data = support_unencrypted_data
21
22
  @downcase = downcase || ignore_case
22
23
  @ignore_case = ignore_case
23
24
  @previous_schemes_param = previous_schemes
@@ -36,7 +37,11 @@ module ActiveRecord
36
37
  end
37
38
 
38
39
  def deterministic?
39
- @deterministic
40
+ !!@deterministic
41
+ end
42
+
43
+ def support_unencrypted_data?
44
+ @support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
40
45
  end
41
46
 
42
47
  def fixed?
@@ -45,10 +50,7 @@ module ActiveRecord
45
50
  end
46
51
 
47
52
  def key_provider
48
- @key_provider ||= begin
49
- validate_keys!
50
- @key_provider_param || build_key_provider
51
- end
53
+ @key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider
52
54
  end
53
55
 
54
56
  def merge(other_scheme)
@@ -56,7 +58,7 @@ module ActiveRecord
56
58
  end
57
59
 
58
60
  def to_h
59
- { key_provider: @key_provider_param, key: @key, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
61
+ { key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
60
62
  previous_schemes: @previous_schemes_param, **@context_properties }.compact
61
63
  end
62
64
 
@@ -68,31 +70,30 @@ module ActiveRecord
68
70
  end
69
71
  end
70
72
 
73
+ def compatible_with?(other_scheme)
74
+ deterministic? == other_scheme.deterministic?
75
+ end
76
+
71
77
  private
72
78
  def validate_config!
73
79
  raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
74
80
  raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
75
81
  end
76
82
 
77
- def validate_keys!
78
- validate_credential :key_derivation_salt
79
- validate_credential :primary_key, "needs to be configured to use non-deterministic encryption" unless @deterministic
80
- validate_credential :deterministic_key, "needs to be configured to use deterministic encryption" if @deterministic
83
+ def key_provider_from_key
84
+ @key_provider_from_key ||= if @key.present?
85
+ DerivedSecretKeyProvider.new(@key)
86
+ end
81
87
  end
82
88
 
83
- def validate_credential(key, error_message = "is not configured")
84
- unless ActiveRecord::Encryption.config.public_send(key).present?
85
- raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
86
- "active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
89
+ def deterministic_key_provider
90
+ @deterministic_key_provider ||= if @deterministic
91
+ DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
87
92
  end
88
93
  end
89
94
 
90
- def build_key_provider
91
- return DerivedSecretKeyProvider.new(@key) if @key.present?
92
-
93
- if @deterministic && (deterministic_key = ActiveRecord::Encryption.config.deterministic_key)
94
- DeterministicKeyProvider.new(deterministic_key)
95
- end
95
+ def default_key_provider
96
+ ActiveRecord::Encryption.key_provider
96
97
  end
97
98
  end
98
99
  end
@@ -8,6 +8,7 @@ module ActiveRecord
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  eager_autoload do
11
+ autoload :AutoFilteredParameters
11
12
  autoload :Cipher
12
13
  autoload :Config
13
14
  autoload :Configurable
@@ -52,4 +53,6 @@ module ActiveRecord
52
53
  Cipher.eager_load!
53
54
  end
54
55
  end
56
+
57
+ ActiveSupport.run_load_hooks :active_record_encryption, Encryption
55
58
  end
@@ -111,23 +111,70 @@ module ActiveRecord
111
111
  #
112
112
  # conversation.comments_inactive!
113
113
  # conversation.comments_active? # => false
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)
114
165
  module Enum
115
166
  def self.extended(base) # :nodoc:
116
167
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
117
168
  end
118
169
 
119
- def inherited(base) # :nodoc:
120
- base.defined_enums = defined_enums.deep_dup
121
- super
122
- end
123
-
124
170
  class EnumType < Type::Value # :nodoc:
125
171
  delegate :type, to: :subtype
126
172
 
127
- def initialize(name, mapping, subtype)
173
+ def initialize(name, mapping, subtype, raise_on_invalid_values: true)
128
174
  @name = name
129
175
  @mapping = mapping
130
176
  @subtype = subtype
177
+ @_raise_on_invalid_values = raise_on_invalid_values
131
178
  end
132
179
 
133
180
  def cast(value)
@@ -153,6 +200,8 @@ module ActiveRecord
153
200
  end
154
201
 
155
202
  def assert_valid_value(value)
203
+ return unless @_raise_on_invalid_values
204
+
156
205
  unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
157
206
  raise ArgumentError, "'#{value}' is not a valid #{name}"
158
207
  end
@@ -170,15 +219,28 @@ module ActiveRecord
170
219
  return _enum(name, values, **options)
171
220
  end
172
221
 
173
- definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
222
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
174
223
  options.transform_keys! { |key| :"#{key[1..-1]}" }
175
224
 
176
225
  definitions.each { |name, values| _enum(name, values, **options) }
226
+
227
+ ActiveRecord.deprecator.warn(<<~MSG)
228
+ Defining enums with keyword arguments is deprecated and will be removed
229
+ in Rails 8.0. Positional arguments should be used instead:
230
+
231
+ #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
232
+ MSG
177
233
  end
178
234
 
179
235
  private
180
- def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options)
236
+ def inherited(base)
237
+ base.defined_enums = defined_enums.deep_dup
238
+ super
239
+ end
240
+
241
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
181
242
  assert_valid_enum_definition_values(values)
243
+ assert_valid_enum_options(options)
182
244
  # statuses = { }
183
245
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
184
246
  name = name.to_s
@@ -191,9 +253,17 @@ module ActiveRecord
191
253
  detect_enum_conflict!(name, name)
192
254
  detect_enum_conflict!(name, "#{name}=")
193
255
 
194
- attribute(name, **options) do |subtype|
256
+ attribute(name, **options)
257
+
258
+ decorate_attributes([name]) do |_name, subtype|
259
+ if subtype == ActiveModel::Type.default_value
260
+ raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
261
+ " backed by a database column or declared with an explicit type" \
262
+ " via `attribute`."
263
+ end
264
+
195
265
  subtype = subtype.subtype if EnumType === subtype
196
- EnumType.new(name, enum_values, subtype)
266
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
197
267
  end
198
268
 
199
269
  value_method_names = []
@@ -213,18 +283,24 @@ module ActiveRecord
213
283
 
214
284
  value_method_name = "#{prefix}#{label}#{suffix}"
215
285
  value_method_names << value_method_name
216
- define_enum_methods(name, value_method_name, value, scopes)
286
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
217
287
 
218
288
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
219
289
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
220
290
 
221
291
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
222
292
  value_method_names << value_method_alias
223
- define_enum_methods(name, value_method_alias, value, scopes)
293
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
224
294
  end
225
295
  end
226
296
  end
227
297
  detect_negative_enum_conditions!(value_method_names) if scopes
298
+
299
+ if validate
300
+ validate = {} unless Hash === validate
301
+ validates_inclusion_of name, in: enum_values.keys, **validate
302
+ end
303
+
228
304
  enum_values.freeze
229
305
  end
230
306
 
@@ -236,21 +312,23 @@ module ActiveRecord
236
312
  private
237
313
  attr_reader :klass
238
314
 
239
- def define_enum_methods(name, value_method_name, value, scopes)
240
- # def active?() status_for_database == 0 end
241
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
242
- define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
315
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
316
+ if instance_methods
317
+ # def active?() status_for_database == 0 end
318
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
319
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
243
320
 
244
- # def active!() update!(status: 0) end
245
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
246
- define_method("#{value_method_name}!") { update!(name => value) }
321
+ # def active!() update!(status: 0) end
322
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
323
+ define_method("#{value_method_name}!") { update!(name => value) }
324
+ end
247
325
 
248
- # scope :active, -> { where(status: 0) }
249
- # scope :not_active, -> { where.not(status: 0) }
250
326
  if scopes
327
+ # scope :active, -> { where(status: 0) }
251
328
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
252
329
  klass.scope value_method_name, -> { where(name => value) }
253
330
 
331
+ # scope :not_active, -> { where.not(status: 0) }
254
332
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
255
333
  klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
256
334
  end
@@ -267,15 +345,36 @@ module ActiveRecord
267
345
  end
268
346
 
269
347
  def assert_valid_enum_definition_values(values)
270
- unless values.is_a?(Hash) || values.all?(Symbol) || values.all?(String)
271
- error_message = <<~MSG
272
- Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
273
- MSG
274
- raise ArgumentError, error_message
348
+ case values
349
+ when Hash
350
+ if values.empty?
351
+ raise ArgumentError, "Enum values #{values} must not be empty."
352
+ end
353
+
354
+ if values.keys.any?(&:blank?)
355
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
356
+ end
357
+ when Array
358
+ if values.empty?
359
+ raise ArgumentError, "Enum values #{values} must not be empty."
360
+ end
361
+
362
+ unless values.all?(Symbol) || values.all?(String)
363
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
364
+ end
365
+
366
+ if values.any?(&:blank?)
367
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
368
+ end
369
+ else
370
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
275
371
  end
372
+ end
276
373
 
277
- if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
278
- raise ArgumentError, "Enum label name must not be blank."
374
+ def assert_valid_enum_options(options)
375
+ invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods]
376
+ unless invalid_keys.empty?
377
+ raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate."
279
378
  end
280
379
  end
281
380
 
@@ -290,6 +389,8 @@ module ActiveRecord
290
389
  raise_conflict_error(enum_name, method_name, type: "class")
291
390
  elsif klass_method && method_defined_within?(method_name, Relation)
292
391
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
392
+ elsif klass_method && method_name.to_sym == :id
393
+ raise_conflict_error(enum_name, method_name)
293
394
  elsif !klass_method && dangerous_attribute_method?(method_name)
294
395
  raise_conflict_error(enum_name, method_name)
295
396
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)