Ruby on Rails
Интерфейс запросов ActiveRecord
Поиск…
Вступление
ActiveRecord - это M в MVC, который является уровнем системы, отвечающей за представление бизнес-данных и логики. Метод, который соединяет богатые объекты приложения с таблицами в системе управления реляционными базами данных, - это O bject R elational M apper ( ORM ).
ActiveRecord будет выполнять запросы в базе данных для вас и совместим с большинством систем баз данных. Независимо от того, какую систему баз данных вы используете, формат метода ActiveRecord всегда будет таким же.
.где
Метод where
доступен для любой модели ActiveRecord
и позволяет запрашивать базу данных для набора записей, соответствующих заданным критериям.
Метод where
принимает хэш, где ключи соответствуют именам столбцов в таблице, представленной моделью.
В качестве простого примера мы будем использовать следующую модель:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
Чтобы найти всех людей с первым именем Sven
:
people = Person.where(first_name: 'Sven')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven'"
Чтобы найти всех людей с именем Sven
и фамилией Schrodinger
:
people = Person.where(first_name: 'Sven', last_name: 'Schrodinger')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven' AND last_name='Schrodinger'"
В приведенном выше примере вывод sql показывает, что записи будут возвращаться только в том случае, если совпадают имя first_name
и last_name
.
запрос с условием ИЛИ
Чтобы найти записи с first_name == 'Bruce'
OR last_name == 'Wayne'
User.where('first_name = ? or last_name = ?', 'Bruce', 'Wayne')
# SELECT "users".* FROM "users" WHERE (first_name = 'Bruce' or last_name = 'Wayne')
. где с массивом
Метод where
для любой модели ActiveRecord может использоваться для генерации SQL формы WHERE column_name IN (a, b, c, ...)
. Это достигается передачей массива в качестве аргумента.
В качестве простого примера мы будем использовать следующую модель:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
people = Person.where(first_name: ['Mark', 'Mary'])
people.to_sql # "SELECT * FROM people WHERE first_name IN ('Mark', 'Mary')"
Если массив содержит nil
, то SQL будет изменено , чтобы проверить , если столбец является null
:
people = Person.where(first_name: ['Mark', 'Mary', nil])
people.to_sql # "SELECT * FROM people WHERE first_name IN ('Mark', 'Mary') OR first_name IS NULL"
Области применения
Области действия действуют как предопределенные фильтры на моделях ActiveRecord
.
Область определения определяется с помощью метода класса scope
.
В качестве простого примера мы будем использовать следующую модель:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
#attribute :age, :integer
# define a scope to get all people under 17
scope :minors, -> { where(age: 0..17) }
# define a scope to search a person by last name
scope :with_last_name, ->(name) { where(last_name: name) }
end
Scopes можно вызывать непосредственно из класса модели:
minors = Person.minors
Области могут быть связаны:
peters_children = Person.minors.with_last_name('Peters')
Метод where
и другие методы типа запроса также могут быть связаны:
mary_smith = Person.with_last_name('Smith').where(first_name: 'Mary')
За кулисами области - это просто синтаксический сахар для стандартного метода класса. Например, эти методы функционально идентичны:
scope :with_last_name, ->(name) { where(name: name) }
# This ^ is the same as this:
def self.with_last_name(name)
where(name: name)
end
Область по умолчанию
в вашей модели, чтобы установить область по умолчанию для всех операций над моделью.
Существует одна заметная разница между методом
scope
методом класса:scope
-defined scopes всегда возвращаютActiveRecord::Relation
, даже если логика внутри возвращает nil. Однако методы класса не имеют такой защитной сетки и могут разрушать цепочки, если они возвращают что-то еще.
where.not
where
clauses можно where.not
синтаксиса where.not
:
class Person < ApplicationRecord #attribute :first_name, :string end people = Person.where.not(first_name: ['Mark', 'Mary']) # => SELECT "people".* FROM "people" WHERE "people"."first_name" NOT IN ('Mark', 'Mary')
Поддерживается ActiveRecord 4.0 и более поздних версий.
заказ
Вы можете заказать результаты запроса ActiveRecord с помощью .order
:
User.order(:created_at)
#=> => [#<User id: 2, created_at: "2015-08-12 21:36:23">, #<User id: 11, created_at: "2015-08-15 10:21:48">]
Если не указано, заказы будут выполняться в порядке возрастания. Вы можете указать его, выполнив:
User.order(created_at: :asc)
#=> => [#<User id: 2, created_at: "2015-08-12 21:36:23">, #<User id: 11, created_at: "2015-08-15 10:21:48">]
User.order(created_at: :desc)
#=> [#<User id: 7585, created_at: "2016-07-13 17:15:27">, #<User id: 7583, created_at: "2016-07-13 16:51:18">]
.order
также принимает строку, поэтому вы также можете сделать
User.order("created_at DESC")
#=> [#<User id: 7585, created_at: "2016-07-13 17:15:27">, #<User id: 7583, created_at: "2016-07-13 16:51:18">]
Поскольку строка является сырым SQL, вы также можете указать таблицу, а не только атрибут. Предполагая, что вы хотите заказать users
соответствии с их именем role
, вы можете сделать это:
Class User < ActiveRecord::Base
belongs_to :role
end
Class Role < ActiveRecord::Base
has_many :users
end
User.includes(:role).order("roles.name ASC")
Область order
также может принимать узел Arel:
User.includes(:role).order(User.arel_table[:name].asc)
Методы ActiveRecord Bang (!)
Если вам нужен метод ActiveRecord для создания исключения вместо false
значения в случае сбоя, вы можете добавить !
им. Это очень важно. Поскольку некоторые исключения / неудачи трудно поймать, если вы не используете! на них. Я рекомендовал сделать это в вашем цикле разработки, чтобы написать весь ваш код ActiveRecord таким образом, чтобы сэкономить ваше время и проблемы.
Class User < ActiveRecord::Base
validates :last_name, presence: true
end
User.create!(first_name: "John")
#=> ActiveRecord::RecordInvalid: Validation failed: Last name can't be blank
Методами ActiveRecord, которые принимают взрыва ( !
), Являются:
-
.create!
-
.take!
-
.first!
-
.last!
-
.find_by!
-
.find_or_create_by!
-
#save!
-
#update!
- все динамические искатели AR
.find_by
Вы можете найти записи по любому полю в своей таблице, используя find_by
.
Итак, если у вас есть модель User
с атрибутом first_name
вы можете:
User.find_by(first_name: "John")
#=> #<User id: 2005, first_name: "John", last_name: "Smith">
find_by
что find_by
по умолчанию не выбрасывает исключение. Если результатом является пустой набор, он возвращает nil
вместо find
.
Если это исключение, вы можете использовать find_by!
что вызывает ошибку ActiveRecord::RecordNotFound
например find
.
.удалить все
Если вам нужно быстро удалить много записей, ActiveRecord предоставляет метод .delete_all
. для вызова непосредственно на модели, для удаления всех записей в этой таблице или коллекции. Остерегайтесь, хотя, поскольку .delete_all
не создает экземпляр какого-либо объекта, следовательно, не предоставляет никакого обратного вызова ( before_*
и after_destroy
не запускаются).
User.delete_all
#=> 39 <-- .delete_all return the number of rows deleted
User.where(name: "John").delete_all
Нечувствительный к регистру поиск по регистру ActiveRecord
Если вам нужно искать модель ActiveRecord для аналогичных значений, у вас может возникнуть соблазн использовать LIKE
или ILIKE
но это не переносится между ILIKE
базы данных. Точно так же, прибегая к постоянному снижению или росту, вы можете создавать проблемы с производительностью.
Вы можете использовать метод matches
ActiveR для ActiveRecord, чтобы сделать это безопасным способом:
addresses = Address.arel_table
Address.where(addresses[:address].matches("%street%"))
Arel применит соответствующую конструкцию LIKE или ILIKE для сконфигурированного ядра базы данных.
Получить первую и последнюю запись
У Rails есть очень простой способ получить first
и last
запись из базы данных.
Чтобы получить first
запись из таблицы users
нам нужно ввести следующую команду:
User.first
Он будет генерировать следующий запрос sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
И вернется следующая запись:
#<User:0x007f8a6db09920 id: 1, first_name: foo, created_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00, updated_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00 >
Чтобы получить last
запись из таблицы users
нам нужно ввести следующую команду:
User.last
Он будет генерировать следующий запрос sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
И вернется следующая запись:
#<User:0x007f8a6db09920 id: 10, first_name: bar, created_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00, updated_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00 >
Передача целочисленного значения первому и последнему методу создает запрос LIMIT и возвращает массив объектов.
User.first(5)
Он будет генерировать следующий запрос sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 5
А также
User.last(5)
Он будет генерировать следующий запрос sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 5
.группа и .count
У нас есть модель Product
и мы хотим сгруппировать их по их category
.
Product.select(:category).group(:category)
Это запросит базу данных следующим образом:
SELECT "product"."category" FROM "product" GROUP BY "product"."category"
Убедитесь, что поле сгруппировано также выбрано. Группирование особенно полезно для подсчета появления - в данном случае - categories
.
Product.select(:category).group(:category).count
Как показывает запрос, он будет использовать базу данных для подсчета, что намного эффективнее, чем сначала получить всю запись, и сделать подсчет в коде:
SELECT COUNT("products"."category") AS count_categories, "products"."category" AS products_category FROM "products" GROUP BY "products"."category"
.distinct (или .uniq)
Если вы хотите удалить дубликаты из результата, вы можете использовать .distinct()
:
Customers.select(:country).distinct
Это запрашивает базу данных следующим образом:
SELECT DISTINCT "customers"."country" FROM "customers"
.uniq()
имеет тот же эффект. С Rails 5.0 он устарел, и он будет удален из Rails с версией 5.1. Причина в том, что слово unique
не имеет того же значения, что и отдельный, и оно может вводить в заблуждение. Кроме того, distinct
подход ближе к синтаксису SQL.
присоединяется
joins()
позволяет присоединить таблицы к текущей модели. Напр.
User.joins(:posts)
будет вызывать следующий SQL-запрос:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""
После соединения таблицы у вас будет доступ к ней:
User.joins(:posts).where(posts: { title: "Hello world" })
Обратите внимание на множественную форму. Если ваше отношение :has_many
, то аргумент join joins()
должен быть плюрализован. В противном случае используйте единственное число.
Вложенные joins
:
User.joins(posts: :images).where(images: { caption: 'First post' })
который будет производить:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id""
Включает в себя
ActiveRecord с includes
гарантирует , что все указанные ассоциаций загружаются с использованием минимально возможного количества запросов. Поэтому при запросе таблицы для данных со связанной таблицей обе таблицы загружаются в память.
@authors = Author.includes(:books).where(books: { bestseller: true } )
# this will print results without additional db hitting
@authors.each do |author|
author.books.each do |book|
puts book.title
end
end
Author.joins(:books).where(books: { bestseller: true } )
загрузит только авторов с условиями в память без загрузки книг . Используйте joins
если дополнительная информация о вложенных ассоциациях не требуется.
@authors = Author.joins(:books).where(books: { bestseller: true } )
# this will print results without additional queries
@authors.each { |author| puts author.name }
# this will print results with additional db queries
@authors.each do |author|
author.books.each do |book|
puts book.title
end
end
Ограничение и смещение
Вы можете использовать limit
чтобы указать количество записей, которые нужно извлечь, и использовать offset
чтобы сообщить количество пропущенных записей, прежде чем начинать возвращать записи.
Например
User.limit(3) #returns first three records
Он будет генерировать следующий запрос sql.
"SELECT `users`.* FROM `users` LIMIT 3"
Поскольку смещение не упоминается в вышеуказанном запросе, оно возвращает первые три записи.
User.limit(5).offset(30) #returns 5 records starting from 31th i.e from 31 to 35
Он будет генерировать следующий запрос sql.
"SELECT `users`.* FROM `users` LIMIT 5 OFFSET 30"