module ActiveRecord::Persistence::ClassMethods
def _delete_record(constraints) # :nodoc:
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
def _insert_record(values, returning) # :nodoc:
def _insert_record(values, returning) # :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, returning: returning ) end
def _update_record(values, constraints) # :nodoc:
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
def build(attributes = nil, &block)
u.is_admin = false
User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
# Building an Array of new objects using a block, where the block is executed for each object:
end
u.is_admin = false
User.build(first_name: 'Jamie') do |u|
# Build a single object and pass it into a block to set other attributes.
User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
# Build an Array of new objects
User.build(first_name: 'Jamie')
# Build a single new object
==== Examples
attributes on the objects that are to be built.
The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
objects.
Builds an object (or multiple objects) and returns either the built object or a list of built
def build(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| build(attr, &block) } else new(attributes, &block) end end
def build_default_constraint
to build `where` clause from default scopes.
Called by +_update_record+ and +_delete_record+
def build_default_constraint return unless default_scopes?(all_queries: true) default_where_clause = default_scoped(all_queries: true).where_clause default_where_clause.ast unless default_where_clause.empty? end
def composite_query_constraints_list # :nodoc:
is for internal use when the primary key is to be treated as an array.
names is derived from +query_constraints_list+ or +primary_key+. This method
Returns an array of column names to be used in queries. The source of column
def composite_query_constraints_list # :nodoc: @composite_query_constraints_list ||= query_constraints_list || Array(primary_key) end
def create(attributes = nil, &block)
u.is_admin = false
User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
# Creating an Array of new objects using a block, where the block is executed for each object:
end
u.is_admin = false
User.create(first_name: 'Jamie') do |u|
# Create a single object and pass it into a block to set other attributes.
User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
# Create an Array of new objects
User.create(first_name: 'Jamie')
# Create a single new object
==== Examples
attributes on the objects that are to be created.
The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
The resulting object is returned whether the object was saved successfully to the database or not.
Creates an object (or multiple objects) and saves it to the database, if validations pass.
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
def create!(attributes = nil, &block)
These describe which attributes to be created on the object, or
The +attributes+ parameter can be either a Hash or an Array of Hashes.
unlike Base#create.
if validations pass. Raises a RecordInvalid error if validations fail,
Creates an object (or multiple objects) and saves it to the database,
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
def delete(id_or_array)
# Delete multiple rows
Todo.delete(1)
# Delete a single row
==== Examples
that ensures referential integrity or performs other essential jobs.
skipping callbacks might bypass business logic in your application
Note: Although it is often much faster than the alternative, #destroy,
You can delete multiple rows at once by passing an Array of ids.
executed, including any :dependent association options.
Record objects are not instantiated, so the object's callbacks are not
SQL +DELETE+ statement, and returns the number of rows deleted. Active
Deletes the row with a primary key matching the +id+ argument, using an
def delete(id_or_array) delete_by(primary_key => id_or_array) end
def destroy(id)
todos = [1,2,3]
# Destroy multiple objects
Todo.destroy(1)
# Destroy a single object
==== Examples
* +id+ - This should be the id or an array of ids to be destroyed.
==== Parameters
from the attributes, and then calls destroy on it.
This essentially finds the object (or multiple objects) with the given id, creates a new object
less efficient than #delete but allows cleanup methods and other actions to be run.
therefore all callbacks and filters are fired off before the object is deleted. This method is
Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
def destroy(id) multiple_ids = if composite_primary_key? id.first.is_a?(Array) else id.is_a?(Array) end if multiple_ids find(id).each(&:destroy) else find(id).destroy end end
def discriminate_class_for_record(record)
See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
record instance.
Called by +instantiate+ to decide which class to use for a new
def discriminate_class_for_record(record) self end
def has_query_constraints? # :nodoc:
def has_query_constraints? # :nodoc: @has_query_constraints end
def inherited(subclass)
def inherited(subclass) super subclass.class_eval do @_query_constraints_list = nil @has_query_constraints = false end end
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
go through Active Record's type casting and serialization.
Active Record callbacks or validations. Though passed values
statement. It does not instantiate any models nor does it trigger
Inserts a single record into the database in a single SQL INSERT
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
def insert!(attributes, returning: nil, record_timestamps: nil)
go through Active Record's type casting and serialization.
Active Record callbacks or validations. Though passed values
statement. It does not instantiate any models nor does it trigger
Inserts a single record into the database in a single SQL INSERT
def insert!(attributes, returning: nil, record_timestamps: nil) insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps) end
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
{ id: 2, title: "Eloquent Ruby" }
{ id: 1, title: "Rework" },
author.books.create_with(created_at: Time.now).insert_all([
# to set default attributes for all inserted records.
# insert_all works on chained scopes, and you can use create_with
])
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
{ id: 1, title: "Rework", author: "David" },
Book.insert_all([
# Here "Eloquent Ruby" is skipped because its id is not unique.
# Insert records and skip inserting any duplicates.
==== Example
Active Record's schema_cache.
:unique_by is recommended to be paired with
Because it relies on the index information from the database
record_timestamps: false # Never set timestamps automatically
record_timestamps: true # Always set timestamps automatically
way or the other, pass :record_timestamps:
To override this and force automatic setting of timestamp columns one
behavior.
the model's record_timestamps config, matching typical
By default, automatic setting of timestamp columns is controlled by
[:record_timestamps]
unique_by: :index_books_on_isbn
unique_by: %i[ author_id name ]
unique_by: :isbn
Unique indexes can be identified by columns or name:
ActiveRecord::RecordNotUnique is raised.
row has an existing id, or is not unique by another unique index,
Consider a Book model where no duplicate ISBNs make sense, but if any
To skip rows according to just one unique index pass :unique_by.
by every unique index on the table. Any duplicate rows are skipped.
(PostgreSQL and SQLite only) By default rows are considered to be unique
[:unique_by]
(for example, returning: Arel.sql("id, name as new_name")).
You can also pass an SQL string if you need more control on the return values
clause entirely.
or returning: false to omit the underlying RETURNING SQL
Pass returning: %w[ id name ] for both id and name
inserted records, which by default is the primary key.
(PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
[:returning]
==== Options
:returning (see below).
Returns an ActiveRecord::Result with its contents based on
Override with :unique_by (see below).
duplicate rows are skipped.
Rows are considered to be unique by every unique index on the table. Any
the attributes for a single row and must have the same keys.
The +attributes+ parameter is an Array of Hashes. Every Hash determines
go through Active Record's type casting and serialization.
Active Record callbacks or validations. Though passed values
statement. It does not instantiate any models nor does it trigger
Inserts multiple records into the database in a single SQL INSERT
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: record_timestamps).execute end
def insert_all!(attributes, returning: nil, record_timestamps: nil)
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
{ id: 1, title: "Rework", author: "David" },
Book.insert_all!([
# does not have a unique id.
# Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
])
{ title: "Eloquent Ruby", author: "Russ" }
{ title: "Rework", author: "David" },
Book.insert_all!([
# Insert multiple records
==== Examples
record_timestamps: false # Never set timestamps automatically
record_timestamps: true # Always set timestamps automatically
way or the other, pass :record_timestamps:
To override this and force automatic setting of timestamp columns one
behavior.
the model's record_timestamps config, matching typical
By default, automatic setting of timestamp columns is controlled by
[:record_timestamps]
(for example, returning: Arel.sql("id, name as new_name")).
You can also pass an SQL string if you need more control on the return values
clause entirely.
or returning: false to omit the underlying RETURNING SQL
Pass returning: %w[ id name ] for both id and name
inserted records, which by default is the primary key.
(PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
[:returning]
==== Options
:returning (see below).
Returns an ActiveRecord::Result with its contents based on
To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
unique index on the table. In that case, no rows are inserted.
Raises ActiveRecord::RecordNotUnique if any rows violate a
the attributes for a single row and must have the same keys.
The +attributes+ parameter is an Array of Hashes. Every Hash determines
go through Active Record's type casting and serialization.
Active Record callbacks or validations. Though passed values
statement. It does not instantiate any models nor does it trigger
Inserts multiple records into the database in a single SQL INSERT
def insert_all!(attributes, returning: nil, record_timestamps: nil) InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps).execute end
def instantiate(attributes, column_types = {}, &block)
See ActiveRecord::Inheritance#discriminate_class_for_record to see
instances of the appropriate class for each record.
+instantiate+ instead of +new+, finder methods ensure they get new
by storing the record's subclass in a +type+ attribute. By calling
For example, +Post.all+ may return Comments, Messages, and Emails
the appropriate class. Accepts only keys as strings.
Given an attributes hash, +instantiate+ returns a new instance of
def instantiate(attributes, column_types = {}, &block) klass = discriminate_class_for_record(attributes) instantiate_instance_of(klass, attributes, column_types, &block) end
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
Given a class, an attributes hash, +instantiate_instance_of+ returns a
def instantiate_instance_of(klass, attributes, column_types = {}, &block) attributes = klass.attributes_builder.build_from_database(attributes, column_types) klass.allocate.init_with_attributes(attributes, &block) end
def query_constraints(*columns_list)
developer.reload
# DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
developer.delete
# DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
developer.destroy!
# UPDATE "developers" SET "name" = 'Bob' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
developer.save!
developer.name = "Bob"
# UPDATE "developers" SET "company_id" = 2 WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
developer.update!(company_id: 2)
# It is possible to update an attribute used in the query_constraints clause:
# UPDATE "developers" SET "name" = 'Nikita' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
developer.update!(name: "Nikita")
developer.inspect # => #
# SELECT "developers".* FROM "developers" ORDER BY "developers"."company_id" ASC, "developers"."id" ASC LIMIT 1
developer = Developer.first
end
query_constraints :company_id, :id
class Developer < ActiveRecord::Base
of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for +#first+ and +#last+ finder methods.
Accepts a list of attribute names to be used in the WHERE clause
def query_constraints(*columns_list) raise ArgumentError, "You must specify at least one column to be used in querying" if columns_list.empty? @query_constraints_list = columns_list.map(&:to_s) @has_query_constraints = @query_constraints_list end
def query_constraints_list # :nodoc:
def query_constraints_list # :nodoc: @query_constraints_list ||= if base_class? || primary_key != base_class.primary_key primary_key if primary_key.is_a?(Array) else base_class.query_constraints_list end end
def update(id = :all, attributes)
it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
When running callbacks is not needed for each record update,
query for each record, which may cause a performance issue.
Note: Updating a large number of records will run an UPDATE
people.update(group: "masters")
people = Person.where(group: "expert")
# Updates multiple records from the result of a relation
Person.update(people.keys, people.values)
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Updates multiple records
Person.update(15, user_name: "Samuel", group: "expert")
# Updates one record
==== Examples
* +attributes+ - This should be a hash of attributes or an array of hashes.
Optional argument, defaults to all records in the relation.
* +id+ - This should be the id or an array of ids to be updated.
==== Parameters
The resulting object is returned whether the object was saved successfully to the database or not.
Updates an object (or multiple objects) and saves it to the database, if validations pass.
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
def update!(id = :all, attributes)
Updates the object (or multiple objects) just like #update but calls #update! instead
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
def upsert(attributes, **kwargs)
go through Active Record's type casting and serialization.
it trigger Active Record callbacks or validations. Though passed values
single SQL INSERT statement. It does not instantiate any models nor does
Updates or inserts (upserts) a single record into the database in a
def upsert(attributes, **kwargs) upsert_all([ attributes ], **kwargs) end
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
], unique_by: :isbn)
{ title: "Eloquent Ruby", author: "Russ", isbn: "1" }
{ title: "Rework", author: "David", isbn: "1" },
Book.upsert_all([
# Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
# Inserts multiple records, performing an upsert when records have duplicate ISBNs.
==== Examples
record_timestamps: false # Never set timestamps automatically
record_timestamps: true # Always set timestamps automatically
way or the other, pass :record_timestamps:
To override this and force automatic setting of timestamp columns one
behavior.
the model's record_timestamps config, matching typical
By default, automatic setting of timestamp columns is controlled by
[:record_timestamps]
See the related +:on_duplicate+ option. Both options can't be used at the same time.
)
update_only: [:price] # Only prices will be updated
],
{ id: 6, name: "Aluminium", price: 0.35 }
{ id: 4, name: "Gold", price: 1380.87 },
{ id: 2, name: "Copper", price: 4.84 },
[
Commodity.upsert_all(
Example:
except primary keys, read-only columns, and columns covered by the optional +unique_by+
+upsert_all+ will update all the columns that can be updated. These are all the columns
Provide a list of column names that will be updated in case of conflict. If not provided,
[:update_only]
See the related +:update_only+ option. Both options can't be used at the same time.
)
on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
],
{ id: 6, name: "Aluminium", price: 0.35 }
{ id: 4, name: "Gold", price: 1380.87 },
{ id: 2, name: "Copper", price: 4.84 },
[
Commodity.upsert_all(
Example:
by yourself.
NOTE: If you use this option you must provide all the columns you want to update
Configure the SQL update sentence that will be used in case of conflict.
[:on_duplicate]
Active Record's schema_cache.
:unique_by is recommended to be paired with
Because it relies on the index information from the database
unique_by: :index_books_on_isbn
unique_by: %i[ author_id name ]
unique_by: :isbn
Unique indexes can be identified by columns or name:
ActiveRecord::RecordNotUnique is raised.
row has an existing id, or is not unique by another unique index,
Consider a Book model where no duplicate ISBNs make sense, but if any
To skip rows according to just one unique index pass :unique_by.
by every unique index on the table. Any duplicate rows are skipped.
(PostgreSQL and SQLite only) By default rows are considered to be unique
[:unique_by]
(for example, returning: Arel.sql("id, name as new_name")).
You can also pass an SQL string if you need more control on the return values
clause entirely.
or returning: false to omit the underlying RETURNING SQL
Pass returning: %w[ id name ] for both id and name
inserted records, which by default is the primary key.
(PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
[:returning]
==== Options
columns, and columns covered by the optional +unique_by+.
there is a conflict. These are all the columns except primary keys, read-only
By default, +upsert_all+ will update all the columns that can be updated when
:returning (see below).
Returns an ActiveRecord::Result with its contents based on
the attributes for a single row and must have the same keys.
The +attributes+ parameter is an Array of Hashes. Every Hash determines
go through Active Record's type casting and serialization.
it trigger Active Record callbacks or validations. Though passed values
single SQL INSERT statement. It does not instantiate any models nor does
Updates or inserts (upserts) multiple records into the database in a
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: record_timestamps).execute end