activerecord 6.1.6 → 7.0.4

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

Potentially problematic release.


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

Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1314 -975
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +124 -95
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +10 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +124 -134
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +67 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +50 -43
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +20 -23
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +1 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +228 -60
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +276 -67
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +189 -88
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +17 -12
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +60 -13
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +7 -2
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +16 -9
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +217 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +55 -11
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # This is a wrapper for a hash of encryption properties. It is used by
6
+ # +Key+ (public tags) and +Message+ (headers).
7
+ #
8
+ # Since properties are serialized in messages, it is important for storage
9
+ # efficiency to keep their keys as short as possible. It defines accessors
10
+ # for common properties that will keep these keys very short while exposing
11
+ # a readable name.
12
+ #
13
+ # message.headers.encrypted_data_key # instead of message.headers[:k]
14
+ #
15
+ # See +Properties::DEFAULT_PROPERTIES+, Key, Message
16
+ class Properties
17
+ ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
18
+
19
+ delegate_missing_to :data
20
+ delegate :==, to: :data
21
+
22
+ # For each entry it generates an accessor exposing the full name
23
+ DEFAULT_PROPERTIES = {
24
+ encrypted_data_key: "k",
25
+ encrypted_data_key_id: "i",
26
+ compressed: "c",
27
+ iv: "iv",
28
+ auth_tag: "at",
29
+ encoding: "e"
30
+ }
31
+
32
+ DEFAULT_PROPERTIES.each do |name, key|
33
+ define_method name do
34
+ self[key.to_sym]
35
+ end
36
+
37
+ define_method "#{name}=" do |value|
38
+ self[key.to_sym] = value
39
+ end
40
+ end
41
+
42
+ def initialize(initial_properties = {})
43
+ @data = {}
44
+ add(initial_properties)
45
+ end
46
+
47
+ # Set a value for a given key
48
+ #
49
+ # It will raise an +EncryptedContentIntegrity+ if the value exists
50
+ def []=(key, value)
51
+ raise Errors::EncryptedContentIntegrity, "Properties can't be overridden: #{key}" if key?(key)
52
+ validate_value_type(value)
53
+ data[key] = value
54
+ end
55
+
56
+ def validate_value_type(value)
57
+ unless ALLOWED_VALUE_CLASSES.find { |klass| value.is_a?(klass) }
58
+ raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
59
+ end
60
+ end
61
+
62
+ def add(other_properties)
63
+ other_properties.each do |key, value|
64
+ self[key.to_sym] = value
65
+ end
66
+ end
67
+
68
+ def to_h
69
+ data
70
+ end
71
+
72
+ private
73
+ attr_reader :data
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A +NullEncryptor+ that will raise an error when trying to encrypt data
6
+ #
7
+ # This is useful when you want to reveal ciphertexts for debugging purposes
8
+ # and you want to make sure you won't overwrite any encryptable attribute with
9
+ # the wrong content.
10
+ class ReadOnlyNullEncryptor
11
+ def encrypt(clean_text, key_provider: nil, cipher_options: {})
12
+ raise Errors::Encryption, "This encryptor is read-only"
13
+ end
14
+
15
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
16
+ encrypted_text
17
+ end
18
+
19
+ def encrypted?(text)
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Encryption
5
+ # A container of attribute encryption options.
6
+ #
7
+ # It validates and serves attribute encryption options.
8
+ #
9
+ # See EncryptedAttributeType, Context
10
+ class Scheme
11
+ attr_accessor :previous_schemes
12
+
13
+ def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
14
+ previous_schemes: nil, **context_properties)
15
+ # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
+ # can merge schemes without overriding values with defaults. See +#merge+
17
+
18
+ @key_provider_param = key_provider
19
+ @key = key
20
+ @deterministic = deterministic
21
+ @downcase = downcase || ignore_case
22
+ @ignore_case = ignore_case
23
+ @previous_schemes_param = previous_schemes
24
+ @previous_schemes = Array.wrap(previous_schemes)
25
+ @context_properties = context_properties
26
+
27
+ validate_config!
28
+ end
29
+
30
+ def ignore_case?
31
+ @ignore_case
32
+ end
33
+
34
+ def downcase?
35
+ @downcase
36
+ end
37
+
38
+ def deterministic?
39
+ @deterministic
40
+ end
41
+
42
+ def fixed?
43
+ # by default deterministic encryption is fixed
44
+ @fixed ||= @deterministic && (!@deterministic.is_a?(Hash) || @deterministic[:fixed])
45
+ end
46
+
47
+ def key_provider
48
+ @key_provider ||= begin
49
+ validate_keys!
50
+ @key_provider_param || build_key_provider
51
+ end
52
+ end
53
+
54
+ def merge(other_scheme)
55
+ self.class.new(**to_h.merge(other_scheme.to_h))
56
+ end
57
+
58
+ def to_h
59
+ { key_provider: @key_provider_param, key: @key, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
60
+ previous_schemes: @previous_schemes_param, **@context_properties }.compact
61
+ end
62
+
63
+ def with_context(&block)
64
+ if @context_properties.present?
65
+ ActiveRecord::Encryption.with_encryption_context(**@context_properties, &block)
66
+ else
67
+ block.call
68
+ end
69
+ end
70
+
71
+ private
72
+ def validate_config!
73
+ raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
74
+ raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
75
+ end
76
+
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
81
+ end
82
+
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}"
87
+ end
88
+ end
89
+
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
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module"
4
+ require "active_support/core_ext/array"
5
+
6
+ module ActiveRecord
7
+ module Encryption
8
+ extend ActiveSupport::Autoload
9
+
10
+ eager_autoload do
11
+ autoload :Cipher
12
+ autoload :Config
13
+ autoload :Configurable
14
+ autoload :Context
15
+ autoload :Contexts
16
+ autoload :DerivedSecretKeyProvider
17
+ autoload :EncryptableRecord
18
+ autoload :EncryptedAttributeType
19
+ autoload :EncryptedFixtures
20
+ autoload :EncryptingOnlyEncryptor
21
+ autoload :DeterministicKeyProvider
22
+ autoload :Encryptor
23
+ autoload :EnvelopeEncryptionKeyProvider
24
+ autoload :Errors
25
+ autoload :ExtendedDeterministicQueries
26
+ autoload :ExtendedDeterministicUniquenessValidator
27
+ autoload :Key
28
+ autoload :KeyGenerator
29
+ autoload :KeyProvider
30
+ autoload :Message
31
+ autoload :MessageSerializer
32
+ autoload :NullEncryptor
33
+ autoload :Properties
34
+ autoload :ReadOnlyNullEncryptor
35
+ autoload :Scheme
36
+ end
37
+
38
+ class Cipher
39
+ extend ActiveSupport::Autoload
40
+
41
+ eager_autoload do
42
+ autoload :Aes256Gcm
43
+ end
44
+ end
45
+
46
+ include Configurable
47
+ include Contexts
48
+
49
+ def self.eager_load!
50
+ super
51
+
52
+ Cipher.eager_load!
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/hash/slice"
3
4
  require "active_support/core_ext/object/deep_dup"
4
5
 
5
6
  module ActiveRecord
@@ -7,7 +8,7 @@ module ActiveRecord
7
8
  # but can be queried by name. Example:
8
9
  #
9
10
  # class Conversation < ActiveRecord::Base
10
- # enum status: [ :active, :archived ]
11
+ # enum :status, [ :active, :archived ]
11
12
  # end
12
13
  #
13
14
  # # conversation.update! status: 0
@@ -41,26 +42,33 @@ module ActiveRecord
41
42
  # Conversation.where(status: [:active, :archived])
42
43
  # Conversation.where.not(status: :active)
43
44
  #
44
- # Defining scopes can be disabled by setting +:_scopes+ to +false+.
45
+ # Defining scopes can be disabled by setting +:scopes+ to +false+.
45
46
  #
46
47
  # class Conversation < ActiveRecord::Base
47
- # enum status: [ :active, :archived ], _scopes: false
48
+ # enum :status, [ :active, :archived ], scopes: false
48
49
  # end
49
50
  #
50
- # You can set the default enum value by setting +:_default+, like:
51
+ # You can set the default enum value by setting +:default+, like:
51
52
  #
52
53
  # class Conversation < ActiveRecord::Base
53
- # enum status: [ :active, :archived ], _default: "active"
54
+ # enum :status, [ :active, :archived ], default: :active
54
55
  # end
55
56
  #
56
57
  # conversation = Conversation.new
57
58
  # conversation.status # => "active"
58
59
  #
59
- # Finally, it's also possible to explicitly map the relation between attribute and
60
+ # It's possible to explicitly map the relation between attribute and
60
61
  # database integer with a hash:
61
62
  #
62
63
  # class Conversation < ActiveRecord::Base
63
- # enum status: { active: 0, archived: 1 }
64
+ # enum :status, active: 0, archived: 1
65
+ # end
66
+ #
67
+ # Finally it's also possible to use a string column to persist the enumerated value.
68
+ # Note that this will likely lead to slower database queries:
69
+ #
70
+ # class Conversation < ActiveRecord::Base
71
+ # enum :status, active: "active", archived: "archived"
64
72
  # end
65
73
  #
66
74
  # Note that when an array is used, the implicit mapping from the values to database
@@ -75,7 +83,7 @@ module ActiveRecord
75
83
  #
76
84
  # In rare circumstances you might need to access the mapping directly.
77
85
  # The mappings are exposed through a class method with the pluralized attribute
78
- # name, which return the mapping in a +HashWithIndifferentAccess+:
86
+ # name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess :
79
87
  #
80
88
  # Conversation.statuses[:active] # => 0
81
89
  # Conversation.statuses["archived"] # => 1
@@ -85,14 +93,14 @@ module ActiveRecord
85
93
  #
86
94
  # Conversation.where("status <> ?", Conversation.statuses[:archived])
87
95
  #
88
- # You can use the +:_prefix+ or +:_suffix+ options when you need to define
96
+ # You can use the +:prefix+ or +:suffix+ options when you need to define
89
97
  # multiple enums with same values. If the passed value is +true+, the methods
90
98
  # are prefixed/suffixed with the name of the enum. It is also possible to
91
99
  # supply a custom value:
92
100
  #
93
101
  # class Conversation < ActiveRecord::Base
94
- # enum status: [:active, :archived], _suffix: true
95
- # enum comments_status: [:active, :inactive], _prefix: :comments
102
+ # enum :status, [ :active, :archived ], suffix: true
103
+ # enum :comments_status, [ :active, :inactive ], prefix: :comments
96
104
  # end
97
105
  #
98
106
  # With the above example, the bang and predicate methods along with the
@@ -103,7 +111,6 @@ module ActiveRecord
103
111
  #
104
112
  # conversation.comments_inactive!
105
113
  # conversation.comments_active? # => false
106
-
107
114
  module Enum
108
115
  def self.extended(base) # :nodoc:
109
116
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
@@ -128,10 +135,8 @@ module ActiveRecord
128
135
  value.to_s
129
136
  elsif mapping.has_value?(value)
130
137
  mapping.key(value)
131
- elsif value.blank?
132
- nil
133
138
  else
134
- assert_valid_value(value)
139
+ value.presence
135
140
  end
136
141
  end
137
142
 
@@ -140,7 +145,11 @@ module ActiveRecord
140
145
  end
141
146
 
142
147
  def serialize(value)
143
- mapping.fetch(value, value)
148
+ subtype.serialize(mapping.fetch(value, value))
149
+ end
150
+
151
+ def serializable?(value, &block)
152
+ subtype.serializable?(mapping.fetch(value, value), &block)
144
153
  end
145
154
 
146
155
  def assert_valid_value(value)
@@ -155,15 +164,20 @@ module ActiveRecord
155
164
  attr_reader :name, :mapping
156
165
  end
157
166
 
158
- def enum(definitions)
159
- enum_prefix = definitions.delete(:_prefix)
160
- enum_suffix = definitions.delete(:_suffix)
161
- enum_scopes = definitions.delete(:_scopes)
167
+ def enum(name = nil, values = nil, **options)
168
+ if name
169
+ values, options = options, {} unless values
170
+ return _enum(name, values, **options)
171
+ end
172
+
173
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
174
+ options.transform_keys! { |key| :"#{key[1..-1]}" }
162
175
 
163
- default = {}
164
- default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
176
+ definitions.each { |name, values| _enum(name, values, **options) }
177
+ end
165
178
 
166
- definitions.each do |name, values|
179
+ private
180
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options)
167
181
  assert_valid_enum_definition_values(values)
168
182
  # statuses = { }
169
183
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -177,24 +191,19 @@ module ActiveRecord
177
191
  detect_enum_conflict!(name, name)
178
192
  detect_enum_conflict!(name, "#{name}=")
179
193
 
180
- attr = attribute_alias?(name) ? attribute_alias(name) : name
181
-
182
- decorate_attribute_type(attr, **default) do |subtype|
183
- EnumType.new(attr, enum_values, subtype)
194
+ attribute(name, **options) do |subtype|
195
+ subtype = subtype.subtype if EnumType === subtype
196
+ EnumType.new(name, enum_values, subtype)
184
197
  end
185
198
 
186
199
  value_method_names = []
187
200
  _enum_methods_module.module_eval do
188
- prefix = if enum_prefix == true
189
- "#{name}_"
190
- elsif enum_prefix
191
- "#{enum_prefix}_"
201
+ prefix = if prefix
202
+ prefix == true ? "#{name}_" : "#{prefix}_"
192
203
  end
193
204
 
194
- suffix = if enum_suffix == true
195
- "_#{name}"
196
- elsif enum_suffix
197
- "_#{enum_suffix}"
205
+ suffix = if suffix
206
+ suffix == true ? "_#{name}" : "_#{suffix}"
198
207
  end
199
208
 
200
209
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
@@ -204,23 +213,21 @@ module ActiveRecord
204
213
 
205
214
  value_method_name = "#{prefix}#{label}#{suffix}"
206
215
  value_method_names << value_method_name
207
- define_enum_methods(name, value_method_name, value, enum_scopes)
216
+ define_enum_methods(name, value_method_name, value, scopes)
208
217
 
209
218
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
210
219
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
211
220
 
212
221
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
213
222
  value_method_names << value_method_alias
214
- define_enum_methods(name, value_method_alias, value, enum_scopes)
223
+ define_enum_methods(name, value_method_alias, value, scopes)
215
224
  end
216
225
  end
217
226
  end
218
- detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
227
+ detect_negative_enum_conditions!(value_method_names) if scopes
219
228
  enum_values.freeze
220
229
  end
221
- end
222
230
 
223
- private
224
231
  class EnumMethods < Module # :nodoc:
225
232
  def initialize(klass)
226
233
  @klass = klass
@@ -229,7 +236,7 @@ module ActiveRecord
229
236
  private
230
237
  attr_reader :klass
231
238
 
232
- def define_enum_methods(name, value_method_name, value, enum_scopes)
239
+ def define_enum_methods(name, value_method_name, value, scopes)
233
240
  # def active?() status_for_database == 0 end
234
241
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
235
242
  define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
@@ -240,7 +247,7 @@ module ActiveRecord
240
247
 
241
248
  # scope :active, -> { where(status: 0) }
242
249
  # scope :not_active, -> { where.not(status: 0) }
243
- if enum_scopes != false
250
+ if scopes
244
251
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
245
252
  klass.scope value_method_name, -> { where(name => value) }
246
253
 
@@ -260,7 +267,7 @@ module ActiveRecord
260
267
  end
261
268
 
262
269
  def assert_valid_enum_definition_values(values)
263
- unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
270
+ unless values.is_a?(Hash) || values.all?(Symbol) || values.all?(String)
264
271
  error_message = <<~MSG
265
272
  Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
266
273
  MSG
@@ -63,6 +63,30 @@ module ActiveRecord
63
63
  class ConnectionTimeoutError < ConnectionNotEstablished
64
64
  end
65
65
 
66
+ # Raised when connection to the database could not been established because it was not
67
+ # able to connect to the host or when the authorization failed.
68
+ class DatabaseConnectionError < ConnectionNotEstablished
69
+ def initialize(message = nil)
70
+ super(message || "Database connection error")
71
+ end
72
+
73
+ class << self
74
+ def hostname_error(hostname)
75
+ DatabaseConnectionError.new(<<~MSG)
76
+ There is an issue connecting with your hostname: #{hostname}.\n
77
+ Please check your database configuration and ensure there is a valid connection to your database.
78
+ MSG
79
+ end
80
+
81
+ def username_error(username)
82
+ DatabaseConnectionError.new(<<~MSG)
83
+ There is an issue connecting to your database with your username/password, username: #{username}.\n
84
+ Please check your database configuration to ensure the username/password are valid.
85
+ MSG
86
+ end
87
+ end
88
+ end
89
+
66
90
  # Raised when a pool was unable to get ahold of all its connections
67
91
  # to perform a "group" action such as
68
92
  # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
@@ -100,7 +124,7 @@ module ActiveRecord
100
124
  end
101
125
 
102
126
  # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
103
- # when a call to {#destroy}[rdoc-ref:Persistence#destroy!]
127
+ # when a call to {#destroy}[rdoc-ref:Persistence#destroy]
104
128
  # would return false.
105
129
  #
106
130
  # begin
@@ -118,6 +142,16 @@ module ActiveRecord
118
142
  end
119
143
  end
120
144
 
145
+ # Raised when Active Record finds multiple records but only expected one.
146
+ class SoleRecordExceeded < ActiveRecordError
147
+ attr_reader :record
148
+
149
+ def initialize(record = nil)
150
+ @record = record
151
+ super "Wanted only one #{record&.name || "record"}"
152
+ end
153
+ end
154
+
121
155
  # Superclass for all database execution errors.
122
156
  #
123
157
  # Wraps the underlying database error as +cause+.
@@ -202,6 +236,30 @@ module ActiveRecord
202
236
 
203
237
  # Raised when a given database does not exist.
204
238
  class NoDatabaseError < StatementInvalid
239
+ include ActiveSupport::ActionableError
240
+
241
+ action "Create database" do
242
+ ActiveRecord::Tasks::DatabaseTasks.create_current
243
+ end
244
+
245
+ def initialize(message = nil)
246
+ super(message || "Database not found")
247
+ end
248
+
249
+ class << self
250
+ def db_error(db_name)
251
+ 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.
253
+
254
+ To resolve this issue:
255
+
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.
258
+
259
+ To create your database, run:\n\n bin/rails db:create
260
+ MSG
261
+ end
262
+ end
205
263
  end
206
264
 
207
265
  # Raised when creating a database if it exists.
@@ -268,7 +326,7 @@ module ActiveRecord
268
326
  # # The system must fail on Friday so that our support department
269
327
  # # won't be out of job. We silently rollback this transaction
270
328
  # # without telling the user.
271
- # raise ActiveRecord::Rollback, "Call tech support!"
329
+ # raise ActiveRecord::Rollback
272
330
  # end
273
331
  # end
274
332
  # # ActiveRecord::Rollback is the only exception that won't be passed on
@@ -363,6 +421,11 @@ module ActiveRecord
363
421
  class TransactionRollbackError < StatementInvalid
364
422
  end
365
423
 
424
+ # AsynchronousQueryInsideTransactionError will be raised when attempting
425
+ # to perform an asynchronous query from inside a transaction
426
+ class AsynchronousQueryInsideTransactionError < ActiveRecordError
427
+ end
428
+
366
429
  # SerializationFailure will be raised when a transaction is rolled
367
430
  # back by the database due to a serialization failure.
368
431
  class SerializationFailure < TransactionRollbackError
@@ -409,12 +472,12 @@ module ActiveRecord
409
472
  #
410
473
  # For example, the following code would raise this exception:
411
474
  #
412
- # Post.order("length(title)").first
475
+ # Post.order("REPLACE(title, 'misc', 'zzzz') asc").pluck(:id)
413
476
  #
414
477
  # The desired result can be accomplished by wrapping the known-safe string
415
478
  # in Arel.sql:
416
479
  #
417
- # Post.order(Arel.sql("length(title)")).first
480
+ # Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz') asc")).pluck(:id)
418
481
  #
419
482
  # Again, such a workaround should *not* be used when passing user-provided
420
483
  # values, such as request parameters or model attributes to query methods.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/per_thread_registry"
3
+ require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ActiveRecord
6
6
  # This is a thread locals registry for EXPLAIN. For example
@@ -8,13 +8,18 @@ module ActiveRecord
8
8
  # ActiveRecord::ExplainRegistry.queries
9
9
  #
10
10
  # returns the collected queries local to the current thread.
11
- #
12
- # See the documentation of ActiveSupport::PerThreadRegistry
13
- # for further details.
14
11
  class ExplainRegistry # :nodoc:
15
- extend ActiveSupport::PerThreadRegistry
12
+ class << self
13
+ delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
14
+
15
+ private
16
+ def instance
17
+ ActiveSupport::IsolatedExecutionState[:active_record_explain_registry] ||= new
18
+ end
19
+ end
16
20
 
17
- attr_accessor :queries, :collect
21
+ attr_accessor :collect
22
+ attr_reader :queries
18
23
 
19
24
  def initialize
20
25
  reset
@@ -41,7 +41,7 @@ module ActiveRecord
41
41
  @config_row ||= begin
42
42
  row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" }
43
43
  if row
44
- row.last
44
+ validate_config_row(row.last)
45
45
  else
46
46
  { 'model_class': nil, 'ignore': nil }
47
47
  end
@@ -58,6 +58,20 @@ module ActiveRecord
58
58
  end
59
59
  end
60
60
 
61
+ def validate_config_row(data)
62
+ unless Hash === data
63
+ raise Fixture::FormatError, "Invalid `_fixture` section: `_fixture` must be a hash: #{@file}"
64
+ end
65
+
66
+ begin
67
+ data.assert_valid_keys("model_class", "ignore")
68
+ rescue ArgumentError => error
69
+ raise Fixture::FormatError, "Invalid `_fixture` section: #{error.message}: #{@file}"
70
+ end
71
+
72
+ data
73
+ end
74
+
61
75
  # Validate our unmarshalled data.
62
76
  def validate(data)
63
77
  unless Hash === data || YAML::Omap === data