Skip to content

Commit ed2c15b

Browse files
committed
Move ActiveRecord::Base.insert in Relation
Ref: rails#50396 As well as related methods.
1 parent 8f419e4 commit ed2c15b

File tree

6 files changed

+290
-290
lines changed

6 files changed

+290
-290
lines changed

activerecord/lib/active_record/association_relation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def #{method}(attributes, **kwargs)
2222
raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
2323
end
2424
25-
scoping { klass.#{method}(attributes, **kwargs) }
25+
super
2626
end
2727
RUBY
2828
end

activerecord/lib/active_record/insert_all.rb

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ class InsertAll # :nodoc:
88
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
99

1010
class << self
11-
def execute(model, ...)
12-
model.with_connection do |c|
13-
new(model, c, ...).execute
11+
def execute(relation, ...)
12+
relation.model.with_connection do |c|
13+
new(relation, c, ...).execute
1414
end
1515
end
1616
end
1717

18-
def initialize(model, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
19-
@model, @connection, @inserts = model, connection, inserts.map(&:stringify_keys)
18+
def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
19+
@relation = relation
20+
@model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
2021
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
2122
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
2223

@@ -31,10 +32,8 @@ def initialize(model, connection, inserts, on_duplicate:, update_only: nil, retu
3132
@keys = @inserts.first.keys
3233
end
3334

34-
if model.scope_attributes?
35-
@scope_attributes = model.scope_attributes
36-
@keys |= @scope_attributes.keys
37-
end
35+
@scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
36+
@keys |= @scope_attributes.keys
3837
@keys = @keys.to_set
3938

4039
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
@@ -74,7 +73,7 @@ def update_duplicates?
7473
def map_key_with_value
7574
inserts.map do |attributes|
7675
attributes = attributes.stringify_keys
77-
attributes.merge!(scope_attributes) if scope_attributes
76+
attributes.merge!(@scope_attributes)
7877
attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
7978

8079
verify_attributes(attributes)
@@ -99,8 +98,6 @@ def keys_including_timestamps
9998
end
10099

101100
private
102-
attr_reader :scope_attributes
103-
104101
def has_attribute_aliases?(attributes)
105102
attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
106103
end

activerecord/lib/active_record/persistence.rb

Lines changed: 0 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -87,282 +87,6 @@ def build(attributes = nil, &block)
8787
end
8888
end
8989

90-
# Inserts a single record into the database in a single SQL INSERT
91-
# statement. It does not instantiate any models nor does it trigger
92-
# Active Record callbacks or validations. Though passed values
93-
# go through Active Record's type casting and serialization.
94-
#
95-
# See #insert_all for documentation.
96-
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
97-
insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
98-
end
99-
100-
# Inserts multiple records into the database in a single SQL INSERT
101-
# statement. It does not instantiate any models nor does it trigger
102-
# Active Record callbacks or validations. Though passed values
103-
# go through Active Record's type casting and serialization.
104-
#
105-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
106-
# the attributes for a single row and must have the same keys.
107-
#
108-
# Rows are considered to be unique by every unique index on the table. Any
109-
# duplicate rows are skipped.
110-
# Override with <tt>:unique_by</tt> (see below).
111-
#
112-
# Returns an ActiveRecord::Result with its contents based on
113-
# <tt>:returning</tt> (see below).
114-
#
115-
# ==== Options
116-
#
117-
# [:returning]
118-
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
119-
# inserted records, which by default is the primary key.
120-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
121-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
122-
# clause entirely.
123-
#
124-
# You can also pass an SQL string if you need more control on the return values
125-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
126-
#
127-
# [:unique_by]
128-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
129-
# by every unique index on the table. Any duplicate rows are skipped.
130-
#
131-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
132-
#
133-
# Consider a Book model where no duplicate ISBNs make sense, but if any
134-
# row has an existing id, or is not unique by another unique index,
135-
# ActiveRecord::RecordNotUnique is raised.
136-
#
137-
# Unique indexes can be identified by columns or name:
138-
#
139-
# unique_by: :isbn
140-
# unique_by: %i[ author_id name ]
141-
# unique_by: :index_books_on_isbn
142-
#
143-
# [:record_timestamps]
144-
# By default, automatic setting of timestamp columns is controlled by
145-
# the model's <tt>record_timestamps</tt> config, matching typical
146-
# behavior.
147-
#
148-
# To override this and force automatic setting of timestamp columns one
149-
# way or the other, pass <tt>:record_timestamps</tt>:
150-
#
151-
# record_timestamps: true # Always set timestamps automatically
152-
# record_timestamps: false # Never set timestamps automatically
153-
#
154-
# Because it relies on the index information from the database
155-
# <tt>:unique_by</tt> is recommended to be paired with
156-
# Active Record's schema_cache.
157-
#
158-
# ==== Example
159-
#
160-
# # Insert records and skip inserting any duplicates.
161-
# # Here "Eloquent Ruby" is skipped because its id is not unique.
162-
#
163-
# Book.insert_all([
164-
# { id: 1, title: "Rework", author: "David" },
165-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
166-
# ])
167-
#
168-
# # insert_all works on chained scopes, and you can use create_with
169-
# # to set default attributes for all inserted records.
170-
#
171-
# author.books.create_with(created_at: Time.now).insert_all([
172-
# { id: 1, title: "Rework" },
173-
# { id: 2, title: "Eloquent Ruby" }
174-
# ])
175-
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
176-
InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
177-
end
178-
179-
# Inserts a single record into the database in a single SQL INSERT
180-
# statement. It does not instantiate any models nor does it trigger
181-
# Active Record callbacks or validations. Though passed values
182-
# go through Active Record's type casting and serialization.
183-
#
184-
# See #insert_all! for more.
185-
def insert!(attributes, returning: nil, record_timestamps: nil)
186-
insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps)
187-
end
188-
189-
# Inserts multiple records into the database in a single SQL INSERT
190-
# statement. It does not instantiate any models nor does it trigger
191-
# Active Record callbacks or validations. Though passed values
192-
# go through Active Record's type casting and serialization.
193-
#
194-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
195-
# the attributes for a single row and must have the same keys.
196-
#
197-
# Raises ActiveRecord::RecordNotUnique if any rows violate a
198-
# unique index on the table. In that case, no rows are inserted.
199-
#
200-
# To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
201-
#
202-
# Returns an ActiveRecord::Result with its contents based on
203-
# <tt>:returning</tt> (see below).
204-
#
205-
# ==== Options
206-
#
207-
# [:returning]
208-
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
209-
# inserted records, which by default is the primary key.
210-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
211-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
212-
# clause entirely.
213-
#
214-
# You can also pass an SQL string if you need more control on the return values
215-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
216-
#
217-
# [:record_timestamps]
218-
# By default, automatic setting of timestamp columns is controlled by
219-
# the model's <tt>record_timestamps</tt> config, matching typical
220-
# behavior.
221-
#
222-
# To override this and force automatic setting of timestamp columns one
223-
# way or the other, pass <tt>:record_timestamps</tt>:
224-
#
225-
# record_timestamps: true # Always set timestamps automatically
226-
# record_timestamps: false # Never set timestamps automatically
227-
#
228-
# ==== Examples
229-
#
230-
# # Insert multiple records
231-
# Book.insert_all!([
232-
# { title: "Rework", author: "David" },
233-
# { title: "Eloquent Ruby", author: "Russ" }
234-
# ])
235-
#
236-
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
237-
# # does not have a unique id.
238-
# Book.insert_all!([
239-
# { id: 1, title: "Rework", author: "David" },
240-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
241-
# ])
242-
def insert_all!(attributes, returning: nil, record_timestamps: nil)
243-
InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps)
244-
end
245-
246-
# Updates or inserts (upserts) a single record into the database in a
247-
# single SQL INSERT statement. It does not instantiate any models nor does
248-
# it trigger Active Record callbacks or validations. Though passed values
249-
# go through Active Record's type casting and serialization.
250-
#
251-
# See #upsert_all for documentation.
252-
def upsert(attributes, **kwargs)
253-
upsert_all([ attributes ], **kwargs)
254-
end
255-
256-
# Updates or inserts (upserts) multiple records into the database in a
257-
# single SQL INSERT statement. It does not instantiate any models nor does
258-
# it trigger Active Record callbacks or validations. Though passed values
259-
# go through Active Record's type casting and serialization.
260-
#
261-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
262-
# the attributes for a single row and must have the same keys.
263-
#
264-
# Returns an ActiveRecord::Result with its contents based on
265-
# <tt>:returning</tt> (see below).
266-
#
267-
# By default, +upsert_all+ will update all the columns that can be updated when
268-
# there is a conflict. These are all the columns except primary keys, read-only
269-
# columns, and columns covered by the optional +unique_by+.
270-
#
271-
# ==== Options
272-
#
273-
# [:returning]
274-
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
275-
# inserted records, which by default is the primary key.
276-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
277-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
278-
# clause entirely.
279-
#
280-
# You can also pass an SQL string if you need more control on the return values
281-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
282-
#
283-
# [:unique_by]
284-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
285-
# by every unique index on the table. Any duplicate rows are skipped.
286-
#
287-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
288-
#
289-
# Consider a Book model where no duplicate ISBNs make sense, but if any
290-
# row has an existing id, or is not unique by another unique index,
291-
# ActiveRecord::RecordNotUnique is raised.
292-
#
293-
# Unique indexes can be identified by columns or name:
294-
#
295-
# unique_by: :isbn
296-
# unique_by: %i[ author_id name ]
297-
# unique_by: :index_books_on_isbn
298-
#
299-
# Because it relies on the index information from the database
300-
# <tt>:unique_by</tt> is recommended to be paired with
301-
# Active Record's schema_cache.
302-
#
303-
# [:on_duplicate]
304-
# Configure the SQL update sentence that will be used in case of conflict.
305-
#
306-
# NOTE: If you use this option you must provide all the columns you want to update
307-
# by yourself.
308-
#
309-
# Example:
310-
#
311-
# Commodity.upsert_all(
312-
# [
313-
# { id: 2, name: "Copper", price: 4.84 },
314-
# { id: 4, name: "Gold", price: 1380.87 },
315-
# { id: 6, name: "Aluminium", price: 0.35 }
316-
# ],
317-
# on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
318-
# )
319-
#
320-
# See the related +:update_only+ option. Both options can't be used at the same time.
321-
#
322-
# [:update_only]
323-
# Provide a list of column names that will be updated in case of conflict. If not provided,
324-
# +upsert_all+ will update all the columns that can be updated. These are all the columns
325-
# except primary keys, read-only columns, and columns covered by the optional +unique_by+
326-
#
327-
# Example:
328-
#
329-
# Commodity.upsert_all(
330-
# [
331-
# { id: 2, name: "Copper", price: 4.84 },
332-
# { id: 4, name: "Gold", price: 1380.87 },
333-
# { id: 6, name: "Aluminium", price: 0.35 }
334-
# ],
335-
# update_only: [:price] # Only prices will be updated
336-
# )
337-
#
338-
# See the related +:on_duplicate+ option. Both options can't be used at the same time.
339-
#
340-
# [:record_timestamps]
341-
# By default, automatic setting of timestamp columns is controlled by
342-
# the model's <tt>record_timestamps</tt> config, matching typical
343-
# behavior.
344-
#
345-
# To override this and force automatic setting of timestamp columns one
346-
# way or the other, pass <tt>:record_timestamps</tt>:
347-
#
348-
# record_timestamps: true # Always set timestamps automatically
349-
# record_timestamps: false # Never set timestamps automatically
350-
#
351-
# ==== Examples
352-
#
353-
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
354-
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
355-
#
356-
# Book.upsert_all([
357-
# { title: "Rework", author: "David", isbn: "1" },
358-
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
359-
# ], unique_by: :isbn)
360-
#
361-
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
362-
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
363-
InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
364-
end
365-
36690
# Given an attributes hash, +instantiate+ returns a new instance of
36791
# the appropriate class. Accepts only keys as strings.
36892
#

activerecord/lib/active_record/querying.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Querying
1919
:count, :average, :minimum, :maximum, :sum, :calculate,
2020
:pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with, :with_recursive,
2121
:async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
22+
:insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
2223
].freeze # :nodoc:
2324
delegate(*QUERYING_METHODS, to: :all)
2425

0 commit comments

Comments
 (0)