activerecord 6.0.0.beta3 → 6.0.0.rc1

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +286 -6
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/associations.rb +3 -2
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/builder/association.rb +14 -18
  8. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  10. data/lib/active_record/associations/builder/has_many.rb +2 -0
  11. data/lib/active_record/associations/builder/has_one.rb +35 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  13. data/lib/active_record/associations/collection_proxy.rb +1 -1
  14. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  15. data/lib/active_record/associations/preloader.rb +11 -6
  16. data/lib/active_record/associations/preloader/association.rb +32 -30
  17. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  18. data/lib/active_record/attribute_methods.rb +4 -3
  19. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  20. data/lib/active_record/attribute_methods/dirty.rb +42 -14
  21. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  22. data/lib/active_record/attribute_methods/query.rb +2 -3
  23. data/lib/active_record/attribute_methods/read.rb +3 -9
  24. data/lib/active_record/attribute_methods/write.rb +6 -12
  25. data/lib/active_record/attributes.rb +13 -0
  26. data/lib/active_record/autosave_association.rb +13 -3
  27. data/lib/active_record/base.rb +0 -1
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
  29. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  30. data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
  31. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  32. data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
  37. data/lib/active_record/connection_adapters/column.rb +17 -13
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  39. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
  41. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  42. data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
  43. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  45. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  46. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  47. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
  48. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  49. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
  53. data/lib/active_record/connection_handling.rb +17 -10
  54. data/lib/active_record/core.rb +15 -20
  55. data/lib/active_record/database_configurations.rb +14 -14
  56. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  57. data/lib/active_record/database_configurations/url_config.rb +12 -12
  58. data/lib/active_record/dynamic_matchers.rb +1 -1
  59. data/lib/active_record/enum.rb +6 -0
  60. data/lib/active_record/errors.rb +1 -1
  61. data/lib/active_record/gem_version.rb +1 -1
  62. data/lib/active_record/insert_all.rb +180 -0
  63. data/lib/active_record/integration.rb +13 -1
  64. data/lib/active_record/internal_metadata.rb +5 -1
  65. data/lib/active_record/locking/optimistic.rb +3 -4
  66. data/lib/active_record/log_subscriber.rb +1 -1
  67. data/lib/active_record/migration.rb +25 -18
  68. data/lib/active_record/migration/command_recorder.rb +28 -14
  69. data/lib/active_record/migration/compatibility.rb +10 -0
  70. data/lib/active_record/persistence.rb +206 -13
  71. data/lib/active_record/querying.rb +17 -12
  72. data/lib/active_record/railties/databases.rake +68 -6
  73. data/lib/active_record/reflection.rb +2 -2
  74. data/lib/active_record/relation.rb +98 -20
  75. data/lib/active_record/relation/calculations.rb +39 -39
  76. data/lib/active_record/relation/delegation.rb +22 -30
  77. data/lib/active_record/relation/finder_methods.rb +3 -9
  78. data/lib/active_record/relation/merger.rb +7 -16
  79. data/lib/active_record/relation/query_methods.rb +153 -38
  80. data/lib/active_record/relation/where_clause.rb +9 -5
  81. data/lib/active_record/sanitization.rb +3 -2
  82. data/lib/active_record/schema_dumper.rb +5 -0
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/scoping/default.rb +6 -7
  85. data/lib/active_record/scoping/named.rb +1 -1
  86. data/lib/active_record/statement_cache.rb +2 -2
  87. data/lib/active_record/store.rb +48 -0
  88. data/lib/active_record/table_metadata.rb +3 -3
  89. data/lib/active_record/tasks/database_tasks.rb +36 -1
  90. data/lib/active_record/touch_later.rb +2 -2
  91. data/lib/active_record/transactions.rb +52 -41
  92. data/lib/active_record/validations/uniqueness.rb +3 -5
  93. data/lib/arel/insert_manager.rb +3 -3
  94. data/lib/arel/nodes.rb +2 -1
  95. data/lib/arel/nodes/comment.rb +29 -0
  96. data/lib/arel/nodes/select_core.rb +16 -12
  97. data/lib/arel/nodes/unary.rb +1 -0
  98. data/lib/arel/nodes/values_list.rb +2 -17
  99. data/lib/arel/select_manager.rb +10 -10
  100. data/lib/arel/visitors/depth_first.rb +6 -1
  101. data/lib/arel/visitors/dot.rb +7 -2
  102. data/lib/arel/visitors/ibm_db.rb +13 -0
  103. data/lib/arel/visitors/informix.rb +6 -0
  104. data/lib/arel/visitors/mssql.rb +15 -1
  105. data/lib/arel/visitors/oracle12.rb +4 -5
  106. data/lib/arel/visitors/postgresql.rb +4 -10
  107. data/lib/arel/visitors/to_sql.rb +87 -108
  108. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  109. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  110. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  111. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  112. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  113. metadata +12 -11
  114. data/lib/active_record/collection_cache_key.rb +0 -53
  115. data/lib/arel/nodes/values.rb +0 -16
@@ -110,7 +110,7 @@ module ActiveRecord
110
110
  end
111
111
 
112
112
  def extract_query_source_location(locations)
113
- backtrace_cleaner.clean(locations).first
113
+ backtrace_cleaner.clean(locations.lazy).first
114
114
  end
115
115
  end
116
116
  end
@@ -4,9 +4,10 @@ require "benchmark"
4
4
  require "set"
5
5
  require "zlib"
6
6
  require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/actionable_error"
7
8
 
8
9
  module ActiveRecord
9
- class MigrationError < ActiveRecordError#:nodoc:
10
+ class MigrationError < ActiveRecordError #:nodoc:
10
11
  def initialize(message = nil)
11
12
  message = "\n\n#{message}\n\n" if message
12
13
  super
@@ -87,7 +88,7 @@ module ActiveRecord
87
88
  class IrreversibleMigration < MigrationError
88
89
  end
89
90
 
90
- class DuplicateMigrationVersionError < MigrationError#:nodoc:
91
+ class DuplicateMigrationVersionError < MigrationError #:nodoc:
91
92
  def initialize(version = nil)
92
93
  if version
93
94
  super("Multiple migrations have the version number #{version}.")
@@ -97,7 +98,7 @@ module ActiveRecord
97
98
  end
98
99
  end
99
100
 
100
- class DuplicateMigrationNameError < MigrationError#:nodoc:
101
+ class DuplicateMigrationNameError < MigrationError #:nodoc:
101
102
  def initialize(name = nil)
102
103
  if name
103
104
  super("Multiple migrations have the name #{name}.")
@@ -117,7 +118,7 @@ module ActiveRecord
117
118
  end
118
119
  end
119
120
 
120
- class IllegalMigrationNameError < MigrationError#:nodoc:
121
+ class IllegalMigrationNameError < MigrationError #:nodoc:
121
122
  def initialize(name = nil)
122
123
  if name
123
124
  super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
@@ -127,7 +128,13 @@ module ActiveRecord
127
128
  end
128
129
  end
129
130
 
130
- class PendingMigrationError < MigrationError#:nodoc:
131
+ class PendingMigrationError < MigrationError #:nodoc:
132
+ include ActiveSupport::ActionableError
133
+
134
+ action "Run pending migrations" do
135
+ ActiveRecord::Tasks::DatabaseTasks.migrate
136
+ end
137
+
131
138
  def initialize(message = nil)
132
139
  if !message && defined?(Rails.env)
133
140
  super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate RAILS_ENV=#{::Rails.env}")
@@ -308,7 +315,7 @@ module ActiveRecord
308
315
  # named +column_name+ from the table called +table_name+.
309
316
  # * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
310
317
  # columns from the table definition.
311
- # * <tt>remove_foreign_key(from_table, options_or_to_table)</tt>: Removes the
318
+ # * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
312
319
  # given foreign key from the table called +table_name+.
313
320
  # * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
314
321
  # specified by +column_names+.
@@ -520,10 +527,10 @@ module ActiveRecord
520
527
  autoload :Compatibility, "active_record/migration/compatibility"
521
528
 
522
529
  # This must be defined before the inherited hook, below
523
- class Current < Migration # :nodoc:
530
+ class Current < Migration #:nodoc:
524
531
  end
525
532
 
526
- def self.inherited(subclass) # :nodoc:
533
+ def self.inherited(subclass) #:nodoc:
527
534
  super
528
535
  if subclass.superclass == Migration
529
536
  raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
@@ -541,7 +548,7 @@ module ActiveRecord
541
548
  ActiveRecord::VERSION::STRING.to_f
542
549
  end
543
550
 
544
- MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
551
+ MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
545
552
 
546
553
  # This class is used to verify that all migrations have been run before
547
554
  # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
@@ -568,10 +575,10 @@ module ActiveRecord
568
575
  end
569
576
 
570
577
  class << self
571
- attr_accessor :delegate # :nodoc:
572
- attr_accessor :disable_ddl_transaction # :nodoc:
578
+ attr_accessor :delegate #:nodoc:
579
+ attr_accessor :disable_ddl_transaction #:nodoc:
573
580
 
574
- def nearest_delegate # :nodoc:
581
+ def nearest_delegate #:nodoc:
575
582
  delegate || superclass.nearest_delegate
576
583
  end
577
584
 
@@ -595,13 +602,13 @@ module ActiveRecord
595
602
  end
596
603
  end
597
604
 
598
- def maintain_test_schema! # :nodoc:
605
+ def maintain_test_schema! #:nodoc:
599
606
  if ActiveRecord::Base.maintain_test_schema
600
607
  suppress_messages { load_schema_if_pending! }
601
608
  end
602
609
  end
603
610
 
604
- def method_missing(name, *args, &block) # :nodoc:
611
+ def method_missing(name, *args, &block) #:nodoc:
605
612
  nearest_delegate.send(name, *args, &block)
606
613
  end
607
614
 
@@ -618,7 +625,7 @@ module ActiveRecord
618
625
  end
619
626
  end
620
627
 
621
- def disable_ddl_transaction # :nodoc:
628
+ def disable_ddl_transaction #:nodoc:
622
629
  self.class.disable_ddl_transaction
623
630
  end
624
631
 
@@ -693,7 +700,7 @@ module ActiveRecord
693
700
  connection.respond_to?(:reverting) && connection.reverting
694
701
  end
695
702
 
696
- ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc:
703
+ ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
697
704
  def up
698
705
  yield unless reverting
699
706
  end
@@ -1006,7 +1013,7 @@ module ActiveRecord
1006
1013
  end
1007
1014
  end
1008
1015
 
1009
- class MigrationContext # :nodoc:
1016
+ class MigrationContext #:nodoc:
1010
1017
  attr_reader :migrations_paths
1011
1018
 
1012
1019
  def initialize(migrations_paths)
@@ -1165,7 +1172,7 @@ module ActiveRecord
1165
1172
  end
1166
1173
  end
1167
1174
 
1168
- class Migrator # :nodoc:
1175
+ class Migrator #:nodoc:
1169
1176
  class << self
1170
1177
  attr_accessor :migrations_paths
1171
1178
 
@@ -14,6 +14,8 @@ module ActiveRecord
14
14
  # * change_column
15
15
  # * change_column_default (must supply a :from and :to option)
16
16
  # * change_column_null
17
+ # * change_column_comment (must supply a :from and :to option)
18
+ # * change_table_comment (must supply a :from and :to option)
17
19
  # * create_join_table
18
20
  # * create_table
19
21
  # * disable_extension
@@ -35,7 +37,8 @@ module ActiveRecord
35
37
  :change_column_default, :add_reference, :remove_reference, :transaction,
36
38
  :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
37
39
  :change_column, :execute, :remove_columns, :change_column_null,
38
- :add_foreign_key, :remove_foreign_key
40
+ :add_foreign_key, :remove_foreign_key,
41
+ :change_column_comment, :change_table_comment
39
42
  ]
40
43
  include JoinTable
41
44
 
@@ -231,28 +234,39 @@ module ActiveRecord
231
234
  end
232
235
 
233
236
  def invert_remove_foreign_key(args)
234
- from_table, options_or_to_table, options_or_nil = args
237
+ options = args.extract_options!
238
+ from_table, to_table = args
235
239
 
236
- to_table = if options_or_to_table.is_a?(Hash)
237
- options_or_to_table[:to_table]
238
- else
239
- options_or_to_table
240
- end
241
-
242
- remove_options = if options_or_to_table.is_a?(Hash)
243
- options_or_to_table.except(:to_table)
244
- else
245
- options_or_nil
246
- end
240
+ to_table ||= options.delete(:to_table)
247
241
 
248
242
  raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
249
243
 
250
244
  reversed_args = [from_table, to_table]
251
- reversed_args << remove_options if remove_options.present?
245
+ reversed_args << options unless options.empty?
252
246
 
253
247
  [:add_foreign_key, reversed_args]
254
248
  end
255
249
 
250
+ def invert_change_column_comment(args)
251
+ table, column, options = *args
252
+
253
+ unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
254
+ raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option."
255
+ end
256
+
257
+ [:change_column_comment, [table, column, from: options[:to], to: options[:from]]]
258
+ end
259
+
260
+ def invert_change_table_comment(args)
261
+ table, options = *args
262
+
263
+ unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
264
+ raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option."
265
+ end
266
+
267
+ [:change_table_comment, [table, from: options[:to], to: options[:from]]]
268
+ end
269
+
256
270
  def respond_to_missing?(method, _)
257
271
  super || delegate.respond_to?(method)
258
272
  end
@@ -27,6 +27,16 @@ module ActiveRecord
27
27
  def invert_transaction(args, &block)
28
28
  [:transaction, args, block]
29
29
  end
30
+
31
+ def invert_change_column_comment(args)
32
+ table_name, column_name, comment = args
33
+ [:change_column_comment, [table_name, column_name, from: comment, to: comment]]
34
+ end
35
+
36
+ def invert_change_table_comment(args)
37
+ table_name, comment = args
38
+ [:change_table_comment, [table_name, from: comment, to: comment]]
39
+ end
30
40
  end
31
41
 
32
42
  def create_table(table_name, **options)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/insert_all"
4
+
3
5
  module ActiveRecord
4
6
  # = Active Record \Persistence
5
7
  module Persistence
@@ -55,6 +57,192 @@ module ActiveRecord
55
57
  end
56
58
  end
57
59
 
60
+ # Inserts a single record into the database in a single SQL INSERT
61
+ # statement. It does not instantiate any models nor does it trigger
62
+ # Active Record callbacks or validations. Though passed values
63
+ # go through Active Record's type casting and serialization.
64
+ #
65
+ # See <tt>ActiveRecord::Persistence#insert_all</tt> for documentation.
66
+ def insert(attributes, returning: nil, unique_by: nil)
67
+ insert_all([ attributes ], returning: returning, unique_by: unique_by)
68
+ end
69
+
70
+ # Inserts multiple records into the database in a single SQL INSERT
71
+ # statement. It does not instantiate any models nor does it trigger
72
+ # Active Record callbacks or validations. Though passed values
73
+ # go through Active Record's type casting and serialization.
74
+ #
75
+ # The +attributes+ parameter is an Array of Hashes. Every Hash determines
76
+ # the attributes for a single row and must have the same keys.
77
+ #
78
+ # Rows are considered to be unique by every unique index on the table. Any
79
+ # duplicate rows are skipped.
80
+ # Override with <tt>:unique_by</tt> (see below).
81
+ #
82
+ # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
83
+ # <tt>:returning</tt> (see below).
84
+ #
85
+ # ==== Options
86
+ #
87
+ # [:returning]
88
+ # (PostgreSQL only) An array of attributes to return for all successfully
89
+ # inserted records, which by default is the primary key.
90
+ # Pass <tt>returning: %w[ id name ]</tt> for both id and name
91
+ # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
92
+ # clause entirely.
93
+ #
94
+ # [:unique_by]
95
+ # (PostgreSQL and SQLite only) By default rows are considered to be unique
96
+ # by every unique index on the table. Any duplicate rows are skipped.
97
+ #
98
+ # To skip rows according to just one unique index pass <tt>:unique_by</tt>.
99
+ #
100
+ # Consider a Book model where no duplicate ISBNs make sense, but if any
101
+ # row has an existing id, or is not unique by another unique index,
102
+ # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
103
+ #
104
+ # Unique indexes can be identified by columns or name:
105
+ #
106
+ # unique_by: :isbn
107
+ # unique_by: %i[ author_id name ]
108
+ # unique_by: :index_books_on_isbn
109
+ #
110
+ # Because it relies on the index information from the database
111
+ # <tt>:unique_by</tt> is recommended to be paired with
112
+ # Active Record's schema_cache.
113
+ #
114
+ # ==== Example
115
+ #
116
+ # # Insert records and skip inserting any duplicates.
117
+ # # Here "Eloquent Ruby" is skipped because its id is not unique.
118
+ #
119
+ # Book.insert_all([
120
+ # { id: 1, title: "Rework", author: "David" },
121
+ # { id: 1, title: "Eloquent Ruby", author: "Russ" }
122
+ # ])
123
+ def insert_all(attributes, returning: nil, unique_by: nil)
124
+ InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
125
+ end
126
+
127
+ # Inserts a single record into the database in a single SQL INSERT
128
+ # statement. It does not instantiate any models nor does it trigger
129
+ # Active Record callbacks or validations. Though passed values
130
+ # go through Active Record's type casting and serialization.
131
+ #
132
+ # See <tt>ActiveRecord::Persistence#insert_all!</tt> for more.
133
+ def insert!(attributes, returning: nil)
134
+ insert_all!([ attributes ], returning: returning)
135
+ end
136
+
137
+ # Inserts multiple records into the database in a single SQL INSERT
138
+ # statement. It does not instantiate any models nor does it trigger
139
+ # Active Record callbacks or validations. Though passed values
140
+ # go through Active Record's type casting and serialization.
141
+ #
142
+ # The +attributes+ parameter is an Array of Hashes. Every Hash determines
143
+ # the attributes for a single row and must have the same keys.
144
+ #
145
+ # Raises <tt>ActiveRecord::RecordNotUnique</tt> if any rows violate a
146
+ # unique index on the table. In that case, no rows are inserted.
147
+ #
148
+ # To skip duplicate rows, see <tt>ActiveRecord::Persistence#insert_all</tt>.
149
+ # To replace them, see <tt>ActiveRecord::Persistence#upsert_all</tt>.
150
+ #
151
+ # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
152
+ # <tt>:returning</tt> (see below).
153
+ #
154
+ # ==== Options
155
+ #
156
+ # [:returning]
157
+ # (PostgreSQL only) An array of attributes to return for all successfully
158
+ # inserted records, which by default is the primary key.
159
+ # Pass <tt>returning: %w[ id name ]</tt> for both id and name
160
+ # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
161
+ # clause entirely.
162
+ #
163
+ # ==== Examples
164
+ #
165
+ # # Insert multiple records
166
+ # Book.insert_all!([
167
+ # { title: "Rework", author: "David" },
168
+ # { title: "Eloquent Ruby", author: "Russ" }
169
+ # ])
170
+ #
171
+ # # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
172
+ # # does not have a unique id.
173
+ # Book.insert_all!([
174
+ # { id: 1, title: "Rework", author: "David" },
175
+ # { id: 1, title: "Eloquent Ruby", author: "Russ" }
176
+ # ])
177
+ def insert_all!(attributes, returning: nil)
178
+ InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
179
+ end
180
+
181
+ # Updates or inserts (upserts) a single record into the database in a
182
+ # single SQL INSERT statement. It does not instantiate any models nor does
183
+ # it trigger Active Record callbacks or validations. Though passed values
184
+ # go through Active Record's type casting and serialization.
185
+ #
186
+ # See <tt>ActiveRecord::Persistence#upsert_all</tt> for documentation.
187
+ def upsert(attributes, returning: nil, unique_by: nil)
188
+ upsert_all([ attributes ], returning: returning, unique_by: unique_by)
189
+ end
190
+
191
+ # Updates or inserts (upserts) multiple records into the database in a
192
+ # single SQL INSERT statement. It does not instantiate any models nor does
193
+ # it trigger Active Record callbacks or validations. Though passed values
194
+ # go through Active Record's type casting and serialization.
195
+ #
196
+ # The +attributes+ parameter is an Array of Hashes. Every Hash determines
197
+ # the attributes for a single row and must have the same keys.
198
+ #
199
+ # Returns an <tt>ActiveRecord::Result</tt> with its contents based on
200
+ # <tt>:returning</tt> (see below).
201
+ #
202
+ # ==== Options
203
+ #
204
+ # [:returning]
205
+ # (PostgreSQL only) An array of attributes to return for all successfully
206
+ # inserted records, which by default is the primary key.
207
+ # Pass <tt>returning: %w[ id name ]</tt> for both id and name
208
+ # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
209
+ # clause entirely.
210
+ #
211
+ # [:unique_by]
212
+ # (PostgreSQL and SQLite only) By default rows are considered to be unique
213
+ # by every unique index on the table. Any duplicate rows are skipped.
214
+ #
215
+ # To skip rows according to just one unique index pass <tt>:unique_by</tt>.
216
+ #
217
+ # Consider a Book model where no duplicate ISBNs make sense, but if any
218
+ # row has an existing id, or is not unique by another unique index,
219
+ # <tt>ActiveRecord::RecordNotUnique</tt> is raised.
220
+ #
221
+ # Unique indexes can be identified by columns or name:
222
+ #
223
+ # unique_by: :isbn
224
+ # unique_by: %i[ author_id name ]
225
+ # unique_by: :index_books_on_isbn
226
+ #
227
+ # Because it relies on the index information from the database
228
+ # <tt>:unique_by</tt> is recommended to be paired with
229
+ # Active Record's schema_cache.
230
+ #
231
+ # ==== Examples
232
+ #
233
+ # # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
234
+ # # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
235
+ #
236
+ # Book.upsert_all([
237
+ # { title: "Rework", author: "David", isbn: "1" },
238
+ # { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
239
+ # ], unique_by: :isbn)
240
+ #
241
+ # Book.find_by(isbn: "1").title # => "Eloquent Ruby"
242
+ def upsert_all(attributes, returning: nil, unique_by: nil)
243
+ InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
244
+ end
245
+
58
246
  # Given an attributes hash, +instantiate+ returns a new instance of
59
247
  # the appropriate class. Accepts only keys as strings.
60
248
  #
@@ -165,6 +353,7 @@ module ActiveRecord
165
353
  end
166
354
 
167
355
  def _insert_record(values) # :nodoc:
356
+ primary_key = self.primary_key
168
357
  primary_key_value = nil
169
358
 
170
359
  if primary_key && Hash === values
@@ -235,20 +424,20 @@ module ActiveRecord
235
424
  # Returns true if this object hasn't been saved yet -- that is, a record
236
425
  # for the object doesn't exist in the database yet; otherwise, returns false.
237
426
  def new_record?
238
- sync_with_transaction_state
427
+ sync_with_transaction_state if @transaction_state&.finalized?
239
428
  @new_record
240
429
  end
241
430
 
242
431
  # Returns true if this object has been destroyed, otherwise returns false.
243
432
  def destroyed?
244
- sync_with_transaction_state
433
+ sync_with_transaction_state if @transaction_state&.finalized?
245
434
  @destroyed
246
435
  end
247
436
 
248
437
  # Returns true if the record is persisted, i.e. it's not a new record and it was
249
438
  # not destroyed, otherwise returns false.
250
439
  def persisted?
251
- sync_with_transaction_state
440
+ sync_with_transaction_state if @transaction_state&.finalized?
252
441
  !(@new_record || @destroyed)
253
442
  end
254
443
 
@@ -342,7 +531,6 @@ module ActiveRecord
342
531
  def destroy
343
532
  _raise_readonly_record_error if readonly?
344
533
  destroy_associations
345
- self.class.connection.add_transaction_record(self)
346
534
  @_trigger_destroy_callback = if persisted?
347
535
  destroy_row > 0
348
536
  else
@@ -380,7 +568,6 @@ module ActiveRecord
380
568
  became.send(:initialize)
381
569
  became.instance_variable_set("@attributes", @attributes)
382
570
  became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
383
- became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
384
571
  became.instance_variable_set("@new_record", new_record?)
385
572
  became.instance_variable_set("@destroyed", destroyed?)
386
573
  became.errors.copy!(errors)
@@ -477,8 +664,13 @@ module ActiveRecord
477
664
  raise ActiveRecordError, "cannot update a new record" if new_record?
478
665
  raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
479
666
 
667
+ attributes = attributes.transform_keys do |key|
668
+ name = key.to_s
669
+ self.class.attribute_aliases[name] || name
670
+ end
671
+
480
672
  attributes.each_key do |key|
481
- verify_readonly_attribute(key.to_s)
673
+ verify_readonly_attribute(key)
482
674
  end
483
675
 
484
676
  id_in_database = self.id_in_database
@@ -488,7 +680,7 @@ module ActiveRecord
488
680
 
489
681
  affected_rows = self.class._update_record(
490
682
  attributes,
491
- self.class.primary_key => id_in_database
683
+ @primary_key => id_in_database
492
684
  )
493
685
 
494
686
  affected_rows == 1
@@ -665,7 +857,9 @@ module ActiveRecord
665
857
  end
666
858
 
667
859
  attribute_names = timestamp_attributes_for_update_in_model
668
- attribute_names |= names.map(&:to_s)
860
+ attribute_names |= names.map!(&:to_s).map! { |name|
861
+ self.class.attribute_aliases[name] || name
862
+ }
669
863
 
670
864
  unless attribute_names.empty?
671
865
  affected_rows = _touch_row(attribute_names, time)
@@ -686,15 +880,14 @@ module ActiveRecord
686
880
  end
687
881
 
688
882
  def _delete_row
689
- self.class._delete_record(self.class.primary_key => id_in_database)
883
+ self.class._delete_record(@primary_key => id_in_database)
690
884
  end
691
885
 
692
886
  def _touch_row(attribute_names, time)
693
887
  time ||= current_time_from_proper_timezone
694
888
 
695
889
  attribute_names.each do |attr_name|
696
- write_attribute(attr_name, time)
697
- clear_attribute_change(attr_name)
890
+ _write_attribute(attr_name, time)
698
891
  end
699
892
 
700
893
  _update_row(attribute_names, "touch")
@@ -703,7 +896,7 @@ module ActiveRecord
703
896
  def _update_row(attribute_names, attempted_action = "update")
704
897
  self.class._update_record(
705
898
  attributes_with_values(attribute_names),
706
- self.class.primary_key => id_in_database
899
+ @primary_key => id_in_database
707
900
  )
708
901
  end
709
902
 
@@ -741,7 +934,7 @@ module ActiveRecord
741
934
  attributes_with_values(attribute_names)
742
935
  )
743
936
 
744
- self.id ||= new_id if self.class.primary_key
937
+ self.id ||= new_id if @primary_key
745
938
 
746
939
  @new_record = false
747
940