activerecord 7.0.8.7 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1795 -1424
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +19 -13
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +319 -217
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +21 -8
  33. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  35. data/lib/active_record/attribute_methods/write.rb +6 -6
  36. data/lib/active_record/attribute_methods.rb +145 -21
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +59 -10
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +10 -24
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  84. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  85. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  88. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  89. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  90. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  91. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  92. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  93. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  94. data/lib/active_record/connection_adapters.rb +3 -1
  95. data/lib/active_record/connection_handling.rb +72 -95
  96. data/lib/active_record/core.rb +181 -154
  97. data/lib/active_record/counter_cache.rb +52 -27
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +15 -10
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +3 -1
  106. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  108. data/lib/active_record/encryption/config.rb +25 -1
  109. data/lib/active_record/encryption/configurable.rb +12 -19
  110. data/lib/active_record/encryption/context.rb +10 -3
  111. data/lib/active_record/encryption/contexts.rb +5 -1
  112. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  113. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  114. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  115. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  116. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  117. data/lib/active_record/encryption/key_generator.rb +12 -1
  118. data/lib/active_record/encryption/message_serializer.rb +2 -0
  119. data/lib/active_record/encryption/properties.rb +3 -3
  120. data/lib/active_record/encryption/scheme.rb +22 -21
  121. data/lib/active_record/encryption.rb +3 -0
  122. data/lib/active_record/enum.rb +112 -28
  123. data/lib/active_record/errors.rb +112 -18
  124. data/lib/active_record/explain.rb +23 -3
  125. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  126. data/lib/active_record/fixture_set/render_context.rb +2 -0
  127. data/lib/active_record/fixture_set/table_row.rb +29 -8
  128. data/lib/active_record/fixtures.rb +135 -71
  129. data/lib/active_record/future_result.rb +40 -5
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +30 -16
  132. data/lib/active_record/insert_all.rb +57 -10
  133. data/lib/active_record/integration.rb +8 -8
  134. data/lib/active_record/internal_metadata.rb +120 -30
  135. data/lib/active_record/locking/optimistic.rb +1 -1
  136. data/lib/active_record/locking/pessimistic.rb +5 -2
  137. data/lib/active_record/log_subscriber.rb +29 -12
  138. data/lib/active_record/marshalling.rb +59 -0
  139. data/lib/active_record/message_pack.rb +124 -0
  140. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  141. data/lib/active_record/middleware/database_selector.rb +6 -8
  142. data/lib/active_record/middleware/shard_selector.rb +3 -1
  143. data/lib/active_record/migration/command_recorder.rb +104 -5
  144. data/lib/active_record/migration/compatibility.rb +145 -5
  145. data/lib/active_record/migration/default_strategy.rb +23 -0
  146. data/lib/active_record/migration/execution_strategy.rb +19 -0
  147. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  148. data/lib/active_record/migration.rb +219 -111
  149. data/lib/active_record/model_schema.rb +69 -44
  150. data/lib/active_record/nested_attributes.rb +37 -8
  151. data/lib/active_record/normalization.rb +167 -0
  152. data/lib/active_record/persistence.rb +188 -37
  153. data/lib/active_record/promise.rb +84 -0
  154. data/lib/active_record/query_cache.rb +4 -22
  155. data/lib/active_record/query_logs.rb +77 -52
  156. data/lib/active_record/query_logs_formatter.rb +41 -0
  157. data/lib/active_record/querying.rb +15 -2
  158. data/lib/active_record/railtie.rb +107 -45
  159. data/lib/active_record/railties/controller_runtime.rb +12 -6
  160. data/lib/active_record/railties/databases.rake +144 -150
  161. data/lib/active_record/railties/job_runtime.rb +23 -0
  162. data/lib/active_record/readonly_attributes.rb +32 -5
  163. data/lib/active_record/reflection.rb +181 -45
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  165. data/lib/active_record/relation/batches.rb +190 -61
  166. data/lib/active_record/relation/calculations.rb +187 -63
  167. data/lib/active_record/relation/delegation.rb +23 -9
  168. data/lib/active_record/relation/finder_methods.rb +77 -16
  169. data/lib/active_record/relation/merger.rb +2 -0
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  173. data/lib/active_record/relation/predicate_builder.rb +26 -14
  174. data/lib/active_record/relation/query_attribute.rb +2 -1
  175. data/lib/active_record/relation/query_methods.rb +371 -68
  176. data/lib/active_record/relation/spawn_methods.rb +18 -1
  177. data/lib/active_record/relation.rb +103 -37
  178. data/lib/active_record/result.rb +19 -5
  179. data/lib/active_record/runtime_registry.rb +24 -1
  180. data/lib/active_record/sanitization.rb +51 -11
  181. data/lib/active_record/schema.rb +2 -3
  182. data/lib/active_record/schema_dumper.rb +46 -7
  183. data/lib/active_record/schema_migration.rb +68 -33
  184. data/lib/active_record/scoping/default.rb +15 -5
  185. data/lib/active_record/scoping/named.rb +2 -2
  186. data/lib/active_record/scoping.rb +2 -1
  187. data/lib/active_record/secure_password.rb +60 -0
  188. data/lib/active_record/secure_token.rb +21 -3
  189. data/lib/active_record/signed_id.rb +7 -5
  190. data/lib/active_record/store.rb +8 -8
  191. data/lib/active_record/suppressor.rb +3 -1
  192. data/lib/active_record/table_metadata.rb +10 -1
  193. data/lib/active_record/tasks/database_tasks.rb +152 -108
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  197. data/lib/active_record/test_fixtures.rb +114 -96
  198. data/lib/active_record/timestamp.rb +30 -16
  199. data/lib/active_record/token_for.rb +113 -0
  200. data/lib/active_record/touch_later.rb +11 -6
  201. data/lib/active_record/transactions.rb +36 -10
  202. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  203. data/lib/active_record/type/internal/timezone.rb +7 -2
  204. data/lib/active_record/type/time.rb +4 -0
  205. data/lib/active_record/validations/absence.rb +1 -1
  206. data/lib/active_record/validations/numericality.rb +5 -4
  207. data/lib/active_record/validations/presence.rb +5 -28
  208. data/lib/active_record/validations/uniqueness.rb +47 -2
  209. data/lib/active_record/validations.rb +8 -4
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/active_record.rb +122 -17
  212. data/lib/arel/errors.rb +10 -0
  213. data/lib/arel/factory_methods.rb +4 -0
  214. data/lib/arel/nodes/binary.rb +6 -1
  215. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  216. data/lib/arel/nodes/cte.rb +36 -0
  217. data/lib/arel/nodes/fragments.rb +35 -0
  218. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  219. data/lib/arel/nodes/leading_join.rb +8 -0
  220. data/lib/arel/nodes/node.rb +111 -2
  221. data/lib/arel/nodes/sql_literal.rb +6 -0
  222. data/lib/arel/nodes/table_alias.rb +4 -0
  223. data/lib/arel/nodes.rb +4 -0
  224. data/lib/arel/predications.rb +2 -0
  225. data/lib/arel/table.rb +9 -5
  226. data/lib/arel/tree_manager.rb +5 -1
  227. data/lib/arel/visitors/mysql.rb +8 -1
  228. data/lib/arel/visitors/to_sql.rb +83 -18
  229. data/lib/arel/visitors/visitor.rb +2 -2
  230. data/lib/arel.rb +16 -2
  231. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  232. data/lib/rails/generators/active_record/migration.rb +3 -1
  233. data/lib/rails/generators/active_record/model/USAGE +113 -0
  234. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  235. metadata +46 -10
  236. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  237. data/lib/active_record/null_relation.rb +0 -63
@@ -6,6 +6,12 @@ module ActiveRecord
6
6
  module Encryption
7
7
  # Utility for generating and deriving random keys.
8
8
  class KeyGenerator
9
+ attr_reader :hash_digest_class
10
+
11
+ def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
12
+ @hash_digest_class = hash_digest_class
13
+ end
14
+
9
15
  # Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
10
16
  def generate_random_key(length: key_length)
11
17
  SecureRandom.random_bytes(length)
@@ -30,10 +36,15 @@ module ActiveRecord
30
36
  #
31
37
  # The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
32
38
  def derive_key_from(password, length: key_length)
33
- ActiveSupport::KeyGenerator.new(password).generate_key(ActiveRecord::Encryption.config.key_derivation_salt, length)
39
+ ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
40
+ .generate_key(key_derivation_salt, length)
34
41
  end
35
42
 
36
43
  private
44
+ def key_derivation_salt
45
+ @key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
46
+ end
47
+
37
48
  def key_length
38
49
  @key_length ||= ActiveRecord::Encryption.cipher.key_length
39
50
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+
3
5
  module ActiveRecord
4
6
  module Encryption
5
7
  # A message serializer that serializes +Messages+ with JSON.
@@ -14,10 +14,10 @@ module ActiveRecord
14
14
  #
15
15
  # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
16
  class Properties
17
- ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
17
+ ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
18
18
 
19
19
  delegate_missing_to :data
20
- delegate :==, to: :data
20
+ delegate :==, :[], :each, :key?, to: :data
21
21
 
22
22
  # For each entry it generates an accessor exposing the full name
23
23
  DEFAULT_PROPERTIES = {
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  end
55
55
 
56
56
  def validate_value_type(value)
57
- unless ALLOWED_VALUE_CLASSES.find { |klass| value.is_a?(klass) }
57
+ unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
58
58
  raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
59
59
  end
60
60
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  class Scheme
11
11
  attr_accessor :previous_schemes
12
12
 
13
- def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
13
+ def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
14
14
  previous_schemes: nil, **context_properties)
15
15
  # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
16
  # can merge schemes without overriding values with defaults. See +#merge+
@@ -18,6 +18,7 @@ module ActiveRecord
18
18
  @key_provider_param = key_provider
19
19
  @key = key
20
20
  @deterministic = deterministic
21
+ @support_unencrypted_data = support_unencrypted_data
21
22
  @downcase = downcase || ignore_case
22
23
  @ignore_case = ignore_case
23
24
  @previous_schemes_param = previous_schemes
@@ -36,7 +37,11 @@ module ActiveRecord
36
37
  end
37
38
 
38
39
  def deterministic?
39
- @deterministic
40
+ !!@deterministic
41
+ end
42
+
43
+ def support_unencrypted_data?
44
+ @support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
40
45
  end
41
46
 
42
47
  def fixed?
@@ -45,10 +50,7 @@ module ActiveRecord
45
50
  end
46
51
 
47
52
  def key_provider
48
- @key_provider ||= begin
49
- validate_keys!
50
- @key_provider_param || build_key_provider
51
- end
53
+ @key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider
52
54
  end
53
55
 
54
56
  def merge(other_scheme)
@@ -56,7 +58,7 @@ module ActiveRecord
56
58
  end
57
59
 
58
60
  def to_h
59
- { key_provider: @key_provider_param, key: @key, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
61
+ { key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
60
62
  previous_schemes: @previous_schemes_param, **@context_properties }.compact
61
63
  end
62
64
 
@@ -68,31 +70,30 @@ module ActiveRecord
68
70
  end
69
71
  end
70
72
 
73
+ def compatible_with?(other_scheme)
74
+ deterministic? == other_scheme.deterministic?
75
+ end
76
+
71
77
  private
72
78
  def validate_config!
73
79
  raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
74
80
  raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
75
81
  end
76
82
 
77
- def validate_keys!
78
- validate_credential :key_derivation_salt
79
- validate_credential :primary_key, "needs to be configured to use non-deterministic encryption" unless @deterministic
80
- validate_credential :deterministic_key, "needs to be configured to use deterministic encryption" if @deterministic
83
+ def key_provider_from_key
84
+ @key_provider_from_key ||= if @key.present?
85
+ DerivedSecretKeyProvider.new(@key)
86
+ end
81
87
  end
82
88
 
83
- def validate_credential(key, error_message = "is not configured")
84
- unless ActiveRecord::Encryption.config.public_send(key).present?
85
- raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
86
- "active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
89
+ def deterministic_key_provider
90
+ @deterministic_key_provider ||= if @deterministic
91
+ DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
87
92
  end
88
93
  end
89
94
 
90
- def build_key_provider
91
- return DerivedSecretKeyProvider.new(@key) if @key.present?
92
-
93
- if @deterministic && (deterministic_key = ActiveRecord::Encryption.config.deterministic_key)
94
- DeterministicKeyProvider.new(deterministic_key)
95
- end
95
+ def default_key_provider
96
+ ActiveRecord::Encryption.key_provider
96
97
  end
97
98
  end
98
99
  end
@@ -8,6 +8,7 @@ module ActiveRecord
8
8
  extend ActiveSupport::Autoload
9
9
 
10
10
  eager_autoload do
11
+ autoload :AutoFilteredParameters
11
12
  autoload :Cipher
12
13
  autoload :Config
13
14
  autoload :Configurable
@@ -52,4 +53,6 @@ module ActiveRecord
52
53
  Cipher.eager_load!
53
54
  end
54
55
  end
56
+
57
+ ActiveSupport.run_load_hooks :active_record_encryption, Encryption
55
58
  end
@@ -111,23 +111,70 @@ module ActiveRecord
111
111
  #
112
112
  # conversation.comments_inactive!
113
113
  # conversation.comments_active? # => false
114
+ #
115
+ # If you want to disable the auto-generated methods on the model, you can do
116
+ # so by setting the +:instance_methods+ option to false:
117
+ #
118
+ # class Conversation < ActiveRecord::Base
119
+ # enum :status, [ :active, :archived ], instance_methods: false
120
+ # end
121
+ #
122
+ # If you want the enum value to be validated before saving, use the option +:validate+:
123
+ #
124
+ # class Conversation < ActiveRecord::Base
125
+ # enum :status, [ :active, :archived ], validate: true
126
+ # end
127
+ #
128
+ # conversation = Conversation.new
129
+ #
130
+ # conversation.status = :unknown
131
+ # conversation.valid? # => false
132
+ #
133
+ # conversation.status = nil
134
+ # conversation.valid? # => false
135
+ #
136
+ # conversation.status = :active
137
+ # conversation.valid? # => true
138
+ #
139
+ # It is also possible to pass additional validation options:
140
+ #
141
+ # class Conversation < ActiveRecord::Base
142
+ # enum :status, [ :active, :archived ], validate: { allow_nil: true }
143
+ # end
144
+ #
145
+ # conversation = Conversation.new
146
+ #
147
+ # conversation.status = :unknown
148
+ # conversation.valid? # => false
149
+ #
150
+ # conversation.status = nil
151
+ # conversation.valid? # => true
152
+ #
153
+ # conversation.status = :active
154
+ # conversation.valid? # => true
155
+ #
156
+ # Otherwise +ArgumentError+ will raise:
157
+ #
158
+ # class Conversation < ActiveRecord::Base
159
+ # enum :status, [ :active, :archived ]
160
+ # end
161
+ #
162
+ # conversation = Conversation.new
163
+ #
164
+ # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
114
165
  module Enum
115
166
  def self.extended(base) # :nodoc:
116
167
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
117
168
  end
118
169
 
119
- def inherited(base) # :nodoc:
120
- base.defined_enums = defined_enums.deep_dup
121
- super
122
- end
123
-
124
170
  class EnumType < Type::Value # :nodoc:
125
171
  delegate :type, to: :subtype
126
172
 
127
- def initialize(name, mapping, subtype)
173
+ def initialize(name, mapping, subtype, raise_on_invalid_values: true)
128
174
  @name = name
129
175
  @mapping = mapping
130
176
  @subtype = subtype
177
+ @_raise_on_invalid_values = raise_on_invalid_values
131
178
  end
132
179
 
133
180
  def cast(value)
@@ -153,6 +200,8 @@ module ActiveRecord
153
200
  end
154
201
 
155
202
  def assert_valid_value(value)
203
+ return unless @_raise_on_invalid_values
204
+
156
205
  unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
157
206
  raise ArgumentError, "'#{value}' is not a valid #{name}"
158
207
  end
@@ -170,14 +219,19 @@ module ActiveRecord
170
219
  return _enum(name, values, **options)
171
220
  end
172
221
 
173
- definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
222
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
174
223
  options.transform_keys! { |key| :"#{key[1..-1]}" }
175
224
 
176
225
  definitions.each { |name, values| _enum(name, values, **options) }
177
226
  end
178
227
 
179
228
  private
180
- def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options)
229
+ def inherited(base)
230
+ base.defined_enums = defined_enums.deep_dup
231
+ super
232
+ end
233
+
234
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
181
235
  assert_valid_enum_definition_values(values)
182
236
  # statuses = { }
183
237
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -192,8 +246,14 @@ module ActiveRecord
192
246
  detect_enum_conflict!(name, "#{name}=")
193
247
 
194
248
  attribute(name, **options) do |subtype|
249
+ if subtype == ActiveModel::Type.default_value
250
+ raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
251
+ " backed by a database column or declared with an explicit type" \
252
+ " via `attribute`."
253
+ end
254
+
195
255
  subtype = subtype.subtype if EnumType === subtype
196
- EnumType.new(name, enum_values, subtype)
256
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
197
257
  end
198
258
 
199
259
  value_method_names = []
@@ -213,18 +273,24 @@ module ActiveRecord
213
273
 
214
274
  value_method_name = "#{prefix}#{label}#{suffix}"
215
275
  value_method_names << value_method_name
216
- define_enum_methods(name, value_method_name, value, scopes)
276
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
217
277
 
218
278
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
219
279
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
220
280
 
221
281
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
222
282
  value_method_names << value_method_alias
223
- define_enum_methods(name, value_method_alias, value, scopes)
283
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
224
284
  end
225
285
  end
226
286
  end
227
287
  detect_negative_enum_conditions!(value_method_names) if scopes
288
+
289
+ if validate
290
+ validate = {} unless Hash === validate
291
+ validates_inclusion_of name, in: enum_values.keys, **validate
292
+ end
293
+
228
294
  enum_values.freeze
229
295
  end
230
296
 
@@ -236,21 +302,23 @@ module ActiveRecord
236
302
  private
237
303
  attr_reader :klass
238
304
 
239
- def define_enum_methods(name, value_method_name, value, scopes)
240
- # def active?() status_for_database == 0 end
241
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
242
- define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
305
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
306
+ if instance_methods
307
+ # def active?() status_for_database == 0 end
308
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
309
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
243
310
 
244
- # def active!() update!(status: 0) end
245
- klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
246
- define_method("#{value_method_name}!") { update!(name => value) }
311
+ # def active!() update!(status: 0) end
312
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
313
+ define_method("#{value_method_name}!") { update!(name => value) }
314
+ end
247
315
 
248
- # scope :active, -> { where(status: 0) }
249
- # scope :not_active, -> { where.not(status: 0) }
250
316
  if scopes
317
+ # scope :active, -> { where(status: 0) }
251
318
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
252
319
  klass.scope value_method_name, -> { where(name => value) }
253
320
 
321
+ # scope :not_active, -> { where.not(status: 0) }
254
322
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
255
323
  klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
256
324
  end
@@ -267,15 +335,29 @@ module ActiveRecord
267
335
  end
268
336
 
269
337
  def assert_valid_enum_definition_values(values)
270
- unless values.is_a?(Hash) || values.all?(Symbol) || values.all?(String)
271
- error_message = <<~MSG
272
- Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
273
- MSG
274
- raise ArgumentError, error_message
275
- end
338
+ case values
339
+ when Hash
340
+ if values.empty?
341
+ raise ArgumentError, "Enum values #{values} must not be empty."
342
+ end
276
343
 
277
- if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
278
- raise ArgumentError, "Enum label name must not be blank."
344
+ if values.keys.any?(&:blank?)
345
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
346
+ end
347
+ when Array
348
+ if values.empty?
349
+ raise ArgumentError, "Enum values #{values} must not be empty."
350
+ end
351
+
352
+ unless values.all?(Symbol) || values.all?(String)
353
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
354
+ end
355
+
356
+ if values.any?(&:blank?)
357
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
358
+ end
359
+ else
360
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
279
361
  end
280
362
  end
281
363
 
@@ -290,6 +372,8 @@ module ActiveRecord
290
372
  raise_conflict_error(enum_name, method_name, type: "class")
291
373
  elsif klass_method && method_defined_within?(method_name, Relation)
292
374
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
375
+ elsif klass_method && method_name.to_sym == :id
376
+ raise_conflict_error(enum_name, method_name)
293
377
  elsif !klass_method && dangerous_attribute_method?(method_name)
294
378
  raise_conflict_error(enum_name, method_name)
295
379
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
@@ -7,9 +7,13 @@ module ActiveRecord
7
7
  class ActiveRecordError < StandardError
8
8
  end
9
9
 
10
- # Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present.
11
- class ActiveJobRequiredError < ActiveRecordError
12
- end
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
13
17
 
14
18
  # Raised when the single-table inheritance mechanism fails to locate the subclass
15
19
  # (for example due to improper usage of column that
@@ -51,10 +55,31 @@ module ActiveRecord
51
55
  class AdapterNotFound < ActiveRecordError
52
56
  end
53
57
 
58
+ # Superclass for all errors raised from an Active Record adapter.
59
+ class AdapterError < ActiveRecordError
60
+ def initialize(message = nil, connection_pool: nil)
61
+ @connection_pool = connection_pool
62
+ super(message)
63
+ end
64
+
65
+ attr_reader :connection_pool
66
+ end
67
+
54
68
  # Raised when connection to the database could not been established (for example when
55
69
  # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
56
70
  # is given a +nil+ object).
57
- class ConnectionNotEstablished < ActiveRecordError
71
+ class ConnectionNotEstablished < AdapterError
72
+ def initialize(message = nil, connection_pool: nil)
73
+ super(message, connection_pool: connection_pool)
74
+ end
75
+
76
+ def set_pool(connection_pool)
77
+ unless @connection_pool
78
+ @connection_pool = connection_pool
79
+ end
80
+
81
+ self
82
+ end
58
83
  end
59
84
 
60
85
  # Raised when a connection could not be obtained within the connection
@@ -90,7 +115,7 @@ module ActiveRecord
90
115
  # Raised when a pool was unable to get ahold of all its connections
91
116
  # to perform a "group" action such as
92
117
  # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
93
- # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
118
+ # or {ActiveRecord::Base.connection_handler.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
94
119
  class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
95
120
  end
96
121
 
@@ -155,14 +180,23 @@ module ActiveRecord
155
180
  # Superclass for all database execution errors.
156
181
  #
157
182
  # Wraps the underlying database error as +cause+.
158
- class StatementInvalid < ActiveRecordError
159
- def initialize(message = nil, sql: nil, binds: nil)
160
- super(message || $!&.message)
183
+ class StatementInvalid < AdapterError
184
+ def initialize(message = nil, sql: nil, binds: nil, connection_pool: nil)
185
+ super(message || $!&.message, connection_pool: connection_pool)
161
186
  @sql = sql
162
187
  @binds = binds
163
188
  end
164
189
 
165
190
  attr_reader :sql, :binds
191
+
192
+ def set_query(sql, binds)
193
+ unless @sql
194
+ @sql = sql
195
+ @binds = binds
196
+ end
197
+
198
+ self
199
+ end
166
200
  end
167
201
 
168
202
  # Defunct wrapper class kept for compatibility.
@@ -189,8 +223,13 @@ module ActiveRecord
189
223
  foreign_key: nil,
190
224
  target_table: nil,
191
225
  primary_key: nil,
192
- primary_key_column: nil
226
+ primary_key_column: nil,
227
+ query_parser: nil,
228
+ connection_pool: nil
193
229
  )
230
+ @original_message = message
231
+ @query_parser = query_parser
232
+
194
233
  if table
195
234
  type = primary_key_column.bigint? ? :bigint : primary_key_column.type
196
235
  msg = <<~EOM.squish
@@ -208,7 +247,24 @@ module ActiveRecord
208
247
  if message
209
248
  msg << "\nOriginal message: #{message}"
210
249
  end
211
- super(msg, sql: sql, binds: binds)
250
+
251
+ super(msg, sql: sql, binds: binds, connection_pool: connection_pool)
252
+ end
253
+
254
+ def set_query(sql, binds)
255
+ if @query_parser && !@sql
256
+ self.class.new(
257
+ message: @original_message,
258
+ sql: sql,
259
+ binds: binds,
260
+ connection_pool: @connection_pool,
261
+ **@query_parser.call(sql)
262
+ ).tap do |exception|
263
+ exception.set_backtrace backtrace
264
+ end
265
+ else
266
+ super
267
+ end
212
268
  end
213
269
  end
214
270
 
@@ -224,6 +280,19 @@ module ActiveRecord
224
280
  class RangeError < StatementInvalid
225
281
  end
226
282
 
283
+ # Raised when a statement produces an SQL warning.
284
+ class SQLWarning < AdapterError
285
+ attr_reader :code, :level
286
+ attr_accessor :sql
287
+
288
+ def initialize(message = nil, code = nil, level = nil, sql = nil, connection_pool = nil)
289
+ super(message, connection_pool: connection_pool)
290
+ @code = code
291
+ @level = level
292
+ @sql = sql
293
+ end
294
+ end
295
+
227
296
  # Raised when the number of placeholders in an SQL fragment passed to
228
297
  # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
229
298
  # does not match the number of values supplied.
@@ -242,21 +311,22 @@ module ActiveRecord
242
311
  ActiveRecord::Tasks::DatabaseTasks.create_current
243
312
  end
244
313
 
245
- def initialize(message = nil)
246
- super(message || "Database not found")
314
+ def initialize(message = nil, connection_pool: nil)
315
+ super(message || "Database not found", connection_pool: connection_pool)
247
316
  end
248
317
 
249
318
  class << self
250
319
  def db_error(db_name)
251
320
  NoDatabaseError.new(<<~MSG)
252
- We could not find your database: #{db_name}. Which can be found in the database configuration file located at config/database.yml.
321
+ We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml.
253
322
 
254
- To resolve this issue:
323
+ To resolve this error:
255
324
 
256
- - Did you create the database for this app, or delete it? You may need to create your database.
257
- - Has the database name changed? Check your database.yml config has the correct database name.
325
+ - Did you not create the database, or did you delete it? To create the database, run:
258
326
 
259
- To create your database, run:\n\n bin/rails db:create
327
+ bin/rails db:create
328
+
329
+ - Has the database name changed? Verify that config/database.yml contains the correct database name.
260
330
  MSG
261
331
  end
262
332
  end
@@ -407,13 +477,26 @@ module ActiveRecord
407
477
  # * You are joining an existing open transaction
408
478
  # * You are creating a nested (savepoint) transaction
409
479
  #
410
- # The mysql2 and postgresql adapters support setting the transaction isolation level.
480
+ # The mysql2, trilogy, and postgresql adapters support setting the transaction isolation level.
411
481
  class TransactionIsolationError < ActiveRecordError
412
482
  end
413
483
 
414
484
  # TransactionRollbackError will be raised when a transaction is rolled
415
485
  # back by the database due to a serialization failure or a deadlock.
416
486
  #
487
+ # These exceptions should not be generally rescued in nested transaction
488
+ # blocks, because they have side-effects in the actual enclosing transaction
489
+ # and internal Active Record state. They can be rescued if you are above the
490
+ # root transaction block, though.
491
+ #
492
+ # In that case, beware of transactional tests, however, because they run test
493
+ # cases in their own umbrella transaction. If you absolutely need to handle
494
+ # these exceptions in tests please consider disabling transactional tests in
495
+ # the affected test class (<tt>self.use_transactional_tests = false</tt>).
496
+ #
497
+ # Due to the aforementioned side-effects, this exception should not be raised
498
+ # manually by users.
499
+ #
417
500
  # See the following:
418
501
  #
419
502
  # * https://www.postgresql.org/docs/current/static/transaction-iso.html
@@ -428,11 +511,17 @@ module ActiveRecord
428
511
 
429
512
  # SerializationFailure will be raised when a transaction is rolled
430
513
  # back by the database due to a serialization failure.
514
+ #
515
+ # This is a subclass of TransactionRollbackError, please make sure to check
516
+ # its documentation to be aware of its caveats.
431
517
  class SerializationFailure < TransactionRollbackError
432
518
  end
433
519
 
434
520
  # Deadlocked will be raised when a transaction is rolled
435
521
  # back by the database when a deadlock is encountered.
522
+ #
523
+ # This is a subclass of TransactionRollbackError, please make sure to check
524
+ # its documentation to be aware of its caveats.
436
525
  class Deadlocked < TransactionRollbackError
437
526
  end
438
527
 
@@ -461,6 +550,11 @@ module ActiveRecord
461
550
  class AdapterTimeout < QueryAborted
462
551
  end
463
552
 
553
+ # ConnectionFailed will be raised when the network connection to the
554
+ # database fails while sending a query or waiting for its result.
555
+ class ConnectionFailed < QueryAborted
556
+ end
557
+
464
558
  # UnknownAttributeReference is raised when an unknown and potentially unsafe
465
559
  # value is passed to a query method. For example, passing a non column name
466
560
  # value to a relation's #order method might cause this exception.
@@ -16,15 +16,15 @@ module ActiveRecord
16
16
 
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
- def exec_explain(queries) # :nodoc:
19
+ def exec_explain(queries, options = []) # :nodoc:
20
20
  str = queries.map do |sql, binds|
21
- msg = +"EXPLAIN for: #{sql}"
21
+ msg = +"#{build_explain_clause(options)} #{sql}"
22
22
  unless binds.empty?
23
23
  msg << " "
24
24
  msg << binds.map { |attr| render_bind(attr) }.inspect
25
25
  end
26
26
  msg << "\n"
27
- msg << connection.explain(sql, binds)
27
+ msg << connection_explain(sql, binds, options)
28
28
  end.join("\n")
29
29
 
30
30
  # Overriding inspect to be more human readable, especially in the console.
@@ -50,5 +50,25 @@ module ActiveRecord
50
50
 
51
51
  [attr&.name, value]
52
52
  end
53
+
54
+ def build_explain_clause(options = [])
55
+ if connection.respond_to?(:build_explain_clause, true)
56
+ connection.build_explain_clause(options)
57
+ else
58
+ "EXPLAIN for:"
59
+ end
60
+ 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
53
73
  end
54
74
  end