activerecord 6.0.3.7 → 6.0.4.3

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -0
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +2 -3
  5. data/lib/active_record/associations/association_scope.rb +7 -1
  6. data/lib/active_record/associations/collection_association.rb +7 -0
  7. data/lib/active_record/associations/collection_proxy.rb +1 -0
  8. data/lib/active_record/associations/join_dependency/join_association.rb +7 -0
  9. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  10. data/lib/active_record/associations/join_dependency.rb +14 -6
  11. data/lib/active_record/associations/preloader/association.rb +41 -23
  12. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  13. data/lib/active_record/associations/preloader.rb +6 -2
  14. data/lib/active_record/associations/through_association.rb +1 -1
  15. data/lib/active_record/autosave_association.rb +10 -10
  16. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -0
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -1
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -0
  19. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +9 -4
  20. data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
  21. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -4
  22. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  23. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  24. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  25. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +1 -1
  26. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -1
  27. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -1
  28. data/lib/active_record/core.rb +5 -6
  29. data/lib/active_record/enum.rb +13 -6
  30. data/lib/active_record/fixture_set/render_context.rb +1 -1
  31. data/lib/active_record/gem_version.rb +2 -2
  32. data/lib/active_record/insert_all.rb +1 -1
  33. data/lib/active_record/locking/optimistic.rb +9 -0
  34. data/lib/active_record/model_schema.rb +29 -0
  35. data/lib/active_record/reflection.rb +11 -13
  36. data/lib/active_record/relation/calculations.rb +1 -1
  37. data/lib/active_record/relation/delegation.rb +2 -1
  38. data/lib/active_record/relation/finder_methods.rb +1 -1
  39. data/lib/active_record/relation/merger.rb +7 -2
  40. data/lib/active_record/relation/query_methods.rb +23 -11
  41. data/lib/active_record/relation.rb +6 -3
  42. data/lib/active_record/scoping/named.rb +5 -0
  43. data/lib/active_record/test_fixtures.rb +19 -1
  44. data/lib/active_record/type/time.rb +10 -0
  45. data/lib/active_record/validations/associated.rb +1 -1
  46. metadata +10 -10
@@ -67,15 +67,34 @@ module ActiveRecord
67
67
  end
68
68
 
69
69
  def extract_bounds(value)
70
- from, to = value[1..-2].split(",")
70
+ from, to = value[1..-2].split(",", 2)
71
71
  {
72
- from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from,
73
- to: (value[-2] == "," || to == "infinity") ? infinity : to,
74
- exclude_start: (value[0] == "("),
75
- exclude_end: (value[-1] == ")")
72
+ from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
73
+ to: (to == "" || to == "infinity") ? infinity : unquote(to),
74
+ exclude_start: value.start_with?("("),
75
+ exclude_end: value.end_with?(")")
76
76
  }
77
77
  end
78
78
 
79
+ # When formatting the bound values of range types, PostgreSQL quotes
80
+ # the bound value using double-quotes in certain conditions. Within
81
+ # a double-quoted string, literal " and \ characters are themselves
82
+ # escaped. In input, PostgreSQL accepts multiple escape styles for "
83
+ # (either \" or "") but in output always uses "".
84
+ # See:
85
+ # * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
86
+ # * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
87
+ def unquote(value)
88
+ if value.start_with?('"') && value.end_with?('"')
89
+ unquoted_value = value[1..-2]
90
+ unquoted_value.gsub!('""', '"')
91
+ unquoted_value.gsub!('\\\\', '\\')
92
+ unquoted_value
93
+ else
94
+ value
95
+ end
96
+ end
97
+
79
98
  def infinity(negative: false)
80
99
  if subtype.respond_to?(:infinity)
81
100
  subtype.infinity(negative: negative)
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  module DatabaseStatements
7
7
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
- :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
8
+ :pragma
9
9
  ) # :nodoc:
10
10
  private_constant :READ_QUERY
11
11
 
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
 
62
62
  def remove_foreign_key(from_table, to_table = nil, **options)
63
63
  to_table ||= options[:to_table]
64
- options = options.except(:name, :to_table)
64
+ options = options.except(:name, :to_table, :validate)
65
65
  foreign_keys = foreign_keys(from_table)
66
66
 
67
67
  fkey = foreign_keys.detect do |fk|
@@ -283,13 +283,14 @@ module ActiveRecord
283
283
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
284
284
  alter_table(table_name) do |definition|
285
285
  definition[column_name].instance_eval do
286
- self.type = type
286
+ self.type = aliased_types(type.to_s, type)
287
287
  self.limit = options[:limit] if options.include?(:limit)
288
288
  self.default = options[:default] if options.include?(:default)
289
289
  self.null = options[:null] if options.include?(:null)
290
290
  self.precision = options[:precision] if options.include?(:precision)
291
291
  self.scale = options[:scale] if options.include?(:scale)
292
292
  self.collation = options[:collation] if options.include?(:collation)
293
+ self.options.merge!(options)
293
294
  end
294
295
  end
295
296
  end
@@ -285,18 +285,17 @@ module ActiveRecord
285
285
  false
286
286
  end
287
287
 
288
- private
289
- def cached_find_by_statement(key, &block)
290
- cache = @find_by_statement_cache[connection.prepared_statements]
291
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
292
- end
288
+ def cached_find_by_statement(key, &block) # :nodoc:
289
+ cache = @find_by_statement_cache[connection.prepared_statements]
290
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
291
+ end
293
292
 
293
+ private
294
294
  def relation
295
295
  relation = Relation.create(self)
296
296
 
297
297
  if finder_needs_type_condition? && !ignore_default_scope?
298
298
  relation.where!(type_condition)
299
- relation.create_with!(inheritance_column.to_s => sti_name)
300
299
  else
301
300
  relation
302
301
  end
@@ -173,6 +173,7 @@ module ActiveRecord
173
173
 
174
174
  _enum_methods_module.module_eval do
175
175
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
176
+ value_method_names = []
176
177
  pairs.each do |label, value|
177
178
  if enum_prefix == true
178
179
  prefix = "#{name}_"
@@ -186,6 +187,7 @@ module ActiveRecord
186
187
  end
187
188
 
188
189
  value_method_name = "#{prefix}#{label}#{suffix}"
190
+ value_method_names << value_method_name
189
191
  enum_values[label] = value
190
192
  label = label.to_s
191
193
 
@@ -200,8 +202,6 @@ module ActiveRecord
200
202
  # scope :active, -> { where(status: 0) }
201
203
  # scope :not_active, -> { where.not(status: 0) }
202
204
  if enum_scopes != false
203
- klass.send(:detect_negative_condition!, value_method_name)
204
-
205
205
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
206
206
  klass.scope value_method_name, -> { where(attr => value) }
207
207
 
@@ -209,6 +209,7 @@ module ActiveRecord
209
209
  klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
210
210
  end
211
211
  end
212
+ klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
212
213
  end
213
214
  enum_values.freeze
214
215
  end
@@ -264,10 +265,16 @@ module ActiveRecord
264
265
  }
265
266
  end
266
267
 
267
- def detect_negative_condition!(method_name)
268
- if method_name.start_with?("not_") && logger
269
- logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
270
- " This will cause a conflict with auto generated negative scopes."
268
+ def detect_negative_enum_conditions!(method_names)
269
+ return unless logger
270
+
271
+ method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
272
+ inverted_form = potential_not.sub("not_", "")
273
+ if method_names.include?(inverted_form)
274
+ logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
275
+ " This has caused a conflict with auto generated negative scopes." \
276
+ " Avoid using enum elements starting with 'not' where the positive form is also an element."
277
+ end
271
278
  end
272
279
  end
273
280
  end
@@ -10,7 +10,7 @@ class ActiveRecord::FixtureSet::RenderContext # :nodoc:
10
10
  end
11
11
 
12
12
  def binary(path)
13
- %(!!binary "#{Base64.strict_encode64(File.read(path))}")
13
+ %(!!binary "#{Base64.strict_encode64(File.read(path, mode: 'rb'))}")
14
14
  end
15
15
  end
16
16
  end
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 0
12
- TINY = 3
13
- PRE = "7"
12
+ TINY = 4
13
+ PRE = "3"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -168,7 +168,7 @@ module ActiveRecord
168
168
  end
169
169
 
170
170
  def format_columns(columns)
171
- quote_columns(columns).join(",")
171
+ columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
172
172
  end
173
173
 
174
174
  def quote_columns(columns)
@@ -60,6 +60,15 @@ module ActiveRecord
60
60
  self.class.locking_enabled?
61
61
  end
62
62
 
63
+ def increment!(*, **) #:nodoc:
64
+ super.tap do
65
+ if locking_enabled?
66
+ self[self.class.locking_column] += 1
67
+ clear_attribute_change(self.class.locking_column)
68
+ end
69
+ end
70
+ end
71
+
63
72
  private
64
73
  def _create_record(attribute_names = self.attribute_names)
65
74
  if locking_enabled?
@@ -287,6 +287,35 @@ module ActiveRecord
287
287
 
288
288
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
289
289
  # accessors defined, and won't be referenced in SQL queries.
290
+ #
291
+ # A common usage pattern for this method is to ensure all references to an attribute
292
+ # have been removed and deployed, before a migration to drop the column from the database
293
+ # has been deployed and run. Using this two step approach to dropping columns ensures there
294
+ # is no code that raises errors due to having a cached schema in memory at the time the
295
+ # schema migration is run.
296
+ #
297
+ # For example, given a model where you want to drop the "category" attribute, first mark it
298
+ # as ignored:
299
+ #
300
+ # class Project < ActiveRecord::Base
301
+ # # schema:
302
+ # # id :bigint
303
+ # # name :string, limit: 255
304
+ # # category :string, limit: 255
305
+ #
306
+ # self.ignored_columns = [:category]
307
+ # end
308
+ #
309
+ # The schema still contains `category`, but now the model omits it, so any meta-driven code or
310
+ # schema caching will not attempt to use the column:
311
+ #
312
+ # Project.columns_hash["category"] => nil
313
+ #
314
+ # You will get an error if accessing that attribute directly, so ensure all usages of the
315
+ # column are removed (automated tests can help you find any usages).
316
+ #
317
+ # user = Project.create!(name: "First Project")
318
+ # user.category # => raises NoMethodError
290
319
  def ignored_columns=(columns)
291
320
  @ignored_columns = columns.map(&:to_s)
292
321
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/filters"
4
- require "concurrent/map"
5
4
 
6
5
  module ActiveRecord
7
6
  # = Active Record Reflection
@@ -201,9 +200,9 @@ module ActiveRecord
201
200
  klass_scope
202
201
  end
203
202
 
204
- def join_scopes(table, predicate_builder) # :nodoc:
203
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
205
204
  if scope
206
- [scope_for(build_scope(table, predicate_builder))]
205
+ [scope_for(build_scope(table, predicate_builder, klass))]
207
206
  else
208
207
  []
209
208
  end
@@ -292,7 +291,7 @@ module ActiveRecord
292
291
  JoinKeys.new(join_primary_key(association_klass), join_foreign_key)
293
292
  end
294
293
 
295
- def build_scope(table, predicate_builder = predicate_builder(table))
294
+ def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
296
295
  Relation.create(
297
296
  klass,
298
297
  table: table,
@@ -432,19 +431,18 @@ module ActiveRecord
432
431
  @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
433
432
  @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type")
434
433
  @constructable = calculate_constructable(macro, options)
435
- @association_scope_cache = Concurrent::Map.new
436
434
 
437
435
  if options[:class_name] && options[:class_name].class == Class
438
436
  raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
439
437
  end
440
438
  end
441
439
 
442
- def association_scope_cache(conn, owner, &block)
443
- key = conn.prepared_statements
440
+ def association_scope_cache(klass, owner, &block)
441
+ key = self
444
442
  if polymorphic?
445
443
  key = [key, owner._read_attribute(@foreign_type)]
446
444
  end
447
- @association_scope_cache.compute_if_absent(key) { StatementCache.create(conn, &block) }
445
+ klass.cached_find_by_statement(key, &block)
448
446
  end
449
447
 
450
448
  def constructable? # :nodoc:
@@ -510,7 +508,7 @@ module ActiveRecord
510
508
  # This is for clearing cache on the reflection. Useful for tests that need to compare
511
509
  # SQL queries on associations.
512
510
  def clear_association_scope_cache # :nodoc:
513
- @association_scope_cache.clear
511
+ klass.initialize_find_by_cache
514
512
  end
515
513
 
516
514
  def nested?
@@ -839,8 +837,8 @@ module ActiveRecord
839
837
  source_reflection.scopes + super
840
838
  end
841
839
 
842
- def join_scopes(table, predicate_builder) # :nodoc:
843
- source_reflection.join_scopes(table, predicate_builder) + super
840
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
841
+ source_reflection.join_scopes(table, predicate_builder, klass) + super
844
842
  end
845
843
 
846
844
  def has_scope?
@@ -1003,9 +1001,9 @@ module ActiveRecord
1003
1001
  @previous_reflection = previous_reflection
1004
1002
  end
1005
1003
 
1006
- def join_scopes(table, predicate_builder) # :nodoc:
1004
+ def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
1007
1005
  scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
1008
- scopes << build_scope(table, predicate_builder).instance_exec(nil, &source_type_scope)
1006
+ scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
1009
1007
  end
1010
1008
 
1011
1009
  def constraints
@@ -134,7 +134,7 @@ module ActiveRecord
134
134
  relation.select_values = [ klass.primary_key || table[Arel.star] ]
135
135
  end
136
136
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
137
- relation.order_values = []
137
+ relation.order_values = [] if group_values.empty?
138
138
  end
139
139
 
140
140
  relation.calculate(operation, column_name)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mutex_m"
4
+ require "active_support/core_ext/module/delegation"
4
5
 
5
6
  module ActiveRecord
6
7
  module Delegation # :nodoc:
@@ -59,7 +60,7 @@ module ActiveRecord
59
60
  synchronize do
60
61
  return if method_defined?(method)
61
62
 
62
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
63
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
63
64
  definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
64
65
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
65
66
  def #{method}(#{definition})
@@ -371,7 +371,7 @@ module ActiveRecord
371
371
 
372
372
  def apply_join_dependency(eager_loading: group_values.empty?)
373
373
  join_dependency = construct_join_dependency(
374
- eager_load_values + includes_values, Arel::Nodes::OuterJoin
374
+ eager_load_values | includes_values, Arel::Nodes::OuterJoin
375
375
  )
376
376
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
377
377
 
@@ -135,11 +135,16 @@ module ActiveRecord
135
135
  if other.klass == relation.klass
136
136
  relation.left_outer_joins!(*other.left_outer_joins_values)
137
137
  else
138
- associations = other.left_outer_joins_values
138
+ associations, others = other.left_outer_joins_values.partition do |join|
139
+ case join
140
+ when Hash, Symbol, Array; true
141
+ end
142
+ end
143
+
139
144
  join_dependency = other.construct_join_dependency(
140
145
  associations, Arel::Nodes::OuterJoin
141
146
  )
142
- relation.joins!(join_dependency)
147
+ relation.left_outer_joins!(join_dependency, *others)
143
148
  end
144
149
  end
145
150
 
@@ -331,7 +331,7 @@ module ActiveRecord
331
331
  def group!(*args) # :nodoc:
332
332
  args.flatten!
333
333
 
334
- self.group_values |= args
334
+ self.group_values += args
335
335
  self
336
336
  end
337
337
 
@@ -1094,12 +1094,14 @@ module ActiveRecord
1094
1094
  end
1095
1095
  end
1096
1096
 
1097
- def select_association_list(associations)
1097
+ def select_association_list(associations, stashed_joins = nil)
1098
1098
  result = []
1099
1099
  associations.each do |association|
1100
1100
  case association
1101
1101
  when Hash, Symbol, Array
1102
1102
  result << association
1103
+ when ActiveRecord::Associations::JoinDependency
1104
+ stashed_joins&.<< association
1103
1105
  else
1104
1106
  yield if block_given?
1105
1107
  end
@@ -1107,28 +1109,32 @@ module ActiveRecord
1107
1109
  result
1108
1110
  end
1109
1111
 
1110
- def valid_association_list(associations)
1111
- select_association_list(associations) do
1112
+ def valid_association_list(associations, stashed_joins)
1113
+ select_association_list(associations, stashed_joins) do
1112
1114
  raise ArgumentError, "only Hash, Symbol and Array are allowed"
1113
1115
  end
1114
1116
  end
1115
1117
 
1116
1118
  def build_left_outer_joins(manager, outer_joins, aliases)
1117
1119
  buckets = Hash.new { |h, k| h[k] = [] }
1118
- buckets[:association_join] = valid_association_list(outer_joins)
1120
+ buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
1119
1121
  build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1120
1122
  end
1121
1123
 
1124
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1125
+ end
1126
+
1122
1127
  def build_joins(manager, joins, aliases)
1123
1128
  buckets = Hash.new { |h, k| h[k] = [] }
1124
1129
 
1125
1130
  unless left_outer_joins_values.empty?
1126
- left_joins = valid_association_list(left_outer_joins_values.flatten)
1127
- buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1131
+ stashed_left_joins = []
1132
+ left_joins = valid_association_list(left_outer_joins_values.flatten, stashed_left_joins)
1133
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1128
1134
  end
1129
1135
 
1130
1136
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1131
- buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
1137
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1132
1138
  end
1133
1139
 
1134
1140
  joins.map! do |join|
@@ -1141,7 +1147,7 @@ module ActiveRecord
1141
1147
 
1142
1148
  while joins.first.is_a?(Arel::Nodes::Join)
1143
1149
  join_node = joins.shift
1144
- if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1150
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1145
1151
  buckets[:join_node] << join_node
1146
1152
  else
1147
1153
  buckets[:leading_join] << join_node
@@ -1161,6 +1167,9 @@ module ActiveRecord
1161
1167
  end
1162
1168
  end
1163
1169
 
1170
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1171
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1172
+
1164
1173
  build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1165
1174
  end
1166
1175
 
@@ -1356,12 +1365,15 @@ module ActiveRecord
1356
1365
  end
1357
1366
  end
1358
1367
 
1359
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1368
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1360
1369
  def structurally_incompatible_values_for_or(other)
1361
1370
  values = other.values
1362
1371
  STRUCTURAL_OR_METHODS.reject do |method|
1363
1372
  default = DEFAULT_VALUES[method]
1364
- @values.fetch(method, default) == values.fetch(method, default)
1373
+ v1, v2 = @values.fetch(method, default), values.fetch(method, default)
1374
+ v1 = v1.uniq if v1.is_a?(Array)
1375
+ v2 = v2.uniq if v2.is_a?(Array)
1376
+ v1 == v2
1365
1377
  end
1366
1378
  end
1367
1379
 
@@ -497,8 +497,8 @@ module ActiveRecord
497
497
  update_all updates
498
498
  end
499
499
 
500
- # Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
501
- # set to the current time or the time specified.
500
+ # Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified.
501
+ # It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations.
502
502
  # This method can be passed attribute names and an optional time argument.
503
503
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
504
504
  # If no time argument is passed, the current time is used as default.
@@ -671,7 +671,10 @@ module ActiveRecord
671
671
  end
672
672
 
673
673
  def scope_for_create
674
- where_values_hash.merge!(create_with_value.stringify_keys)
674
+ hash = where_values_hash
675
+ hash.delete(klass.inheritance_column) if klass.finder_needs_type_condition?
676
+ create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
677
+ hash
675
678
  end
676
679
 
677
680
  # Returns true if relation needs eager loading.
@@ -200,11 +200,16 @@ module ActiveRecord
200
200
  scope
201
201
  end
202
202
  end
203
+ singleton_class.send(:ruby2_keywords, name) if respond_to?(:ruby2_keywords, true)
203
204
 
204
205
  generate_relation_method(name)
205
206
  end
206
207
 
207
208
  private
209
+ def singleton_method_added(name)
210
+ generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
211
+ end
212
+
208
213
  def valid_scope_name?(name)
209
214
  if respond_to?(name, true) && logger
210
215
  logger.warn "Creating scope :#{name}. " \
@@ -112,6 +112,8 @@ module ActiveRecord
112
112
 
113
113
  # Load fixtures once and begin transaction.
114
114
  if run_in_transaction?
115
+ @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
116
+
115
117
  if @@already_loaded_fixtures[self.class]
116
118
  @loaded_fixtures = @@already_loaded_fixtures[self.class]
117
119
  else
@@ -166,6 +168,7 @@ module ActiveRecord
166
168
  connection.pool.lock_thread = false
167
169
  end
168
170
  @fixture_connections.clear
171
+ teardown_shared_connection_pool
169
172
  else
170
173
  ActiveRecord::FixtureSet.reset_cache
171
174
  end
@@ -187,7 +190,7 @@ module ActiveRecord
187
190
  # need to share a connection pool so that the reading connection
188
191
  # can see data in the open transaction on the writing connection.
189
192
  def setup_shared_connection_pool
190
- writing_handler = ActiveRecord::Base.connection_handler
193
+ writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
191
194
 
192
195
  ActiveRecord::Base.connection_handlers.values.each do |handler|
193
196
  if handler != writing_handler
@@ -195,12 +198,27 @@ module ActiveRecord
195
198
  name = pool.spec.name
196
199
  writing_connection = writing_handler.retrieve_connection_pool(name)
197
200
  return unless writing_connection
201
+
202
+ reading_connection = handler.send(:owner_to_pool)[name]
203
+ next if reading_connection == writing_connection
204
+
205
+ @saved_pool_configs[handler][name] = reading_connection
198
206
  handler.send(:owner_to_pool)[name] = writing_connection
199
207
  end
200
208
  end
201
209
  end
202
210
  end
203
211
 
212
+ def teardown_shared_connection_pool
213
+ @saved_pool_configs.each_pair do |handler, pools|
214
+ pools.each_pair do |name, pool|
215
+ handler.send(:owner_to_pool)[name] = pool
216
+ end
217
+ end
218
+
219
+ @saved_pool_configs.clear
220
+ end
221
+
204
222
  def load_fixtures(config)
205
223
  fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
206
224
  Hash[fixtures.map { |f| [f.name, f] }]
@@ -16,6 +16,16 @@ module ActiveRecord
16
16
  value
17
17
  end
18
18
  end
19
+
20
+ private
21
+ def cast_value(value)
22
+ case value = super
23
+ when Value
24
+ value.__getobj__
25
+ else
26
+ value
27
+ end
28
+ end
19
29
  end
20
30
  end
21
31
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
6
6
  def validate_each(record, attribute, value)
7
7
  if Array(value).reject { |r| valid_object?(r) }.any?
8
- record.errors.add(attribute, :invalid, **options.merge(value: value))
8
+ record.errors.add(attribute, :invalid, options.merge(value: value))
9
9
  end
10
10
  end
11
11