Ruby on Rails
Interfaccia di query ActiveRecord
Ricerca…
introduzione
ActiveRecord è la M in MVC, che è il livello del sistema responsabile della rappresentazione dei dati e della logica aziendale. La tecnica che collega gli oggetti ricchi di un'applicazione alle tabelle in un sistema di gestione di database relazionali è O bject R elational M apper ( ORM ).
ActiveRecord eseguirà query sul database per te ed è compatibile con la maggior parte dei sistemi di database. Indipendentemente dal sistema di database che stai utilizzando, il formato del metodo ActiveRecord sarà sempre lo stesso.
.dove
Il metodo where
è disponibile su qualsiasi modello ActiveRecord
e consente di eseguire query sul database per un set di record che soddisfano i criteri specificati.
Il metodo where
accetta un hash in cui le chiavi corrispondono ai nomi delle colonne sulla tabella rappresentata dal modello.
Come semplice esempio, utilizzeremo il seguente modello:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
Per trovare tutte le persone con il nome di Sven
:
people = Person.where(first_name: 'Sven')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven'"
Per trovare tutte le persone con il nome di Sven
e il cognome di Schrodinger
:
people = Person.where(first_name: 'Sven', last_name: 'Schrodinger')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven' AND last_name='Schrodinger'"
Nell'esempio sopra, l'output sql mostra che i record verranno restituiti solo se sia il first_name
che il last_name
coincidono.
interrogare con la condizione OR
Per trovare i record con 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 con un array
Il metodo where
su qualsiasi modello di ActiveRecord può essere utilizzato per generare SQL del modulo WHERE column_name IN (a, b, c, ...)
. Questo risultato è ottenuto passando un array come argomento.
Come semplice esempio, utilizzeremo il seguente modello:
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')"
Se la matrice contiene un valore nil
, lo SQL verrà modificato per verificare se la colonna è 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"
Scopes
Gli ambiti agiscono come filtri predefiniti sui modelli ActiveRecord
.
Un ambito viene definito utilizzando il metodo della classe scope
.
Come semplice esempio, utilizzeremo il seguente modello:
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
Gli ambiti possono essere chiamati direttamente dalla classe del modello:
minors = Person.minors
Gli ambiti possono essere concatenati:
peters_children = Person.minors.with_last_name('Peters')
Il metodo where
e altri metodi di tipo query possono anche essere concatenati:
mary_smith = Person.with_last_name('Smith').where(first_name: 'Mary')
Dietro le quinte, gli ambiti sono semplicemente zucchero sintattico per un metodo di classe standard. Ad esempio, questi metodi sono funzionalmente identici:
scope :with_last_name, ->(name) { where(name: name) }
# This ^ is the same as this:
def self.with_last_name(name)
where(name: name)
end
Ambito di validità
nel modello per impostare un ambito predefinito per tutte le operazioni sul modello.
C'è una differenza notevole tra il metodo
scope
e un metodo di classe: gliscope
definiti da scope restituiranno sempre unActiveRecord::Relation
, anche se la logica all'interno restituisce nil. I metodi di classe, tuttavia, non hanno una simile rete di sicurezza e possono interrompere la chainability se restituiscono qualcos'altro.
where.not
where
clausole possono essere negate usando la sintassi 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')
Supportato da ActiveRecord 4.0 e versioni successive.
ordinazione
È possibile ordinare i risultati delle query di ActiveRecord utilizzando .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">]
Se non specificato, l'ordine verrà eseguito in ordine crescente. Puoi specificarlo facendo:
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
accetta anche una stringa, quindi puoi anche fare
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">]
Poiché la stringa è SQL raw, puoi anche specificare una tabella e non solo un attributo. Supponendo che tu voglia ordinare gli users
base al loro nome di role
, puoi farlo:
Class User < ActiveRecord::Base
belongs_to :role
end
Class Role < ActiveRecord::Base
has_many :users
end
User.includes(:role).order("roles.name ASC")
L'ambito order
può accettare anche un nodo Arel:
User.includes(:role).order(User.arel_table[:name].asc)
ActiveRecord Bang (!) Metodi
Se è necessario un metodo ActiveRecord per generare un'eccezione anziché un valore false
in caso di errore, è possibile aggiungere !
a loro. Questo è molto importante. Dato che alcune eccezioni / fallimenti sono difficili da catturare se non le usi! su di essi. Ho raccomandato di farlo nel tuo ciclo di sviluppo per scrivere tutto il tuo codice ActiveRecord in questo modo per farti risparmiare tempo e fatica.
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
I metodi ActiveRecord che accettano un botto ( !
) Sono:
-
.create!
-
.take!
-
.first!
-
.last!
-
.find_by!
-
.find_or_create_by!
-
#save!
-
#update!
- tutti i cercatori dinamici AR
.find_by
Puoi trovare i record di qualsiasi campo nella tua tabella usando find_by
.
Quindi, se hai un modello User
con un first_name
puoi fare:
User.find_by(first_name: "John")
#=> #<User id: 2005, first_name: "John", last_name: "Smith">
find_by
che find_by
non genera eccezioni per impostazione predefinita. Se il risultato è un set vuoto, restituisce nil
invece di find
.
Se è necessaria l'eccezione, puoi utilizzare find_by!
che solleva un errore ActiveRecord::RecordNotFound
come find
.
.cancella tutto
Se è necessario eliminare rapidamente molti record, ActiveRecord fornisce il metodo .delete_all
. per essere chiamato direttamente su un modello, per cancellare tutti i record in quella tabella o una raccolta. Attenzione però, dato che .delete_all
non istanzia nessun oggetto, quindi non fornisce alcuna callback ( before_*
e after_destroy
non vengono attivati).
User.delete_all
#=> 39 <-- .delete_all return the number of rows deleted
User.where(name: "John").delete_all
ActiveRecord ricerca tra maiuscole e minuscole
Se è necessario cercare un modello di ActiveRecord per valori simili, si potrebbe essere tentati di utilizzare LIKE
o ILIKE
ma questo non è portabile tra i motori di database. Allo stesso modo, il ricorso a downcasing o upcasing sempre in grado di creare problemi di prestazioni.
È possibile utilizzare il metodo di matches
Arel sottostante di ActiveRecord per farlo in modo sicuro:
addresses = Address.arel_table
Address.where(addresses[:address].matches("%street%"))
Arel applicherà il costrutto LIKE o ILIKE appropriato per il motore di database configurato.
Ricevi il primo e l'ultimo record
Le rotaie hanno un modo molto semplice per ottenere il first
e l' last
record dal database.
Per ottenere il first
record dalla tabella degli users
, dobbiamo digitare il seguente comando:
User.first
sql
seguente query sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
E restituirà il seguente record:
#<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 >
Per ottenere l' last
record dalla tabella degli users
, dobbiamo digitare il seguente comando:
User.last
sql
seguente query sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
E restituirà il seguente record:
#<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 >
Il passaggio di un intero al primo e all'ultimo metodo crea una query LIMIT e restituisce una matrice di oggetti.
User.first(5)
sql
seguente query sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 5
E
User.last(5)
sql
seguente query sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 5
.group e .count
Abbiamo un modello di Product
e vogliamo raggrupparli per category
.
Product.select(:category).group(:category)
Questo interrogherà il database come segue:
SELECT "product"."category" FROM "product" GROUP BY "product"."category"
Assicurati che anche il campo raggruppato sia selezionato. Il raggruppamento è particolarmente utile per il conteggio dell'occorrenza - in questo caso - delle categories
.
Product.select(:category).group(:category).count
Come mostra la query, userà il database per il conteggio, che è molto più efficiente, rispetto al recupero di tutti i record e al conteggio nel codice:
SELECT COUNT("products"."category") AS count_categories, "products"."category" AS products_category FROM "products" GROUP BY "products"."category"
.distinct (o .uniq)
Se vuoi rimuovere i duplicati da un risultato, puoi usare .distinct()
:
Customers.select(:country).distinct
Questa query il database come segue:
SELECT DISTINCT "customers"."country" FROM "customers"
.uniq()
ha lo stesso effetto. Con Rails 5.0 è stato deprecato e verrà rimosso da Rails con la versione 5.1. Il motivo è che la parola unique
non ha lo stesso significato di distinta e può essere fuorviante. Inoltre distinct
è più vicino alla sintassi SQL.
Si unisce
joins()
ti permette di unire le tabelle al tuo modello attuale. Per es.
User.joins(:posts)
produrrà la seguente query SQL:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""
Avendo unito la tabella, avrai accesso ad essa:
User.joins(:posts).where(posts: { title: "Hello world" })
Fai attenzione alla forma plurale. Se la tua relazione è :has_many
, allora l'argomento :has_many
joins()
dovrebbe essere pluralizzato. Altrimenti, usa il singolare.
Nidificati joins
:
User.joins(posts: :images).where(images: { caption: 'First post' })
che produrrà:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id""
include
ActiveRecord with includes
assicura che tutte le associazioni specificate vengano caricate utilizzando il numero minimo possibile di query. Pertanto, quando si esegue una query su una tabella per i dati con una tabella associata, entrambe le tabelle vengono caricate in memoria.
@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 } )
caricherà solo gli autori con condizioni in memoria senza caricare libri . Utilizza i joins
quando non sono richieste informazioni aggiuntive sulle associazioni nidificate.
@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 e offset
È possibile utilizzare il limit
per indicare il numero di record da recuperare e utilizzare l' offset
per indicare il numero di record da saltare prima di iniziare a restituire i record.
Per esempio
User.limit(3) #returns first three records
Genererà la seguente query sql.
"SELECT `users`.* FROM `users` LIMIT 3"
Poiché l'offset non è menzionato nella query precedente, restituirà i primi tre record.
User.limit(5).offset(30) #returns 5 records starting from 31th i.e from 31 to 35
Genererà la seguente query sql.
"SELECT `users`.* FROM `users` LIMIT 5 OFFSET 30"