activerecord 6.0.0.beta1 → 6.0.1.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +27 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. data/lib/arel/nodes/values.rb +0 -16
@@ -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
@@ -16,13 +16,65 @@ module ActiveRecord
16
16
  V6_0 = Current
17
17
 
18
18
  class V5_2 < V6_0
19
+ module TableDefinition
20
+ def timestamps(**options)
21
+ options[:precision] ||= nil
22
+ super
23
+ end
24
+ end
25
+
19
26
  module CommandRecorder
20
27
  def invert_transaction(args, &block)
21
28
  [:transaction, args, block]
22
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
40
+ end
41
+
42
+ def create_table(table_name, **options)
43
+ if block_given?
44
+ super { |t| yield compatible_table_definition(t) }
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def change_table(table_name, **options)
51
+ if block_given?
52
+ super { |t| yield compatible_table_definition(t) }
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def create_join_table(table_1, table_2, **options)
59
+ if block_given?
60
+ super { |t| yield compatible_table_definition(t) }
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def add_timestamps(table_name, **options)
67
+ options[:precision] ||= nil
68
+ super
23
69
  end
24
70
 
25
71
  private
72
+ def compatible_table_definition(t)
73
+ class << t
74
+ prepend TableDefinition
75
+ end
76
+ t
77
+ end
26
78
 
27
79
  def command_recorder
28
80
  recorder = super
@@ -35,20 +87,18 @@ module ActiveRecord
35
87
 
36
88
  class V5_1 < V5_2
37
89
  def change_column(table_name, column_name, type, options = {})
38
- if adapter_name == "PostgreSQL"
39
- clear_cache!
40
- sql = connection.send(:change_column_sql, table_name, column_name, type, options)
41
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
42
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
43
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
44
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
90
+ if connection.adapter_name == "PostgreSQL"
91
+ super(table_name, column_name, type, options.except(:default, :null, :comment))
92
+ connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
93
+ connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
94
+ connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
45
95
  else
46
96
  super
47
97
  end
48
98
  end
49
99
 
50
100
  def create_table(table_name, options = {})
51
- if adapter_name == "Mysql2"
101
+ if connection.adapter_name == "Mysql2"
52
102
  super(table_name, options: "ENGINE=InnoDB", **options)
53
103
  else
54
104
  super
@@ -70,13 +120,13 @@ module ActiveRecord
70
120
  end
71
121
 
72
122
  def create_table(table_name, options = {})
73
- if adapter_name == "PostgreSQL"
123
+ if connection.adapter_name == "PostgreSQL"
74
124
  if options[:id] == :uuid && !options.key?(:default)
75
125
  options[:default] = "uuid_generate_v4()"
76
126
  end
77
127
  end
78
128
 
79
- unless adapter_name == "Mysql2" && options[:id] == :bigint
129
+ unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
80
130
  if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
81
131
  options[:default] = nil
82
132
  end
@@ -89,35 +139,12 @@ module ActiveRecord
89
139
  options[:id] = :integer
90
140
  end
91
141
 
92
- if block_given?
93
- super do |t|
94
- yield compatible_table_definition(t)
95
- end
96
- else
97
- super
98
- end
99
- end
100
-
101
- def change_table(table_name, options = {})
102
- if block_given?
103
- super do |t|
104
- yield compatible_table_definition(t)
105
- end
106
- else
107
- super
108
- end
142
+ super
109
143
  end
110
144
 
111
145
  def create_join_table(table_1, table_2, column_options: {}, **options)
112
146
  column_options.reverse_merge!(type: :integer)
113
-
114
- if block_given?
115
- super do |t|
116
- yield compatible_table_definition(t)
117
- end
118
- else
119
- super
120
- end
147
+ super
121
148
  end
122
149
 
123
150
  def add_column(table_name, column_name, type, options = {})
@@ -138,7 +165,7 @@ module ActiveRecord
138
165
  class << t
139
166
  prepend TableDefinition
140
167
  end
141
- t
168
+ super
142
169
  end
143
170
  end
144
171
 
@@ -156,33 +183,13 @@ module ActiveRecord
156
183
  end
157
184
  end
158
185
 
159
- def create_table(table_name, options = {})
160
- if block_given?
161
- super do |t|
162
- yield compatible_table_definition(t)
163
- end
164
- else
165
- super
166
- end
167
- end
168
-
169
- def change_table(table_name, options = {})
170
- if block_given?
171
- super do |t|
172
- yield compatible_table_definition(t)
173
- end
174
- else
175
- super
176
- end
177
- end
178
-
179
- def add_reference(*, **options)
186
+ def add_reference(table_name, ref_name, **options)
180
187
  options[:index] ||= false
181
188
  super
182
189
  end
183
190
  alias :add_belongs_to :add_reference
184
191
 
185
- def add_timestamps(_, **options)
192
+ def add_timestamps(table_name, **options)
186
193
  options[:null] = true if options[:null].nil?
187
194
  super
188
195
  end
@@ -193,7 +200,7 @@ module ActiveRecord
193
200
  if options[:name].present?
194
201
  options[:name].to_s
195
202
  else
196
- index_name(table_name, column: column_names)
203
+ connection.index_name(table_name, column: column_names)
197
204
  end
198
205
  super
199
206
  end
@@ -213,15 +220,17 @@ module ActiveRecord
213
220
  end
214
221
 
215
222
  def index_name_for_remove(table_name, options = {})
216
- index_name = index_name(table_name, options)
223
+ index_name = connection.index_name(table_name, options)
217
224
 
218
- unless index_name_exists?(table_name, index_name)
225
+ unless connection.index_name_exists?(table_name, index_name)
219
226
  if options.is_a?(Hash) && options.has_key?(:name)
220
227
  options_without_column = options.dup
221
228
  options_without_column.delete :column
222
- index_name_without_column = index_name(table_name, options_without_column)
229
+ index_name_without_column = connection.index_name(table_name, options_without_column)
223
230
 
224
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
231
+ if connection.index_name_exists?(table_name, index_name_without_column)
232
+ return index_name_without_column
233
+ end
225
234
  end
226
235
 
227
236
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
@@ -480,6 +480,9 @@ module ActiveRecord
480
480
  load_schema!
481
481
 
482
482
  @schema_loaded = true
483
+ rescue
484
+ reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
485
+ raise
483
486
  end
484
487
  end
485
488
 
@@ -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
  #
@@ -142,7 +330,7 @@ module ActiveRecord
142
330
  end
143
331
  end
144
332
 
145
- # Deletes the row with a primary key matching the +id+ argument, using a
333
+ # Deletes the row with a primary key matching the +id+ argument, using an
146
334
  # SQL +DELETE+ statement, and returns the number of rows deleted. Active
147
335
  # Record objects are not instantiated, so the object's callbacks are not
148
336
  # executed, including any <tt>:dependent</tt> association options.
@@ -161,10 +349,11 @@ module ActiveRecord
161
349
  # # Delete multiple rows
162
350
  # Todo.delete([2,3,4])
163
351
  def delete(id_or_array)
164
- where(primary_key => id_or_array).delete_all
352
+ delete_by(primary_key => id_or_array)
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)
@@ -436,7 +623,7 @@ module ActiveRecord
436
623
  end
437
624
 
438
625
  alias update_attributes update
439
- deprecate :update_attributes
626
+ deprecate update_attributes: "please, use update instead"
440
627
 
441
628
  # Updates its receiver just like #update but calls #save! instead
442
629
  # of +save+, so an exception is raised if the record is invalid and saving will fail.
@@ -450,7 +637,7 @@ module ActiveRecord
450
637
  end
451
638
 
452
639
  alias update_attributes! update!
453
- deprecate :update_attributes!
640
+ deprecate update_attributes!: "please, use update! instead"
454
641
 
455
642
  # Equivalent to <code>update_columns(name => value)</code>.
456
643
  def update_column(name, value)
@@ -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,14 +896,14 @@ 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
 
710
- def create_or_update(*args, &block)
903
+ def create_or_update(**, &block)
711
904
  _raise_readonly_record_error if readonly?
712
905
  return false if destroyed?
713
- result = new_record? ? _create_record(&block) : _update_record(*args, &block)
906
+ result = new_record? ? _create_record(&block) : _update_record(&block)
714
907
  result != false
715
908
  end
716
909
 
@@ -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