activerecord 7.0.0.alpha2 → 7.0.0

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

Potentially problematic release.


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

Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +539 -11
  3. data/lib/active_record/associations/association.rb +2 -8
  4. data/lib/active_record/associations/builder/collection_association.rb +9 -2
  5. data/lib/active_record/associations/collection_association.rb +10 -2
  6. data/lib/active_record/associations/join_dependency.rb +6 -2
  7. data/lib/active_record/associations/preloader/association.rb +68 -48
  8. data/lib/active_record/associations/preloader/batch.rb +3 -6
  9. data/lib/active_record/associations/preloader/through_association.rb +19 -9
  10. data/lib/active_record/associations/preloader.rb +14 -24
  11. data/lib/active_record/associations/through_association.rb +2 -2
  12. data/lib/active_record/associations.rb +16 -3
  13. data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
  14. data/lib/active_record/attribute_methods/dirty.rb +9 -1
  15. data/lib/active_record/attribute_methods.rb +7 -5
  16. data/lib/active_record/autosave_association.rb +3 -3
  17. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
  18. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
  25. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
  27. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/column.rb +4 -0
  29. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
  30. data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
  31. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/pool_config.rb +7 -5
  33. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  34. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  35. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
  36. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  37. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
  41. data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
  43. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  44. data/lib/active_record/connection_handling.rb +31 -19
  45. data/lib/active_record/core.rb +13 -24
  46. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  47. data/lib/active_record/database_configurations/database_config.rb +0 -9
  48. data/lib/active_record/database_configurations/hash_config.rb +40 -8
  49. data/lib/active_record/database_configurations.rb +2 -27
  50. data/lib/active_record/delegated_type.rb +19 -0
  51. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  52. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  53. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
  54. data/lib/active_record/encryption/message_serializer.rb +11 -1
  55. data/lib/active_record/encryption/scheme.rb +1 -1
  56. data/lib/active_record/enum.rb +8 -1
  57. data/lib/active_record/errors.rb +1 -1
  58. data/lib/active_record/explain_registry.rb +11 -6
  59. data/lib/active_record/fixture_set/table_row.rb +1 -1
  60. data/lib/active_record/fixtures.rb +1 -9
  61. data/lib/active_record/future_result.rb +2 -2
  62. data/lib/active_record/gem_version.rb +1 -1
  63. data/lib/active_record/insert_all.rb +52 -15
  64. data/lib/active_record/integration.rb +3 -2
  65. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  66. data/lib/active_record/locking/pessimistic.rb +9 -3
  67. data/lib/active_record/log_subscriber.rb +8 -1
  68. data/lib/active_record/middleware/shard_selector.rb +60 -0
  69. data/lib/active_record/migration.rb +2 -2
  70. data/lib/active_record/model_schema.rb +1 -28
  71. data/lib/active_record/nested_attributes.rb +11 -10
  72. data/lib/active_record/no_touching.rb +1 -1
  73. data/lib/active_record/persistence.rb +99 -21
  74. data/lib/active_record/query_logs.rb +18 -83
  75. data/lib/active_record/railtie.rb +11 -1
  76. data/lib/active_record/railties/databases.rake +4 -91
  77. data/lib/active_record/reflection.rb +22 -6
  78. data/lib/active_record/relation/calculations.rb +1 -10
  79. data/lib/active_record/relation/finder_methods.rb +0 -13
  80. data/lib/active_record/relation/query_methods.rb +5 -14
  81. data/lib/active_record/relation/record_fetch_warning.rb +5 -7
  82. data/lib/active_record/relation/where_clause.rb +2 -15
  83. data/lib/active_record/relation.rb +11 -15
  84. data/lib/active_record/result.rb +0 -5
  85. data/lib/active_record/runtime_registry.rb +10 -12
  86. data/lib/active_record/schema_dumper.rb +7 -0
  87. data/lib/active_record/schema_migration.rb +4 -0
  88. data/lib/active_record/scoping.rb +34 -22
  89. data/lib/active_record/suppressor.rb +11 -15
  90. data/lib/active_record/tasks/database_tasks.rb +18 -44
  91. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
  92. data/lib/active_record/validations/uniqueness.rb +1 -1
  93. data/lib/active_record.rb +41 -33
  94. data/lib/arel/crud.rb +12 -2
  95. data/lib/arel/delete_manager.rb +16 -0
  96. data/lib/arel/filter_predications.rb +9 -0
  97. data/lib/arel/nodes/delete_statement.rb +5 -1
  98. data/lib/arel/nodes/filter.rb +10 -0
  99. data/lib/arel/nodes/function.rb +1 -0
  100. data/lib/arel/nodes/update_statement.rb +5 -1
  101. data/lib/arel/nodes.rb +1 -0
  102. data/lib/arel/predications.rb +10 -2
  103. data/lib/arel/update_manager.rb +16 -0
  104. data/lib/arel/visitors/mysql.rb +2 -1
  105. data/lib/arel/visitors/to_sql.rb +15 -0
  106. data/lib/arel.rb +1 -0
  107. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  108. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  109. metadata +18 -12
@@ -77,6 +77,8 @@ module ActiveRecord
77
77
 
78
78
  class_attribute :default_shard, instance_writer: false
79
79
 
80
+ class_attribute :shard_selector, instance_accessor: false, default: nil
81
+
80
82
  def self.application_record_class? # :nodoc:
81
83
  if ActiveRecord.application_record_class
82
84
  self == ActiveRecord.application_record_class
@@ -90,11 +92,11 @@ module ActiveRecord
90
92
  self.filter_attributes = []
91
93
 
92
94
  def self.connection_handler
93
- Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
95
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
94
96
  end
95
97
 
96
98
  def self.connection_handler=(handler)
97
- Thread.current.thread_variable_set(:ar_connection_handler, handler)
99
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
98
100
  end
99
101
 
100
102
  def self.connection_handlers
@@ -129,8 +131,8 @@ module ActiveRecord
129
131
  end
130
132
 
131
133
  def self.asynchronous_queries_tracker # :nodoc:
132
- Thread.current.thread_variable_get(:ar_asynchronous_queries_tracker) ||
133
- Thread.current.thread_variable_set(:ar_asynchronous_queries_tracker, AsynchronousQueriesTracker.new)
134
+ ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
135
+ AsynchronousQueriesTracker.new
134
136
  end
135
137
 
136
138
  # Returns the symbol representing the current connected role.
@@ -148,7 +150,7 @@ module ActiveRecord
148
150
  else
149
151
  connected_to_stack.reverse_each do |hash|
150
152
  return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
151
- return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
153
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
152
154
  end
153
155
 
154
156
  default_role
@@ -167,7 +169,7 @@ module ActiveRecord
167
169
  def self.current_shard
168
170
  connected_to_stack.reverse_each do |hash|
169
171
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
170
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
172
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
171
173
  end
172
174
 
173
175
  default_shard
@@ -189,7 +191,7 @@ module ActiveRecord
189
191
  else
190
192
  connected_to_stack.reverse_each do |hash|
191
193
  return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
192
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
194
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
193
195
  end
194
196
 
195
197
  false
@@ -197,11 +199,11 @@ module ActiveRecord
197
199
  end
198
200
 
199
201
  def self.connected_to_stack # :nodoc:
200
- if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
202
+ if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
201
203
  connected_to_stack
202
204
  else
203
205
  connected_to_stack = Concurrent::Array.new
204
- Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
206
+ ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
205
207
  connected_to_stack
206
208
  end
207
209
  end
@@ -218,7 +220,7 @@ module ActiveRecord
218
220
  self.connection_class
219
221
  end
220
222
 
221
- def self.connection_classes # :nodoc:
223
+ def self.connection_class_for_self # :nodoc:
222
224
  klass = self
223
225
 
224
226
  until klass == Base
@@ -229,14 +231,6 @@ module ActiveRecord
229
231
  klass
230
232
  end
231
233
 
232
- def self.allow_unsafe_raw_sql # :nodoc:
233
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 7.0")
234
- end
235
-
236
- def self.allow_unsafe_raw_sql=(value) # :nodoc:
237
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 7.0")
238
- end
239
-
240
234
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
241
235
  self.default_role = ActiveRecord.writing_role
242
236
  self.default_shard = :default
@@ -427,11 +421,6 @@ module ActiveRecord
427
421
  @arel_table ||= Arel::Table.new(table_name, klass: self)
428
422
  end
429
423
 
430
- def arel_attribute(name, table = arel_table) # :nodoc:
431
- table[name]
432
- end
433
- deprecate :arel_attribute
434
-
435
424
  def predicate_builder # :nodoc:
436
425
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
437
426
  end
@@ -497,7 +486,7 @@ module ActiveRecord
497
486
  # post.init_with(coder)
498
487
  # post.title # => 'hello world'
499
488
  def init_with(coder, &block)
500
- coder = LegacyYamlAdapter.convert(self.class, coder)
489
+ coder = LegacyYamlAdapter.convert(coder)
501
490
  attributes = self.class.yaml_encoder.decode(coder)
502
491
  init_with_attributes(attributes, coder["new_record"], &block)
503
492
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "active_support/core_ext/enumerable"
4
5
 
5
6
  module ActiveRecord
@@ -67,7 +68,7 @@ module ActiveRecord
67
68
  database: uri.opaque
68
69
  )
69
70
  else
70
- query_hash.merge(
71
+ query_hash.reverse_merge(
71
72
  adapter: @adapter,
72
73
  username: uri.user,
73
74
  password: uri.password,
@@ -15,15 +15,6 @@ module ActiveRecord
15
15
  @name = name
16
16
  end
17
17
 
18
- def spec_name
19
- @name
20
- end
21
- deprecate spec_name: "please use name instead"
22
-
23
- def config
24
- raise NotImplementedError
25
- end
26
-
27
18
  def adapter_method
28
19
  "#{adapter}_connection"
29
20
  end
@@ -32,11 +32,6 @@ module ActiveRecord
32
32
  @configuration_hash = configuration_hash.symbolize_keys.freeze
33
33
  end
34
34
 
35
- def config
36
- ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
37
- configuration_hash.stringify_keys
38
- end
39
-
40
35
  # Determines whether a database configuration is for a replica / readonly
41
36
  # connection. If the +replica+ key is present in the config, +replica?+ will
42
37
  # return +true+.
@@ -109,14 +104,51 @@ module ActiveRecord
109
104
  configuration_hash[:schema_cache_path]
110
105
  end
111
106
 
112
- # Determines whether to dump the schema for a database.
113
- def schema_dump
114
- configuration_hash.fetch(:schema_dump, true)
107
+ def default_schema_cache_path
108
+ "db/schema_cache.yml"
109
+ end
110
+
111
+ def lazy_schema_cache_path
112
+ schema_cache_path || default_schema_cache_path
113
+ end
114
+
115
+ def primary? # :nodoc:
116
+ Base.configurations.primary?(name)
117
+ end
118
+
119
+ # Determines whether to dump the schema/structure files and the
120
+ # filename that should be used.
121
+ #
122
+ # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
123
+ # the schema will not be dumped.
124
+ #
125
+ # If the config option is set that will be used. Otherwise Rails
126
+ # will generate the filename from the database config name.
127
+ def schema_dump(format = ActiveRecord.schema_format)
128
+ if configuration_hash.key?(:schema_dump)
129
+ if config = configuration_hash[:schema_dump]
130
+ config
131
+ end
132
+ elsif primary?
133
+ schema_file_type(format)
134
+ else
135
+ "#{name}_#{schema_file_type(format)}"
136
+ end
115
137
  end
116
138
 
117
139
  def database_tasks? # :nodoc:
118
140
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
119
141
  end
142
+
143
+ private
144
+ def schema_file_type(format)
145
+ case format
146
+ when :ruby
147
+ "schema.rb"
148
+ when :sql
149
+ "structure.sql"
150
+ end
151
+ end
120
152
  end
121
153
  end
122
154
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
3
4
  require "active_record/database_configurations/database_config"
4
5
  require "active_record/database_configurations/hash_config"
5
6
  require "active_record/database_configurations/url_config"
@@ -41,12 +42,7 @@ module ActiveRecord
41
42
  # hidden by +database_tasks: false+ in the returned list. Most of the time we're only
42
43
  # iterating over the primary connections (i.e. migrations don't need to run for the
43
44
  # write and read connection). Defaults to +false+.
44
- def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false, include_hidden: false)
45
- if spec_name
46
- name = spec_name
47
- ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
48
- end
49
-
45
+ def configs_for(env_name: nil, name: nil, include_replicas: false, include_hidden: false)
50
46
  if include_replicas
51
47
  include_hidden = include_replicas
52
48
  ActiveSupport::Deprecation.warn("The kwarg `include_replicas` is deprecated in favor of `include_hidden`. When `include_hidden` is passed, configurations with `replica: true` or `database_tasks: false` will be returned. `include_replicas` will be removed in Rails 7.1.")
@@ -70,19 +66,6 @@ module ActiveRecord
70
66
  end
71
67
  end
72
68
 
73
- # Returns the config hash that corresponds with the environment
74
- #
75
- # If the application has multiple databases +default_hash+ will
76
- # return the first config hash for the environment.
77
- #
78
- # { database: "my_db", adapter: "mysql2" }
79
- def default_hash(env = default_env)
80
- default = find_db_config(env)
81
- default.configuration_hash if default
82
- end
83
- alias :[] :default_hash
84
- deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
85
-
86
69
  # Returns a single DatabaseConfig object based on the requested environment.
87
70
  #
88
71
  # If the application has multiple databases +find_db_config+ will return
@@ -109,14 +92,6 @@ module ActiveRecord
109
92
  first_config && name == first_config.name
110
93
  end
111
94
 
112
- # Returns the DatabaseConfigurations object as a Hash.
113
- def to_h
114
- configurations.inject({}) do |memo, db_config|
115
- memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
116
- end
117
- end
118
- deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
119
-
120
95
  # Checks if the application's configurations are empty.
121
96
  #
122
97
  # Aliased to blank?
@@ -137,6 +137,21 @@ module ActiveRecord
137
137
  # end
138
138
  #
139
139
  # Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
140
+ #
141
+ # == Nested Attributes
142
+ #
143
+ # Enabling nested attributes on a delegated_type association allows you to
144
+ # create the entry and message in one go:
145
+ #
146
+ # class Entry < ApplicationRecord
147
+ # delegated_type :entryable, types: %w[ Message Comment ]
148
+ # accepts_nested_attributes_for :entryable
149
+ # end
150
+ #
151
+ # params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
152
+ # entry = Entry.create(params[:entry])
153
+ # entry.entryable.id # => 2
154
+ # entry.entryable.subject # => 'Smiling'
140
155
  module DelegatedType
141
156
  # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
142
157
  # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
@@ -207,6 +222,10 @@ module ActiveRecord
207
222
  public_send("#{role}_class").model_name.singular.inquiry
208
223
  end
209
224
 
225
+ define_method "build_#{role}" do |*params|
226
+ public_send("#{role}=", public_send("#{role}_class").new(*params))
227
+ end
228
+
210
229
  types.each do |type|
211
230
  scope_name = type.tableize.tr("/", "_")
212
231
  singular = scope_name.singularize
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  # in preserving it.
38
38
  # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
39
39
  # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
40
- # server. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
40
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
41
41
  # is true.
42
42
  # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
43
43
  # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
 
18
18
  # === Options
19
19
  #
20
- # * <tt>:scheme</tt> - An +Scheme+ with the encryption properties for this attribute.
20
+ # * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
21
21
  # * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
22
22
  # (after decrypting). +ActiveModel::Type::String+ by default.
23
23
  def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
@@ -12,7 +12,7 @@ module ActiveRecord
12
12
  super(record, attribute, value)
13
13
 
14
14
  klass = record.class
15
- if klass.deterministic_encrypted_attributes&.each do |attribute_name|
15
+ klass.deterministic_encrypted_attributes&.each do |attribute_name|
16
16
  encrypted_type = klass.type_for_attribute(attribute_name)
17
17
  [ encrypted_type, *encrypted_type.previous_types ].each do |type|
18
18
  encrypted_value = type.serialize(value)
@@ -21,7 +21,6 @@ module ActiveRecord
21
21
  end
22
22
  end
23
23
  end
24
- end
25
24
  end
26
25
  end
27
26
  end
@@ -33,10 +33,20 @@ module ActiveRecord
33
33
 
34
34
  private
35
35
  def parse_message(data, level)
36
- raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported" if level > 2
36
+ validate_message_data_format(data, level)
37
37
  ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level))
38
38
  end
39
39
 
40
+ def validate_message_data_format(data, level)
41
+ if level > 2
42
+ raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported"
43
+ end
44
+
45
+ unless data.is_a?(Hash) && data.has_key?("p")
46
+ raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload"
47
+ end
48
+ end
49
+
40
50
  def parse_properties(headers, level)
41
51
  ActiveRecord::Encryption::Properties.new.tap do |properties|
42
52
  headers&.each do |key, value|
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
 
83
83
  def validate_credential(key, error_message = "is not configured")
84
84
  unless ActiveRecord::Encryption.config.public_send(key).present?
85
- raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential"\
85
+ raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential "\
86
86
  "active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
87
87
  end
88
88
  end
@@ -57,13 +57,20 @@ module ActiveRecord
57
57
  # conversation = Conversation.new
58
58
  # conversation.status # => "active"
59
59
  #
60
- # 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
61
61
  # database integer with a hash:
62
62
  #
63
63
  # class Conversation < ActiveRecord::Base
64
64
  # enum :status, active: 0, archived: 1
65
65
  # end
66
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"
72
+ # end
73
+ #
67
74
  # Note that when an array is used, the implicit mapping from the values to database
68
75
  # integers is derived from the order the values appear in the array. In the example,
69
76
  # <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
@@ -326,7 +326,7 @@ module ActiveRecord
326
326
  # # The system must fail on Friday so that our support department
327
327
  # # won't be out of job. We silently rollback this transaction
328
328
  # # without telling the user.
329
- # raise ActiveRecord::Rollback, "Call tech support!"
329
+ # raise ActiveRecord::Rollback
330
330
  # end
331
331
  # end
332
332
  # # ActiveRecord::Rollback is the only exception that won't be passed on
@@ -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
@@ -126,7 +126,7 @@ module ActiveRecord
126
126
  end
127
127
 
128
128
  def resolve_enums
129
- model_class.defined_enums.each do |name, values|
129
+ reflection_class.defined_enums.each do |name, values|
130
130
  if @row.include?(name)
131
131
  @row[name] = values.fetch(@row[name], @row[name])
132
132
  end
@@ -407,7 +407,7 @@ module ActiveRecord
407
407
  # defaults:
408
408
  #
409
409
  # DEFAULTS: &DEFAULTS
410
- # created_on: <%= 3.weeks.ago.to_s(:db) %>
410
+ # created_on: <%= 3.weeks.ago.to_formatted_s(:db) %>
411
411
  #
412
412
  # first:
413
413
  # name: Smurf
@@ -585,14 +585,6 @@ module ActiveRecord
585
585
  end
586
586
  end
587
587
 
588
- def signed_global_id(fixture_set_name, label, column_type: :integer, **options)
589
- identifier = identify(label, column_type)
590
- model_name = default_fixture_model_name(fixture_set_name)
591
- uri = URI::GID.build([GlobalID.app, model_name, identifier, {}])
592
-
593
- SignedGlobalID.new(uri, **options)
594
- end
595
-
596
588
  # Superclass for the evaluation contexts used by ERB fixtures.
597
589
  def context_class
598
590
  @context_class ||= Class.new
@@ -102,12 +102,12 @@ module ActiveRecord
102
102
 
103
103
  def execute_or_wait
104
104
  if pending?
105
- start = Concurrent.monotonic_time
105
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
106
106
  @mutex.synchronize do
107
107
  if pending?
108
108
  execute_query(@pool.connection)
109
109
  else
110
- @lock_wait = (Concurrent.monotonic_time - start) * 1_000
110
+ @lock_wait = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start)
111
111
  end
112
112
  end
113
113
  else
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
12
  TINY = 0
13
- PRE = "alpha2"
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -5,21 +5,19 @@ require "active_support/core_ext/enumerable"
5
5
  module ActiveRecord
6
6
  class InsertAll # :nodoc:
7
7
  attr_reader :model, :connection, :inserts, :keys
8
- attr_reader :on_duplicate, :returning, :unique_by, :update_sql
8
+ attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
- def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
10
+ def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
11
11
  raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
12
12
 
13
13
  @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
14
- @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
14
+ @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
15
+ @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
15
16
 
16
- disallow_raw_sql!(returning)
17
17
  disallow_raw_sql!(on_duplicate)
18
+ disallow_raw_sql!(returning)
18
19
 
19
- if Arel.arel_node?(on_duplicate)
20
- @update_sql = on_duplicate
21
- @on_duplicate = :update
22
- end
20
+ configure_on_duplicate_update_logic
23
21
 
24
22
  if model.scope_attributes?
25
23
  @scope_attributes = model.scope_attributes
@@ -44,7 +42,7 @@ module ActiveRecord
44
42
  end
45
43
 
46
44
  def updatable_columns
47
- keys - readonly_columns - unique_by_columns
45
+ @updatable_columns ||= keys - readonly_columns - unique_by_columns
48
46
  end
49
47
 
50
48
  def primary_keys
@@ -64,18 +62,50 @@ module ActiveRecord
64
62
  inserts.map do |attributes|
65
63
  attributes = attributes.stringify_keys
66
64
  attributes.merge!(scope_attributes) if scope_attributes
65
+ attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
67
66
 
68
67
  verify_attributes(attributes)
69
68
 
70
- keys.map do |key|
69
+ keys_including_timestamps.map do |key|
71
70
  yield key, attributes[key]
72
71
  end
73
72
  end
74
73
  end
75
74
 
75
+ def record_timestamps?
76
+ @record_timestamps
77
+ end
78
+
79
+ # TODO: Consider remaining this method, as it only conditionally extends keys, not always
80
+ def keys_including_timestamps
81
+ @keys_including_timestamps ||= if record_timestamps?
82
+ keys + model.all_timestamp_attributes_in_model
83
+ else
84
+ keys
85
+ end
86
+ end
87
+
76
88
  private
77
89
  attr_reader :scope_attributes
78
90
 
91
+ def configure_on_duplicate_update_logic
92
+ if custom_update_sql_provided? && update_only.present?
93
+ raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
94
+ end
95
+
96
+ if update_only.present?
97
+ @updatable_columns = Array(update_only)
98
+ @on_duplicate = :update
99
+ elsif custom_update_sql_provided?
100
+ @update_sql = on_duplicate
101
+ @on_duplicate = :update
102
+ end
103
+ end
104
+
105
+ def custom_update_sql_provided?
106
+ @custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
107
+ end
108
+
79
109
  def find_unique_index_for(unique_by)
80
110
  if !connection.supports_insert_conflict_target?
81
111
  return if unique_by.nil?
@@ -134,7 +164,7 @@ module ActiveRecord
134
164
 
135
165
 
136
166
  def verify_attributes(attributes)
137
- if keys != attributes.keys.to_set
167
+ if keys_including_timestamps != attributes.keys.to_set
138
168
  raise ArgumentError, "All objects being inserted must have the same keys"
139
169
  end
140
170
  end
@@ -148,10 +178,14 @@ module ActiveRecord
148
178
  "by wrapping them in Arel.sql()."
149
179
  end
150
180
 
181
+ def timestamps_for_create
182
+ model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
183
+ end
184
+
151
185
  class Builder # :nodoc:
152
186
  attr_reader :model
153
187
 
154
- delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
188
+ delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
155
189
 
156
190
  def initialize(insert_all)
157
191
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -162,9 +196,10 @@ module ActiveRecord
162
196
  end
163
197
 
164
198
  def values_list
165
- types = extract_types_from_columns_on(model.table_name, keys: keys)
199
+ types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
166
200
 
167
201
  values_list = insert_all.map_key_with_value do |key, value|
202
+ next value if Arel::Nodes::SqlLiteral === value
168
203
  connection.with_yaml_fallback(types[key].serialize(value))
169
204
  end
170
205
 
@@ -196,6 +231,8 @@ module ActiveRecord
196
231
  end
197
232
 
198
233
  def touch_model_timestamps_unless(&block)
234
+ return "" unless update_duplicates? && record_timestamps?
235
+
199
236
  model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
200
237
  if touch_timestamp_attribute?(column_name)
201
238
  "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
@@ -213,11 +250,11 @@ module ActiveRecord
213
250
  attr_reader :connection, :insert_all
214
251
 
215
252
  def touch_timestamp_attribute?(column_name)
216
- update_duplicates? && !insert_all.updatable_columns.include?(column_name)
253
+ insert_all.updatable_columns.exclude?(column_name)
217
254
  end
218
255
 
219
256
  def columns_list
220
- format_columns(insert_all.keys)
257
+ format_columns(insert_all.keys_including_timestamps)
221
258
  end
222
259
 
223
260
  def extract_types_from_columns_on(table_name, keys:)
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  timestamp = max_updated_column_timestamp
80
80
 
81
81
  if timestamp
82
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
82
+ timestamp = timestamp.utc.to_formatted_s(cache_timestamp_format)
83
83
  "#{model_name.cache_key}/#{id}-#{timestamp}"
84
84
  else
85
85
  "#{model_name.cache_key}/#{id}"
@@ -101,8 +101,9 @@ module ActiveRecord
101
101
  timestamp = updated_at_before_type_cast
102
102
  if can_use_fast_cache_version?(timestamp)
103
103
  raw_timestamp_to_cache_version(timestamp)
104
+
104
105
  elsif timestamp = updated_at
105
- timestamp.utc.to_s(cache_timestamp_format)
106
+ timestamp.utc.to_formatted_s(cache_timestamp_format)
106
107
  end
107
108
  elsif self.class.has_attribute?("updated_at")
108
109
  raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"