Ruby on Rails
Interface de requête ActiveRecord
Recherche…
Introduction
ActiveRecord est le M dans MVC qui est la couche du système chargée de représenter les données et la logique métier. La technique qui relie les objets riches d'une application à tables dans un système de gestion de base de données relationnelle est O bjet R elational M apper (ORM).
ActiveRecord effectuera des requêtes sur la base de données pour vous et est compatible avec la plupart des systèmes de base de données. Quel que soit le système de base de données que vous utilisez, le format de la méthode ActiveRecord sera toujours le même.
.où
La méthode where
est disponible sur tout modèle ActiveRecord
et permet d'interroger la base de données pour un ensemble d'enregistrements correspondant aux critères donnés.
La méthode where
accepte un hachage où les clés correspondent aux noms de colonne de la table que le modèle représente.
Comme exemple simple, nous utiliserons le modèle suivant:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
Pour trouver toutes les personnes avec le prénom de Sven
:
people = Person.where(first_name: 'Sven')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven'"
Pour trouver toutes les personnes avec le prénom de Sven
et le nom de famille de Schrodinger
:
people = Person.where(first_name: 'Sven', last_name: 'Schrodinger')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven' AND last_name='Schrodinger'"
Dans l'exemple ci - dessus, la sortie sql montre que les enregistrements ne seront retournés à la fois si le first_name
et la last_name
correspondance.
requête avec condition OU
Pour rechercher des enregistrements avec 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')
.part avec un tableau
La méthode where
de tout modèle ActiveRecord peut être utilisée pour générer du code SQL de la forme WHERE column_name IN (a, b, c, ...)
. Ceci est réalisé en passant un tableau en argument.
Comme exemple simple, nous utiliserons le modèle suivant:
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')"
Si le tableau contient un nil
, le SQL sera modifié pour vérifier si la colonne est 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"
Les portées
Les portées agissent comme des filtres prédéfinis sur les modèles ActiveRecord
.
Une portée est définie à l'aide de la méthode de la classe de scope
.
Comme exemple simple, nous utiliserons le modèle suivant:
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
Les étendues peuvent être appelées directement à partir de la classe de modèle:
minors = Person.minors
Les portées peuvent être chaînées:
peters_children = Person.minors.with_last_name('Peters')
La méthode where
et les autres méthodes de type requête peuvent également être chaînées:
mary_smith = Person.with_last_name('Smith').where(first_name: 'Mary')
En coulisses, les scopes sont simplement du sucre syntaxique pour une méthode de classe standard. Par exemple, ces méthodes sont fonctionnellement identiques:
scope :with_last_name, ->(name) { where(name: name) }
# This ^ is the same as this:
def self.with_last_name(name)
where(name: name)
end
Portée par défaut
dans votre modèle pour définir une étendue par défaut pour toutes les opérations sur le modèle.
Il y a une différence notable entre la méthode de
scope
et la méthode de classe: les étendues définies par lascope
renvoient toujours unActiveRecord::Relation
, même si la logique dans un résultat est nulle. Les méthodes de classe, cependant, n'ont pas un tel filet de sécurité et peuvent briser la chaîne si elles renvoient autre chose.
où.pas
where
clauses peuvent être annulées en utilisant la syntaxe 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')
Pris en charge par ActiveRecord 4.0 et versions ultérieures.
Commande
Vous pouvez commander les résultats de la requête ActiveRecord à l'aide de .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">]
Si non spécifié, la commande sera effectuée dans l'ordre croissant. Vous pouvez le spécifier en faisant:
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
accepte également une chaîne, vous pouvez donc aussi le faire
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">]
Comme la chaîne est du SQL brut, vous pouvez également spécifier une table et pas seulement un attribut. En supposant que vous voulez commander les users
fonction de leur nom de role
, vous pouvez le faire:
Class User < ActiveRecord::Base
belongs_to :role
end
Class Role < ActiveRecord::Base
has_many :users
end
User.includes(:role).order("roles.name ASC")
La portée de la order
peut également accepter un noeud Arel:
User.includes(:role).order(User.arel_table[:name].asc)
Méthodes ActiveRecord Bang (!)
Si vous avez besoin d'une méthode ActiveRecord pour générer une exception au lieu d'une valeur false
en cas d'échec, vous pouvez ajouter !
pour eux. C'est très important. Comme certaines exceptions / échecs sont difficiles à détecter si vous n'utilisez pas! sur eux. J'ai recommandé de le faire dans votre cycle de développement pour écrire tout votre code ActiveRecord de cette manière afin de vous faire gagner du temps et d'éviter des problèmes.
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
Les méthodes ActiveRecord qui acceptent un bang ( !
) Sont:
-
.create!
-
.take!
-
.first!
-
.last!
-
.find_by!
-
.find_or_create_by!
-
#save!
-
#update!
- tous les détecteurs dynamiques AR
.find_by
Vous pouvez trouver des enregistrements par n'importe quel champ de votre table en utilisant find_by
.
Donc, si vous avez un modèle User
avec un attribut first_name
vous pouvez le faire:
User.find_by(first_name: "John")
#=> #<User id: 2005, first_name: "John", last_name: "Smith">
find_by
que find_by
ne lance aucune exception par défaut. Si le résultat est un ensemble vide, il renvoie nil
au lieu de find
.
Si l'exception est nécessaire, utilisez find_by!
qui déclenche une erreur ActiveRecord::RecordNotFound
comme find
.
.delete_all
Si vous devez supprimer beaucoup d’enregistrements rapidement, ActiveRecord donne la méthode .delete_all
. être appelé directement sur un modèle, pour supprimer tous les enregistrements de cette table ou une collection. Attention cependant, comme .delete_all
aucun objet et ne fournit donc aucun rappel ( before_*
et after_destroy
ne sont pas déclenchés).
User.delete_all
#=> 39 <-- .delete_all return the number of rows deleted
User.where(name: "John").delete_all
ActiveRecord recherche insensible à la casse
Si vous devez rechercher un modèle ActiveRecord pour des valeurs similaires, vous pouvez être tenté d'utiliser LIKE
ou ILIKE
mais ce n'est pas portable entre les moteurs de base de données. De même, le recours à la réduction des coûts ou à la mise à jour permanente peut créer des problèmes de performance.
Vous pouvez utiliser la méthode de matches
Arel sous-jacente d'ActiveRecord pour le faire en toute sécurité:
addresses = Address.arel_table
Address.where(addresses[:address].matches("%street%"))
Arel appliquera la construction LIKE ou ILIKE appropriée pour le moteur de base de données configuré.
Obtenez le premier et le dernier enregistrement
Les rails ont un moyen très facile d'obtenir le first
et le last
enregistrement de la base de données.
Pour obtenir le first
enregistrement de la table des users
, vous devez taper la commande suivante:
User.first
Il va générer une requête sql
suivante:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Et reviendra l'enregistrement suivant:
#<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 >
Pour obtenir le last
enregistrement de la table des users
, vous devez taper la commande suivante:
User.last
Il va générer une requête sql
suivante:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
Et reviendra l'enregistrement suivant:
#<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 >
Passer un entier à la première et à la dernière méthode crée une requête LIMIT et renvoie un tableau d'objets.
User.first(5)
Il générera la requête sql
suivante.
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 5
Et
User.last(5)
Il générera la requête sql
suivante.
SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 5
.group et .count
Nous avons un modèle de Product
et nous voulons les regrouper par category
.
Product.select(:category).group(:category)
Cela interrogera la base de données comme suit:
SELECT "product"."category" FROM "product" GROUP BY "product"."category"
Assurez-vous que le champ groupé est également sélectionné. Le regroupement est particulièrement utile pour compter l'occurrence - dans ce cas - des categories
.
Product.select(:category).group(:category).count
Comme l'indique la requête, la base de données sera utilisée pour le comptage, ce qui est beaucoup plus efficace que la récupération de tous les enregistrements et le comptage dans le code:
SELECT COUNT("products"."category") AS count_categories, "products"."category" AS products_category FROM "products" GROUP BY "products"."category"
.distinct (ou .uniq)
Si vous souhaitez supprimer les doublons d'un résultat, vous pouvez utiliser .distinct()
:
Customers.select(:country).distinct
Cela interroge la base de données comme suit:
SELECT DISTINCT "customers"."country" FROM "customers"
.uniq()
a le même effet. Avec Rails 5.0, il est devenu obsolète et il sera supprimé de Rails avec la version 5.1. La raison en est que le mot unique
n'a pas le même sens que distinct et peut être trompeur. De plus, distinct
est plus proche de la syntaxe SQL.
Joint
joins()
vous permet de joindre des tables à votre modèle actuel. Pour ex.
User.joins(:posts)
produira la requête SQL suivante:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""
Ayant joint la table, vous y aurez accès:
User.joins(:posts).where(posts: { title: "Hello world" })
Faites attention au pluriel. Si votre relation est :has_many
, alors l'argument joins()
doit être pluralisé. Sinon, utilisez singulier.
Emboîtée joins
:
User.joins(posts: :images).where(images: { caption: 'First post' })
qui produira:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id""
Comprend
ActiveRecord avec includes
garantit que toutes les associations spécifiées sont chargées en utilisant le nombre minimum possible de requêtes. Ainsi, lorsque vous interrogez une table pour des données avec une table associée, les deux tables sont chargées en mémoire.
@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 } )
ne chargera que les auteurs avec des conditions en mémoire sans charger les livres . Utilisez des joins
lorsque des informations supplémentaires sur les associations imbriquées ne sont pas requises.
@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
Limite et décalage
Vous pouvez utiliser limit
pour indiquer le nombre d'enregistrements à récupérer et utiliser offset
pour indiquer le nombre d'enregistrements à ignorer avant de commencer à renvoyer les enregistrements.
Par exemple
User.limit(3) #returns first three records
Il générera la requête SQL suivante.
"SELECT `users`.* FROM `users` LIMIT 3"
Comme offset n'est pas mentionné dans la requête ci-dessus, il retournera les trois premiers enregistrements.
User.limit(5).offset(30) #returns 5 records starting from 31th i.e from 31 to 35
Il générera la requête SQL suivante.
"SELECT `users`.* FROM `users` LIMIT 5 OFFSET 30"