Module: ActiveRecord::Persistence::ClassMethods
- Defined in:
- lib/active_record/persistence.rb
Instance Method Summary collapse
-
#_delete_record(constraints) ⇒ Object
:nodoc:.
-
#_insert_record(values) ⇒ Object
:nodoc:.
-
#_update_record(values, constraints) ⇒ Object
:nodoc:.
-
#create(attributes = nil, &block) ⇒ Object
Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
#create!(attributes = nil, &block) ⇒ Object
Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
#delete(id_or_array) ⇒ Object
Deletes the row with a primary key matching the
id
argument, using an SQLDELETE
statement, and returns the number of rows deleted. -
#destroy(id) ⇒ Object
Destroy an object (or multiple objects) that has the given id.
-
#insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Inserts a single record into the database in a single SQL INSERT statement.
-
#insert!(attributes, returning: nil, record_timestamps: nil) ⇒ Object
Inserts a single record into the database in a single SQL INSERT statement.
-
#insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Inserts multiple records into the database in a single SQL INSERT statement.
-
#insert_all!(attributes, returning: nil, record_timestamps: nil) ⇒ Object
Inserts multiple records into the database in a single SQL INSERT statement.
-
#instantiate(attributes, column_types = {}, &block) ⇒ Object
Given an attributes hash,
instantiate
returns a new instance of the appropriate class. -
#update(id = :all, attributes) ⇒ Object
Updates an object (or multiple objects) and saves it to the database, if validations pass.
-
#update!(id = :all, attributes) ⇒ Object
Updates the object (or multiple objects) just like #update but calls #update! instead of
update
, so an exception is raised if the record is invalid and saving will fail. -
#upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Updates or inserts (upserts) a single record into the database in a single SQL INSERT statement.
-
#upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Updates or inserts (upserts) multiple records into the database in a single SQL INSERT statement.
Instance Method Details
#_delete_record(constraints) ⇒ Object
:nodoc:
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/active_record/persistence.rb', line 517 def _delete_record(constraints) # :nodoc: constraints = constraints.map { |name, value| predicate_builder[name, value] } default_constraint = build_default_constraint constraints << default_constraint if default_constraint if current_scope = self.global_current_scope constraints << current_scope.where_clause.ast end dm = Arel::DeleteManager.new(arel_table) dm.wheres = constraints connection.delete(dm, "#{self} Destroy") end |
#_insert_record(values) ⇒ Object
:nodoc:
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'lib/active_record/persistence.rb', line 478 def _insert_record(values) # :nodoc: primary_key = self.primary_key primary_key_value = nil if prefetch_primary_key? && primary_key values[primary_key] ||= begin primary_key_value = next_sequence_value _default_attributes[primary_key].with_cast_value(primary_key_value) end end im = Arel::InsertManager.new(arel_table) if values.empty? im.insert(connection.empty_insert_statement_value(primary_key)) else im.insert(values.transform_keys { |name| arel_table[name] }) end connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) end |
#_update_record(values, constraints) ⇒ Object
:nodoc:
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 |
# File 'lib/active_record/persistence.rb', line 500 def _update_record(values, constraints) # :nodoc: constraints = constraints.map { |name, value| predicate_builder[name, value] } default_constraint = build_default_constraint constraints << default_constraint if default_constraint if current_scope = self.global_current_scope constraints << current_scope.where_clause.ast end um = Arel::UpdateManager.new(arel_table) um.set(values.transform_keys { |name| arel_table[name] }) um.wheres = constraints connection.update(um, "#{self} Update") end |
#create(attributes = nil, &block) ⇒ Object
Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
The attributes
parameter can be either a Hash or an Array of Hashes. These Hashes describe the attributes on the objects that are to be created.
Examples
# Create a single new object
User.create(first_name: 'Jamie')
# Create an Array of new objects
User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
# Create a single object and pass it into a block to set other attributes.
User.create(first_name: 'Jamie') do |u|
u.is_admin = false
end
# Creating an Array of new objects using a block, where the block is executed for each object:
User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
u.is_admin = false
end
33 34 35 36 37 38 39 40 41 |
# File 'lib/active_record/persistence.rb', line 33 def create(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create(attr, &block) } else object = new(attributes, &block) object.save object end end |
#create!(attributes = nil, &block) ⇒ Object
Creates an object (or multiple objects) and saves it to the database, if validations pass. Raises a RecordInvalid error if validations fail, unlike Base#create.
The attributes
parameter can be either a Hash or an Array of Hashes. These describe which attributes to be created on the object, or multiple objects when given an Array of Hashes.
50 51 52 53 54 55 56 57 58 |
# File 'lib/active_record/persistence.rb', line 50 def create!(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create!(attr, &block) } else object = new(attributes, &block) object.save! object end end |
#delete(id_or_array) ⇒ Object
Deletes the row with a primary key matching the id
argument, using an SQL DELETE
statement, and returns the number of rows deleted. Active Record objects are not instantiated, so the object’s callbacks are not executed, including any :dependent
association options.
You can delete multiple rows at once by passing an Array of id
s.
Note: Although it is often much faster than the alternative, #destroy, skipping callbacks might bypass business logic in your application that ensures referential integrity or performs other essential jobs.
Examples
# Delete a single row
Todo.delete(1)
# Delete multiple rows
Todo.delete([2,3,4])
474 475 476 |
# File 'lib/active_record/persistence.rb', line 474 def delete(id_or_array) delete_by(primary_key => id_or_array) end |
#destroy(id) ⇒ Object
Destroy an object (or multiple objects) that has the given id. The object is instantiated first, therefore all callbacks and filters are fired off before the object is deleted. This method is less efficient than #delete but allows cleanup methods and other actions to be run.
This essentially finds the object (or multiple objects) with the given id, creates a new object from the attributes, and then calls destroy on it.
Parameters
-
id
- This should be the id or an array of ids to be destroyed.
Examples
# Destroy a single object
Todo.destroy(1)
# Destroy multiple objects
todos = [1,2,3]
Todo.destroy(todos)
448 449 450 451 452 453 454 |
# File 'lib/active_record/persistence.rb', line 448 def destroy(id) if id.is_a?(Array) find(id).each(&:destroy) else find(id).destroy end end |
#insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Inserts a single record into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
See ActiveRecord::Persistence#insert_all
for documentation.
66 67 68 |
# File 'lib/active_record/persistence.rb', line 66 def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: ) end |
#insert!(attributes, returning: nil, record_timestamps: nil) ⇒ Object
Inserts a single record into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
See ActiveRecord::Persistence#insert_all!
for more.
155 156 157 |
# File 'lib/active_record/persistence.rb', line 155 def insert!(attributes, returning: nil, record_timestamps: nil) insert_all!([ attributes ], returning: returning, record_timestamps: ) end |
#insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Inserts multiple records into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
The attributes
parameter is an Array of Hashes. Every Hash determines the attributes for a single row and must have the same keys.
Rows are considered to be unique by every unique index on the table. Any duplicate rows are skipped. Override with :unique_by
(see below).
Returns an ActiveRecord::Result
with its contents based on :returning
(see below).
Options
- :returning
-
(PostgreSQL only) An array of attributes to return for all successfully inserted records, which by default is the primary key. Pass
returning: %w[ id name ]
for both id and name orreturning: false
to omit the underlyingRETURNING
SQL clause entirely.You can also pass an SQL string if you need more control on the return values (for example,
returning: "id, name as new_name"
). - :unique_by
-
(PostgreSQL and SQLite only) By default rows are considered to be unique by every unique index on the table. Any duplicate rows are skipped.
To skip rows according to just one unique index pass
:unique_by
.Consider a Book model where no duplicate ISBNs make sense, but if any row has an existing id, or is not unique by another unique index,
ActiveRecord::RecordNotUnique
is raised.Unique indexes can be identified by columns or name:
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
- :record_timestamps
-
By default, automatic setting of timestamp columns is controlled by the model’s
record_timestamps
config, matching typical behavior.To override this and force automatic setting of timestamp columns one way or the other, pass
:record_timestamps
:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Because it relies on the index information from the database :unique_by
is recommended to be paired with Active Record’s schema_cache.
Example
# Insert records and skip inserting any duplicates.
# Here "Eloquent Ruby" is skipped because its id is not unique.
Book.insert_all([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
# insert_all works on chained scopes, and you can use create_with
# to set default attributes for all inserted records.
.books.create_with(created_at: Time.now).insert_all([
{ id: 1, title: "Rework" },
{ id: 2, title: "Eloquent Ruby" }
])
145 146 147 |
# File 'lib/active_record/persistence.rb', line 145 def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: ).execute end |
#insert_all!(attributes, returning: nil, record_timestamps: nil) ⇒ Object
Inserts multiple records into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
The attributes
parameter is an Array of Hashes. Every Hash determines the attributes for a single row and must have the same keys.
Raises ActiveRecord::RecordNotUnique
if any rows violate a unique index on the table. In that case, no rows are inserted.
To skip duplicate rows, see ActiveRecord::Persistence#insert_all
. To replace them, see ActiveRecord::Persistence#upsert_all
.
Returns an ActiveRecord::Result
with its contents based on :returning
(see below).
Options
- :returning
-
(PostgreSQL only) An array of attributes to return for all successfully inserted records, which by default is the primary key. Pass
returning: %w[ id name ]
for both id and name orreturning: false
to omit the underlyingRETURNING
SQL clause entirely.You can also pass an SQL string if you need more control on the return values (for example,
returning: "id, name as new_name"
). - :record_timestamps
-
By default, automatic setting of timestamp columns is controlled by the model’s
record_timestamps
config, matching typical behavior.To override this and force automatic setting of timestamp columns one way or the other, pass
:record_timestamps
:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Examples
# Insert multiple records
Book.insert_all!([
{ title: "Rework", author: "David" },
{ title: "Eloquent Ruby", author: "Russ" }
])
# Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
# does not have a unique id.
Book.insert_all!([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
213 214 215 |
# File 'lib/active_record/persistence.rb', line 213 def insert_all!(attributes, returning: nil, record_timestamps: nil) InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: ).execute end |
#instantiate(attributes, column_types = {}, &block) ⇒ Object
Given an attributes hash, instantiate
returns a new instance of the appropriate class. Accepts only keys as strings.
For example, Post.all
may return Comments, Messages, and Emails by storing the record’s subclass in a type
attribute. By calling instantiate
instead of new
, finder methods ensure they get new instances of the appropriate class for each record.
See ActiveRecord::Inheritance#discriminate_class_for_record
to see how this “single-table” inheritance mapping is implemented.
347 348 349 350 |
# File 'lib/active_record/persistence.rb', line 347 def instantiate(attributes, column_types = {}, &block) klass = discriminate_class_for_record(attributes) instantiate_instance_of(klass, attributes, column_types, &block) end |
#update(id = :all, attributes) ⇒ Object
Updates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
Parameters
-
id
- This should be the id or an array of ids to be updated. Optional argument, defaults to all records in the relation. -
attributes
- This should be a hash of attributes or an array of hashes.
Examples
# Updates one record
Person.update(15, user_name: "Samuel", group: "expert")
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# Updates multiple records from the result of a relation
people = Person.where(group: "expert")
people.update(group: "masters")
Note: Updating a large number of records will run an UPDATE query for each record, which may cause a performance issue. When running callbacks is not needed for each record update, it is preferred to use update_all for updating all records in a single query.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/active_record/persistence.rb', line 379 def update(id = :all, attributes) if id.is_a?(Array) if id.any?(ActiveRecord::Base) raise ArgumentError, "You are passing an array of ActiveRecord::Base instances to `update`. " \ "Please pass the ids of the objects by calling `pluck(:id)` or `map(&:id)`." end id.map { |one_id| find(one_id) }.each_with_index { |object, idx| object.update(attributes[idx]) } elsif id == :all all.each { |record| record.update(attributes) } else if ActiveRecord::Base === id raise ArgumentError, "You are passing an instance of ActiveRecord::Base to `update`. " \ "Please pass the id of the object by calling `.id`." end object = find(id) object.update(attributes) object end end |
#update!(id = :all, attributes) ⇒ Object
Updates the object (or multiple objects) just like #update but calls #update! instead of update
, so an exception is raised if the record is invalid and saving will fail.
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/active_record/persistence.rb', line 405 def update!(id = :all, attributes) if id.is_a?(Array) if id.any?(ActiveRecord::Base) raise ArgumentError, "You are passing an array of ActiveRecord::Base instances to `update!`. " \ "Please pass the ids of the objects by calling `pluck(:id)` or `map(&:id)`." end id.map { |one_id| find(one_id) }.each_with_index { |object, idx| object.update!(attributes[idx]) } elsif id == :all all.each { |record| record.update!(attributes) } else if ActiveRecord::Base === id raise ArgumentError, "You are passing an instance of ActiveRecord::Base to `update!`. " \ "Please pass the id of the object by calling `.id`." end object = find(id) object.update!(attributes) object end end |
#upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Updates or inserts (upserts) a single record into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
See ActiveRecord::Persistence#upsert_all
for documentation.
223 224 225 |
# File 'lib/active_record/persistence.rb', line 223 def upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil, record_timestamps: nil) upsert_all([ attributes ], on_duplicate: on_duplicate, returning: returning, unique_by: unique_by, record_timestamps: ) end |
#upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) ⇒ Object
Updates or inserts (upserts) multiple records into the database in a single SQL INSERT statement. It does not instantiate any models nor does it trigger Active Record callbacks or validations. Though passed values go through Active Record’s type casting and serialization.
The attributes
parameter is an Array of Hashes. Every Hash determines the attributes for a single row and must have the same keys.
Returns an ActiveRecord::Result
with its contents based on :returning
(see below).
By default, upsert_all
will update all the columns that can be updated when there is a conflict. These are all the columns except primary keys, read-only columns, and columns covered by the optional unique_by
.
Options
- :returning
-
(PostgreSQL only) An array of attributes to return for all successfully inserted records, which by default is the primary key. Pass
returning: %w[ id name ]
for both id and name orreturning: false
to omit the underlyingRETURNING
SQL clause entirely.You can also pass an SQL string if you need more control on the return values (for example,
returning: "id, name as new_name"
). - :unique_by
-
(PostgreSQL and SQLite only) By default rows are considered to be unique by every unique index on the table. Any duplicate rows are skipped.
To skip rows according to just one unique index pass
:unique_by
.Consider a Book model where no duplicate ISBNs make sense, but if any row has an existing id, or is not unique by another unique index,
ActiveRecord::RecordNotUnique
is raised.Unique indexes can be identified by columns or name:
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
Because it relies on the index information from the database :unique_by
is recommended to be paired with Active Record’s schema_cache.
- :on_duplicate
-
Configure the SQL update sentence that will be used in case of conflict.
NOTE: If you use this option you must provide all the columns you want to update by yourself.
Example:
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)") )
See the related
:update_only
option. Both options can’t be used at the same time. - :update_only
-
Provide a list of column names that will be updated in case of conflict. If not provided,
upsert_all
will update all the columns that can be updated. These are all the columns except primary keys, read-only columns, and columns covered by the optionalunique_by
Example:
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], update_only: [:price] # Only prices will be updated )
See the related
:on_duplicate
option. Both options can’t be used at the same time. - :record_timestamps
-
By default, automatic setting of timestamp columns is controlled by the model’s
record_timestamps
config, matching typical behavior.To override this and force automatic setting of timestamp columns one way or the other, pass
:record_timestamps
:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Examples
# Inserts multiple records, performing an upsert when records have duplicate ISBNs.
# Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
Book.upsert_all([
{ title: "Rework", author: "David", isbn: "1" },
{ title: "Eloquent Ruby", author: "Russ", isbn: "1" }
], unique_by: :isbn)
Book.find_by(isbn: "1").title # => "Eloquent Ruby"
333 334 335 |
# File 'lib/active_record/persistence.rb', line 333 def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.new(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: ).execute end |