activerecord 7.0.8 → 7.2.0

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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +58 -45
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +10 -24
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +317 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -128
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +274 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. data/lib/active_record/null_relation.rb +0 -63
@@ -19,6 +19,10 @@ module ActiveRecord
19
19
  def encrypted?(text)
20
20
  false
21
21
  end
22
+
23
+ def binary?
24
+ false
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -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
@@ -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,26 @@ 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) }
226
+
227
+ ActiveRecord.deprecator.warn(<<~MSG)
228
+ Defining enums with keyword arguments is deprecated and will be removed
229
+ in Rails 8.0. Positional arguments should be used instead:
230
+
231
+ #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
232
+ MSG
177
233
  end
178
234
 
179
235
  private
180
- def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options)
236
+ def inherited(base)
237
+ base.defined_enums = defined_enums.deep_dup
238
+ super
239
+ end
240
+
241
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
181
242
  assert_valid_enum_definition_values(values)
182
243
  # statuses = { }
183
244
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -191,9 +252,17 @@ module ActiveRecord
191
252
  detect_enum_conflict!(name, name)
192
253
  detect_enum_conflict!(name, "#{name}=")
193
254
 
194
- attribute(name, **options) do |subtype|
255
+ attribute(name, **options)
256
+
257
+ decorate_attributes([name]) do |_name, subtype|
258
+ if subtype == ActiveModel::Type.default_value
259
+ raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
260
+ " backed by a database column or declared with an explicit type" \
261
+ " via `attribute`."
262
+ end
263
+
195
264
  subtype = subtype.subtype if EnumType === subtype
196
- EnumType.new(name, enum_values, subtype)
265
+ EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
197
266
  end
198
267
 
199
268
  value_method_names = []
@@ -213,18 +282,24 @@ module ActiveRecord
213
282
 
214
283
  value_method_name = "#{prefix}#{label}#{suffix}"
215
284
  value_method_names << value_method_name
216
- define_enum_methods(name, value_method_name, value, scopes)
285
+ define_enum_methods(name, value_method_name, value, scopes, instance_methods)
217
286
 
218
287
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
219
288
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
220
289
 
221
290
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
222
291
  value_method_names << value_method_alias
223
- define_enum_methods(name, value_method_alias, value, scopes)
292
+ define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
224
293
  end
225
294
  end
226
295
  end
227
296
  detect_negative_enum_conditions!(value_method_names) if scopes
297
+
298
+ if validate
299
+ validate = {} unless Hash === validate
300
+ validates_inclusion_of name, in: enum_values.keys, **validate
301
+ end
302
+
228
303
  enum_values.freeze
229
304
  end
230
305
 
@@ -236,21 +311,23 @@ module ActiveRecord
236
311
  private
237
312
  attr_reader :klass
238
313
 
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 }
314
+ def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
315
+ if instance_methods
316
+ # def active?() status_for_database == 0 end
317
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
318
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
243
319
 
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) }
320
+ # def active!() update!(status: 0) end
321
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
322
+ define_method("#{value_method_name}!") { update!(name => value) }
323
+ end
247
324
 
248
- # scope :active, -> { where(status: 0) }
249
- # scope :not_active, -> { where.not(status: 0) }
250
325
  if scopes
326
+ # scope :active, -> { where(status: 0) }
251
327
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
252
328
  klass.scope value_method_name, -> { where(name => value) }
253
329
 
330
+ # scope :not_active, -> { where.not(status: 0) }
254
331
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
255
332
  klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
256
333
  end
@@ -267,15 +344,29 @@ module ActiveRecord
267
344
  end
268
345
 
269
346
  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
347
+ case values
348
+ when Hash
349
+ if values.empty?
350
+ raise ArgumentError, "Enum values #{values} must not be empty."
351
+ end
352
+
353
+ if values.keys.any?(&:blank?)
354
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
355
+ end
356
+ when Array
357
+ if values.empty?
358
+ raise ArgumentError, "Enum values #{values} must not be empty."
359
+ end
360
+
361
+ unless values.all?(Symbol) || values.all?(String)
362
+ raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
363
+ end
276
364
 
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."
365
+ if values.any?(&:blank?)
366
+ raise ArgumentError, "Enum values #{values} must not contain a blank name."
367
+ end
368
+ else
369
+ raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
279
370
  end
280
371
  end
281
372
 
@@ -290,6 +381,8 @@ module ActiveRecord
290
381
  raise_conflict_error(enum_name, method_name, type: "class")
291
382
  elsif klass_method && method_defined_within?(method_name, Relation)
292
383
  raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
384
+ elsif klass_method && method_name.to_sym == :id
385
+ raise_conflict_error(enum_name, method_name)
293
386
  elsif !klass_method && dangerous_attribute_method?(method_name)
294
387
  raise_conflict_error(enum_name, method_name)
295
388
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/deprecation"
4
+
3
5
  module ActiveRecord
6
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
7
+
4
8
  # = Active Record Errors
5
9
  #
6
10
  # Generic Active Record exception class.
7
11
  class ActiveRecordError < StandardError
8
12
  end
9
13
 
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
13
-
14
14
  # Raised when the single-table inheritance mechanism fails to locate the subclass
15
15
  # (for example due to improper usage of column that
16
16
  # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
@@ -51,10 +51,31 @@ module ActiveRecord
51
51
  class AdapterNotFound < ActiveRecordError
52
52
  end
53
53
 
54
+ # Superclass for all errors raised from an Active Record adapter.
55
+ class AdapterError < ActiveRecordError
56
+ def initialize(message = nil, connection_pool: nil)
57
+ @connection_pool = connection_pool
58
+ super(message)
59
+ end
60
+
61
+ attr_reader :connection_pool
62
+ end
63
+
54
64
  # Raised when connection to the database could not been established (for example when
55
- # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
65
+ # {ActiveRecord::Base.lease_connection=}[rdoc-ref:ConnectionHandling#lease_connection]
56
66
  # is given a +nil+ object).
57
- class ConnectionNotEstablished < ActiveRecordError
67
+ class ConnectionNotEstablished < AdapterError
68
+ def initialize(message = nil, connection_pool: nil)
69
+ super(message, connection_pool: connection_pool)
70
+ end
71
+
72
+ def set_pool(connection_pool)
73
+ unless @connection_pool
74
+ @connection_pool = connection_pool
75
+ end
76
+
77
+ self
78
+ end
58
79
  end
59
80
 
60
81
  # Raised when a connection could not be obtained within the connection
@@ -90,7 +111,7 @@ module ActiveRecord
90
111
  # Raised when a pool was unable to get ahold of all its connections
91
112
  # to perform a "group" action such as
92
113
  # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
93
- # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
114
+ # or {ActiveRecord::Base.connection_handler.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
94
115
  class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
95
116
  end
96
117
 
@@ -112,8 +133,18 @@ module ActiveRecord
112
133
  end
113
134
 
114
135
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
115
- # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
116
- # methods when a record is invalid and cannot be saved.
136
+ # {ActiveRecord::Base.update_attribute!}[rdoc-ref:Persistence#update_attribute!]
137
+ # methods when a record failed to validate or cannot be saved due to any of the
138
+ # <tt>before_*</tt> callbacks throwing +:abort+. See
139
+ # ActiveRecord::Callbacks for further details.
140
+ #
141
+ # class Product < ActiveRecord::Base
142
+ # before_save do
143
+ # throw :abort if price < 0
144
+ # end
145
+ # end
146
+ #
147
+ # Product.create! # => raises an ActiveRecord::RecordNotSaved
117
148
  class RecordNotSaved < ActiveRecordError
118
149
  attr_reader :record
119
150
 
@@ -124,15 +155,17 @@ module ActiveRecord
124
155
  end
125
156
 
126
157
  # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
127
- # when a call to {#destroy}[rdoc-ref:Persistence#destroy]
128
- # would return false.
158
+ # when a record cannot be destroyed due to any of the
159
+ # <tt>before_destroy</tt> callbacks throwing +:abort+. See
160
+ # ActiveRecord::Callbacks for further details.
129
161
  #
130
- # begin
131
- # complex_operation_that_internally_calls_destroy!
132
- # rescue ActiveRecord::RecordNotDestroyed => invalid
133
- # puts invalid.record.errors
162
+ # class User < ActiveRecord::Base
163
+ # before_destroy do
164
+ # throw :abort if still_active?
165
+ # end
134
166
  # end
135
167
  #
168
+ # User.first.destroy! # => raises an ActiveRecord::RecordNotDestroyed
136
169
  class RecordNotDestroyed < ActiveRecordError
137
170
  attr_reader :record
138
171
 
@@ -155,14 +188,23 @@ module ActiveRecord
155
188
  # Superclass for all database execution errors.
156
189
  #
157
190
  # Wraps the underlying database error as +cause+.
158
- class StatementInvalid < ActiveRecordError
159
- def initialize(message = nil, sql: nil, binds: nil)
160
- super(message || $!&.message)
191
+ class StatementInvalid < AdapterError
192
+ def initialize(message = nil, sql: nil, binds: nil, connection_pool: nil)
193
+ super(message || $!&.message, connection_pool: connection_pool)
161
194
  @sql = sql
162
195
  @binds = binds
163
196
  end
164
197
 
165
198
  attr_reader :sql, :binds
199
+
200
+ def set_query(sql, binds)
201
+ unless @sql
202
+ @sql = sql
203
+ @binds = binds
204
+ end
205
+
206
+ self
207
+ end
166
208
  end
167
209
 
168
210
  # Defunct wrapper class kept for compatibility.
@@ -189,8 +231,13 @@ module ActiveRecord
189
231
  foreign_key: nil,
190
232
  target_table: nil,
191
233
  primary_key: nil,
192
- primary_key_column: nil
234
+ primary_key_column: nil,
235
+ query_parser: nil,
236
+ connection_pool: nil
193
237
  )
238
+ @original_message = message
239
+ @query_parser = query_parser
240
+
194
241
  if table
195
242
  type = primary_key_column.bigint? ? :bigint : primary_key_column.type
196
243
  msg = <<~EOM.squish
@@ -208,7 +255,24 @@ module ActiveRecord
208
255
  if message
209
256
  msg << "\nOriginal message: #{message}"
210
257
  end
211
- super(msg, sql: sql, binds: binds)
258
+
259
+ super(msg, sql: sql, binds: binds, connection_pool: connection_pool)
260
+ end
261
+
262
+ def set_query(sql, binds)
263
+ if @query_parser && !@sql
264
+ self.class.new(
265
+ message: @original_message,
266
+ sql: sql,
267
+ binds: binds,
268
+ connection_pool: @connection_pool,
269
+ **@query_parser.call(sql)
270
+ ).tap do |exception|
271
+ exception.set_backtrace backtrace
272
+ end
273
+ else
274
+ super
275
+ end
212
276
  end
213
277
  end
214
278
 
@@ -224,6 +288,19 @@ module ActiveRecord
224
288
  class RangeError < StatementInvalid
225
289
  end
226
290
 
291
+ # Raised when a statement produces an SQL warning.
292
+ class SQLWarning < AdapterError
293
+ attr_reader :code, :level
294
+ attr_accessor :sql
295
+
296
+ def initialize(message = nil, code = nil, level = nil, sql = nil, connection_pool = nil)
297
+ super(message, connection_pool: connection_pool)
298
+ @code = code
299
+ @level = level
300
+ @sql = sql
301
+ end
302
+ end
303
+
227
304
  # Raised when the number of placeholders in an SQL fragment passed to
228
305
  # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
229
306
  # does not match the number of values supplied.
@@ -242,21 +319,22 @@ module ActiveRecord
242
319
  ActiveRecord::Tasks::DatabaseTasks.create_current
243
320
  end
244
321
 
245
- def initialize(message = nil)
246
- super(message || "Database not found")
322
+ def initialize(message = nil, connection_pool: nil)
323
+ super(message || "Database not found", connection_pool: connection_pool)
247
324
  end
248
325
 
249
326
  class << self
250
327
  def db_error(db_name)
251
328
  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.
329
+ We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml.
330
+
331
+ To resolve this error:
253
332
 
254
- To resolve this issue:
333
+ - Did you not create the database, or did you delete it? To create the database, run:
255
334
 
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.
335
+ bin/rails db:create
258
336
 
259
- To create your database, run:\n\n bin/rails db:create
337
+ - Has the database name changed? Verify that config/database.yml contains the correct database name.
260
338
  MSG
261
339
  end
262
340
  end
@@ -304,6 +382,12 @@ module ActiveRecord
304
382
  end
305
383
 
306
384
  # Raised on attempt to lazily load records that are marked as strict loading.
385
+ #
386
+ # You can resolve this error by eager loading marked records before accessing
387
+ # them. The
388
+ # {Eager Loading Associations}[https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations]
389
+ # guide covers solutions, such as using
390
+ # {ActiveRecord::Base.includes}[rdoc-ref:QueryMethods#includes].
307
391
  class StrictLoadingViolationError < ActiveRecordError
308
392
  end
309
393
 
@@ -396,10 +480,15 @@ module ActiveRecord
396
480
  # relation.loaded? # => true
397
481
  #
398
482
  # # Methods which try to mutate a loaded relation fail.
399
- # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
400
- # relation.limit!(5) # => ActiveRecord::ImmutableRelation
401
- class ImmutableRelation < ActiveRecordError
483
+ # relation.where!(title: 'TODO') # => ActiveRecord::UnmodifiableRelation
484
+ # relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
485
+ class UnmodifiableRelation < ActiveRecordError
402
486
  end
487
+ deprecate_constant(
488
+ :ImmutableRelation,
489
+ "ActiveRecord::UnmodifiableRelation",
490
+ deprecator: ActiveRecord.deprecator
491
+ )
403
492
 
404
493
  # TransactionIsolationError will be raised under the following conditions:
405
494
  #
@@ -407,13 +496,26 @@ module ActiveRecord
407
496
  # * You are joining an existing open transaction
408
497
  # * You are creating a nested (savepoint) transaction
409
498
  #
410
- # The mysql2 and postgresql adapters support setting the transaction isolation level.
499
+ # The mysql2, trilogy, and postgresql adapters support setting the transaction isolation level.
411
500
  class TransactionIsolationError < ActiveRecordError
412
501
  end
413
502
 
414
503
  # TransactionRollbackError will be raised when a transaction is rolled
415
504
  # back by the database due to a serialization failure or a deadlock.
416
505
  #
506
+ # These exceptions should not be generally rescued in nested transaction
507
+ # blocks, because they have side-effects in the actual enclosing transaction
508
+ # and internal Active Record state. They can be rescued if you are above the
509
+ # root transaction block, though.
510
+ #
511
+ # In that case, beware of transactional tests, however, because they run test
512
+ # cases in their own umbrella transaction. If you absolutely need to handle
513
+ # these exceptions in tests please consider disabling transactional tests in
514
+ # the affected test class (<tt>self.use_transactional_tests = false</tt>).
515
+ #
516
+ # Due to the aforementioned side-effects, this exception should not be raised
517
+ # manually by users.
518
+ #
417
519
  # See the following:
418
520
  #
419
521
  # * https://www.postgresql.org/docs/current/static/transaction-iso.html
@@ -428,11 +530,17 @@ module ActiveRecord
428
530
 
429
531
  # SerializationFailure will be raised when a transaction is rolled
430
532
  # back by the database due to a serialization failure.
533
+ #
534
+ # This is a subclass of TransactionRollbackError, please make sure to check
535
+ # its documentation to be aware of its caveats.
431
536
  class SerializationFailure < TransactionRollbackError
432
537
  end
433
538
 
434
539
  # Deadlocked will be raised when a transaction is rolled
435
540
  # back by the database when a deadlock is encountered.
541
+ #
542
+ # This is a subclass of TransactionRollbackError, please make sure to check
543
+ # its documentation to be aware of its caveats.
436
544
  class Deadlocked < TransactionRollbackError
437
545
  end
438
546
 
@@ -461,6 +569,11 @@ module ActiveRecord
461
569
  class AdapterTimeout < QueryAborted
462
570
  end
463
571
 
572
+ # ConnectionFailed will be raised when the network connection to the
573
+ # database fails while sending a query or waiting for its result.
574
+ class ConnectionFailed < QueryAborted
575
+ end
576
+
464
577
  # UnknownAttributeReference is raised when an unknown and potentially unsafe
465
578
  # value is passed to a query method. For example, passing a non column name
466
579
  # value to a relation's #order method might cause this exception.
@@ -483,4 +596,11 @@ module ActiveRecord
483
596
  # values, such as request parameters or model attributes to query methods.
484
597
  class UnknownAttributeReference < ActiveRecordError
485
598
  end
599
+
600
+ # DatabaseVersionError will be raised when the database version is not supported, or when
601
+ # the database version cannot be determined.
602
+ class DatabaseVersionError < ActiveRecordError
603
+ end
486
604
  end
605
+
606
+ require "active_record/associations/errors"