activerecord 6.1.4.1 → 7.0.0.rc2

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 (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1050 -981
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +187 -55
  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 +90 -82
  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 +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +13 -14
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +6 -21
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  48. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  51. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +34 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +69 -18
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  61. data/lib/active_record/connection_adapters/column.rb +4 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +35 -23
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +4 -1
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +27 -16
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +205 -105
  84. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  85. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  86. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  87. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  88. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  89. data/lib/active_record/connection_adapters.rb +6 -5
  90. data/lib/active_record/connection_handling.rb +47 -53
  91. data/lib/active_record/core.rb +122 -132
  92. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  93. data/lib/active_record/database_configurations/database_config.rb +12 -9
  94. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  95. data/lib/active_record/database_configurations/url_config.rb +2 -2
  96. data/lib/active_record/database_configurations.rb +16 -32
  97. data/lib/active_record/delegated_type.rb +52 -11
  98. data/lib/active_record/destroy_association_async_job.rb +1 -1
  99. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  102. data/lib/active_record/encryption/cipher.rb +53 -0
  103. data/lib/active_record/encryption/config.rb +44 -0
  104. data/lib/active_record/encryption/configurable.rb +61 -0
  105. data/lib/active_record/encryption/context.rb +35 -0
  106. data/lib/active_record/encryption/contexts.rb +72 -0
  107. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  108. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  109. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  111. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  112. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  113. data/lib/active_record/encryption/encryptor.rb +155 -0
  114. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  115. data/lib/active_record/encryption/errors.rb +15 -0
  116. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  117. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  118. data/lib/active_record/encryption/key.rb +28 -0
  119. data/lib/active_record/encryption/key_generator.rb +42 -0
  120. data/lib/active_record/encryption/key_provider.rb +46 -0
  121. data/lib/active_record/encryption/message.rb +33 -0
  122. data/lib/active_record/encryption/message_serializer.rb +90 -0
  123. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  124. data/lib/active_record/encryption/properties.rb +76 -0
  125. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  126. data/lib/active_record/encryption/scheme.rb +99 -0
  127. data/lib/active_record/encryption.rb +55 -0
  128. data/lib/active_record/enum.rb +49 -42
  129. data/lib/active_record/errors.rb +67 -4
  130. data/lib/active_record/explain_registry.rb +11 -6
  131. data/lib/active_record/fixture_set/file.rb +15 -1
  132. data/lib/active_record/fixture_set/table_row.rb +41 -6
  133. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  134. data/lib/active_record/fixtures.rb +17 -20
  135. data/lib/active_record/future_result.rb +139 -0
  136. data/lib/active_record/gem_version.rb +4 -4
  137. data/lib/active_record/inheritance.rb +55 -17
  138. data/lib/active_record/insert_all.rb +80 -14
  139. data/lib/active_record/integration.rb +4 -3
  140. data/lib/active_record/internal_metadata.rb +3 -5
  141. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  142. data/lib/active_record/locking/optimistic.rb +10 -9
  143. data/lib/active_record/locking/pessimistic.rb +9 -3
  144. data/lib/active_record/log_subscriber.rb +14 -3
  145. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  146. data/lib/active_record/middleware/database_selector.rb +8 -3
  147. data/lib/active_record/middleware/shard_selector.rb +60 -0
  148. data/lib/active_record/migration/command_recorder.rb +4 -4
  149. data/lib/active_record/migration/compatibility.rb +83 -1
  150. data/lib/active_record/migration/join_table.rb +1 -1
  151. data/lib/active_record/migration.rb +109 -79
  152. data/lib/active_record/model_schema.rb +45 -58
  153. data/lib/active_record/nested_attributes.rb +13 -12
  154. data/lib/active_record/no_touching.rb +3 -3
  155. data/lib/active_record/null_relation.rb +2 -6
  156. data/lib/active_record/persistence.rb +219 -52
  157. data/lib/active_record/query_cache.rb +2 -2
  158. data/lib/active_record/query_logs.rb +138 -0
  159. data/lib/active_record/querying.rb +15 -5
  160. data/lib/active_record/railtie.rb +127 -17
  161. data/lib/active_record/railties/controller_runtime.rb +1 -1
  162. data/lib/active_record/railties/databases.rake +66 -129
  163. data/lib/active_record/readonly_attributes.rb +11 -0
  164. data/lib/active_record/reflection.rb +67 -50
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  166. data/lib/active_record/relation/batches.rb +3 -3
  167. data/lib/active_record/relation/calculations.rb +40 -36
  168. data/lib/active_record/relation/delegation.rb +6 -6
  169. data/lib/active_record/relation/finder_methods.rb +31 -35
  170. data/lib/active_record/relation/merger.rb +20 -13
  171. data/lib/active_record/relation/predicate_builder.rb +1 -6
  172. data/lib/active_record/relation/query_attribute.rb +5 -11
  173. data/lib/active_record/relation/query_methods.rb +235 -61
  174. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  175. data/lib/active_record/relation/spawn_methods.rb +2 -2
  176. data/lib/active_record/relation/where_clause.rb +10 -19
  177. data/lib/active_record/relation.rb +171 -84
  178. data/lib/active_record/result.rb +17 -7
  179. data/lib/active_record/runtime_registry.rb +9 -13
  180. data/lib/active_record/sanitization.rb +11 -7
  181. data/lib/active_record/schema_dumper.rb +10 -3
  182. data/lib/active_record/schema_migration.rb +0 -4
  183. data/lib/active_record/scoping/default.rb +61 -12
  184. data/lib/active_record/scoping/named.rb +3 -11
  185. data/lib/active_record/scoping.rb +64 -34
  186. data/lib/active_record/serialization.rb +1 -1
  187. data/lib/active_record/signed_id.rb +1 -1
  188. data/lib/active_record/suppressor.rb +11 -15
  189. data/lib/active_record/tasks/database_tasks.rb +116 -58
  190. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  191. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  192. data/lib/active_record/test_databases.rb +1 -1
  193. data/lib/active_record/test_fixtures.rb +4 -4
  194. data/lib/active_record/timestamp.rb +3 -4
  195. data/lib/active_record/transactions.rb +9 -14
  196. data/lib/active_record/translation.rb +2 -2
  197. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  198. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  199. data/lib/active_record/type/internal/timezone.rb +2 -2
  200. data/lib/active_record/type/serialized.rb +1 -1
  201. data/lib/active_record/type/type_map.rb +17 -20
  202. data/lib/active_record/type.rb +1 -2
  203. data/lib/active_record/validations/associated.rb +1 -1
  204. data/lib/active_record/validations/uniqueness.rb +1 -1
  205. data/lib/active_record.rb +204 -28
  206. data/lib/arel/attributes/attribute.rb +0 -8
  207. data/lib/arel/crud.rb +28 -22
  208. data/lib/arel/delete_manager.rb +18 -4
  209. data/lib/arel/filter_predications.rb +9 -0
  210. data/lib/arel/insert_manager.rb +2 -3
  211. data/lib/arel/nodes/casted.rb +1 -1
  212. data/lib/arel/nodes/delete_statement.rb +12 -13
  213. data/lib/arel/nodes/filter.rb +10 -0
  214. data/lib/arel/nodes/function.rb +1 -0
  215. data/lib/arel/nodes/insert_statement.rb +2 -2
  216. data/lib/arel/nodes/select_core.rb +2 -2
  217. data/lib/arel/nodes/select_statement.rb +2 -2
  218. data/lib/arel/nodes/update_statement.rb +8 -3
  219. data/lib/arel/nodes.rb +1 -0
  220. data/lib/arel/predications.rb +11 -3
  221. data/lib/arel/select_manager.rb +10 -4
  222. data/lib/arel/table.rb +0 -1
  223. data/lib/arel/tree_manager.rb +0 -12
  224. data/lib/arel/update_manager.rb +18 -4
  225. data/lib/arel/visitors/dot.rb +80 -90
  226. data/lib/arel/visitors/mysql.rb +8 -2
  227. data/lib/arel/visitors/postgresql.rb +0 -10
  228. data/lib/arel/visitors/to_sql.rb +58 -2
  229. data/lib/arel.rb +2 -1
  230. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  231. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  232. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  233. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  234. metadata +55 -12
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "active_record/database_configurations/database_config"
4
5
  require "active_record/database_configurations/hash_config"
5
6
  require "active_record/database_configurations/url_config"
@@ -20,7 +21,7 @@ module ActiveRecord
20
21
  end
21
22
 
22
23
  # Collects the configs for the environment and optionally the specification
23
- # 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>.
24
25
  #
25
26
  # If a name is provided a single DatabaseConfig object will be
26
27
  # returned, otherwise an array of DatabaseConfig objects will be
@@ -33,22 +34,26 @@ module ActiveRecord
33
34
  # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
34
35
  # to +nil+. If no +env_name+ is specified the config for the default env and the
35
36
  # passed +name+ will be returned.
36
- # * <tt>include_replicas:</tt> Determines whether to include replicas in
37
+ # * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
37
38
  # the returned list. Most of the time we're only iterating over the write
38
39
  # connection (i.e. migrations don't need to run for the write and read connection).
39
40
  # Defaults to +false+.
40
- def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
41
- if spec_name
42
- name = spec_name
43
- ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 6.2")
41
+ # * <tt>include_hidden:</tte 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.")
44
49
  end
45
50
 
46
51
  env_name ||= default_env if name
47
52
  configs = env_with_configs(env_name)
48
53
 
49
- unless include_replicas
54
+ unless include_hidden
50
55
  configs = configs.select do |db_config|
51
- !db_config.replica?
56
+ db_config.database_tasks?
52
57
  end
53
58
  end
54
59
 
@@ -61,19 +66,6 @@ module ActiveRecord
61
66
  end
62
67
  end
63
68
 
64
- # Returns the config hash that corresponds with the environment
65
- #
66
- # If the application has multiple databases +default_hash+ will
67
- # return the first config hash for the environment.
68
- #
69
- # { database: "my_db", adapter: "mysql2" }
70
- def default_hash(env = default_env)
71
- default = find_db_config(env)
72
- default.configuration_hash if default
73
- end
74
- alias :[] :default_hash
75
- deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
76
-
77
69
  # Returns a single DatabaseConfig object based on the requested environment.
78
70
  #
79
71
  # If the application has multiple databases +find_db_config+ will return
@@ -100,14 +92,6 @@ module ActiveRecord
100
92
  first_config && name == first_config.name
101
93
  end
102
94
 
103
- # Returns the DatabaseConfigurations object as a Hash.
104
- def to_h
105
- configurations.inject({}) do |memo, db_config|
106
- memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
107
- end
108
- end
109
- deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
110
-
111
95
  # Checks if the application's configurations are empty.
112
96
  #
113
97
  # Aliased to blank?
@@ -166,7 +150,7 @@ module ActiveRecord
166
150
  return configs if configs.is_a?(Array)
167
151
 
168
152
  db_configs = configs.flat_map do |env_name, config|
169
- if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
153
+ if config.is_a?(Hash) && config.values.all?(Hash)
170
154
  walk_configs(env_name.to_s, config)
171
155
  else
172
156
  build_db_config_from_raw_config(env_name.to_s, "primary", config)
@@ -193,7 +177,7 @@ module ActiveRecord
193
177
  raise AdapterNotSpecified, <<~MSG
194
178
  The `#{name}` database is not configured for the `#{default_env}` environment.
195
179
 
196
- Available databases configurations are:
180
+ Available database configurations are:
197
181
 
198
182
  #{build_configuration_sentence}
199
183
  MSG
@@ -201,7 +185,7 @@ module ActiveRecord
201
185
  end
202
186
 
203
187
  def build_configuration_sentence
204
- configs = configs_for(include_replicas: true)
188
+ configs = configs_for(include_hidden: true)
205
189
 
206
190
  configs.group_by(&:env_name).map do |env, config|
207
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
@@ -136,6 +137,21 @@ module ActiveRecord
136
137
  # end
137
138
  #
138
139
  # Now you can list a bunch of entries, call +Entry#title+, 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
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # Configuration API for +ActiveRecord::Encryption+
6
+ module Configurable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ mattr_reader :config, default: Config.new
11
+ mattr_accessor :encrypted_attribute_declaration_listeners
12
+ end
13
+
14
+ class_methods do
15
+ # Expose getters for context properties
16
+ Context::PROPERTIES.each do |name|
17
+ delegate name, to: :context
18
+ end
19
+
20
+ def configure(primary_key:, deterministic_key:, key_derivation_salt:, **properties) # :nodoc:
21
+ config.primary_key = primary_key
22
+ config.deterministic_key = deterministic_key
23
+ config.key_derivation_salt = key_derivation_salt
24
+
25
+ context.key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key)
26
+
27
+ 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
32
+ end
33
+ end
34
+
35
+ # Register callback to be invoked when an encrypted attribute is declared.
36
+ #
37
+ # === Example:
38
+ #
39
+ # ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
40
+ # ...
41
+ # end
42
+ def on_encrypted_attribute_declared(&block)
43
+ self.encrypted_attribute_declaration_listeners ||= Concurrent::Array.new
44
+ self.encrypted_attribute_declaration_listeners << block
45
+ end
46
+
47
+ def encrypted_attribute_was_declared(klass, name) # :nodoc:
48
+ self.encrypted_attribute_declaration_listeners&.each do |block|
49
+ block.call(klass, name)
50
+ end
51
+ end
52
+
53
+ def install_auto_filtered_parameters(application) # :nodoc:
54
+ ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
55
+ application.config.filter_parameters << encrypted_attribute_name unless ActiveRecord::Encryption.config.excluded_from_filter_parameters.include?(name)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # An encryption context configures the different entities used to perform encryption:
6
+ #
7
+ # * A key provider
8
+ # * A key generator
9
+ # * An encryptor, the facade to encrypt data
10
+ # * A cipher, the encryption algorithm
11
+ # * A message serializer
12
+ class Context
13
+ PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
14
+
15
+ PROPERTIES.each do |name|
16
+ attr_accessor name
17
+ end
18
+
19
+ def initialize
20
+ set_defaults
21
+ end
22
+
23
+ alias frozen_encryption? frozen_encryption
24
+
25
+ private
26
+ def set_defaults
27
+ self.frozen_encryption = false
28
+ self.key_generator = ActiveRecord::Encryption::KeyGenerator.new
29
+ self.cipher = ActiveRecord::Encryption::Cipher.new
30
+ self.encryptor = ActiveRecord::Encryption::Encryptor.new
31
+ self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # +ActiveRecord::Encryption+ uses encryption contexts to configure the different entities used to
6
+ # encrypt/decrypt at a given moment in time.
7
+ #
8
+ # By default, the library uses a default encryption context. This is the +Context+ that gets configured
9
+ # initially via +config.active_record.encryption+ options. Library users can define nested encryption contexts
10
+ # when running blocks of code.
11
+ #
12
+ # See +Context+.
13
+ module Contexts
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ mattr_reader :default_context, default: Context.new
18
+ thread_mattr_accessor :custom_contexts
19
+ end
20
+
21
+ class_methods do
22
+ # Configures a custom encryption context to use when running the provided block of code.
23
+ #
24
+ # It supports overriding all the properties defined in +Context+.
25
+ #
26
+ # Example:
27
+ #
28
+ # ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do
29
+ # ...
30
+ # end
31
+ #
32
+ # Encryption contexts can be nested.
33
+ def with_encryption_context(properties)
34
+ self.custom_contexts ||= []
35
+ self.custom_contexts << default_context.dup
36
+ properties.each do |key, value|
37
+ self.current_custom_context.send("#{key}=", value)
38
+ end
39
+
40
+ yield
41
+ ensure
42
+ self.custom_contexts.pop
43
+ end
44
+
45
+ # Runs the provided block in an encryption context where encryption is disabled:
46
+ #
47
+ # * Reading encrypted content will return its ciphertexts.
48
+ # * Writing encrypted content will write its clear text.
49
+ def without_encryption(&block)
50
+ with_encryption_context encryptor: ActiveRecord::Encryption::NullEncryptor.new, &block
51
+ end
52
+
53
+ # Runs the provided block in an encryption context where:
54
+ #
55
+ # * Reading encrypted content will return its ciphertext.
56
+ # * Writing encrypted content will fail.
57
+ def protecting_encrypted_data(&block)
58
+ with_encryption_context encryptor: ActiveRecord::Encryption::EncryptingOnlyEncryptor.new, frozen_encryption: true, &block
59
+ end
60
+
61
+ # Returns the current context. By default it will return the current context.
62
+ def context
63
+ self.current_custom_context || self.default_context
64
+ end
65
+
66
+ def current_custom_context
67
+ self.custom_contexts&.last
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A +KeyProvider+ that derives keys from passwords.
6
+ class DerivedSecretKeyProvider < KeyProvider
7
+ def initialize(passwords)
8
+ super(Array(passwords).collect { |password| Key.derive_from(password) })
9
+ end
10
+ end
11
+ end
12
+ end