activerecord 6.1.6 → 7.0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1314 -975
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +124 -95
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +10 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +124 -134
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +67 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +50 -43
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +20 -23
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +1 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +228 -60
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +276 -67
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +189 -88
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +17 -12
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +60 -13
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +7 -2
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +16 -9
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +217 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +55 -11
@@ -15,15 +15,6 @@ module ActiveRecord
15
15
  @name = name
16
16
  end
17
17
 
18
- def spec_name
19
- @name
20
- end
21
- deprecate spec_name: "please use name instead"
22
-
23
- def config
24
- raise NotImplementedError
25
- end
26
-
27
18
  def adapter_method
28
19
  "#{adapter}_connection"
29
20
  end
@@ -48,6 +39,18 @@ module ActiveRecord
48
39
  raise NotImplementedError
49
40
  end
50
41
 
42
+ def min_threads
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def max_threads
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def max_queue
51
+ raise NotImplementedError
52
+ end
53
+
51
54
  def checkout_timeout
52
55
  raise NotImplementedError
53
56
  end
@@ -26,16 +26,12 @@ module ActiveRecord
26
26
  # connections.
27
27
  class HashConfig < DatabaseConfig
28
28
  attr_reader :configuration_hash
29
+
29
30
  def initialize(env_name, name, configuration_hash)
30
31
  super(env_name, name)
31
32
  @configuration_hash = configuration_hash.symbolize_keys.freeze
32
33
  end
33
34
 
34
- def config
35
- ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfig#configuration_hash which returns a hash with symbol keys")
36
- configuration_hash.stringify_keys
37
- end
38
-
39
35
  # Determines whether a database configuration is for a replica / readonly
40
36
  # connection. If the +replica+ key is present in the config, +replica?+ will
41
37
  # return +true+.
@@ -54,6 +50,10 @@ module ActiveRecord
54
50
  configuration_hash[:host]
55
51
  end
56
52
 
53
+ def socket # :nodoc:
54
+ configuration_hash[:socket]
55
+ end
56
+
57
57
  def database
58
58
  configuration_hash[:database]
59
59
  end
@@ -66,6 +66,18 @@ module ActiveRecord
66
66
  (configuration_hash[:pool] || 5).to_i
67
67
  end
68
68
 
69
+ def min_threads
70
+ (configuration_hash[:min_threads] || 0).to_i
71
+ end
72
+
73
+ def max_threads
74
+ (configuration_hash[:max_threads] || pool).to_i
75
+ end
76
+
77
+ def max_queue
78
+ max_threads * 4
79
+ end
80
+
69
81
  def checkout_timeout
70
82
  (configuration_hash[:checkout_timeout] || 5).to_f
71
83
  end
@@ -91,6 +103,52 @@ module ActiveRecord
91
103
  def schema_cache_path
92
104
  configuration_hash[:schema_cache_path]
93
105
  end
106
+
107
+ def default_schema_cache_path
108
+ "db/schema_cache.yml"
109
+ end
110
+
111
+ def lazy_schema_cache_path
112
+ schema_cache_path || default_schema_cache_path
113
+ end
114
+
115
+ def primary? # :nodoc:
116
+ Base.configurations.primary?(name)
117
+ end
118
+
119
+ # Determines whether to dump the schema/structure files and the
120
+ # filename that should be used.
121
+ #
122
+ # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
123
+ # the schema will not be dumped.
124
+ #
125
+ # If the config option is set that will be used. Otherwise Rails
126
+ # will generate the filename from the database config name.
127
+ def schema_dump(format = ActiveRecord.schema_format)
128
+ if configuration_hash.key?(:schema_dump)
129
+ if config = configuration_hash[:schema_dump]
130
+ config
131
+ end
132
+ elsif primary?
133
+ schema_file_type(format)
134
+ else
135
+ "#{name}_#{schema_file_type(format)}"
136
+ end
137
+ end
138
+
139
+ def database_tasks? # :nodoc:
140
+ !replica? && !!configuration_hash.fetch(:database_tasks, true)
141
+ end
142
+
143
+ private
144
+ def schema_file_type(format)
145
+ case format
146
+ when :ruby
147
+ "schema.rb"
148
+ when :sql
149
+ "structure.sql"
150
+ end
151
+ end
94
152
  end
95
153
  end
96
154
  end
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  #
20
20
  # ==== Options
21
21
  #
22
- # * <tt>:env_name</tt> - The Rails environment, ie "development".
22
+ # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
23
23
  # * <tt>:name</tt> - The db config name. In a standard two-tier
24
24
  # database configuration this will default to "primary". In a multiple
25
25
  # database three-tier database configuration this corresponds to the name
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  # Return a Hash that can be merged into the main config that represents
43
43
  # the passed in url
44
44
  def build_url_hash
45
- if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) }
45
+ if url.nil? || url.start_with?("jdbc:", "http:", "https:")
46
46
  { url: url }
47
47
  else
48
48
  ConnectionUrlResolver.new(url).to_hash
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  end
22
22
 
23
23
  # Collects the configs for the environment and optionally the specification
24
- # name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
24
+ # name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
25
25
  #
26
26
  # If a name is provided a single DatabaseConfig object will be
27
27
  # returned, otherwise an array of DatabaseConfig objects will be
@@ -34,22 +34,26 @@ module ActiveRecord
34
34
  # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
35
35
  # to +nil+. If no +env_name+ is specified the config for the default env and the
36
36
  # passed +name+ will be returned.
37
- # * <tt>include_replicas:</tt> Determines whether to include replicas in
37
+ # * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
38
38
  # the returned list. Most of the time we're only iterating over the write
39
39
  # connection (i.e. migrations don't need to run for the write and read connection).
40
40
  # Defaults to +false+.
41
- def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
42
- if spec_name
43
- name = spec_name
44
- ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
41
+ # * <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
43
+ # iterating over the primary connections (i.e. migrations don't need to run for the
44
+ # 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.")
45
49
  end
46
50
 
47
51
  env_name ||= default_env if name
48
52
  configs = env_with_configs(env_name)
49
53
 
50
- unless include_replicas
54
+ unless include_hidden
51
55
  configs = configs.select do |db_config|
52
- !db_config.replica?
56
+ db_config.database_tasks?
53
57
  end
54
58
  end
55
59
 
@@ -62,19 +66,6 @@ module ActiveRecord
62
66
  end
63
67
  end
64
68
 
65
- # Returns the config hash that corresponds with the environment
66
- #
67
- # If the application has multiple databases +default_hash+ will
68
- # return the first config hash for the environment.
69
- #
70
- # { database: "my_db", adapter: "mysql2" }
71
- def default_hash(env = default_env)
72
- default = find_db_config(env)
73
- default.configuration_hash if default
74
- end
75
- alias :[] :default_hash
76
- deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
77
-
78
69
  # Returns a single DatabaseConfig object based on the requested environment.
79
70
  #
80
71
  # If the application has multiple databases +find_db_config+ will return
@@ -101,14 +92,6 @@ module ActiveRecord
101
92
  first_config && name == first_config.name
102
93
  end
103
94
 
104
- # Returns the DatabaseConfigurations object as a Hash.
105
- def to_h
106
- configurations.inject({}) do |memo, db_config|
107
- memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
108
- end
109
- end
110
- deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
111
-
112
95
  # Checks if the application's configurations are empty.
113
96
  #
114
97
  # Aliased to blank?
@@ -167,7 +150,7 @@ module ActiveRecord
167
150
  return configs if configs.is_a?(Array)
168
151
 
169
152
  db_configs = configs.flat_map do |env_name, config|
170
- if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
153
+ if config.is_a?(Hash) && config.values.all?(Hash)
171
154
  walk_configs(env_name.to_s, config)
172
155
  else
173
156
  build_db_config_from_raw_config(env_name.to_s, "primary", config)
@@ -194,7 +177,7 @@ module ActiveRecord
194
177
  raise AdapterNotSpecified, <<~MSG
195
178
  The `#{name}` database is not configured for the `#{default_env}` environment.
196
179
 
197
- Available databases configurations are:
180
+ Available database configurations are:
198
181
 
199
182
  #{build_configuration_sentence}
200
183
  MSG
@@ -202,7 +185,7 @@ module ActiveRecord
202
185
  end
203
186
 
204
187
  def build_configuration_sentence
205
- configs = configs_for(include_replicas: true)
188
+ configs = configs_for(include_hidden: true)
206
189
 
207
190
  configs.group_by(&:env_name).map do |env, config|
208
191
  names = config.map(&:name)
@@ -51,10 +51,9 @@ module ActiveRecord
51
51
  # end
52
52
  # end
53
53
  #
54
- # # Schema: messages[ id, subject ]
54
+ # # Schema: messages[ id, subject, body ]
55
55
  # class Message < ApplicationRecord
56
56
  # include Entryable
57
- # has_rich_text :content
58
57
  # end
59
58
  #
60
59
  # # Schema: comments[ id, content ]
@@ -66,7 +65,7 @@ module ActiveRecord
66
65
  # resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
67
66
  # in particular. You can now easily do things like:
68
67
  #
69
- # Account.entries.order(created_at: :desc).limit(50)
68
+ # Account.find(1).entries.order(created_at: :desc).limit(50)
70
69
  #
71
70
  # Which is exactly what you want when displaying both comments and messages together. The entry itself can
72
71
  # be rendered as its delegated type easily, like so:
@@ -76,7 +75,9 @@ module ActiveRecord
76
75
  #
77
76
  # # entries/entryables/_message.html.erb
78
77
  # <div class="message">
79
- # Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
78
+ # <div class="subject"><%= entry.message.subject %></div>
79
+ # <p><%= entry.message.body %></p>
80
+ # <i>Posted on <%= entry.created_at %> by <%= entry.creator.name %></i>
80
81
  # </div>
81
82
  #
82
83
  # # entries/entryables/_comment.html.erb
@@ -135,7 +136,22 @@ module ActiveRecord
135
136
  # end
136
137
  # end
137
138
  #
138
- # Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
139
+ # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
140
+ #
141
+ # == Nested Attributes
142
+ #
143
+ # Enabling nested attributes on a delegated_type association allows you to
144
+ # create the entry and message in one go:
145
+ #
146
+ # class Entry < ApplicationRecord
147
+ # delegated_type :entryable, types: %w[ Message Comment ]
148
+ # accepts_nested_attributes_for :entryable
149
+ # end
150
+ #
151
+ # params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
152
+ # entry = Entry.create(params[:entry])
153
+ # entry.entryable.id # => 2
154
+ # entry.entryable.subject # => 'Smiling'
139
155
  module DelegatedType
140
156
  # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
141
157
  # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
@@ -156,8 +172,6 @@ module ActiveRecord
156
172
  # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
157
173
  # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
158
174
  #
159
- # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
160
- #
161
175
  # You can also declare namespaced types:
162
176
  #
163
177
  # class Entry < ApplicationRecord
@@ -167,15 +181,38 @@ module ActiveRecord
167
181
  # Entry.access_notice_messages
168
182
  # entry.access_notice_message
169
183
  # entry.access_notice_message?
184
+ #
185
+ # === Options
186
+ #
187
+ # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
188
+ # The following options can be included to specialize the behavior of the delegated type convenience methods.
189
+ #
190
+ # [:foreign_key]
191
+ # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
192
+ # +role+ with an "_id" suffix. So a class that defines a
193
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
194
+ # the default <tt>:foreign_key</tt>.
195
+ # [:primary_key]
196
+ # Specify the method that returns the primary key of associated object used for the convenience methods.
197
+ # By default this is +id+.
198
+ #
199
+ # Option examples:
200
+ # class Entry < ApplicationRecord
201
+ # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
202
+ # end
203
+ #
204
+ # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
205
+ # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
170
206
  def delegated_type(role, types:, **options)
171
207
  belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
172
- define_delegated_type_methods role, types: types
208
+ define_delegated_type_methods role, types: types, options: options
173
209
  end
174
210
 
175
211
  private
176
- def define_delegated_type_methods(role, types:)
212
+ def define_delegated_type_methods(role, types:, options:)
213
+ primary_key = options[:primary_key] || "id"
177
214
  role_type = "#{role}_type"
178
- role_id = "#{role}_id"
215
+ role_id = options[:foreign_key] || "#{role}_id"
179
216
 
180
217
  define_method "#{role}_class" do
181
218
  public_send("#{role}_type").constantize
@@ -185,8 +222,12 @@ module ActiveRecord
185
222
  public_send("#{role}_class").model_name.singular.inquiry
186
223
  end
187
224
 
225
+ define_method "build_#{role}" do |*params|
226
+ public_send("#{role}=", public_send("#{role}_class").new(*params))
227
+ end
228
+
188
229
  types.each do |type|
189
- scope_name = type.tableize.gsub("/", "_")
230
+ scope_name = type.tableize.tr("/", "_")
190
231
  singular = scope_name.singularize
191
232
  query = "#{singular}?"
192
233
 
@@ -200,7 +241,7 @@ module ActiveRecord
200
241
  public_send(role) if public_send(query)
201
242
  end
202
243
 
203
- define_method "#{singular}_id" do
244
+ define_method "#{singular}_#{primary_key}" do
204
245
  public_send(role_id) if public_send(query)
205
246
  end
206
247
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
 
7
7
  # Job to destroy the records associated with a destroyed record in background.
8
8
  class DestroyAssociationAsyncJob < ActiveJob::Base
9
- queue_as { ActiveRecord::Base.queues[:destroy] }
9
+ queue_as { ActiveRecord.queues[:destroy] }
10
10
 
11
11
  discard_on ActiveJob::DeserializationError
12
12
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DisableJoinsAssociationRelation < Relation # :nodoc:
5
+ attr_reader :ids, :key
6
+
7
+ def initialize(klass, key, ids)
8
+ @ids = ids.uniq
9
+ @key = key
10
+ super(klass)
11
+ end
12
+
13
+ def limit(value)
14
+ records.take(value)
15
+ end
16
+
17
+ def first(limit = nil)
18
+ if limit
19
+ records.limit(limit).first
20
+ else
21
+ records.first
22
+ end
23
+ end
24
+
25
+ def load
26
+ super
27
+ records = @records
28
+
29
+ records_by_id = records.group_by do |record|
30
+ record[key]
31
+ end
32
+
33
+ records = ids.flat_map { |id| records_by_id[id.to_i] }
34
+ records.compact!
35
+
36
+ @records = records
37
+ end
38
+ end
39
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- module DynamicMatchers #:nodoc:
4
+ module DynamicMatchers # :nodoc:
5
5
  private
6
6
  def respond_to_missing?(name, _)
7
7
  if self == Base
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+
6
+ module ActiveRecord
7
+ module Encryption
8
+ class Cipher
9
+ # A 256-GCM cipher.
10
+ #
11
+ # By default it will use random initialization vectors. For deterministic encryption, it will use a SHA-256 hash of
12
+ # the text to encrypt and the secret.
13
+ #
14
+ # See +Encryptor+
15
+ class Aes256Gcm
16
+ CIPHER_TYPE = "aes-256-gcm"
17
+
18
+ class << self
19
+ def key_length
20
+ OpenSSL::Cipher.new(CIPHER_TYPE).key_len
21
+ end
22
+
23
+ def iv_length
24
+ OpenSSL::Cipher.new(CIPHER_TYPE).iv_len
25
+ end
26
+ end
27
+
28
+ # When iv not provided, it will generate a random iv on each encryption operation (default and
29
+ # recommended operation)
30
+ def initialize(secret, deterministic: false)
31
+ @secret = secret
32
+ @deterministic = deterministic
33
+ end
34
+
35
+ def encrypt(clear_text)
36
+ # This code is extracted from +ActiveSupport::MessageEncryptor+. Not using it directly because we want to control
37
+ # the message format and only serialize things once at the +ActiveRecord::Encryption::Message+ level. Also, this
38
+ # cipher is prepared to deal with deterministic/non deterministic encryption modes.
39
+
40
+ cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
41
+ cipher.encrypt
42
+ cipher.key = @secret
43
+
44
+ iv = generate_iv(cipher, clear_text)
45
+ cipher.iv = iv
46
+
47
+ encrypted_data = clear_text.empty? ? clear_text.dup : cipher.update(clear_text)
48
+ encrypted_data << cipher.final
49
+
50
+ ActiveRecord::Encryption::Message.new(payload: encrypted_data).tap do |message|
51
+ message.headers.iv = iv
52
+ message.headers.auth_tag = cipher.auth_tag
53
+ end
54
+ end
55
+
56
+ def decrypt(encrypted_message)
57
+ encrypted_data = encrypted_message.payload
58
+ iv = encrypted_message.headers.iv
59
+ auth_tag = encrypted_message.headers.auth_tag
60
+
61
+ # Currently the OpenSSL bindings do not raise an error if auth_tag is
62
+ # truncated, which would allow an attacker to easily forge it. See
63
+ # https://github.com/ruby/openssl/issues/63
64
+ raise ActiveRecord::Encryption::Errors::EncryptedContentIntegrity if auth_tag.nil? || auth_tag.bytes.length != 16
65
+
66
+ cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
67
+
68
+ cipher.decrypt
69
+ cipher.key = @secret
70
+ cipher.iv = iv
71
+
72
+ cipher.auth_tag = auth_tag
73
+ cipher.auth_data = ""
74
+
75
+ decrypted_data = encrypted_data.empty? ? encrypted_data : cipher.update(encrypted_data)
76
+ decrypted_data << cipher.final
77
+
78
+ decrypted_data
79
+ rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
80
+ raise ActiveRecord::Encryption::Errors::Decryption
81
+ end
82
+
83
+ private
84
+ def generate_iv(cipher, clear_text)
85
+ if @deterministic
86
+ generate_deterministic_iv(clear_text)
87
+ else
88
+ cipher.random_iv
89
+ end
90
+ end
91
+
92
+ def generate_deterministic_iv(clear_text)
93
+ OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret, clear_text)[0, ActiveRecord::Encryption.cipher.iv_length]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # The algorithm used for encrypting and decrypting +Message+ objects.
6
+ #
7
+ # It uses AES-256-GCM. It will generate a random IV for non deterministic encryption (default)
8
+ # or derive an initialization vector from the encrypted content for deterministic encryption.
9
+ #
10
+ # See +Cipher::Aes256Gcm+.
11
+ class Cipher
12
+ DEFAULT_ENCODING = Encoding::UTF_8
13
+
14
+ # Encrypts the provided text and return an encrypted +Message+.
15
+ def encrypt(clean_text, key:, deterministic: false)
16
+ cipher_for(key, deterministic: deterministic).encrypt(clean_text).tap do |message|
17
+ message.headers.encoding = clean_text.encoding.name unless clean_text.encoding == DEFAULT_ENCODING
18
+ end
19
+ end
20
+
21
+ # Decrypt the provided +Message+.
22
+ #
23
+ # When +key+ is an Array, it will try all the keys raising a
24
+ # +ActiveRecord::Encryption::Errors::Decryption+ if none works.
25
+ def decrypt(encrypted_message, key:)
26
+ try_to_decrypt_with_each(encrypted_message, keys: Array(key)).tap do |decrypted_text|
27
+ decrypted_text.force_encoding(encrypted_message.headers.encoding || DEFAULT_ENCODING)
28
+ end
29
+ end
30
+
31
+ def key_length
32
+ Aes256Gcm.key_length
33
+ end
34
+
35
+ def iv_length
36
+ Aes256Gcm.iv_length
37
+ end
38
+
39
+ private
40
+ def try_to_decrypt_with_each(encrypted_text, keys:)
41
+ keys.each.with_index do |key, index|
42
+ return cipher_for(key).decrypt(encrypted_text)
43
+ rescue ActiveRecord::Encryption::Errors::Decryption
44
+ raise if index == keys.length - 1
45
+ end
46
+ end
47
+
48
+ def cipher_for(secret, deterministic: false)
49
+ Aes256Gcm.new(secret, deterministic: deterministic)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Container of configuration options
6
+ class Config
7
+ attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt,
8
+ :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
9
+ :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
10
+
11
+ def initialize
12
+ set_defaults
13
+ end
14
+
15
+ # Configure previous encryption schemes.
16
+ #
17
+ # config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
18
+ def previous=(previous_schemes_properties)
19
+ previous_schemes_properties.each do |properties|
20
+ add_previous_scheme(**properties)
21
+ end
22
+ end
23
+
24
+ private
25
+ def set_defaults
26
+ self.store_key_references = false
27
+ self.support_unencrypted_data = false
28
+ self.encrypt_fixtures = false
29
+ self.validate_column_size = true
30
+ self.add_to_filter_parameters = true
31
+ self.excluded_from_filter_parameters = []
32
+ self.previous_schemes = []
33
+ self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
34
+
35
+ # TODO: Setting to false for now as the implementation is a bit experimental
36
+ self.extend_queries = false
37
+ end
38
+
39
+ def add_previous_scheme(**properties)
40
+ previous_schemes << ActiveRecord::Encryption::Scheme.new(**properties)
41
+ end
42
+ end
43
+ end
44
+ end