activerecord 5.2.0 → 5.2.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +214 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations.rb +9 -9
  5. data/lib/active_record/associations/alias_tracker.rb +1 -1
  6. data/lib/active_record/associations/association.rb +25 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  8. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  9. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +25 -1
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency.rb +39 -64
  17. data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
  18. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  19. data/lib/active_record/associations/singular_association.rb +4 -10
  20. data/lib/active_record/associations/through_association.rb +1 -1
  21. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  22. data/lib/active_record/attribute_methods/read.rb +1 -1
  23. data/lib/active_record/autosave_association.rb +7 -2
  24. data/lib/active_record/callbacks.rb +4 -0
  25. data/lib/active_record/collection_cache_key.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +14 -8
  27. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -14
  33. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  36. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  37. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  39. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  43. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  44. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  46. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  47. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  48. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -4
  49. data/lib/active_record/core.rb +2 -1
  50. data/lib/active_record/counter_cache.rb +17 -13
  51. data/lib/active_record/enum.rb +1 -0
  52. data/lib/active_record/errors.rb +18 -12
  53. data/lib/active_record/gem_version.rb +1 -1
  54. data/lib/active_record/log_subscriber.rb +1 -1
  55. data/lib/active_record/migration.rb +1 -1
  56. data/lib/active_record/migration/compatibility.rb +15 -15
  57. data/lib/active_record/model_schema.rb +1 -1
  58. data/lib/active_record/persistence.rb +6 -5
  59. data/lib/active_record/query_cache.rb +4 -11
  60. data/lib/active_record/querying.rb +1 -1
  61. data/lib/active_record/railtie.rb +1 -3
  62. data/lib/active_record/relation.rb +39 -20
  63. data/lib/active_record/relation/calculations.rb +11 -8
  64. data/lib/active_record/relation/delegation.rb +30 -0
  65. data/lib/active_record/relation/finder_methods.rb +10 -8
  66. data/lib/active_record/relation/merger.rb +10 -11
  67. data/lib/active_record/relation/predicate_builder.rb +20 -14
  68. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  69. data/lib/active_record/relation/query_attribute.rb +5 -3
  70. data/lib/active_record/relation/query_methods.rb +45 -19
  71. data/lib/active_record/relation/spawn_methods.rb +1 -1
  72. data/lib/active_record/scoping/named.rb +2 -0
  73. data/lib/active_record/tasks/database_tasks.rb +1 -1
  74. data/lib/active_record/timestamp.rb +8 -1
  75. data/lib/active_record/transactions.rb +23 -20
  76. data/lib/active_record/type/serialized.rb +4 -0
  77. metadata +9 -10
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
  alias :in_use? :owner
82
82
 
83
83
  def self.type_cast_config_to_integer(config)
84
- if config =~ SIMPLE_INT
84
+ if config.is_a?(Integer)
85
+ config
86
+ elsif config =~ SIMPLE_INT
85
87
  config.to_i
86
88
  else
87
89
  config
@@ -560,17 +560,6 @@ module ActiveRecord
560
560
  @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
561
561
  end
562
562
 
563
- def with_multi_statements
564
- previous_flags = @config[:flags]
565
- @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
566
- reconnect!
567
-
568
- yield
569
- ensure
570
- @config[:flags] = previous_flags
571
- reconnect!
572
- end
573
-
574
563
  def initialize_type_map(m = type_map)
575
564
  super
576
565
 
@@ -815,15 +804,25 @@ module ActiveRecord
815
804
  end
816
805
 
817
806
  def mismatched_foreign_key(message)
818
- parts = message.scan(/`(\w+)`[ $)]/).flatten
819
- MismatchedForeignKey.new(
820
- self,
807
+ match = %r/
808
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
+ /xmi.match(message)
812
+
813
+ options = {
821
814
  message: message,
822
- table: parts[0],
823
- foreign_key: parts[1],
824
- target_table: parts[2],
825
- primary_key: parts[3],
826
- )
815
+ }
816
+
817
+ if match
818
+ options[:table] = match[:table]
819
+ options[:foreign_key] = match[:foreign_key]
820
+ options[:target_table] = match[:target_table]
821
+ options[:primary_key] = match[:primary_key]
822
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
+ end
824
+
825
+ MismatchedForeignKey.new(options)
827
826
  end
828
827
 
829
828
  def integer_to_sql(limit) # :nodoc:
@@ -195,12 +195,12 @@ module ActiveRecord
195
195
  if e.path == path_to_adapter
196
196
  # We can assume that a non-builtin adapter was specified, so it's
197
197
  # either misspelled or missing from Gemfile.
198
- raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
198
+ raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
199
199
 
200
200
  # Bubbled up from the adapter require. Prefix the exception message
201
201
  # with some guidance about how to address it and reraise.
202
202
  else
203
- raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
203
+ raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
204
204
  end
205
205
  end
206
206
 
@@ -3,15 +3,24 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module DetermineIfPreparableVisitor
6
- attr_reader :preparable
6
+ attr_accessor :preparable
7
7
 
8
8
  def accept(*)
9
9
  @preparable = true
10
10
  super
11
11
  end
12
12
 
13
- def visit_Arel_Nodes_In(*)
13
+ def visit_Arel_Nodes_In(o, collector)
14
14
  @preparable = false
15
+
16
+ if Array === o.right && !o.right.empty?
17
+ o.right.delete_if do |bind|
18
+ if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value
19
+ !bind.value.boundable?
20
+ end
21
+ end
22
+ end
23
+
15
24
  super
16
25
  end
17
26
 
@@ -62,6 +62,42 @@ module ActiveRecord
62
62
  @connection.next_result while @connection.more_results?
63
63
  end
64
64
 
65
+ def supports_set_server_option?
66
+ @connection.respond_to?(:set_server_option)
67
+ end
68
+
69
+ def multi_statements_enabled?(flags)
70
+ if flags.is_a?(Array)
71
+ flags.include?("MULTI_STATEMENTS")
72
+ else
73
+ (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
74
+ end
75
+ end
76
+
77
+ def with_multi_statements
78
+ previous_flags = @config[:flags]
79
+
80
+ unless multi_statements_enabled?(previous_flags)
81
+ if supports_set_server_option?
82
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
83
+ else
84
+ @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
85
+ reconnect!
86
+ end
87
+ end
88
+
89
+ yield
90
+ ensure
91
+ unless multi_statements_enabled?(previous_flags)
92
+ if supports_set_server_option?
93
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
94
+ else
95
+ @config[:flags] = previous_flags
96
+ reconnect!
97
+ end
98
+ end
99
+ end
100
+
65
101
  def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
66
102
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
67
103
  # made since we established the connection
@@ -80,8 +80,8 @@ module ActiveRecord
80
80
 
81
81
  def new_column_from_field(table_name, field)
82
82
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
83
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\(\))?\z/i.match?(field[:Default])
84
- default, default_function = nil, "CURRENT_TIMESTAMP"
83
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
84
+ default, default_function = nil, field[:Default]
85
85
  else
86
86
  default, default_function = field[:Default], nil
87
87
  end
@@ -33,7 +33,13 @@ module ActiveRecord
33
33
 
34
34
  def cast(value)
35
35
  if value.is_a?(::String)
36
- value = @pg_decoder.decode(value)
36
+ value = begin
37
+ @pg_decoder.decode(value)
38
+ rescue TypeError
39
+ # malformed array string is treated as [], will raise in PG 2.0 gem
40
+ # this keeps a consistent implementation
41
+ []
42
+ end
37
43
  end
38
44
  type_cast_array(value, :cast)
39
45
  end
@@ -66,6 +72,10 @@ module ActiveRecord
66
72
  deserialize(raw_old_value) != new_value
67
73
  end
68
74
 
75
+ def force_equality?(value)
76
+ value.is_a?(::Array)
77
+ end
78
+
69
79
  private
70
80
 
71
81
  def type_cast_array(value, method)
@@ -53,6 +53,10 @@ module ActiveRecord
53
53
  ::Range.new(new_begin, new_end, value.exclude_end?)
54
54
  end
55
55
 
56
+ def force_equality?(value)
57
+ value.is_a?(::Range)
58
+ end
59
+
56
60
  private
57
61
 
58
62
  def type_cast_single(value)
@@ -17,6 +17,42 @@ module ActiveRecord
17
17
  "VALIDATE CONSTRAINT #{quote_column_name(name)}"
18
18
  end
19
19
 
20
+ def visit_ChangeColumnDefinition(o)
21
+ column = o.column
22
+ column.sql_type = type_to_sql(column.type, column.options)
23
+ quoted_column_name = quote_column_name(o.name)
24
+
25
+ change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
26
+
27
+ options = column_options(column)
28
+
29
+ if options[:collation]
30
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
31
+ end
32
+
33
+ if options[:using]
34
+ change_column_sql << " USING #{options[:using]}"
35
+ elsif options[:cast_as]
36
+ cast_as_type = type_to_sql(options[:cast_as], options)
37
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
38
+ end
39
+
40
+ if options.key?(:default)
41
+ if options[:default].nil?
42
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
43
+ else
44
+ quoted_default = quote_default_expression(options[:default], column)
45
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
46
+ end
47
+ end
48
+
49
+ if options.key?(:null)
50
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
51
+ end
52
+
53
+ change_column_sql
54
+ end
55
+
20
56
  def add_column_options!(sql, options)
21
57
  if options[:collation]
22
58
  sql << " COLLATE \"#{options[:collation]}\""
@@ -124,7 +124,7 @@ module ActiveRecord
124
124
 
125
125
  # add info on sort order (only desc order is explicitly specified, asc is the default)
126
126
  # and non-default opclasses
127
- expressions.scan(/(?<column>\w+)\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
127
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
128
128
  opclasses[column] = opclass.to_sym if opclass
129
129
  if nulls
130
130
  orders[column] = [desc, nulls].compact.join(" ")
@@ -683,34 +683,20 @@ module ActiveRecord
683
683
  end
684
684
  end
685
685
 
686
- def change_column_sql(table_name, column_name, type, options = {})
687
- quoted_column_name = quote_column_name(column_name)
688
- sql_type = type_to_sql(type, options)
689
- sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
690
- if options[:collation]
691
- sql << " COLLATE \"#{options[:collation]}\""
692
- end
693
- if options[:using]
694
- sql << " USING #{options[:using]}"
695
- elsif options[:cast_as]
696
- cast_as_type = type_to_sql(options[:cast_as], options)
697
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
698
- end
699
-
700
- sql
686
+ def add_column_for_alter(table_name, column_name, type, options = {})
687
+ return super unless options.key?(:comment)
688
+ [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
701
689
  end
702
690
 
703
691
  def change_column_for_alter(table_name, column_name, type, options = {})
704
- sqls = [change_column_sql(table_name, column_name, type, options)]
705
- sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
706
- sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
692
+ td = create_table_definition(table_name)
693
+ cd = td.new_column_definition(column_name, type, options)
694
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
707
695
  sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
708
696
  sqls
709
697
  end
710
698
 
711
-
712
- # Changes the default value of a table column.
713
- def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
699
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
714
700
  column = column_for(table_name, column_name)
715
701
  return unless column
716
702
 
@@ -725,8 +711,8 @@ module ActiveRecord
725
711
  end
726
712
  end
727
713
 
728
- def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
729
- "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
714
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
715
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
730
716
  end
731
717
 
732
718
  def add_timestamps_for_alter(table_name, options = {})
@@ -751,7 +737,7 @@ module ActiveRecord
751
737
 
752
738
  def data_source_sql(name = nil, type: nil)
753
739
  scope = quoted_scope(name, type: type)
754
- scope[:type] ||= "'r','v','m','f'" # (r)elation/table, (v)iew, (m)aterialized view, (f)oreign table
740
+ scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
755
741
 
756
742
  sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
757
743
  sql << " WHERE n.nspname = #{scope[:schema]}"
@@ -765,7 +751,7 @@ module ActiveRecord
765
751
  type = \
766
752
  case type
767
753
  when "BASE TABLE"
768
- "'r'"
754
+ "'r','p'"
769
755
  when "VIEW"
770
756
  "'v','m'"
771
757
  when "FOREIGN TABLE"
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  # * <tt>"schema_name".table_name</tt>
69
69
  # * <tt>"schema.name"."table name"</tt>
70
70
  def extract_schema_qualified_name(string)
71
- schema, table = string.scan(/[^".\s]+|"[^"]*"/)
71
+ schema, table = string.scan(/[^".]+|"[^"]*"/)
72
72
  if table.nil?
73
73
  table = schema
74
74
  schema = nil
@@ -4,6 +4,14 @@
4
4
  gem "pg", ">= 0.18", "< 2.0"
5
5
  require "pg"
6
6
 
7
+ # Use async_exec instead of exec_params on pg versions before 1.1
8
+ class ::PG::Connection
9
+ unless self.public_method_defined?(:async_exec_params)
10
+ remove_method :exec_params
11
+ alias exec_params async_exec
12
+ end
13
+ end
14
+
7
15
  require "active_record/connection_adapters/abstract_adapter"
8
16
  require "active_record/connection_adapters/statement_pool"
9
17
  require "active_record/connection_adapters/postgresql/column"
@@ -600,7 +608,7 @@ module ActiveRecord
600
608
  type_casted_binds = type_casted_binds(binds)
601
609
  log(sql, name, binds, type_casted_binds) do
602
610
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
603
- @connection.async_exec(sql, type_casted_binds)
611
+ @connection.exec_params(sql, type_casted_binds)
604
612
  end
605
613
  end
606
614
  end
@@ -12,11 +12,16 @@ module ActiveRecord
12
12
  quote_column_name(attr)
13
13
  end
14
14
 
15
+ def quote_table_name(name)
16
+ @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
17
+ end
18
+
15
19
  def quote_column_name(name)
16
20
  @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
17
21
  end
18
22
 
19
23
  def quoted_time(value)
24
+ value = value.change(year: 2000, month: 1, day: 1)
20
25
  quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
21
26
  end
22
27
 
@@ -7,6 +7,10 @@ module ActiveRecord
7
7
  # Returns an array of indexes for the given table.
8
8
  def indexes(table_name)
9
9
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
10
+ # Indexes SQLite creates implicitly for internal use start with "sqlite_".
11
+ # See https://www.sqlite.org/fileformat2.html#intschema
12
+ next if row["name"].starts_with?("sqlite_")
13
+
10
14
  index_sql = query_value(<<-SQL, "SCHEMA")
11
15
  SELECT sql
12
16
  FROM sqlite_master
@@ -40,7 +44,7 @@ module ActiveRecord
40
44
  where: where,
41
45
  orders: orders
42
46
  )
43
- end
47
+ end.compact
44
48
  end
45
49
 
46
50
  def create_schema_dumper(options)
@@ -9,7 +9,7 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
9
9
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
10
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
11
 
12
- gem "sqlite3", "~> 1.3.6"
12
+ gem "sqlite3", "~> 1.3", ">= 1.3.6"
13
13
  require "sqlite3"
14
14
 
15
15
  module ActiveRecord
@@ -453,9 +453,6 @@ module ActiveRecord
453
453
  def copy_table_indexes(from, to, rename = {})
454
454
  indexes(from).each do |index|
455
455
  name = index.name
456
- # indexes sqlite creates for internal use start with `sqlite_` and
457
- # don't need to be copied
458
- next if name.starts_with?("sqlite_")
459
456
  if to == "a#{from}"
460
457
  name = "t#{name}"
461
458
  elsif from == "a#{to}"
@@ -184,7 +184,8 @@ module ActiveRecord
184
184
  end
185
185
 
186
186
  def find_by(*args) # :nodoc:
187
- return super if scope_attributes? || reflect_on_all_aggregations.any?
187
+ return super if scope_attributes? || reflect_on_all_aggregations.any? ||
188
+ columns_hash.key?(inheritance_column) && base_class != self
188
189
 
189
190
  hash = args.first
190
191
 
@@ -47,8 +47,12 @@ module ActiveRecord
47
47
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
48
48
  counter_name = reflection.counter_cache_column
49
49
 
50
- updates = { counter_name.to_sym => object.send(counter_association).count(:all) }
51
- updates.merge!(touch_updates(touch)) if touch
50
+ updates = { counter_name => object.send(counter_association).count(:all) }
51
+
52
+ if touch
53
+ names = touch if touch != true
54
+ updates.merge!(touch_attributes_with_time(*names))
55
+ end
52
56
 
53
57
  unscoped.where(primary_key => object.id).update_all(updates)
54
58
  end
@@ -68,8 +72,8 @@ module ActiveRecord
68
72
  # * +counters+ - A Hash containing the names of the fields
69
73
  # to update as keys and the amount to update the field by as values.
70
74
  # * <tt>:touch</tt> option - Touch timestamp columns when updating.
71
- # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
72
- # touch that column or an array of symbols to touch just those ones.
75
+ # If attribute names are passed, they are updated along with updated_at/on
76
+ # attributes.
73
77
  #
74
78
  # ==== Examples
75
79
  #
@@ -107,11 +111,18 @@ module ActiveRecord
107
111
  end
108
112
 
109
113
  if touch
110
- touch_updates = touch_updates(touch)
114
+ names = touch if touch != true
115
+ touch_updates = touch_attributes_with_time(*names)
111
116
  updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
112
117
  end
113
118
 
114
- unscoped.where(primary_key => id).update_all updates.join(", ")
119
+ if id.is_a?(Relation) && self == id.klass
120
+ relation = id
121
+ else
122
+ relation = unscoped.where!(primary_key => id)
123
+ end
124
+
125
+ relation.update_all updates.join(", ")
115
126
  end
116
127
 
117
128
  # Increment a numeric field by one, via a direct SQL update.
@@ -165,13 +176,6 @@ module ActiveRecord
165
176
  def decrement_counter(counter_name, id, touch: nil)
166
177
  update_counters(id, counter_name => -1, touch: touch)
167
178
  end
168
-
169
- private
170
- def touch_updates(touch)
171
- touch = timestamp_attributes_for_update_in_model if touch == true
172
- touch_time = current_time_from_proper_timezone
173
- Array(touch).map { |column| [ column, touch_time ] }.to_h
174
- end
175
179
  end
176
180
 
177
181
  private