activerecord 7.1.5.1 → 7.2.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -1,53 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActiveRecord
4
6
  class DatabaseConfigurations
5
- # = Active Record Database Hash Config
7
+ # # Active Record Database Hash Config
6
8
  #
7
- # A +HashConfig+ object is created for each database configuration entry that
8
- # is created from a hash.
9
+ # A `HashConfig` object is created for each database configuration entry that is
10
+ # created from a hash.
9
11
  #
10
12
  # A hash config:
11
13
  #
12
- # { "development" => { "database" => "db_name" } }
14
+ # { "development" => { "database" => "db_name" } }
13
15
  #
14
16
  # Becomes:
15
17
  #
16
- # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
17
- # @env_name="development", @name="primary", @config={database: "db_name"}>
18
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
19
+ # @env_name="development", @name="primary", @config={database: "db_name"}>
18
20
  #
19
21
  # See ActiveRecord::DatabaseConfigurations for more info.
20
22
  class HashConfig < DatabaseConfig
21
23
  attr_reader :configuration_hash
22
24
 
23
-
24
- # Initialize a new +HashConfig+ object
25
+ # Initialize a new `HashConfig` object
26
+ #
27
+ # #### Parameters
25
28
  #
26
- # ==== Options
29
+ # * `env_name` - The Rails environment, i.e. "development".
30
+ # * `name` - The db config name. In a standard two-tier database configuration
31
+ # this will default to "primary". In a multiple database three-tier database
32
+ # configuration this corresponds to the name used in the second tier, for
33
+ # example "primary_readonly".
34
+ # * `configuration_hash` - The config hash. This is the hash that contains the
35
+ # database adapter, name, and other important information for database
36
+ # connections.
27
37
  #
28
- # * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
29
- # * <tt>:name</tt> - The db config name. In a standard two-tier
30
- # database configuration this will default to "primary". In a multiple
31
- # database three-tier database configuration this corresponds to the name
32
- # used in the second tier, for example "primary_readonly".
33
- # * <tt>:config</tt> - The config hash. This is the hash that contains the
34
- # database adapter, name, and other important information for database
35
- # connections.
36
38
  def initialize(env_name, name, configuration_hash)
37
39
  super(env_name, name)
38
40
  @configuration_hash = configuration_hash.symbolize_keys.freeze
39
41
  end
40
42
 
41
43
  # Determines whether a database configuration is for a replica / readonly
42
- # connection. If the +replica+ key is present in the config, +replica?+ will
43
- # return +true+.
44
+ # connection. If the `replica` key is present in the config, `replica?` will
45
+ # return `true`.
44
46
  def replica?
45
47
  configuration_hash[:replica]
46
48
  end
47
49
 
48
- # The migrations paths for a database configuration. If the
49
- # +migrations_paths+ key is present in the config, +migrations_paths+
50
- # will return its value.
50
+ # The migrations paths for a database configuration. If the `migrations_paths`
51
+ # key is present in the config, `migrations_paths` will return its value.
51
52
  def migrations_paths
52
53
  configuration_hash[:migrations_paths]
53
54
  end
@@ -92,8 +93,8 @@ module ActiveRecord
92
93
  (configuration_hash[:checkout_timeout] || 5).to_f
93
94
  end
94
95
 
95
- # +reaping_frequency+ is configurable mostly for historical reasons, but it could
96
- # also be useful if someone wants a very low +idle_timeout+.
96
+ # `reaping_frequency` is configurable mostly for historical reasons, but it
97
+ # could also be useful if someone wants a very low `idle_timeout`.
97
98
  def reaping_frequency
98
99
  configuration_hash.fetch(:reaping_frequency, 60)&.to_f
99
100
  end
@@ -104,12 +105,11 @@ module ActiveRecord
104
105
  end
105
106
 
106
107
  def adapter
107
- configuration_hash[:adapter]
108
+ configuration_hash[:adapter]&.to_s
108
109
  end
109
110
 
110
- # The path to the schema cache dump file for a database.
111
- # If omitted, the filename will be read from ENV or a
112
- # default will be derived.
111
+ # The path to the schema cache dump file for a database. If omitted, the
112
+ # filename will be read from ENV or a default will be derived.
113
113
  def schema_cache_path
114
114
  configuration_hash[:schema_cache_path]
115
115
  end
@@ -130,14 +130,14 @@ module ActiveRecord
130
130
  Base.configurations.primary?(name)
131
131
  end
132
132
 
133
- # Determines whether to dump the schema/structure files and the
134
- # filename that should be used.
133
+ # Determines whether to dump the schema/structure files and the filename that
134
+ # should be used.
135
135
  #
136
- # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
137
- # the schema will not be dumped.
136
+ # If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
137
+ # will not be dumped.
138
138
  #
139
- # If the config option is set that will be used. Otherwise \Rails
140
- # will generate the filename from the database config name.
139
+ # If the config option is set that will be used. Otherwise Rails will generate
140
+ # the filename from the database config name.
141
141
  def schema_dump(format = ActiveRecord.schema_format)
142
142
  if configuration_hash.key?(:schema_dump)
143
143
  if config = configuration_hash[:schema_dump]
@@ -154,6 +154,10 @@ module ActiveRecord
154
154
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
155
155
  end
156
156
 
157
+ def use_metadata_table? # :nodoc:
158
+ configuration_hash.fetch(:use_metadata_table, true)
159
+ end
160
+
157
161
  private
158
162
  def schema_file_type(format)
159
163
  case format
@@ -41,10 +41,29 @@ module ActiveRecord
41
41
  super(env_name, name, configuration_hash)
42
42
 
43
43
  @url = url
44
- @configuration_hash = @configuration_hash.merge(build_url_hash).freeze
44
+ @configuration_hash = @configuration_hash.merge(build_url_hash)
45
+
46
+ if @configuration_hash[:schema_dump] == "false"
47
+ @configuration_hash[:schema_dump] = false
48
+ end
49
+
50
+ if @configuration_hash[:query_cache] == "false"
51
+ @configuration_hash[:query_cache] = false
52
+ end
53
+
54
+ to_boolean!(@configuration_hash, :replica)
55
+ to_boolean!(@configuration_hash, :database_tasks)
56
+
57
+ @configuration_hash.freeze
45
58
  end
46
59
 
47
60
  private
61
+ def to_boolean!(configuration_hash, key)
62
+ if configuration_hash[key].is_a?(String)
63
+ configuration_hash[key] = configuration_hash[key] != "false"
64
+ end
65
+ end
66
+
48
67
  # Return a Hash that can be merged into the main config that represents
49
68
  # the passed in url
50
69
  def build_url_hash
@@ -113,7 +113,7 @@ module ActiveRecord
113
113
 
114
114
  if name
115
115
  configs.find do |db_config|
116
- db_config.name == name
116
+ db_config.name == name.to_s
117
117
  end
118
118
  else
119
119
  configs
@@ -113,6 +113,26 @@ module ActiveRecord
113
113
  # end
114
114
  # end
115
115
  #
116
+ # == Querying across records
117
+ #
118
+ # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
119
+ # tricky, but not impossible.
120
+ #
121
+ # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
122
+ # in appropriate places:
123
+ #
124
+ # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
125
+ #
126
+ # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
127
+ # the method:
128
+ #
129
+ # # app/models/concerns/entryable.rb
130
+ # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
131
+ #
132
+ # Now the query can be shortened significantly:
133
+ #
134
+ # Comment.where(content: 'Hello!').with_entry(creator: Current.user)
135
+ #
116
136
  # == Adding further delegation
117
137
  #
118
138
  # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
@@ -219,6 +239,10 @@ module ActiveRecord
219
239
  role_type = options[:foreign_type] || "#{role}_type"
220
240
  role_id = options[:foreign_key] || "#{role}_id"
221
241
 
242
+ define_singleton_method "#{role}_types" do
243
+ types.map(&:to_s)
244
+ end
245
+
222
246
  define_method "#{role}_class" do
223
247
  public_send(role_type).constantize
224
248
  end
@@ -12,12 +12,12 @@ module ActiveRecord
12
12
  end
13
13
  end
14
14
 
15
- def method_missing(name, *arguments, &block)
15
+ def method_missing(name, ...)
16
16
  match = Method.match(self, name)
17
17
 
18
18
  if match && match.valid?
19
19
  match.define
20
- send(name, *arguments, &block)
20
+ send(name, ...)
21
21
  else
22
22
  super
23
23
  end
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
85
85
  encrypted_attributes << name.to_sym
86
86
 
87
- attribute name do |cast_type|
87
+ decorate_attributes([name]) do |name, cast_type|
88
88
  scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
89
89
  downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
90
90
 
@@ -221,7 +221,7 @@ module ActiveRecord
221
221
  end
222
222
 
223
223
  def cant_modify_encrypted_attributes_when_frozen
224
- self.class&.encrypted_attributes.each do |attribute|
224
+ self.class.encrypted_attributes.each do |attribute|
225
225
  errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
226
226
  end
227
227
  end
@@ -81,7 +81,7 @@ module ActiveRecord
81
81
  @previous_type
82
82
  end
83
83
 
84
- def decrypt(value)
84
+ def decrypt_as_text(value)
85
85
  with_context do
86
86
  unless value.nil?
87
87
  if @default && @default == value
@@ -99,6 +99,10 @@ module ActiveRecord
99
99
  end
100
100
  end
101
101
 
102
+ def decrypt(value)
103
+ text_to_database_type decrypt_as_text(value)
104
+ end
105
+
102
106
  def try_to_deserialize_with_previous_encrypted_types(value)
103
107
  previous_types.each.with_index do |type, index|
104
108
  break type.deserialize(value)
@@ -129,12 +133,20 @@ module ActiveRecord
129
133
  encrypt(casted_value.to_s) unless casted_value.nil?
130
134
  end
131
135
 
132
- def encrypt(value)
136
+ def encrypt_as_text(value)
133
137
  with_context do
138
+ if encryptor.binary? && !cast_type.binary?
139
+ raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
140
+ end
141
+
134
142
  encryptor.encrypt(value, **encryption_options)
135
143
  end
136
144
  end
137
145
 
146
+ def encrypt(value)
147
+ text_to_database_type encrypt_as_text(value)
148
+ end
149
+
138
150
  def encryptor
139
151
  ActiveRecord::Encryption.encryptor
140
152
  end
@@ -150,6 +162,14 @@ module ActiveRecord
150
162
  def clean_text_scheme
151
163
  @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
152
164
  end
165
+
166
+ def text_to_database_type(value)
167
+ if value && cast_type.binary?
168
+ ActiveModel::Type::Binary::Data.new(value)
169
+ else
170
+ value
171
+ end
172
+ end
153
173
  end
154
174
  end
155
175
  end
@@ -12,6 +12,14 @@ module ActiveRecord
12
12
  # It interacts with a KeyProvider for getting the keys, and delegate to
13
13
  # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
14
  class Encryptor
15
+ # === Options
16
+ #
17
+ # * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
18
+ # Defaults to +true+.
19
+ def initialize(compress: true)
20
+ @compress = compress
21
+ end
22
+
15
23
  # Encrypts +clean_text+ and returns the encrypted result
16
24
  #
17
25
  # Internally, it will:
@@ -66,6 +74,10 @@ module ActiveRecord
66
74
  false
67
75
  end
68
76
 
77
+ def binary?
78
+ serializer.binary?
79
+ end
80
+
69
81
  private
70
82
  DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
71
83
  ENCODING_ERRORS = [EncodingError, Errors::Encoding]
@@ -100,7 +112,6 @@ module ActiveRecord
100
112
  end
101
113
 
102
114
  def deserialize_message(message)
103
- raise Errors::Encoding unless message.is_a?(String)
104
115
  serializer.load message
105
116
  rescue ArgumentError, TypeError, Errors::ForbiddenClass
106
117
  raise Errors::Encoding
@@ -112,13 +123,17 @@ module ActiveRecord
112
123
 
113
124
  # Under certain threshold, ZIP compression is actually worse that not compressing
114
125
  def compress_if_worth_it(string)
115
- if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
126
+ if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
116
127
  [compress(string), true]
117
128
  else
118
129
  [string, false]
119
130
  end
120
131
  end
121
132
 
133
+ def compress?
134
+ @compress
135
+ end
136
+
122
137
  def compress(data)
123
138
  Zlib::Deflate.deflate(data).tap do |compressed_data|
124
139
  compressed_data.force_encoding(data.encoding)
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/message_pack"
4
+
5
+ module ActiveRecord
6
+ module Encryption
7
+ # A message serializer that serializes +Messages+ with MessagePack.
8
+ #
9
+ # The message is converted to a hash with this structure:
10
+ #
11
+ # {
12
+ # p: <payload>,
13
+ # h: {
14
+ # header1: value1,
15
+ # header2: value2,
16
+ # ...
17
+ # }
18
+ # }
19
+ #
20
+ # Then it is converted to the MessagePack format.
21
+ class MessagePackMessageSerializer
22
+ def dump(message)
23
+ raise Errors::ForbiddenClass unless message.is_a?(Message)
24
+ ActiveSupport::MessagePack.dump(message_to_hash(message))
25
+ end
26
+
27
+ def load(serialized_content)
28
+ data = ActiveSupport::MessagePack.load(serialized_content)
29
+ hash_to_message(data, 1)
30
+ rescue RuntimeError
31
+ raise Errors::Decryption
32
+ end
33
+
34
+ def binary?
35
+ true
36
+ end
37
+
38
+ private
39
+ def message_to_hash(message)
40
+ {
41
+ "p" => message.payload,
42
+ "h" => headers_to_hash(message.headers)
43
+ }
44
+ end
45
+
46
+ def headers_to_hash(headers)
47
+ headers.transform_values do |value|
48
+ value.is_a?(Message) ? message_to_hash(value) : value
49
+ end
50
+ end
51
+
52
+ def hash_to_message(data, level)
53
+ validate_message_data_format(data, level)
54
+ Message.new(payload: data["p"], headers: parse_properties(data["h"], level))
55
+ end
56
+
57
+ def validate_message_data_format(data, level)
58
+ if level > 2
59
+ raise Errors::Decryption, "More than one level of hash nesting in headers is not supported"
60
+ end
61
+
62
+ unless data.is_a?(Hash) && data.has_key?("p")
63
+ raise Errors::Decryption, "Invalid data format: hash without payload"
64
+ end
65
+ end
66
+
67
+ def parse_properties(headers, level)
68
+ Properties.new.tap do |properties|
69
+ headers&.each do |key, value|
70
+ properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -33,6 +33,10 @@ module ActiveRecord
33
33
  JSON.dump message_to_json(message)
34
34
  end
35
35
 
36
+ def binary?
37
+ false
38
+ end
39
+
36
40
  private
37
41
  def parse_message(data, level)
38
42
  validate_message_data_format(data, level)
@@ -16,6 +16,10 @@ module ActiveRecord
16
16
  def encrypted?(text)
17
17
  false
18
18
  end
19
+
20
+ def binary?
21
+ false
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -19,6 +19,10 @@ module ActiveRecord
19
19
  def encrypted?(text)
20
20
  false
21
21
  end
22
+
23
+ def binary?
24
+ false
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -53,6 +53,4 @@ module ActiveRecord
53
53
  Cipher.eager_load!
54
54
  end
55
55
  end
56
-
57
- ActiveSupport.run_load_hooks :active_record_encryption, Encryption
58
56
  end
@@ -223,6 +223,13 @@ module ActiveRecord
223
223
  options.transform_keys! { |key| :"#{key[1..-1]}" }
224
224
 
225
225
  definitions.each { |name, values| _enum(name, values, **options) }
226
+
227
+ ActiveRecord.deprecator.warn(<<~MSG)
228
+ Defining enums with keyword arguments is deprecated and will be removed
229
+ in Rails 7.3. Positional arguments should be used instead:
230
+
231
+ #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
232
+ MSG
226
233
  end
227
234
 
228
235
  private
@@ -245,7 +252,9 @@ module ActiveRecord
245
252
  detect_enum_conflict!(name, name)
246
253
  detect_enum_conflict!(name, "#{name}=")
247
254
 
248
- attribute(name, **options) do |subtype|
255
+ attribute(name, **options)
256
+
257
+ decorate_attributes([name]) do |_name, subtype|
249
258
  if subtype == ActiveModel::Type.default_value
250
259
  raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
251
260
  " backed by a database column or declared with an explicit type" \
@@ -7,14 +7,6 @@ module ActiveRecord
7
7
  class ActiveRecordError < StandardError
8
8
  end
9
9
 
10
- # DEPRECATED: Previously raised when trying to use a feature in Active Record which
11
- # requires Active Job but the gem is not present. Now raises a NameError.
12
- include ActiveSupport::Deprecation::DeprecatedConstantAccessor
13
- DeprecatedActiveJobRequiredError = Class.new(ActiveRecordError) # :nodoc:
14
- deprecate_constant "ActiveJobRequiredError", "ActiveRecord::DeprecatedActiveJobRequiredError",
15
- message: "ActiveRecord::ActiveJobRequiredError has been deprecated. If Active Job is not present, a NameError will be raised instead.",
16
- deprecator: ActiveRecord.deprecator
17
-
18
10
  # Raised when the single-table inheritance mechanism fails to locate the subclass
19
11
  # (for example due to improper usage of column that
20
12
  # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
@@ -66,7 +58,7 @@ module ActiveRecord
66
58
  end
67
59
 
68
60
  # Raised when connection to the database could not been established (for example when
69
- # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
61
+ # {ActiveRecord::Base.lease_connection=}[rdoc-ref:ConnectionHandling#lease_connection]
70
62
  # is given a +nil+ object).
71
63
  class ConnectionNotEstablished < AdapterError
72
64
  def initialize(message = nil, connection_pool: nil)
@@ -137,8 +129,10 @@ module ActiveRecord
137
129
  end
138
130
 
139
131
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
140
- # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
141
- # methods when a record is invalid and cannot be saved.
132
+ # {ActiveRecord::Base.update_attribute!}[rdoc-ref:Persistence#update_attribute!]
133
+ # methods when a record is failed to validate or cannot be saved due to any of the
134
+ # <tt>before_*</tt> callbacks throwing +:abort+. See
135
+ # ActiveRecord::Callbacks for further details
142
136
  class RecordNotSaved < ActiveRecordError
143
137
  attr_reader :record
144
138
 
@@ -374,6 +368,12 @@ module ActiveRecord
374
368
  end
375
369
 
376
370
  # Raised on attempt to lazily load records that are marked as strict loading.
371
+ #
372
+ # You can resolve this error by eager loading marked records before accessing
373
+ # them. The
374
+ # {Eager Loading Associations}[https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations]
375
+ # guide covers solutions, such as using
376
+ # {ActiveRecord::Base.includes}[rdoc-ref:QueryMethods#includes].
377
377
  class StrictLoadingViolationError < ActiveRecordError
378
378
  end
379
379
 
@@ -577,4 +577,9 @@ module ActiveRecord
577
577
  # values, such as request parameters or model attributes to query methods.
578
578
  class UnknownAttributeReference < ActiveRecordError
579
579
  end
580
+
581
+ # DatabaseVersionError will be raised when the database version is not supported, or when
582
+ # the database version cannot be determined.
583
+ class DatabaseVersionError < ActiveRecordError
584
+ end
580
585
  end
@@ -17,16 +17,17 @@ module ActiveRecord
17
17
  # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
18
18
  # Returns a formatted string ready to be logged.
19
19
  def exec_explain(queries, options = []) # :nodoc:
20
- str = queries.map do |sql, binds|
21
- msg = +"#{build_explain_clause(options)} #{sql}"
22
- unless binds.empty?
23
- msg << " "
24
- msg << binds.map { |attr| render_bind(attr) }.inspect
25
- end
26
- msg << "\n"
27
- msg << connection_explain(sql, binds, options)
28
- end.join("\n")
29
-
20
+ str = with_connection do |c|
21
+ queries.map do |sql, binds|
22
+ msg = +"#{build_explain_clause(c, options)} #{sql}"
23
+ unless binds.empty?
24
+ msg << " "
25
+ msg << binds.map { |attr| render_bind(c, attr) }.inspect
26
+ end
27
+ msg << "\n"
28
+ msg << c.explain(sql, binds, options)
29
+ end.join("\n")
30
+ end
30
31
  # Overriding inspect to be more human readable, especially in the console.
31
32
  def str.inspect
32
33
  self
@@ -36,7 +37,7 @@ module ActiveRecord
36
37
  end
37
38
 
38
39
  private
39
- def render_bind(attr)
40
+ def render_bind(connection, attr)
40
41
  if ActiveModel::Attribute === attr
41
42
  value = if attr.type.binary? && attr.value
42
43
  "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
@@ -51,24 +52,12 @@ module ActiveRecord
51
52
  [attr&.name, value]
52
53
  end
53
54
 
54
- def build_explain_clause(options = [])
55
+ def build_explain_clause(connection, options = [])
55
56
  if connection.respond_to?(:build_explain_clause, true)
56
57
  connection.build_explain_clause(options)
57
58
  else
58
59
  "EXPLAIN for:"
59
60
  end
60
61
  end
61
-
62
- def connection_explain(sql, binds, options)
63
- if connection.method(:explain).parameters.size == 2
64
- ActiveRecord.deprecator.warn(<<~MSG.squish)
65
- The current database adapter, #{connection.adapter_name}, does not support explain options.
66
- To remove this warning, the adapter must implement `build_explain_clause(options = [])`.
67
- MSG
68
- connection.explain(sql, binds)
69
- else
70
- connection.explain(sql, binds, options)
71
- end
72
- end
73
62
  end
74
63
  end