activerecord 6.1.3.2 → 7.0.0.alpha2

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -1058
  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 +35 -7
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +16 -6
  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 +1 -1
  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 +24 -25
  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 +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -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 +41 -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 +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  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 +11 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -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 +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  67. data/lib/active_record/connection_adapters/pool_manager.rb +5 -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 +6 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  82. data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
  83. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  87. data/lib/active_record/connection_adapters.rb +8 -5
  88. data/lib/active_record/connection_handling.rb +20 -38
  89. data/lib/active_record/core.rb +129 -117
  90. data/lib/active_record/database_configurations/database_config.rb +12 -0
  91. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  92. data/lib/active_record/database_configurations/url_config.rb +2 -2
  93. data/lib/active_record/database_configurations.rb +18 -9
  94. data/lib/active_record/delegated_type.rb +33 -11
  95. data/lib/active_record/destroy_association_async_job.rb +1 -1
  96. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  97. data/lib/active_record/dynamic_matchers.rb +1 -1
  98. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  99. data/lib/active_record/encryption/cipher.rb +53 -0
  100. data/lib/active_record/encryption/config.rb +44 -0
  101. data/lib/active_record/encryption/configurable.rb +61 -0
  102. data/lib/active_record/encryption/context.rb +35 -0
  103. data/lib/active_record/encryption/contexts.rb +72 -0
  104. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  105. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  106. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  107. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  108. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  109. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  110. data/lib/active_record/encryption/encryptor.rb +155 -0
  111. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  112. data/lib/active_record/encryption/errors.rb +15 -0
  113. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  114. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  115. data/lib/active_record/encryption/key.rb +28 -0
  116. data/lib/active_record/encryption/key_generator.rb +42 -0
  117. data/lib/active_record/encryption/key_provider.rb +46 -0
  118. data/lib/active_record/encryption/message.rb +33 -0
  119. data/lib/active_record/encryption/message_serializer.rb +80 -0
  120. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  121. data/lib/active_record/encryption/properties.rb +76 -0
  122. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  123. data/lib/active_record/encryption/scheme.rb +99 -0
  124. data/lib/active_record/encryption.rb +55 -0
  125. data/lib/active_record/enum.rb +44 -46
  126. data/lib/active_record/errors.rb +66 -3
  127. data/lib/active_record/fixture_set/file.rb +15 -1
  128. data/lib/active_record/fixture_set/table_row.rb +40 -5
  129. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  130. data/lib/active_record/fixtures.rb +16 -11
  131. data/lib/active_record/future_result.rb +139 -0
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +55 -17
  134. data/lib/active_record/insert_all.rb +39 -6
  135. data/lib/active_record/integration.rb +1 -1
  136. data/lib/active_record/internal_metadata.rb +3 -5
  137. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  138. data/lib/active_record/locking/optimistic.rb +10 -9
  139. data/lib/active_record/log_subscriber.rb +6 -2
  140. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  141. data/lib/active_record/middleware/database_selector.rb +8 -3
  142. data/lib/active_record/migration/command_recorder.rb +4 -4
  143. data/lib/active_record/migration/compatibility.rb +83 -1
  144. data/lib/active_record/migration/join_table.rb +1 -1
  145. data/lib/active_record/migration.rb +109 -79
  146. data/lib/active_record/model_schema.rb +46 -32
  147. data/lib/active_record/nested_attributes.rb +3 -3
  148. data/lib/active_record/no_touching.rb +2 -2
  149. data/lib/active_record/null_relation.rb +2 -6
  150. data/lib/active_record/persistence.rb +134 -45
  151. data/lib/active_record/query_cache.rb +2 -2
  152. data/lib/active_record/query_logs.rb +203 -0
  153. data/lib/active_record/querying.rb +15 -5
  154. data/lib/active_record/railtie.rb +117 -17
  155. data/lib/active_record/railties/controller_runtime.rb +1 -1
  156. data/lib/active_record/railties/databases.rake +83 -58
  157. data/lib/active_record/readonly_attributes.rb +11 -0
  158. data/lib/active_record/reflection.rb +45 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  160. data/lib/active_record/relation/batches.rb +3 -3
  161. data/lib/active_record/relation/calculations.rb +42 -25
  162. data/lib/active_record/relation/delegation.rb +6 -6
  163. data/lib/active_record/relation/finder_methods.rb +32 -23
  164. data/lib/active_record/relation/merger.rb +20 -13
  165. data/lib/active_record/relation/predicate_builder.rb +1 -6
  166. data/lib/active_record/relation/query_attribute.rb +5 -11
  167. data/lib/active_record/relation/query_methods.rb +233 -50
  168. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  169. data/lib/active_record/relation/spawn_methods.rb +2 -2
  170. data/lib/active_record/relation/where_clause.rb +22 -15
  171. data/lib/active_record/relation.rb +170 -87
  172. data/lib/active_record/result.rb +17 -2
  173. data/lib/active_record/runtime_registry.rb +2 -4
  174. data/lib/active_record/sanitization.rb +11 -7
  175. data/lib/active_record/schema_dumper.rb +3 -3
  176. data/lib/active_record/schema_migration.rb +0 -4
  177. data/lib/active_record/scoping/default.rb +62 -15
  178. data/lib/active_record/scoping/named.rb +3 -11
  179. data/lib/active_record/scoping.rb +40 -22
  180. data/lib/active_record/serialization.rb +1 -1
  181. data/lib/active_record/signed_id.rb +1 -1
  182. data/lib/active_record/statement_cache.rb +2 -2
  183. data/lib/active_record/tasks/database_tasks.rb +107 -23
  184. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  185. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  186. data/lib/active_record/test_databases.rb +1 -1
  187. data/lib/active_record/test_fixtures.rb +45 -4
  188. data/lib/active_record/timestamp.rb +3 -4
  189. data/lib/active_record/transactions.rb +9 -14
  190. data/lib/active_record/translation.rb +2 -2
  191. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  192. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  193. data/lib/active_record/type/internal/timezone.rb +2 -2
  194. data/lib/active_record/type/serialized.rb +1 -1
  195. data/lib/active_record/type/type_map.rb +17 -20
  196. data/lib/active_record/type.rb +1 -2
  197. data/lib/active_record/validations/associated.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +1 -1
  199. data/lib/active_record.rb +170 -2
  200. data/lib/arel/attributes/attribute.rb +0 -8
  201. data/lib/arel/collectors/bind.rb +2 -2
  202. data/lib/arel/collectors/composite.rb +3 -3
  203. data/lib/arel/collectors/sql_string.rb +1 -1
  204. data/lib/arel/collectors/substitute_binds.rb +1 -1
  205. data/lib/arel/crud.rb +18 -22
  206. data/lib/arel/delete_manager.rb +2 -4
  207. data/lib/arel/insert_manager.rb +2 -3
  208. data/lib/arel/nodes/casted.rb +1 -1
  209. data/lib/arel/nodes/delete_statement.rb +8 -13
  210. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  211. data/lib/arel/nodes/insert_statement.rb +2 -2
  212. data/lib/arel/nodes/select_core.rb +2 -2
  213. data/lib/arel/nodes/select_statement.rb +2 -2
  214. data/lib/arel/nodes/update_statement.rb +3 -2
  215. data/lib/arel/predications.rb +3 -3
  216. data/lib/arel/select_manager.rb +10 -4
  217. data/lib/arel/table.rb +0 -1
  218. data/lib/arel/tree_manager.rb +0 -12
  219. data/lib/arel/update_manager.rb +2 -4
  220. data/lib/arel/visitors/dot.rb +80 -90
  221. data/lib/arel/visitors/mysql.rb +6 -1
  222. data/lib/arel/visitors/postgresql.rb +0 -10
  223. data/lib/arel/visitors/to_sql.rb +44 -3
  224. data/lib/arel.rb +1 -1
  225. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  227. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  228. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  229. metadata +55 -16
@@ -48,6 +48,18 @@ module ActiveRecord
48
48
  raise NotImplementedError
49
49
  end
50
50
 
51
+ def min_threads
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def max_threads
56
+ raise NotImplementedError
57
+ end
58
+
59
+ def max_queue
60
+ raise NotImplementedError
61
+ end
62
+
51
63
  def checkout_timeout
52
64
  raise NotImplementedError
53
65
  end
@@ -26,13 +26,14 @@ 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
35
  def config
35
- ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 6.2.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
36
+ ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
36
37
  configuration_hash.stringify_keys
37
38
  end
38
39
 
@@ -54,6 +55,10 @@ module ActiveRecord
54
55
  configuration_hash[:host]
55
56
  end
56
57
 
58
+ def socket # :nodoc:
59
+ configuration_hash[:socket]
60
+ end
61
+
57
62
  def database
58
63
  configuration_hash[:database]
59
64
  end
@@ -66,6 +71,18 @@ module ActiveRecord
66
71
  (configuration_hash[:pool] || 5).to_i
67
72
  end
68
73
 
74
+ def min_threads
75
+ (configuration_hash[:min_threads] || 0).to_i
76
+ end
77
+
78
+ def max_threads
79
+ (configuration_hash[:max_threads] || pool).to_i
80
+ end
81
+
82
+ def max_queue
83
+ max_threads * 4
84
+ end
85
+
69
86
  def checkout_timeout
70
87
  (configuration_hash[:checkout_timeout] || 5).to_f
71
88
  end
@@ -91,6 +108,15 @@ module ActiveRecord
91
108
  def schema_cache_path
92
109
  configuration_hash[:schema_cache_path]
93
110
  end
111
+
112
+ # Determines whether to dump the schema for a database.
113
+ def schema_dump
114
+ configuration_hash.fetch(:schema_dump, true)
115
+ end
116
+
117
+ def database_tasks? # :nodoc:
118
+ !replica? && !!configuration_hash.fetch(:database_tasks, true)
119
+ end
94
120
  end
95
121
  end
96
122
  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
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  # Collects the configs for the environment and optionally the specification
23
- # name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
23
+ # name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
24
24
  #
25
25
  # If a name is provided a single DatabaseConfig object will be
26
26
  # returned, otherwise an array of DatabaseConfig objects will be
@@ -33,22 +33,31 @@ module ActiveRecord
33
33
  # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
34
34
  # to +nil+. If no +env_name+ is specified the config for the default env and the
35
35
  # passed +name+ will be returned.
36
- # * <tt>include_replicas:</tt> Determines whether to include replicas in
36
+ # * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
37
37
  # the returned list. Most of the time we're only iterating over the write
38
38
  # connection (i.e. migrations don't need to run for the write and read connection).
39
39
  # Defaults to +false+.
40
- def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
40
+ # * <tt>include_hidden:</tte Determines whether to include replicas and configurations
41
+ # hidden by +database_tasks: false+ in the returned list. Most of the time we're only
42
+ # iterating over the primary connections (i.e. migrations don't need to run for the
43
+ # write and read connection). Defaults to +false+.
44
+ def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false, include_hidden: false)
41
45
  if spec_name
42
46
  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")
47
+ ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
48
+ end
49
+
50
+ if include_replicas
51
+ include_hidden = include_replicas
52
+ 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
53
  end
45
54
 
46
55
  env_name ||= default_env if name
47
56
  configs = env_with_configs(env_name)
48
57
 
49
- unless include_replicas
58
+ unless include_hidden
50
59
  configs = configs.select do |db_config|
51
- !db_config.replica?
60
+ db_config.database_tasks?
52
61
  end
53
62
  end
54
63
 
@@ -166,7 +175,7 @@ module ActiveRecord
166
175
  return configs if configs.is_a?(Array)
167
176
 
168
177
  db_configs = configs.flat_map do |env_name, config|
169
- if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
178
+ if config.is_a?(Hash) && config.values.all?(Hash)
170
179
  walk_configs(env_name.to_s, config)
171
180
  else
172
181
  build_db_config_from_raw_config(env_name.to_s, "primary", config)
@@ -193,7 +202,7 @@ module ActiveRecord
193
202
  raise AdapterNotSpecified, <<~MSG
194
203
  The `#{name}` database is not configured for the `#{default_env}` environment.
195
204
 
196
- Available databases configurations are:
205
+ Available database configurations are:
197
206
 
198
207
  #{build_configuration_sentence}
199
208
  MSG
@@ -201,7 +210,7 @@ module ActiveRecord
201
210
  end
202
211
 
203
212
  def build_configuration_sentence
204
- configs = configs_for(include_replicas: true)
213
+ configs = configs_for(include_hidden: true)
205
214
 
206
215
  configs.group_by(&:env_name).map do |env, config|
207
216
  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
@@ -156,8 +157,6 @@ module ActiveRecord
156
157
  # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
157
158
  # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
158
159
  #
159
- # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
160
- #
161
160
  # You can also declare namespaced types:
162
161
  #
163
162
  # class Entry < ApplicationRecord
@@ -167,15 +166,38 @@ module ActiveRecord
167
166
  # Entry.access_notice_messages
168
167
  # entry.access_notice_message
169
168
  # entry.access_notice_message?
169
+ #
170
+ # === Options
171
+ #
172
+ # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
173
+ # The following options can be included to specialize the behavior of the delegated type convenience methods.
174
+ #
175
+ # [:foreign_key]
176
+ # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
177
+ # +role+ with an "_id" suffix. So a class that defines a
178
+ # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
179
+ # the default <tt>:foreign_key</tt>.
180
+ # [:primary_key]
181
+ # Specify the method that returns the primary key of associated object used for the convenience methods.
182
+ # By default this is +id+.
183
+ #
184
+ # Option examples:
185
+ # class Entry < ApplicationRecord
186
+ # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
187
+ # end
188
+ #
189
+ # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
190
+ # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
170
191
  def delegated_type(role, types:, **options)
171
192
  belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
172
- define_delegated_type_methods role, types: types
193
+ define_delegated_type_methods role, types: types, options: options
173
194
  end
174
195
 
175
196
  private
176
- def define_delegated_type_methods(role, types:)
197
+ def define_delegated_type_methods(role, types:, options:)
198
+ primary_key = options[:primary_key] || "id"
177
199
  role_type = "#{role}_type"
178
- role_id = "#{role}_id"
200
+ role_id = options[:foreign_key] || "#{role}_id"
179
201
 
180
202
  define_method "#{role}_class" do
181
203
  public_send("#{role}_type").constantize
@@ -186,7 +208,7 @@ module ActiveRecord
186
208
  end
187
209
 
188
210
  types.each do |type|
189
- scope_name = type.tableize.gsub("/", "_")
211
+ scope_name = type.tableize.tr("/", "_")
190
212
  singular = scope_name.singularize
191
213
  query = "#{singular}?"
192
214
 
@@ -200,7 +222,7 @@ module ActiveRecord
200
222
  public_send(role) if public_send(query)
201
223
  end
202
224
 
203
- define_method "#{singular}_id" do
225
+ define_method "#{singular}_#{primary_key}" do
204
226
  public_send(role_id) if public_send(query)
205
227
  end
206
228
  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