activerecord 6.1.7 → 7.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # This is a wrapper for a hash of encryption properties. It is used by
6
+ # +Key+ (public tags) and +Message+ (headers).
7
+ #
8
+ # Since properties are serialized in messages, it is important for storage
9
+ # efficiency to keep their keys as short as possible. It defines accessors
10
+ # for common properties that will keep these keys very short while exposing
11
+ # a readable name.
12
+ #
13
+ # message.headers.encrypted_data_key # instead of message.headers[:k]
14
+ #
15
+ # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
+ class Properties
17
+ ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
18
+
19
+ delegate_missing_to :data
20
+ delegate :==, :[], :each, :key?, to: :data
21
+
22
+ # For each entry it generates an accessor exposing the full name
23
+ DEFAULT_PROPERTIES = {
24
+ encrypted_data_key: "k",
25
+ encrypted_data_key_id: "i",
26
+ compressed: "c",
27
+ iv: "iv",
28
+ auth_tag: "at",
29
+ encoding: "e"
30
+ }
31
+
32
+ DEFAULT_PROPERTIES.each do |name, key|
33
+ define_method name do
34
+ self[key.to_sym]
35
+ end
36
+
37
+ define_method "#{name}=" do |value|
38
+ self[key.to_sym] = value
39
+ end
40
+ end
41
+
42
+ def initialize(initial_properties = {})
43
+ @data = {}
44
+ add(initial_properties)
45
+ end
46
+
47
+ # Set a value for a given key
48
+ #
49
+ # It will raise an +EncryptedContentIntegrity+ if the value exists
50
+ def []=(key, value)
51
+ raise Errors::EncryptedContentIntegrity, "Properties can't be overridden: #{key}" if key?(key)
52
+ validate_value_type(value)
53
+ data[key] = value
54
+ end
55
+
56
+ def validate_value_type(value)
57
+ unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
58
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
59
+ end
60
+ end
61
+
62
+ def add(other_properties)
63
+ other_properties.each do |key, value|
64
+ self[key.to_sym] = value
65
+ end
66
+ end
67
+
68
+ def to_h
69
+ data
70
+ end
71
+
72
+ private
73
+ attr_reader :data
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A +NullEncryptor+ that will raise an error when trying to encrypt data
6
+ #
7
+ # This is useful when you want to reveal ciphertexts for debugging purposes
8
+ # and you want to make sure you won't overwrite any encryptable attribute with
9
+ # the wrong content.
10
+ class ReadOnlyNullEncryptor
11
+ def encrypt(clean_text, key_provider: nil, cipher_options: {})
12
+ raise Errors::Encryption, "This encryptor is read-only"
13
+ end
14
+
15
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
16
+ encrypted_text
17
+ end
18
+
19
+ def encrypted?(text)
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A container of attribute encryption options.
6
+ #
7
+ # It validates and serves attribute encryption options.
8
+ #
9
+ # See EncryptedAttributeType, Context
10
+ class Scheme
11
+ attr_accessor :previous_schemes
12
+
13
+ def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
14
+ previous_schemes: nil, **context_properties)
15
+ # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
+ # can merge schemes without overriding values with defaults. See +#merge+
17
+
18
+ @key_provider_param = key_provider
19
+ @key = key
20
+ @deterministic = deterministic
21
+ @support_unencrypted_data = support_unencrypted_data
22
+ @downcase = downcase || ignore_case
23
+ @ignore_case = ignore_case
24
+ @previous_schemes_param = previous_schemes
25
+ @previous_schemes = Array.wrap(previous_schemes)
26
+ @context_properties = context_properties
27
+
28
+ validate_config!
29
+ end
30
+
31
+ def ignore_case?
32
+ @ignore_case
33
+ end
34
+
35
+ def downcase?
36
+ @downcase
37
+ end
38
+
39
+ def 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
45
+ end
46
+
47
+ def fixed?
48
+ # by default deterministic encryption is fixed
49
+ @fixed ||= @deterministic && (!@deterministic.is_a?(Hash) || @deterministic[:fixed])
50
+ end
51
+
52
+ def key_provider
53
+ @key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider
54
+ end
55
+
56
+ def merge(other_scheme)
57
+ self.class.new(**to_h.merge(other_scheme.to_h))
58
+ end
59
+
60
+ def to_h
61
+ { key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
62
+ previous_schemes: @previous_schemes_param, **@context_properties }.compact
63
+ end
64
+
65
+ def with_context(&block)
66
+ if @context_properties.present?
67
+ ActiveRecord::Encryption.with_encryption_context(**@context_properties, &block)
68
+ else
69
+ block.call
70
+ end
71
+ end
72
+
73
+ def compatible_with?(other_scheme)
74
+ deterministic? == other_scheme.deterministic?
75
+ end
76
+
77
+ private
78
+ def validate_config!
79
+ raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
80
+ raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
81
+ end
82
+
83
+ def key_provider_from_key
84
+ @key_provider_from_key ||= if @key.present?
85
+ DerivedSecretKeyProvider.new(@key)
86
+ end
87
+ end
88
+
89
+ def deterministic_key_provider
90
+ @deterministic_key_provider ||= if @deterministic
91
+ DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
92
+ end
93
+ end
94
+
95
+ def default_key_provider
96
+ ActiveRecord::Encryption.key_provider
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,58 @@
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
+
57
+ ActiveSupport.run_load_hooks :active_record_encryption, Encryption
58
+ 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,70 @@ 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
115
- end
116
-
117
170
  class EnumType < Type::Value # :nodoc:
118
171
  delegate :type, to: :subtype
119
172
 
120
- def initialize(name, mapping, subtype)
173
+ def initialize(name, mapping, subtype, raise_on_invalid_values: true)
121
174
  @name = name
122
175
  @mapping = mapping
123
176
  @subtype = subtype
177
+ @_raise_on_invalid_values = raise_on_invalid_values
124
178
  end
125
179
 
126
180
  def cast(value)
@@ -128,10 +182,8 @@ module ActiveRecord
128
182
  value.to_s
129
183
  elsif mapping.has_value?(value)
130
184
  mapping.key(value)
131
- elsif value.blank?
132
- nil
133
185
  else
134
- assert_valid_value(value)
186
+ value.presence
135
187
  end
136
188
  end
137
189
 
@@ -140,10 +192,16 @@ module ActiveRecord
140
192
  end
141
193
 
142
194
  def serialize(value)
143
- mapping.fetch(value, value)
195
+ subtype.serialize(mapping.fetch(value, value))
196
+ end
197
+
198
+ def serializable?(value, &block)
199
+ subtype.serializable?(mapping.fetch(value, value), &block)
144
200
  end
145
201
 
146
202
  def assert_valid_value(value)
203
+ return unless @_raise_on_invalid_values
204
+
147
205
  unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
148
206
  raise ArgumentError, "'#{value}' is not a valid #{name}"
149
207
  end
@@ -155,15 +213,25 @@ module ActiveRecord
155
213
  attr_reader :name, :mapping
156
214
  end
157
215
 
158
- def enum(definitions)
159
- enum_prefix = definitions.delete(:_prefix)
160
- enum_suffix = definitions.delete(:_suffix)
161
- enum_scopes = definitions.delete(:_scopes)
216
+ def enum(name = nil, values = nil, **options)
217
+ if name
218
+ values, options = options, {} unless values
219
+ return _enum(name, values, **options)
220
+ end
221
+
222
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
223
+ options.transform_keys! { |key| :"#{key[1..-1]}" }
224
+
225
+ definitions.each { |name, values| _enum(name, values, **options) }
226
+ end
162
227
 
163
- default = {}
164
- default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
228
+ private
229
+ def inherited(base)
230
+ base.defined_enums = defined_enums.deep_dup
231
+ super
232
+ end
165
233
 
166
- definitions.each do |name, values|
234
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
167
235
  assert_valid_enum_definition_values(values)
168
236
  # statuses = { }
169
237
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -177,24 +245,25 @@ module ActiveRecord
177
245
  detect_enum_conflict!(name, name)
178
246
  detect_enum_conflict!(name, "#{name}=")
179
247
 
180
- attr = attribute_alias?(name) ? attribute_alias(name) : name
248
+ attribute(name, **options) do |subtype|
249
+ if subtype == ActiveModel::Type.default_value
250
+ raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
251
+ " backed by a database column or declared with an explicit type" \
252
+ " via `attribute`."
253
+ end
181
254
 
182
- decorate_attribute_type(attr, **default) do |subtype|
183
- EnumType.new(attr, enum_values, subtype)
255
+ subtype = subtype.subtype if EnumType === subtype
256
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
184
257
  end
185
258
 
186
259
  value_method_names = []
187
260
  _enum_methods_module.module_eval do
188
- prefix = if enum_prefix == true
189
- "#{name}_"
190
- elsif enum_prefix
191
- "#{enum_prefix}_"
261
+ prefix = if prefix
262
+ prefix == true ? "#{name}_" : "#{prefix}_"
192
263
  end
193
264
 
194
- suffix = if enum_suffix == true
195
- "_#{name}"
196
- elsif enum_suffix
197
- "_#{enum_suffix}"
265
+ suffix = if suffix
266
+ suffix == true ? "_#{name}" : "_#{suffix}"
198
267
  end
199
268
 
200
269
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
@@ -204,23 +273,27 @@ module ActiveRecord
204
273
 
205
274
  value_method_name = "#{prefix}#{label}#{suffix}"
206
275
  value_method_names << value_method_name
207
- define_enum_methods(name, value_method_name, value, enum_scopes)
276
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
208
277
 
209
278
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
210
279
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
211
280
 
212
281
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
213
282
  value_method_names << value_method_alias
214
- define_enum_methods(name, value_method_alias, value, enum_scopes)
283
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
215
284
  end
216
285
  end
217
286
  end
218
- detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
287
+ detect_negative_enum_conditions!(value_method_names) if scopes
288
+
289
+ if validate
290
+ validate = {} unless Hash === validate
291
+ validates_inclusion_of name, in: enum_values.keys, **validate
292
+ end
293
+
219
294
  enum_values.freeze
220
295
  end
221
- end
222
296
 
223
- private
224
297
  class EnumMethods < Module # :nodoc:
225
298
  def initialize(klass)
226
299
  @klass = klass
@@ -229,21 +302,23 @@ module ActiveRecord
229
302
  private
230
303
  attr_reader :klass
231
304
 
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 }
305
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
306
+ if instance_methods
307
+ # def active?() status_for_database == 0 end
308
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
309
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
236
310
 
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) }
311
+ # def active!() update!(status: 0) end
312
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
313
+ define_method("#{value_method_name}!") { update!(name => value) }
314
+ end
240
315
 
241
- # scope :active, -> { where(status: 0) }
242
- # scope :not_active, -> { where.not(status: 0) }
243
- if enum_scopes != false
316
+ if scopes
317
+ # scope :active, -> { where(status: 0) }
244
318
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
245
319
  klass.scope value_method_name, -> { where(name => value) }
246
320
 
321
+ # scope :not_active, -> { where.not(status: 0) }
247
322
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
248
323
  klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
249
324
  end
@@ -260,15 +335,29 @@ module ActiveRecord
260
335
  end
261
336
 
262
337
  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
338
+ case values
339
+ when Hash
340
+ if values.empty?
341
+ raise ArgumentError, "Enum values #{values} must not be empty."
342
+ end
269
343
 
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."
344
+ if values.keys.any?(&:blank?)
345
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
346
+ end
347
+ when Array
348
+ if values.empty?
349
+ raise ArgumentError, "Enum values #{values} must not be empty."
350
+ end
351
+
352
+ unless values.all?(Symbol) || values.all?(String)
353
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
354
+ end
355
+
356
+ if values.any?(&:blank?)
357
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
358
+ end
359
+ else
360
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
272
361
  end
273
362
  end
274
363
 
@@ -283,6 +372,8 @@ module ActiveRecord
283
372
  raise_conflict_error(enum_name, method_name, type: "class")
284
373
  elsif klass_method && method_defined_within?(method_name, Relation)
285
374
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
375
+ elsif klass_method && method_name.to_sym == :id
376
+ raise_conflict_error(enum_name, method_name)
286
377
  elsif !klass_method && dangerous_attribute_method?(method_name)
287
378
  raise_conflict_error(enum_name, method_name)
288
379
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)