activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -6,11 +6,27 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false
10
+
9
11
  ##
10
12
  # :singleton-method:
11
13
  # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
12
14
  # Within \Rails, this is automatically set using the \Rails application key generator.
13
15
  class_attribute :signed_id_verifier_secret, instance_writer: false
16
+ module DeprecateSignedIdVerifierSecret
17
+ def signed_id_verifier_secret=(secret)
18
+ ActiveRecord.deprecator.warn(<<~MSG)
19
+ ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in Rails 8.2.
20
+
21
+ If the secret is model-specific, set Model.signed_id_verifier instead.
22
+
23
+ Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
24
+ MSG
25
+
26
+ super
27
+ end
28
+ end
29
+ singleton_class.prepend DeprecateSignedIdVerifierSecret
14
30
  end
15
31
 
16
32
  module RelationMethods # :nodoc:
@@ -49,53 +65,66 @@ module ActiveRecord
49
65
  #
50
66
  # travel_back
51
67
  # User.find_signed signed_id, purpose: :password_reset # => User.first
52
- def find_signed(signed_id, purpose: nil)
68
+ def find_signed(signed_id, purpose: nil, on_rotation: nil)
53
69
  raise UnknownPrimaryKey.new(self) if primary_key.nil?
54
70
 
55
- if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
71
+ options = { on_rotation: on_rotation }.compact
72
+ if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
56
73
  find_by primary_key => id
57
74
  end
58
75
  end
59
76
 
60
- # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
77
+ # Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
61
78
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
62
- # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
79
+ # or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
63
80
  # the valid signed id can't find a record.
64
81
  #
65
- # === Examples
82
+ # ==== Examples
66
83
  #
67
84
  # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
68
85
  #
69
86
  # signed_id = User.first.signed_id
70
87
  # User.first.destroy
71
88
  # User.find_signed! signed_id # => ActiveRecord::RecordNotFound
72
- def find_signed!(signed_id, purpose: nil)
73
- if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
89
+ def find_signed!(signed_id, purpose: nil, on_rotation: nil)
90
+ options = { on_rotation: on_rotation }.compact
91
+ if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
74
92
  find(id)
75
93
  end
76
94
  end
77
95
 
78
- # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
79
- # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
80
- # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
81
96
  def signed_id_verifier
82
- @signed_id_verifier ||= begin
83
- secret = signed_id_verifier_secret
84
- secret = secret.call if secret.respond_to?(:call)
97
+ if signed_id_verifier_secret
98
+ @signed_id_verifier ||= begin
99
+ secret = signed_id_verifier_secret
100
+ secret = secret.call if secret.respond_to?(:call)
101
+
102
+ if secret.nil?
103
+ raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed IDs"
104
+ end
85
105
 
86
- if secret.nil?
87
- raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
88
- else
89
106
  ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
90
107
  end
108
+ else
109
+ return _signed_id_verifier if _signed_id_verifier
110
+
111
+ if ActiveRecord.message_verifiers.nil?
112
+ raise "You must set ActiveRecord.message_verifiers to use signed IDs"
113
+ end
114
+
115
+ ActiveRecord.message_verifiers["active_record/signed_id"]
91
116
  end
92
117
  end
93
118
 
94
119
  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
95
120
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
96
- # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
121
+ # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
97
122
  def signed_id_verifier=(verifier)
98
- @signed_id_verifier = verifier
123
+ if signed_id_verifier_secret
124
+ @signed_id_verifier = verifier
125
+ else
126
+ self._signed_id_verifier = verifier
127
+ end
99
128
  end
100
129
 
101
130
  # :nodoc:
@@ -31,8 +31,11 @@ module ActiveRecord
31
31
  class Substitute; end # :nodoc:
32
32
 
33
33
  class Query # :nodoc:
34
- def initialize(sql)
34
+ attr_reader :retryable
35
+
36
+ def initialize(sql, retryable:)
35
37
  @sql = sql
38
+ @retryable = retryable
36
39
  end
37
40
 
38
41
  def sql_for(binds, connection)
@@ -41,11 +44,12 @@ module ActiveRecord
41
44
  end
42
45
 
43
46
  class PartialQuery < Query # :nodoc:
44
- def initialize(values)
47
+ def initialize(values, retryable:)
45
48
  @values = values
46
49
  @indexes = values.each_with_index.find_all { |thing, i|
47
50
  Substitute === thing
48
51
  }.map(&:last)
52
+ @retryable = retryable
49
53
  end
50
54
 
51
55
  def sql_for(binds, connection)
@@ -74,13 +78,13 @@ module ActiveRecord
74
78
  self
75
79
  end
76
80
 
77
- def add_bind(obj)
81
+ def add_bind(obj, &)
78
82
  @binds << obj
79
83
  @parts << Substitute.new
80
84
  self
81
85
  end
82
86
 
83
- def add_binds(binds, proc_for_binds = nil)
87
+ def add_binds(binds, proc_for_binds = nil, &)
84
88
  @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
85
89
  binds.size.times do |i|
86
90
  @parts << ", " unless i == 0
@@ -94,12 +98,12 @@ module ActiveRecord
94
98
  end
95
99
  end
96
100
 
97
- def self.query(sql)
98
- Query.new(sql)
101
+ def self.query(...)
102
+ Query.new(...)
99
103
  end
100
104
 
101
- def self.partial_query(values)
102
- PartialQuery.new(values)
105
+ def self.partial_query(...)
106
+ PartialQuery.new(...)
103
107
  end
104
108
 
105
109
  def self.partial_query_collector
@@ -133,23 +137,26 @@ module ActiveRecord
133
137
  relation = (callable || block).call Params.new
134
138
  query_builder, binds = connection.cacheable_query(self, relation.arel)
135
139
  bind_map = BindMap.new(binds)
136
- new(query_builder, bind_map, relation.klass)
140
+ new(query_builder, bind_map, relation.model)
137
141
  end
138
142
 
139
- def initialize(query_builder, bind_map, klass)
143
+ def initialize(query_builder, bind_map, model)
140
144
  @query_builder = query_builder
141
145
  @bind_map = bind_map
142
- @klass = klass
146
+ @model = model
143
147
  end
144
148
 
145
- def execute(params, connection, allow_retry: false, &block)
146
- bind_values = bind_map.bind params
147
-
148
- sql = query_builder.sql_for bind_values, connection
149
+ def execute(params, connection, async: false, &block)
150
+ bind_values = @bind_map.bind params
151
+ sql = @query_builder.sql_for bind_values, connection
149
152
 
150
- klass.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
153
+ if async
154
+ @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
155
+ else
156
+ @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
157
+ end
151
158
  rescue ::RangeError
152
- []
159
+ async ? Promise.wrap([]) : []
153
160
  end
154
161
 
155
162
  def self.unsupported_value?(value)
@@ -157,8 +164,5 @@ module ActiveRecord
157
164
  when NilClass, Array, Range, Hash, Relation, Base then true
158
165
  end
159
166
  end
160
-
161
- private
162
- attr_reader :query_builder, :bind_map, :klass
163
167
  end
164
168
  end
@@ -25,8 +25,8 @@ module ActiveRecord
25
25
  # You can set custom coder to encode/decode your serialized attributes to/from different formats.
26
26
  # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
27
27
  #
28
- # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
29
- # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
28
+ # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, MySQL 5.7+
29
+ # +json+, or SQLite 3.38+ +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
30
30
  # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
31
31
  # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
32
32
  # using a symbol.
@@ -146,37 +146,43 @@ module ActiveRecord
146
146
  define_method("#{accessor_key}_changed?") do
147
147
  return false unless attribute_changed?(store_attribute)
148
148
  prev_store, new_store = changes[store_attribute]
149
- prev_store&.dig(key) != new_store&.dig(key)
149
+ accessor = store_accessor_for(store_attribute)
150
+ accessor.get(prev_store, key) != accessor.get(new_store, key)
150
151
  end
151
152
 
152
153
  define_method("#{accessor_key}_change") do
153
154
  return unless attribute_changed?(store_attribute)
154
155
  prev_store, new_store = changes[store_attribute]
155
- [prev_store&.dig(key), new_store&.dig(key)]
156
+ accessor = store_accessor_for(store_attribute)
157
+ [accessor.get(prev_store, key), accessor.get(new_store, key)]
156
158
  end
157
159
 
158
160
  define_method("#{accessor_key}_was") do
159
161
  return unless attribute_changed?(store_attribute)
160
162
  prev_store, _new_store = changes[store_attribute]
161
- prev_store&.dig(key)
163
+ accessor = store_accessor_for(store_attribute)
164
+ accessor.get(prev_store, key)
162
165
  end
163
166
 
164
167
  define_method("saved_change_to_#{accessor_key}?") do
165
168
  return false unless saved_change_to_attribute?(store_attribute)
166
169
  prev_store, new_store = saved_changes[store_attribute]
167
- prev_store&.dig(key) != new_store&.dig(key)
170
+ accessor = store_accessor_for(store_attribute)
171
+ accessor.get(prev_store, key) != accessor.get(new_store, key)
168
172
  end
169
173
 
170
174
  define_method("saved_change_to_#{accessor_key}") do
171
175
  return unless saved_change_to_attribute?(store_attribute)
172
176
  prev_store, new_store = saved_changes[store_attribute]
173
- [prev_store&.dig(key), new_store&.dig(key)]
177
+ accessor = store_accessor_for(store_attribute)
178
+ [accessor.get(prev_store, key), accessor.get(new_store, key)]
174
179
  end
175
180
 
176
181
  define_method("#{accessor_key}_before_last_save") do
177
182
  return unless saved_change_to_attribute?(store_attribute)
178
183
  prev_store, _new_store = saved_changes[store_attribute]
179
- prev_store&.dig(key)
184
+ accessor = store_accessor_for(store_attribute)
185
+ accessor.get(prev_store, key)
180
186
  end
181
187
  end
182
188
  end
@@ -217,43 +223,66 @@ module ActiveRecord
217
223
  end
218
224
 
219
225
  def store_accessor_for(store_attribute)
220
- type_for_attribute(store_attribute).accessor
226
+ type_for_attribute(store_attribute).tap do |type|
227
+ unless type.respond_to?(:accessor)
228
+ raise ConfigurationError, "the column '#{store_attribute}' has not been configured as a store. Please make sure the column is declared serializable via 'ActiveRecord.store' or, if your database supports it, use a structured column type like hstore or json."
229
+ end
230
+ end.accessor
221
231
  end
222
232
 
223
233
  class HashAccessor # :nodoc:
234
+ def self.get(store_object, key)
235
+ if store_object
236
+ store_object[key]
237
+ end
238
+ end
239
+
224
240
  def self.read(object, attribute, key)
225
- prepare(object, attribute)
226
- object.public_send(attribute)[key]
241
+ store_object = prepare(object, attribute)
242
+ store_object[key]
227
243
  end
228
244
 
229
245
  def self.write(object, attribute, key, value)
230
- prepare(object, attribute)
231
- object.public_send(attribute)[key] = value if value != read(object, attribute, key)
246
+ store_object = prepare(object, attribute)
247
+ store_object[key] = value if value != store_object[key]
232
248
  end
233
249
 
234
250
  def self.prepare(object, attribute)
235
- object.public_send :"#{attribute}=", {} unless object.send(attribute)
251
+ store_object = object.public_send(attribute)
252
+
253
+ if store_object.nil?
254
+ store_object = {}
255
+ object.public_send(:"#{attribute}=", store_object)
256
+ end
257
+
258
+ store_object
236
259
  end
237
260
  end
238
261
 
239
262
  class StringKeyedHashAccessor < HashAccessor # :nodoc:
263
+ def self.get(store_object, key)
264
+ super store_object, Symbol === key ? key.name : key.to_s
265
+ end
266
+
240
267
  def self.read(object, attribute, key)
241
- super object, attribute, key.to_s
268
+ super object, attribute, Symbol === key ? key.name : key.to_s
242
269
  end
243
270
 
244
271
  def self.write(object, attribute, key, value)
245
- super object, attribute, key.to_s, value
272
+ super object, attribute, Symbol === key ? key.name : key.to_s, value
246
273
  end
247
274
  end
248
275
 
249
276
  class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
250
- def self.prepare(object, store_attribute)
251
- attribute = object.send(store_attribute)
252
- unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
253
- attribute = IndifferentCoder.as_indifferent_hash(attribute)
254
- object.public_send :"#{store_attribute}=", attribute
277
+ def self.prepare(object, attribute)
278
+ store_object = object.public_send(attribute)
279
+
280
+ unless store_object.is_a?(ActiveSupport::HashWithIndifferentAccess)
281
+ store_object = IndifferentCoder.as_indifferent_hash(store_object)
282
+ object.public_send :"#{attribute}=", store_object
255
283
  end
256
- attribute
284
+
285
+ store_object
257
286
  end
258
287
  end
259
288
 
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveRecord
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
8
+
9
+ def strict_loading_violation(event)
10
+ owner = event.payload[:owner]
11
+ reflection = event.payload[:reflection]
12
+
13
+ emit_debug_event("active_record.strict_loading_violation",
14
+ owner: owner.name,
15
+ class: reflection.polymorphic? ? nil : reflection.klass.name,
16
+ name: reflection.name,
17
+ )
18
+ end
19
+ debug_only :strict_loading_violation
20
+
21
+ def sql(event)
22
+ payload = event.payload
23
+
24
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
25
+
26
+ binds = nil
27
+
28
+ if payload[:binds]&.any?
29
+ casted_params = type_casted_binds(payload[:type_casted_binds])
30
+
31
+ binds = []
32
+ payload[:binds].each_with_index do |attr, i|
33
+ attribute_name = if attr.respond_to?(:name)
34
+ attr.name
35
+ elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
36
+ attr[i].name
37
+ else
38
+ nil
39
+ end
40
+
41
+ filtered_params = filter(attribute_name, casted_params[i])
42
+
43
+ binds << render_bind(attr, filtered_params)
44
+ end
45
+ end
46
+
47
+ emit_debug_event("active_record.sql",
48
+ async: payload[:async],
49
+ name: payload[:name],
50
+ sql: payload[:sql],
51
+ cached: payload[:cached],
52
+ lock_wait: payload[:lock_wait],
53
+ binds: binds,
54
+ duration_ms: event.duration.round(2),
55
+ )
56
+ end
57
+ debug_only :sql
58
+
59
+ private
60
+ def type_casted_binds(casted_binds)
61
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
62
+ end
63
+
64
+ def render_bind(attr, value)
65
+ case attr
66
+ when ActiveModel::Attribute
67
+ if attr.type.binary? && attr.value
68
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
69
+ end
70
+ when Array
71
+ attr = attr.first
72
+ else
73
+ attr = nil
74
+ end
75
+
76
+ [attr&.name, value]
77
+ end
78
+
79
+ def filter(name, value)
80
+ ActiveRecord::Base.inspection_filter.filter_param(name, value)
81
+ end
82
+ end
83
+ end
84
+
85
+ ActiveRecord::StructuredEventSubscriber.attach_to :active_record
@@ -2,12 +2,9 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
- delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
6
-
7
- def initialize(klass, arel_table, reflection = nil)
5
+ def initialize(klass, arel_table)
8
6
  @klass = klass
9
7
  @arel_table = arel_table
10
- @reflection = reflection
11
8
  end
12
9
 
13
10
  def primary_key
@@ -22,7 +19,7 @@ module ActiveRecord
22
19
  klass&.columns_hash&.key?(column_name)
23
20
  end
24
21
 
25
- def associated_with?(table_name)
22
+ def associated_with(table_name)
26
23
  klass&._reflect_on_association(table_name)
27
24
  end
28
25
 
@@ -42,26 +39,14 @@ module ActiveRecord
42
39
  if association_klass
43
40
  arel_table = association_klass.arel_table
44
41
  arel_table = arel_table.alias(table_name) if arel_table.name != table_name
45
- TableMetadata.new(association_klass, arel_table, reflection)
42
+ TableMetadata.new(association_klass, arel_table)
46
43
  else
47
44
  type_caster = TypeCaster::Connection.new(klass, table_name)
48
45
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
49
- TableMetadata.new(nil, arel_table, reflection)
46
+ TableMetadata.new(nil, arel_table)
50
47
  end
51
48
  end
52
49
 
53
- def polymorphic_association?
54
- reflection&.polymorphic?
55
- end
56
-
57
- def polymorphic_name_association
58
- reflection&.polymorphic_name
59
- end
60
-
61
- def through_association?
62
- reflection&.through_reflection?
63
- end
64
-
65
50
  def reflect_on_aggregation(aggregation_name)
66
51
  klass&.reflect_on_aggregation(aggregation_name)
67
52
  end
@@ -69,9 +54,7 @@ module ActiveRecord
69
54
 
70
55
  def predicate_builder
71
56
  if klass
72
- predicate_builder = klass.predicate_builder.dup
73
- predicate_builder.instance_variable_set(:@table, self)
74
- predicate_builder
57
+ klass.predicate_builder.with(self)
75
58
  else
76
59
  PredicateBuilder.new(self)
77
60
  end
@@ -80,6 +63,6 @@ module ActiveRecord
80
63
  attr_reader :arel_table
81
64
 
82
65
  private
83
- attr_reader :klass, :reflection
66
+ attr_reader :klass
84
67
  end
85
68
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Tasks # :nodoc:
5
+ class AbstractTasks # :nodoc:
6
+ def self.using_database_configurations?
7
+ true
8
+ end
9
+
10
+ def initialize(db_config)
11
+ @db_config = db_config
12
+ @configuration_hash = db_config.configuration_hash
13
+ end
14
+
15
+ def charset
16
+ connection.encoding
17
+ end
18
+
19
+ def collation
20
+ connection.collation
21
+ end
22
+
23
+ def check_current_protected_environment!(db_config, migration_class)
24
+ with_temporary_pool(db_config, migration_class) do |pool|
25
+ migration_context = pool.migration_context
26
+ current = migration_context.current_environment
27
+ stored = migration_context.last_stored_environment
28
+
29
+ if migration_context.protected_environment?
30
+ raise ActiveRecord::ProtectedEnvironmentError.new(stored)
31
+ end
32
+
33
+ if stored && stored != current
34
+ raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
35
+ end
36
+ rescue ActiveRecord::NoDatabaseError
37
+ end
38
+ end
39
+
40
+ private
41
+ attr_reader :db_config, :configuration_hash
42
+
43
+ def connection
44
+ ActiveRecord::Base.lease_connection
45
+ end
46
+
47
+ def establish_connection(config = db_config)
48
+ ActiveRecord::Base.establish_connection(config)
49
+ end
50
+
51
+ def configuration_hash_without_database
52
+ configuration_hash.merge(database: nil)
53
+ end
54
+
55
+ def run_cmd(cmd, *args, **opts)
56
+ fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, opts)
57
+ end
58
+
59
+ def run_cmd_error(cmd, args)
60
+ msg = +"failed to execute:\n"
61
+ msg << "#{cmd} #{args.join(' ')}\n\n"
62
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
63
+ msg
64
+ end
65
+
66
+ def with_temporary_pool(db_config, migration_class, clobber: false)
67
+ original_db_config = migration_class.connection_db_config
68
+ pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
69
+
70
+ yield pool
71
+ ensure
72
+ migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
73
+ end
74
+ end
75
+ end
76
+ end