activerecord 7.0.8.7 → 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 -1944
  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 +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -7,15 +7,69 @@ require "active_record/database_configurations/url_config"
7
7
  require "active_record/database_configurations/connection_url_resolver"
8
8
 
9
9
  module ActiveRecord
10
- # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
11
- # objects (either a HashConfig or UrlConfig) that are constructed from the
12
- # application's database configuration hash or URL string.
10
+ # = Active Record Database Configurations
11
+ #
12
+ # +ActiveRecord::DatabaseConfigurations+ returns an array of +DatabaseConfig+
13
+ # objects that are constructed from the application's database
14
+ # configuration hash or URL string.
15
+ #
16
+ # The array of +DatabaseConfig+ objects in an application default to either a
17
+ # HashConfig or UrlConfig. You can retrieve your application's config by using
18
+ # ActiveRecord::Base.configurations.
19
+ #
20
+ # If you register a custom handler, objects will be created according to the
21
+ # conditions of the handler. See ::register_db_config_handler for more on
22
+ # registering custom handlers.
13
23
  class DatabaseConfigurations
14
24
  class InvalidConfigurationError < StandardError; end
15
25
 
16
26
  attr_reader :configurations
17
27
  delegate :any?, to: :configurations
18
28
 
29
+ singleton_class.attr_accessor :db_config_handlers # :nodoc:
30
+ self.db_config_handlers = [] # :nodoc:
31
+
32
+ # Allows an application to register a custom handler for database configuration
33
+ # objects. This is useful for creating a custom handler that responds to
34
+ # methods your application needs but Active Record doesn't implement. For
35
+ # example if you are using Vitess, you may want your Vitess configurations
36
+ # to respond to `sharded?`. To implement this define the following in an
37
+ # initializer:
38
+ #
39
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
+ # next unless config.key?(:vitess)
41
+ # VitessConfig.new(env_name, name, config)
42
+ # end
43
+ #
44
+ # Note: applications must handle the condition in which custom config should be
45
+ # created in your handler registration otherwise all objects will use the custom
46
+ # handler.
47
+ #
48
+ # Then define your +VitessConfig+ to respond to the methods your application
49
+ # needs. It is recommended that you inherit from one of the existing
50
+ # database config classes to avoid having to reimplement all methods. Custom
51
+ # config handlers should only implement methods Active Record does not.
52
+ #
53
+ # class VitessConfig < ActiveRecord::DatabaseConfigurations::UrlConfig
54
+ # def sharded?
55
+ # configuration_hash.fetch("sharded", false)
56
+ # end
57
+ # end
58
+ #
59
+ # For configs that have a +:vitess+ key, a +VitessConfig+ object will be
60
+ # created instead of a +UrlConfig+.
61
+ def self.register_db_config_handler(&block)
62
+ db_config_handlers << block
63
+ end
64
+
65
+ register_db_config_handler do |env_name, name, url, config|
66
+ if url
67
+ UrlConfig.new(env_name, name, url, config)
68
+ else
69
+ HashConfig.new(env_name, name, config)
70
+ end
71
+ end
72
+
19
73
  def initialize(configurations = {})
20
74
  @configurations = build_configs(configurations)
21
75
  end
@@ -23,8 +77,8 @@ module ActiveRecord
23
77
  # Collects the configs for the environment and optionally the specification
24
78
  # name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
25
79
  #
26
- # If a name is provided a single DatabaseConfig object will be
27
- # returned, otherwise an array of DatabaseConfig objects will be
80
+ # If a name is provided a single +DatabaseConfig+ object will be
81
+ # returned, otherwise an array of +DatabaseConfig+ objects will be
28
82
  # returned that corresponds with the environment and type requested.
29
83
  #
30
84
  # ==== Options
@@ -34,20 +88,14 @@ module ActiveRecord
34
88
  # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
35
89
  # to +nil+. If no +env_name+ is specified the config for the default env and the
36
90
  # passed +name+ will be returned.
37
- # * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
38
- # the returned list. Most of the time we're only iterating over the write
39
- # connection (i.e. migrations don't need to run for the write and read connection).
40
- # Defaults to +false+.
91
+ # * <tt>config_key:</tt> Selects configs that contain a particular key in the configuration
92
+ # hash. Useful for selecting configs that use a custom db config handler or finding
93
+ # configs with hashes that contain a particular key.
41
94
  # * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
42
- # hidden by +database_tasks: false+ in the returned list. Most of the time we're only
95
+ # hidden by <tt>database_tasks: false</tt> in the returned list. Most of the time we're only
43
96
  # iterating over the primary connections (i.e. migrations don't need to run for the
44
97
  # write and read connection). Defaults to +false+.
45
- def configs_for(env_name: nil, name: nil, include_replicas: false, include_hidden: false)
46
- if include_replicas
47
- include_hidden = include_replicas
48
- ActiveSupport::Deprecation.warn("The kwarg `include_replicas` is deprecated in favor of `include_hidden`. When `include_hidden` is passed, configurations with `replica: true` or `database_tasks: false` will be returned. `include_replicas` will be removed in Rails 7.1.")
49
- end
50
-
98
+ def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false)
51
99
  env_name ||= default_env if name
52
100
  configs = env_with_configs(env_name)
53
101
 
@@ -57,26 +105,32 @@ module ActiveRecord
57
105
  end
58
106
  end
59
107
 
108
+ if config_key
109
+ configs = configs.select do |db_config|
110
+ db_config.configuration_hash.key?(config_key)
111
+ end
112
+ end
113
+
60
114
  if name
61
115
  configs.find do |db_config|
62
- db_config.name == name
116
+ db_config.name == name.to_s
63
117
  end
64
118
  else
65
119
  configs
66
120
  end
67
121
  end
68
122
 
69
- # Returns a single DatabaseConfig object based on the requested environment.
123
+ # Returns a single +DatabaseConfig+ object based on the requested environment.
70
124
  #
71
125
  # If the application has multiple databases +find_db_config+ will return
72
- # the first DatabaseConfig for the environment.
126
+ # the first +DatabaseConfig+ for the environment.
73
127
  def find_db_config(env)
74
- configurations
75
- .sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] }
76
- .find do |db_config|
77
- db_config.env_name == env.to_s ||
78
- (db_config.for_current_env? && db_config.name == env.to_s)
79
- end
128
+ env = env.to_s
129
+ configurations.find do |db_config|
130
+ db_config.for_current_env? && (db_config.env_name == env || db_config.name == env)
131
+ end || configurations.find do |db_config|
132
+ db_config.env_name == env
133
+ end
80
134
  end
81
135
 
82
136
  # A primary configuration is one that is named primary or if there is
@@ -93,8 +147,6 @@ module ActiveRecord
93
147
  end
94
148
 
95
149
  # Checks if the application's configurations are empty.
96
- #
97
- # Aliased to blank?
98
150
  def empty?
99
151
  configurations.empty?
100
152
  end
@@ -219,15 +271,16 @@ module ActiveRecord
219
271
  end
220
272
 
221
273
  def build_db_config_from_hash(env_name, name, config)
222
- if config.has_key?(:url)
223
- url = config[:url]
224
- config_without_url = config.dup
225
- config_without_url.delete :url
274
+ url = config[:url]
275
+ config_without_url = config.dup
276
+ config_without_url.delete :url
226
277
 
227
- UrlConfig.new(env_name, name, url, config_without_url)
228
- else
229
- HashConfig.new(env_name, name, config)
278
+ DatabaseConfigurations.db_config_handlers.reverse_each do |handler|
279
+ config = handler.call(env_name, name, url, config_without_url)
280
+ return config if config
230
281
  end
282
+
283
+ nil
231
284
  end
232
285
 
233
286
  def merge_db_environment_variables(current_env, configs)
@@ -3,7 +3,7 @@
3
3
  require "active_support/core_ext/string/inquiry"
4
4
 
5
5
  module ActiveRecord
6
- # == Delegated types
6
+ # = Delegated types
7
7
  #
8
8
  # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
9
9
  # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  #
37
37
  # Let's look at that entry/message/comment example using delegated types:
38
38
  #
39
- # # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
39
+ # # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
40
40
  # class Entry < ApplicationRecord
41
41
  # belongs_to :account
42
42
  # belongs_to :creator
@@ -51,12 +51,12 @@ module ActiveRecord
51
51
  # end
52
52
  # end
53
53
  #
54
- # # Schema: messages[ id, subject, body ]
54
+ # # Schema: messages[ id, subject, body, created_at, updated_at ]
55
55
  # class Message < ApplicationRecord
56
56
  # include Entryable
57
57
  # end
58
58
  #
59
- # # Schema: comments[ id, content ]
59
+ # # Schema: comments[ id, content, created_at, updated_at ]
60
60
  # class Comment < ApplicationRecord
61
61
  # include Entryable
62
62
  # end
@@ -102,17 +102,37 @@ module ActiveRecord
102
102
  # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
103
103
  # like so:
104
104
  #
105
- # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
105
+ # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
106
106
  #
107
107
  # If you need more complicated composition, or you need to perform dependent validation, you should build a factory
108
108
  # method or class to take care of the complicated needs. This could be as simple as:
109
109
  #
110
110
  # class Entry < ApplicationRecord
111
- # def self.create_with_comment(content, creator: Current.user)
112
- # create! entryable: Comment.new(content: content), creator: creator
111
+ # def self.create_with_comment(content, creator: Current.user, account: Current.account)
112
+ # create! entryable: Comment.new(content: content), creator: creator, account: account
113
113
  # end
114
114
  # end
115
115
  #
116
+ # == Querying across records
117
+ #
118
+ # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
119
+ # tricky, but not impossible.
120
+ #
121
+ # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
122
+ # in appropriate places:
123
+ #
124
+ # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
125
+ #
126
+ # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
127
+ # the method:
128
+ #
129
+ # # app/models/concerns/entryable.rb
130
+ # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
131
+ #
132
+ # Now the query can be shortened significantly:
133
+ #
134
+ # Comment.where(content: 'Hello!').with_entry(creator: Current.user)
135
+ #
116
136
  # == Adding further delegation
117
137
  #
118
138
  # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
@@ -138,7 +158,7 @@ module ActiveRecord
138
158
  #
139
159
  # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
140
160
  #
141
- # == Nested Attributes
161
+ # == Nested \Attributes
142
162
  #
143
163
  # Enabling nested attributes on a delegated_type association allows you to
144
164
  # create the entry and message in one go:
@@ -192,6 +212,11 @@ module ActiveRecord
192
212
  # +role+ with an "_id" suffix. So a class that defines a
193
213
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
194
214
  # the default <tt>:foreign_key</tt>.
215
+ # [:foreign_type]
216
+ # Specify the column used to store the associated object's type. By default this is inferred to be the passed
217
+ # +role+ with a "_type" suffix. A class that defines a
218
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
219
+ # the default <tt>:foreign_type</tt>.
195
220
  # [:primary_key]
196
221
  # Specify the method that returns the primary key of associated object used for the convenience methods.
197
222
  # By default this is +id+.
@@ -211,11 +236,15 @@ module ActiveRecord
211
236
  private
212
237
  def define_delegated_type_methods(role, types:, options:)
213
238
  primary_key = options[:primary_key] || "id"
214
- role_type = "#{role}_type"
239
+ role_type = options[:foreign_type] || "#{role}_type"
215
240
  role_id = options[:foreign_key] || "#{role}_id"
216
241
 
242
+ define_singleton_method "#{role}_types" do
243
+ types.map(&:to_s)
244
+ end
245
+
217
246
  define_method "#{role}_class" do
218
- public_send("#{role}_type").constantize
247
+ public_send(role_type).constantize
219
248
  end
220
249
 
221
250
  define_method "#{role}_name" do
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  class DestroyAssociationAsyncError < StandardError
5
5
  end
6
6
 
7
+ # = Active Record Destroy Association Async Job
8
+ #
7
9
  # Job to destroy the records associated with a destroyed record in background.
8
10
  class DestroyAssociationAsyncJob < ActiveJob::Base
9
11
  queue_as { ActiveRecord.queues[:destroy] }
@@ -17,7 +19,7 @@ module ActiveRecord
17
19
  )
18
20
  association_model = association_class.constantize
19
21
  owner_class = owner_model_name.constantize
20
- owner = owner_class.find_by(owner_class.primary_key.to_sym => owner_id)
22
+ owner = owner_class.find_by(owner_class.primary_key => [owner_id])
21
23
 
22
24
  if !owner_destroyed?(owner, ensuring_owner_was_method)
23
25
  raise DestroyAssociationAsyncError, "owner record not destroyed"
@@ -12,12 +12,12 @@ module ActiveRecord
12
12
  end
13
13
  end
14
14
 
15
- def method_missing(name, *arguments, &block)
15
+ def method_missing(name, ...)
16
16
  match = Method.match(self, name)
17
17
 
18
18
  if match && match.valid?
19
19
  match.define
20
- send(name, *arguments, &block)
20
+ send(name, ...)
21
21
  else
22
22
  super
23
23
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ class AutoFilteredParameters
6
+ def initialize(app)
7
+ @app = app
8
+ @attributes_by_class = Concurrent::Map.new
9
+ @collecting = true
10
+
11
+ install_collecting_hook
12
+ end
13
+
14
+ def enable
15
+ apply_collected_attributes
16
+ @collecting = false
17
+ end
18
+
19
+ private
20
+ attr_reader :app
21
+
22
+ def install_collecting_hook
23
+ ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute|
24
+ attribute_was_declared(klass, attribute)
25
+ end
26
+ end
27
+
28
+ def attribute_was_declared(klass, attribute)
29
+ if collecting?
30
+ collect_for_later(klass, attribute)
31
+ else
32
+ apply_filter(klass, attribute)
33
+ end
34
+ end
35
+
36
+ def apply_collected_attributes
37
+ @attributes_by_class.each do |klass, attributes|
38
+ attributes.each do |attribute|
39
+ apply_filter(klass, attribute)
40
+ end
41
+ end
42
+ end
43
+
44
+ def collecting?
45
+ @collecting
46
+ end
47
+
48
+ def collect_for_later(klass, attribute)
49
+ @attributes_by_class[klass] ||= Concurrent::Array.new
50
+ @attributes_by_class[klass] << attribute
51
+ end
52
+
53
+ def apply_filter(klass, attribute)
54
+ filter = [("#{klass.model_name.element}" if klass.name), attribute.to_s].compact.join(".")
55
+ unless excluded_from_filter_parameters?(filter)
56
+ app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
57
+ klass.filter_attributes += [ attribute ]
58
+ end
59
+ end
60
+
61
+ def excluded_from_filter_parameters?(filter_parameter)
62
+ ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "openssl"
4
- require "base64"
5
4
 
6
5
  module ActiveRecord
7
6
  module Encryption
@@ -80,6 +79,10 @@ module ActiveRecord
80
79
  raise ActiveRecord::Encryption::Errors::Decryption
81
80
  end
82
81
 
82
+ def inspect # :nodoc:
83
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
84
+ end
85
+
83
86
  private
84
87
  def generate_iv(cipher, clear_text)
85
88
  if @deterministic
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
4
+
3
5
  module ActiveRecord
4
6
  module Encryption
5
7
  # Container of configuration options
6
8
  class Config
7
- attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt,
9
+ attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
8
10
  :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
9
11
  :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
10
12
 
@@ -21,6 +23,27 @@ module ActiveRecord
21
23
  end
22
24
  end
23
25
 
26
+ def support_sha1_for_non_deterministic_encryption=(value)
27
+ if value && has_primary_key?
28
+ sha1_key_generator = ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: OpenSSL::Digest::SHA1)
29
+ sha1_key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key, key_generator: sha1_key_generator)
30
+ add_previous_scheme key_provider: sha1_key_provider
31
+ end
32
+ end
33
+
34
+ %w(key_derivation_salt primary_key deterministic_key).each do |key|
35
+ silence_redefinition_of_method "has_#{key}?"
36
+ define_method("has_#{key}?") do
37
+ instance_variable_get(:"@#{key}").presence
38
+ end
39
+
40
+ silence_redefinition_of_method key
41
+ define_method(key) do
42
+ public_send("has_#{key}?") or
43
+ raise Errors::Configuration, "Missing Active Record encryption credential: active_record_encryption.#{key}"
44
+ end
45
+ end
46
+
24
47
  private
25
48
  def set_defaults
26
49
  self.store_key_references = false
@@ -31,6 +54,7 @@ module ActiveRecord
31
54
  self.excluded_from_filter_parameters = []
32
55
  self.previous_schemes = []
33
56
  self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
57
+ self.hash_digest_class = OpenSSL::Digest::SHA1
34
58
 
35
59
  # TODO: Setting to false for now as the implementation is a bit experimental
36
60
  self.extend_queries = false
@@ -17,24 +17,29 @@ module ActiveRecord
17
17
  delegate name, to: :context
18
18
  end
19
19
 
20
- def configure(primary_key:, deterministic_key:, key_derivation_salt:, **properties) # :nodoc:
20
+ def configure(primary_key: nil, deterministic_key: nil, key_derivation_salt: nil, **properties) # :nodoc:
21
21
  config.primary_key = primary_key
22
22
  config.deterministic_key = deterministic_key
23
23
  config.key_derivation_salt = key_derivation_salt
24
24
 
25
- context.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key)
25
+ # Set the default for this property here instead of in +Config#set_defaults+ as this needs
26
+ # to happen *after* the keys have been set.
27
+ properties[:support_sha1_for_non_deterministic_encryption] = true if properties[:support_sha1_for_non_deterministic_encryption].nil?
26
28
 
27
29
  properties.each do |name, value|
28
- [:context, :config].each do |configurable_object_name|
29
- configurable_object = ActiveRecord::Encryption.send(configurable_object_name)
30
- configurable_object.send "#{name}=", value if configurable_object.respond_to?("#{name}=")
31
- end
30
+ ActiveRecord::Encryption.config.send "#{name}=", value if ActiveRecord::Encryption.config.respond_to?("#{name}=")
31
+ end
32
+
33
+ ActiveRecord::Encryption.reset_default_context
34
+
35
+ properties.each do |name, value|
36
+ ActiveRecord::Encryption.context.send "#{name}=", value if ActiveRecord::Encryption.context.respond_to?("#{name}=")
32
37
  end
33
38
  end
34
39
 
35
40
  # Register callback to be invoked when an encrypted attribute is declared.
36
41
  #
37
- # === Example:
42
+ # === Example
38
43
  #
39
44
  # ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
40
45
  # ...
@@ -49,18 +54,6 @@ module ActiveRecord
49
54
  block.call(klass, name)
50
55
  end
51
56
  end
52
-
53
- def install_auto_filtered_parameters_hook(application) # :nodoc:
54
- ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
55
- filter_parameter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
56
- application.config.filter_parameters << filter_parameter unless excluded_from_filter_parameters?(filter_parameter)
57
- end
58
- end
59
-
60
- private
61
- def excluded_from_filter_parameters?(filter_parameter)
62
- ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
63
- end
64
57
  end
65
58
  end
66
59
  end
@@ -12,9 +12,7 @@ module ActiveRecord
12
12
  class Context
13
13
  PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
14
14
 
15
- PROPERTIES.each do |name|
16
- attr_accessor name
17
- end
15
+ attr_accessor(*PROPERTIES)
18
16
 
19
17
  def initialize
20
18
  set_defaults
@@ -22,6 +20,11 @@ module ActiveRecord
22
20
 
23
21
  alias frozen_encryption? frozen_encryption
24
22
 
23
+ silence_redefinition_of_method :key_provider
24
+ def key_provider
25
+ @key_provider ||= build_default_key_provider
26
+ end
27
+
25
28
  private
26
29
  def set_defaults
27
30
  self.frozen_encryption = false
@@ -30,6 +33,10 @@ module ActiveRecord
30
33
  self.encryptor = ActiveRecord::Encryption::Encryptor.new
31
34
  self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
32
35
  end
36
+
37
+ def build_default_key_provider
38
+ ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
39
+ end
33
40
  end
34
41
  end
35
42
  end
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  extend ActiveSupport::Concern
15
15
 
16
16
  included do
17
- mattr_reader :default_context, default: Context.new
17
+ mattr_accessor :default_context, default: Context.new
18
18
  thread_mattr_accessor :custom_contexts
19
19
  end
20
20
 
@@ -66,6 +66,10 @@ module ActiveRecord
66
66
  def current_custom_context
67
67
  self.custom_contexts&.last
68
68
  end
69
+
70
+ def reset_default_context
71
+ self.default_context = Context.new
72
+ end
69
73
  end
70
74
  end
71
75
  end
@@ -4,9 +4,15 @@ module ActiveRecord
4
4
  module Encryption
5
5
  # A KeyProvider that derives keys from passwords.
6
6
  class DerivedSecretKeyProvider < KeyProvider
7
- def initialize(passwords)
8
- super(Array(passwords).collect { |password| Key.derive_from(password) })
7
+ def initialize(passwords, key_generator: ActiveRecord::Encryption.key_generator)
8
+ super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) })
9
9
  end
10
+
11
+ private
12
+ def derive_key_from(password, using: key_generator)
13
+ secret = using.derive_key_from(password)
14
+ ActiveRecord::Encryption::Key.new(secret)
15
+ end
10
16
  end
11
17
  end
12
18
  end