activerecord 6.1.1 → 6.1.2

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +10 -0
  5. data/lib/active_record/associations/association.rb +7 -7
  6. data/lib/active_record/associations/builder/association.rb +23 -2
  7. data/lib/active_record/associations/join_dependency.rb +1 -1
  8. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +4 -4
  9. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  10. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  11. data/lib/active_record/connection_adapters/abstract_adapter.rb +5 -6
  12. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +6 -10
  13. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
  14. data/lib/active_record/connection_adapters/pool_config.rb +13 -3
  15. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -3
  16. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  17. data/lib/active_record/connection_handling.rb +11 -3
  18. data/lib/active_record/core.rb +42 -24
  19. data/lib/active_record/database_configurations/url_config.rb +1 -1
  20. data/lib/active_record/enum.rb +19 -13
  21. data/lib/active_record/gem_version.rb +1 -1
  22. data/lib/active_record/locking/optimistic.rb +14 -4
  23. data/lib/active_record/log_subscriber.rb +3 -2
  24. data/lib/active_record/railties/console_sandbox.rb +2 -4
  25. data/lib/active_record/reflection.rb +1 -1
  26. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -1
  27. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +2 -0
  28. metadata +9 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56f128436e13685f9715b6dbe295150f61a11e7af686fc5d758044f080fe1879
4
- data.tar.gz: b80991c1de4020e4e770f64464683e6e3cd88f38dbc93e8909c270ade9f32d20
3
+ metadata.gz: 4112bd4b4d7a00b26db10332f2f6452d2b6ad1fdf3f2ad50988a8229abac9192
4
+ data.tar.gz: 6fbcb021ae1942e307730ef7aaad54d1f594cd52b749d407f8aafe052bee6794
5
5
  SHA512:
6
- metadata.gz: 10f8bd5fccf212ad4807ae6c08a8264333af73373606dce2eb2338e2f6edcfa7469351f5a905836e4be6764548e580d0c205564c8aa2493cbb1476f02ed555a4
7
- data.tar.gz: 2ff2988d43a99595a0a14d796afeca8aa53b54a8f7e516721a026a053cdc7bb0c23fcf934d180a165023b58c5e9335d42bb1c0e0b9daf1b6df078c1e969fc8c1
6
+ metadata.gz: eb1830fe0587253ecede381d53dd0be5c8de80070559458653d4a6e149c002a03a3ba37a8723e0e869693500b55ec9956f9f18d2d556884f40d28ac9af0e34f6
7
+ data.tar.gz: d78aeab68b5c4d4d0e2d8a315b7175a0257b6e1bb6db82c1ae04551f44540d72ffd122881af9cfb93cf8c0d8d57b6310a5bd303e612e24795f54fc07b777f307
data/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ## Rails 6.1.2 (February 09, 2021) ##
2
+
3
+ * Fix timestamp type for sqlite3.
4
+
5
+ *Eileen M. Uchitelle*
6
+
7
+ * Make destroy async transactional.
8
+
9
+ An active record rollback could occur while enqueuing a job. In this
10
+ case the job would enqueue even though the database deletion
11
+ rolledback putting things in a funky state.
12
+
13
+ Now the jobs are only enqueued until after the db transaction has been committed.
14
+
15
+ *Cory Gwin*
16
+
17
+ * Fix malformed packet error in MySQL statement for connection configuration.
18
+
19
+ *robinroestenburg*
20
+
21
+ * Connection specification now passes the "url" key as a configuration for the
22
+ adapter if the "url" protocol is "jdbc", "http", or "https". Previously only
23
+ urls with the "jdbc" prefix were passed to the Active Record Adapter, others
24
+ are assumed to be adapter specification urls.
25
+
26
+ Fixes #41137.
27
+
28
+ *Jonathan Bracy*
29
+
30
+ * Fix granular connection swapping when there are multiple abstract classes.
31
+
32
+ *Eileen M. Uchitelle*
33
+
34
+ * Fix `find_by` with custom primary key for belongs_to association.
35
+
36
+ *Ryuta Kamizono*
37
+
38
+ * Add support for `rails console --sandbox` for multiple database applications.
39
+
40
+ *alpaca-tc*
41
+
42
+ * Fix `where` on polymorphic association with empty array.
43
+
44
+ *Ryuta Kamizono*
45
+
46
+ * Fix preventing writes for `ApplicationRecord`.
47
+
48
+ *Eileen M. Uchitelle*
49
+
50
+
1
51
  ## Rails 6.1.1 (January 07, 2021) ##
2
52
 
3
53
  * Fix fixtures loading when strict loading is enabled for the association.
data/README.rdoc CHANGED
@@ -194,7 +194,7 @@ The latest version of Active Record can be installed with RubyGems:
194
194
 
195
195
  Source code can be downloaded as part of the Rails project on GitHub:
196
196
 
197
- * https://github.com/rails/rails/tree/master/activerecord
197
+ * https://github.com/rails/rails/tree/main/activerecord
198
198
 
199
199
 
200
200
  == License
@@ -27,6 +27,16 @@ module ActiveRecord
27
27
  RUBY
28
28
  end
29
29
 
30
+ def build(attributes = nil, &block)
31
+ if attributes.is_a?(Array)
32
+ attributes.collect { |attr| build(attr, &block) }
33
+ else
34
+ block = current_scope_restoring_block(&block)
35
+ scoping { _new(attributes, &block) }
36
+ end
37
+ end
38
+ alias new build
39
+
30
40
  private
31
41
  def _new(attributes, &block)
32
42
  @association.build(attributes, &block)
@@ -211,12 +211,8 @@ module ActiveRecord
211
211
 
212
212
  private
213
213
  def find_target
214
- if owner.strict_loading? && owner.validation_context.nil?
215
- Base.strict_loading_violation!(owner: owner.class, association: klass)
216
- end
217
-
218
- if reflection.strict_loading? && owner.validation_context.nil?
219
- Base.strict_loading_violation!(owner: owner.class, association: reflection.name)
214
+ if (owner.strict_loading? || reflection.strict_loading?) && owner.validation_context.nil?
215
+ Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
220
216
  end
221
217
 
222
218
  scope = self.scope
@@ -331,7 +327,11 @@ module ActiveRecord
331
327
  end
332
328
 
333
329
  def enqueue_destroy_association(options)
334
- owner.class.destroy_association_async_job&.perform_later(**options)
330
+ job_class = owner.class.destroy_association_async_job
331
+
332
+ if job_class
333
+ owner._after_commit_jobs.push([job_class, options])
334
+ end
335
335
  end
336
336
 
337
337
  def inversable?(record)
@@ -76,6 +76,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
76
76
  if dependent = reflection.options[:dependent]
77
77
  check_dependent_options(dependent, model)
78
78
  add_destroy_callbacks(model, reflection)
79
+ add_after_commit_jobs_callback(model, dependent)
79
80
  end
80
81
 
81
82
  Association.extensions.each do |extension|
@@ -132,11 +133,31 @@ module ActiveRecord::Associations::Builder # :nodoc:
132
133
 
133
134
  def self.add_destroy_callbacks(model, reflection)
134
135
  name = reflection.name
135
- model.before_destroy lambda { |o| o.association(name).handle_dependency }
136
+ model.before_destroy(->(o) { o.association(name).handle_dependency })
137
+ end
138
+
139
+ def self.add_after_commit_jobs_callback(model, dependent)
140
+ if dependent == :destroy_async
141
+ mixin = model.generated_association_methods
142
+
143
+ unless mixin.method_defined?(:_after_commit_jobs)
144
+ model.after_commit(-> do
145
+ _after_commit_jobs.each do |job_class, job_arguments|
146
+ job_class.perform_later(**job_arguments)
147
+ end
148
+ end)
149
+
150
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
151
+ def _after_commit_jobs
152
+ @_after_commit_jobs ||= []
153
+ end
154
+ CODE
155
+ end
156
+ end
136
157
  end
137
158
 
138
159
  private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
139
160
  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
140
- :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
161
+ :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback
141
162
  end
142
163
  end
@@ -195,7 +195,7 @@ module ActiveRecord
195
195
  next table, true
196
196
  end
197
197
 
198
- table_name = @references[reflection.name.to_sym]
198
+ table_name = @references[reflection.name.to_sym]&.to_s
199
199
 
200
200
  table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
201
201
  name = reflection.alias_candidate(parent.table_name)
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
 
25
25
  attr_accessor :schema_cache
26
26
 
27
- def owner_name
27
+ def connection_klass
28
28
  nil
29
29
  end
30
30
  end
@@ -360,7 +360,7 @@ module ActiveRecord
360
360
  include ConnectionAdapters::AbstractPool
361
361
 
362
362
  attr_accessor :automatic_reconnect, :checkout_timeout
363
- attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
363
+ attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass
364
364
 
365
365
  delegate :schema_cache, :schema_cache=, to: :pool_config
366
366
 
@@ -375,7 +375,7 @@ module ActiveRecord
375
375
 
376
376
  @pool_config = pool_config
377
377
  @db_config = pool_config.db_config
378
- @owner_name = pool_config.connection_specification_name
378
+ @connection_klass = pool_config.connection_klass
379
379
 
380
380
  @checkout_timeout = db_config.checkout_timeout
381
381
  @idle_timeout = db_config.idle_timeout
@@ -1040,7 +1040,7 @@ module ActiveRecord
1040
1040
  end
1041
1041
  alias :connection_pools :connection_pool_list
1042
1042
 
1043
- def establish_connection(config, owner_name: Base.name, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1043
+ def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
1044
  owner_name = config.to_s if config.is_a?(Symbol)
1045
1045
 
1046
1046
  pool_config = resolve_pool_config(config, owner_name)
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  module QueryCache
8
8
  class << self
9
9
  def included(base) #:nodoc:
10
- dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
10
+ dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables,
11
11
  :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
12
12
 
13
13
  base.set_callback :checkout, :after, :configure_query_cache!
@@ -71,6 +71,10 @@ module ActiveRecord
71
71
  end
72
72
  CODE
73
73
  end
74
+
75
+ def aliased_types(name, fallback)
76
+ "timestamp" == name ? :datetime : fallback
77
+ end
74
78
  end
75
79
 
76
80
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -111,7 +111,7 @@ module ActiveRecord
111
111
  @config.fetch(:use_metadata_table, true)
112
112
  end
113
113
 
114
- # Determines whether writes are currently being prevents.
114
+ # Determines whether writes are currently being prevented.
115
115
  #
116
116
  # Returns true if the connection is a replica.
117
117
  #
@@ -123,10 +123,9 @@ module ActiveRecord
123
123
  def preventing_writes?
124
124
  return true if replica?
125
125
  return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling
126
- return false if owner_name.nil?
126
+ return false if connection_klass.nil?
127
127
 
128
- klass = self.owner_name.safe_constantize
129
- klass&.current_preventing_writes
128
+ connection_klass.current_preventing_writes
130
129
  end
131
130
 
132
131
  def migrations_paths # :nodoc:
@@ -202,8 +201,8 @@ module ActiveRecord
202
201
  @owner = Thread.current
203
202
  end
204
203
 
205
- def owner_name # :nodoc:
206
- @pool.owner_name
204
+ def connection_klass # :nodoc:
205
+ @pool.connection_klass
207
206
  end
208
207
 
209
208
  def schema_cache
@@ -751,6 +751,11 @@ module ActiveRecord
751
751
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
752
752
  variables["wait_timeout"] = wait_timeout
753
753
 
754
+ # Set the collation of the connection character set.
755
+ if @config[:collation]
756
+ variables["collation_connection"] = @config[:collation]
757
+ end
758
+
754
759
  defaults = [":default", :default].to_set
755
760
 
756
761
  # Make MySQL reject illegal values rather than truncating or blanking them, see
@@ -770,15 +775,6 @@ module ActiveRecord
770
775
  end
771
776
  sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
772
777
 
773
- # NAMES does not have an equals sign, see
774
- # https://dev.mysql.com/doc/refman/en/set-names.html
775
- # (trailing comma because variable_assignments will always have content)
776
- if @config[:encoding]
777
- encoding = +"NAMES #{@config[:encoding]}"
778
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
779
- encoding << ", "
780
- end
781
-
782
778
  # Gather up all of the SET variables...
783
779
  variable_assignments = variables.map do |k, v|
784
780
  if defaults.include?(v)
@@ -790,7 +786,7 @@ module ActiveRecord
790
786
  end.compact.join(", ")
791
787
 
792
788
  # ...and send them all in one query
793
- execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
789
+ execute("SET #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
794
790
  end
795
791
 
796
792
  def column_definitions(table_name) # :nodoc:
@@ -79,7 +79,10 @@ module ActiveRecord
79
79
  " WHERE table_schema = #{scope[:schema]}" \
80
80
  " AND table_name = #{scope[:name]}" \
81
81
  " AND column_name = #{column_name}"
82
- @connection.query_value(sql, "SCHEMA").inspect
82
+ # Calling .inspect leads into issues with the query result
83
+ # which already returns escaped quotes.
84
+ # We remove the escape sequence from the result in order to deal with double escaping issues.
85
+ @connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect
83
86
  end
84
87
  end
85
88
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include Mutex_m
7
7
 
8
- attr_reader :db_config, :connection_specification_name
8
+ attr_reader :db_config, :connection_klass
9
9
  attr_accessor :schema_cache
10
10
 
11
11
  INSTANCES = ObjectSpace::WeakMap.new
@@ -17,14 +17,24 @@ module ActiveRecord
17
17
  end
18
18
  end
19
19
 
20
- def initialize(connection_specification_name, db_config)
20
+ def initialize(connection_klass, db_config)
21
21
  super()
22
- @connection_specification_name = connection_specification_name
22
+ @connection_klass = connection_klass
23
23
  @db_config = db_config
24
24
  @pool = nil
25
25
  INSTANCES[self] = self
26
26
  end
27
27
 
28
+ def connection_specification_name
29
+ if connection_klass.is_a?(String)
30
+ connection_klass
31
+ elsif connection_klass.primary_class?
32
+ "ActiveRecord::Base"
33
+ else
34
+ connection_klass.name
35
+ end
36
+ end
37
+
28
38
  def disconnect!
29
39
  ActiveSupport::ForkTracker.check!
30
40
 
@@ -649,9 +649,7 @@ module ActiveRecord
649
649
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
650
650
  end
651
651
 
652
- if without_prepared_statement?(binds)
653
- result = exec_no_cache(sql, name, [])
654
- elsif !prepare
652
+ if !prepare || without_prepared_statement?(binds)
655
653
  result = exec_no_cache(sql, name, binds)
656
654
  else
657
655
  result = exec_cache(sql, name, binds)
@@ -271,7 +271,7 @@ module ActiveRecord
271
271
  def change_column(table_name, column_name, type, **options) #:nodoc:
272
272
  alter_table(table_name) do |definition|
273
273
  definition[column_name].instance_eval do
274
- self.type = type
274
+ self.type = aliased_types(type.to_s, type)
275
275
  self.options.merge!(options)
276
276
  end
277
277
  end
@@ -91,6 +91,7 @@ module ActiveRecord
91
91
  db_config, owner_name = resolve_config_for_connection(database_key)
92
92
  handler = lookup_connection_handler(role.to_sym)
93
93
 
94
+ self.connection_class = true
94
95
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
95
96
  end
96
97
 
@@ -99,6 +100,7 @@ module ActiveRecord
99
100
  db_config, owner_name = resolve_config_for_connection(database_key)
100
101
  handler = lookup_connection_handler(role.to_sym)
101
102
 
103
+ self.connection_class = true
102
104
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
103
105
  end
104
106
  end
@@ -143,6 +145,10 @@ module ActiveRecord
143
145
  if self != Base && !abstract_class
144
146
  raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
145
147
  end
148
+
149
+ if name != connection_specification_name && !primary_class?
150
+ raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
151
+ end
146
152
  end
147
153
 
148
154
  if database && (role || shard)
@@ -180,12 +186,14 @@ module ActiveRecord
180
186
  #
181
187
  # Usage:
182
188
  #
183
- # ActiveRecord::Base.connected_to(AnimalsRecord, MealsRecord], role: :reading) do
189
+ # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
184
190
  # Dog.first # Read from animals replica
185
191
  # Dinner.first # Read from meals replica
186
192
  # Person.first # Read from primary writer
187
193
  # end
188
- def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
194
+ def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
195
+ classes = classes.flatten
196
+
189
197
  if legacy_connection_handling
190
198
  raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
191
199
  end
@@ -357,7 +365,7 @@ module ActiveRecord
357
365
  self.connection_specification_name = owner_name
358
366
 
359
367
  db_config = Base.configurations.resolve(config_or_env)
360
- [db_config, owner_name]
368
+ [db_config, self]
361
369
  end
362
370
 
363
371
  def with_handler(handler_key, &blk)
@@ -196,7 +196,7 @@ module ActiveRecord
196
196
  else
197
197
  connected_to_stack.reverse_each do |hash|
198
198
  return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
199
- return hash[:role] if hash[:role] && hash[:klasses].include?(abstract_base_class)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
200
200
  end
201
201
 
202
202
  default_role
@@ -215,7 +215,7 @@ module ActiveRecord
215
215
  def self.current_shard
216
216
  connected_to_stack.reverse_each do |hash|
217
217
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
218
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(abstract_base_class)
218
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
219
219
  end
220
220
 
221
221
  default_shard
@@ -237,7 +237,7 @@ module ActiveRecord
237
237
  else
238
238
  connected_to_stack.reverse_each do |hash|
239
239
  return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
240
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(abstract_base_class)
240
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
241
241
  end
242
242
 
243
243
  false
@@ -254,11 +254,23 @@ module ActiveRecord
254
254
  end
255
255
  end
256
256
 
257
- def self.abstract_base_class # :nodoc:
257
+ def self.connection_class=(b) # :nodoc:
258
+ @connection_class = b
259
+ end
260
+
261
+ def self.connection_class # :nodoc
262
+ @connection_class ||= false
263
+ end
264
+
265
+ def self.connection_class? # :nodoc:
266
+ self.connection_class
267
+ end
268
+
269
+ def self.connection_classes # :nodoc:
258
270
  klass = self
259
271
 
260
272
  until klass == Base
261
- break if klass.abstract_class?
273
+ break if klass.connection_class?
262
274
  klass = klass.superclass
263
275
  end
264
276
 
@@ -277,14 +289,14 @@ module ActiveRecord
277
289
  self.default_role = writing_role
278
290
  self.default_shard = :default
279
291
 
280
- def self.strict_loading_violation!(owner:, association:) # :nodoc:
292
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
281
293
  case action_on_strict_loading_violation
282
294
  when :raise
283
- message = "`#{association}` called on `#{owner}` is marked for strict_loading and cannot be lazily loaded."
295
+ message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
284
296
  raise ActiveRecord::StrictLoadingViolationError.new(message)
285
297
  when :log
286
298
  name = "strict_loading_violation.active_record"
287
- ActiveSupport::Notifications.instrument(name, owner: owner, association: association)
299
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
288
300
  end
289
301
  end
290
302
  end
@@ -332,31 +344,37 @@ module ActiveRecord
332
344
  hash = args.first
333
345
  return super unless Hash === hash
334
346
 
335
- values = hash.values.map! { |value| value.respond_to?(:id) ? value.id : value }
336
- return super if values.any? { |v| StatementCache.unsupported_value?(v) }
337
-
338
- keys = hash.keys.map! do |key|
339
- attribute_aliases[name = key.to_s] || begin
340
- reflection = _reflect_on_association(name)
341
- if reflection&.belongs_to? && !reflection.polymorphic?
342
- reflection.join_foreign_key
343
- elsif reflect_on_aggregation(name)
344
- return super
345
- else
346
- name
347
- end
347
+ hash = hash.each_with_object({}) do |(key, value), h|
348
+ key = key.to_s
349
+ key = attribute_aliases[key] || key
350
+
351
+ return super if reflect_on_aggregation(key)
352
+
353
+ reflection = _reflect_on_association(key)
354
+
355
+ if !reflection
356
+ value = value.id if value.respond_to?(:id)
357
+ elsif reflection.belongs_to? && !reflection.polymorphic?
358
+ key = reflection.join_foreign_key
359
+ pkey = reflection.join_primary_key
360
+ value = value.public_send(pkey) if value.respond_to?(pkey)
348
361
  end
349
- end
350
362
 
351
- return super unless keys.all? { |k| columns_hash.key?(k) }
363
+ if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
364
+ return super
365
+ end
366
+
367
+ h[key] = value
368
+ end
352
369
 
370
+ keys = hash.keys
353
371
  statement = cached_find_by_statement(keys) { |params|
354
372
  wheres = keys.index_with { params.bind }
355
373
  where(wheres).limit(1)
356
374
  }
357
375
 
358
376
  begin
359
- statement.execute(values, connection).first
377
+ statement.execute(hash.values, connection).first
360
378
  rescue TypeError
361
379
  raise ActiveRecord::StatementInvalid
362
380
  end
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  # Return a Hash that can be merged into the main config that represents
43
43
  # the passed in url
44
44
  def build_url_hash
45
- if url.nil? || url.start_with?("jdbc:")
45
+ if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) }
46
46
  { url: url }
47
47
  else
48
48
  ConnectionUrlResolver.new(url).to_hash
@@ -187,27 +187,33 @@ module ActiveRecord
187
187
 
188
188
  value_method_names = []
189
189
  _enum_methods_module.module_eval do
190
- enum_prefix = name if enum_prefix == true
191
- prefix = "#{enum_prefix}_" if enum_prefix
190
+ prefix = if enum_prefix == true
191
+ "#{name}_"
192
+ elsif enum_prefix
193
+ "#{enum_prefix}_"
194
+ end
192
195
 
193
- enum_suffix = name if enum_suffix == true
194
- suffix = "_#{enum_suffix}" if enum_suffix
196
+ suffix = if enum_suffix == true
197
+ "_#{name}"
198
+ elsif enum_suffix
199
+ "_#{enum_suffix}"
200
+ end
195
201
 
196
202
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
197
203
  pairs.each do |label, value|
198
- label = label.to_s
199
204
  enum_values[label] = value
205
+ label = label.to_s
200
206
 
201
207
  value_method_name = "#{prefix}#{label}#{suffix}"
202
208
  value_method_names << value_method_name
203
- define_enum_methods(name, value_method_name, label, enum_scopes)
209
+ define_enum_methods(name, value_method_name, value, enum_scopes)
204
210
 
205
211
  method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
206
212
  value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
207
213
 
208
214
  if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
209
215
  value_method_names << value_method_alias
210
- define_enum_methods(name, value_method_alias, label, enum_scopes)
216
+ define_enum_methods(name, value_method_alias, value, enum_scopes)
211
217
  end
212
218
  end
213
219
  end
@@ -225,23 +231,23 @@ module ActiveRecord
225
231
  private
226
232
  attr_reader :klass
227
233
 
228
- def define_enum_methods(name, value_method_name, label, enum_scopes)
229
- # def active?() status == "active" end
234
+ def define_enum_methods(name, value_method_name, value, enum_scopes)
235
+ # def active?() status_for_database == 0 end
230
236
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
231
- define_method("#{value_method_name}?") { self[name] == label }
237
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
232
238
 
233
239
  # def active!() update!(status: 0) end
234
240
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
235
- define_method("#{value_method_name}!") { update!(name => label) }
241
+ define_method("#{value_method_name}!") { update!(name => value) }
236
242
 
237
243
  # scope :active, -> { where(status: 0) }
238
244
  # scope :not_active, -> { where.not(status: 0) }
239
245
  if enum_scopes != false
240
246
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
241
- klass.scope value_method_name, -> { where(name => label) }
247
+ klass.scope value_method_name, -> { where(name => value) }
242
248
 
243
249
  klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
244
- klass.scope "not_#{value_method_name}", -> { where.not(name => label) }
250
+ klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
245
251
  end
246
252
  end
247
253
  end
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 1
12
- TINY = 1
12
+ TINY = 2
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -89,7 +89,9 @@ module ActiveRecord
89
89
 
90
90
  begin
91
91
  locking_column = self.class.locking_column
92
- previous_lock_value = attribute_before_type_cast(locking_column)
92
+ lock_attribute_was = @attributes[locking_column]
93
+ lock_value_for_database = _lock_value_for_database(locking_column)
94
+
93
95
  attribute_names = attribute_names.dup if attribute_names.frozen?
94
96
  attribute_names << locking_column
95
97
 
@@ -98,7 +100,7 @@ module ActiveRecord
98
100
  affected_rows = self.class._update_record(
99
101
  attributes_with_values(attribute_names),
100
102
  @primary_key => id_in_database,
101
- locking_column => @attributes[locking_column].original_value_for_database
103
+ locking_column => lock_value_for_database
102
104
  )
103
105
 
104
106
  if affected_rows != 1
@@ -109,7 +111,7 @@ module ActiveRecord
109
111
 
110
112
  # If something went wrong, revert the locking_column value.
111
113
  rescue Exception
112
- self[locking_column] = previous_lock_value.to_i
114
+ @attributes[locking_column] = lock_attribute_was
113
115
  raise
114
116
  end
115
117
  end
@@ -121,7 +123,7 @@ module ActiveRecord
121
123
 
122
124
  affected_rows = self.class._delete_record(
123
125
  @primary_key => id_in_database,
124
- locking_column => attribute_before_type_cast(locking_column)
126
+ locking_column => _lock_value_for_database(locking_column)
125
127
  )
126
128
 
127
129
  if affected_rows != 1
@@ -131,6 +133,14 @@ module ActiveRecord
131
133
  affected_rows
132
134
  end
133
135
 
136
+ def _lock_value_for_database(locking_column)
137
+ if will_save_change_to_attribute?(locking_column)
138
+ @attributes[locking_column].value_for_database
139
+ else
140
+ @attributes[locking_column].original_value_for_database
141
+ end
142
+ end
143
+
134
144
  module ClassMethods
135
145
  DEFAULT_LOCKING_COLUMN = "lock_version"
136
146
 
@@ -22,9 +22,10 @@ module ActiveRecord
22
22
  def strict_loading_violation(event)
23
23
  debug do
24
24
  owner = event.payload[:owner]
25
- association = event.payload[:association]
25
+ association = event.payload[:reflection].klass
26
+ name = event.payload[:reflection].name
26
27
 
27
- color("Strict loading violation: #{association} lazily loaded on #{owner}.", RED)
28
+ color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
28
29
  end
29
30
  end
30
31
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
4
-
5
- at_exit do
6
- ActiveRecord::Base.connection.rollback_transaction
3
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after) do
4
+ begin_transaction(joinable: false)
7
5
  end
@@ -162,7 +162,7 @@ module ActiveRecord
162
162
  # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
163
163
  # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
164
164
  def class_name
165
- @class_name ||= -(options[:class_name]&.to_s || derive_class_name)
165
+ @class_name ||= -(options[:class_name] || derive_class_name).to_s
166
166
  end
167
167
 
168
168
  # Returns a list of scopes that should be applied for this Reflection
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [associated_table.join_foreign_key => ids]
12
+ [ associated_table.join_foreign_key => ids ]
13
13
  end
14
14
 
15
15
  private
@@ -9,6 +9,8 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
+ return [ associated_table.join_foreign_key => values ] if values.empty?
13
+
12
14
  type_to_ids_mapping.map do |type, ids|
13
15
  query = {}
14
16
  query[associated_table.join_foreign_type] = type if type
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.1
4
+ version: 6.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-07 00:00:00.000000000 Z
11
+ date: 2021-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 6.1.1
19
+ version: 6.1.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 6.1.1
26
+ version: 6.1.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 6.1.1
33
+ version: 6.1.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 6.1.1
40
+ version: 6.1.2
41
41
  description: Databases on Rails. Build a persistent domain model by mapping database
42
42
  tables to Ruby classes. Strong conventions for associations, validations, aggregations,
43
43
  migrations, and testing come baked-in.
@@ -390,10 +390,10 @@ licenses:
390
390
  - MIT
391
391
  metadata:
392
392
  bug_tracker_uri: https://github.com/rails/rails/issues
393
- changelog_uri: https://github.com/rails/rails/blob/v6.1.1/activerecord/CHANGELOG.md
394
- documentation_uri: https://api.rubyonrails.org/v6.1.1/
393
+ changelog_uri: https://github.com/rails/rails/blob/v6.1.2/activerecord/CHANGELOG.md
394
+ documentation_uri: https://api.rubyonrails.org/v6.1.2/
395
395
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
396
- source_code_uri: https://github.com/rails/rails/tree/v6.1.1/activerecord
396
+ source_code_uri: https://github.com/rails/rails/tree/v6.1.2/activerecord
397
397
  post_install_message:
398
398
  rdoc_options:
399
399
  - "--main"