activerecord 6.1.7.2 → 7.0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1295 -1007
  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 +4 -8
  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 +71 -71
  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 +206 -105
  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 +123 -148
  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 +149 -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 +1 -1
  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 +9 -6
  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 +225 -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 +53 -9
@@ -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