Ruby on Rails
Interfaz de consulta ActiveRecord
Buscar..
Introducción
ActiveRecord es la M en MVC, que es la capa del sistema responsable de representar los datos y la lógica de negocios. La técnica que conecta los objetos ricos de una aplicación a las tablas en un sistema de administración de base de datos relacional es O bject R elational M apper ( ORM ).
ActiveRecord realizará consultas en la base de datos por usted y es compatible con la mayoría de los sistemas de bases de datos. Independientemente del sistema de base de datos que esté utilizando, el formato del método ActiveRecord siempre será el mismo.
.dónde
El método where
está disponible en cualquier modelo ActiveRecord
y permite consultar la base de datos para un conjunto de registros que coincidan con los criterios dados.
El método where
acepta un hash donde las claves corresponden a los nombres de columna en la tabla que representa el modelo.
Como ejemplo simple, usaremos el siguiente modelo:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
Para encontrar a todas las personas con el nombre de Sven
:
people = Person.where(first_name: 'Sven')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven'"
Para encontrar a todas las personas con el nombre de Sven
y el apellido 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'"
En el ejemplo anterior, la salida sql muestra que sólo los registros serán devueltos si tanto el first_name
y el last_name
partido.
consulta con condición OR
Para buscar registros con el primer nombre first_name == 'Bruce'
O el 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 una matriz
El método where
en cualquier modelo ActiveRecord se puede usar para generar SQL de la forma WHERE column_name IN (a, b, c, ...)
. Esto se logra pasando una matriz como argumento.
Como ejemplo simple, usaremos el siguiente modelo:
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 la matriz contiene un valor nil
, el SQL se modificará para verificar si la columna es 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"
Alcances
Los ámbitos actúan como filtros predefinidos en los modelos ActiveRecord
.
Un alcance se define utilizando el método de clase de scope
.
Como ejemplo simple, usaremos el siguiente modelo:
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
Los ámbitos se pueden llamar directamente desde la clase modelo:
minors = Person.minors
Los alcances pueden ser encadenados:
peters_children = Person.minors.with_last_name('Peters')
El método where
y otros métodos de tipo de consulta también se pueden encadenar:
mary_smith = Person.with_last_name('Smith').where(first_name: 'Mary')
Detrás de las escenas, los ámbitos son simplemente azúcar sintáctica para un método de clase estándar. Por ejemplo, estos métodos son funcionalmente idénticos:
scope :with_last_name, ->(name) { where(name: name) }
# This ^ is the same as this:
def self.with_last_name(name)
where(name: name)
end
Alcance predeterminado
en su modelo para establecer un alcance predeterminado para todas las operaciones en el modelo.
Hay una diferencia notable entre el
scope
método y un método de clase:scope
-definida alcances siempre devolverá unActiveRecord::Relation
, incluso si la lógica dentro devuelve nil. Los métodos de clase, sin embargo, no tienen dicha red de seguridad y pueden romper la capacidad de cadena si devuelven algo más.
donde no
where
cláusulas pueden ser negadas usando la sintaxis de 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')
Compatible con ActiveRecord 4.0 y versiones posteriores.
Ordenando
Puede ordenar los resultados de la consulta de ActiveRecord usando .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 no se especifica, el orden se realizará en orden ascendente. Puedes especificarlo haciendo:
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
también acepta una cadena, por lo que también podría hacer
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">]
Como la cadena es SQL sin formato, también puede especificar una tabla y no solo un atributo. Suponiendo que desea ordenar a los users
según el nombre de su role
, puede hacer esto:
Class User < ActiveRecord::Base
belongs_to :role
end
Class Role < ActiveRecord::Base
has_many :users
end
User.includes(:role).order("roles.name ASC")
El alcance del order
también puede aceptar un nodo Arel:
User.includes(:role).order(User.arel_table[:name].asc)
Métodos ActiveRecord Bang (!)
Si necesita un método ActiveRecord para generar una excepción en lugar de un valor false
en caso de fallo, ¡puede agregar !
a ellos Esto es muy importante. ¡Como algunas excepciones / fallas son difíciles de detectar si no las usa! en ellos. Recomendé hacer esto en su ciclo de desarrollo para escribir todo su código ActiveRecord de esta manera para ahorrarle tiempo y problemas.
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
Los métodos de ActiveRecord que aceptan un bang ( !
) Son:
-
.create!
-
.take!
-
.first!
-
.last!
-
.find_by!
-
.find_or_create_by!
-
#save!
-
#update!
- todos los buscadores dinámicos AR
.find_by
Puede encontrar registros por cualquier campo en su tabla usando find_by
.
Por lo tanto, si tiene un modelo de User
con un atributo first_name
, puede hacerlo:
User.find_by(first_name: "John")
#=> #<User id: 2005, first_name: "John", last_name: "Smith">
find_by
cuenta que find_by
no lanza ninguna excepción de forma predeterminada. Si el resultado es un conjunto vacío, devuelve nil
lugar de find
.
Si la excepción es necesaria puede usar find_by!
que plantea un error ActiveRecord::RecordNotFound
como find
.
.eliminar todos
Si necesita eliminar muchos registros rápidamente, ActiveRecord le da el método .delete_all
. para ser llamado directamente en un modelo, para eliminar todos los registros en esa tabla, o una colección. Sin embargo, .delete_all
cuidado, ya que .delete_all
no .delete_all
una instancia de ningún objeto, por lo tanto, no proporciona ninguna devolución de llamada ( before_*
y after_destroy
no se activan)
User.delete_all
#=> 39 <-- .delete_all return the number of rows deleted
User.where(name: "John").delete_all
ActiveRecord caso de búsqueda insensible
Si necesita buscar valores similares en un modelo ActiveRecord, es posible que tenga la tentación de usar LIKE
o ILIKE
pero esto no es portátil entre los motores de base de datos. Del mismo modo, recurrir siempre a la reducción o mejora de la situación puede crear problemas de rendimiento.
Puede utilizar Arel subyacente de ActiveRecord matches
método para hacer esto de una manera segura:
addresses = Address.arel_table
Address.where(addresses[:address].matches("%street%"))
Arel aplicará la construcción LIKE o ILIKE apropiada para el motor de base de datos configurado.
Obtener primer y último registro
Los rieles tienen una forma muy sencilla de obtener el first
y last
registro de la base de datos.
Para obtener el first
registro de la tabla de users
, debemos escribir el siguiente comando:
User.first
Se generará la siguiente consulta sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Y volverá el siguiente registro:
#<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 >
Para obtener el last
registro de la tabla de users
, debemos escribir el siguiente comando:
User.last
Se generará la siguiente consulta sql
:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
Y volverá el siguiente registro:
#<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 >
Pasar un entero al primer y último método crea una consulta LIMIT y devuelve una matriz de objetos.
User.first(5)
Se generará la siguiente consulta sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 5
Y
User.last(5)
Se generará la siguiente consulta sql
.
SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 5
.group y .count
Tenemos un modelo de Product
y queremos agruparlos por category
.
Product.select(:category).group(:category)
Esto consultará la base de datos de la siguiente manera:
SELECT "product"."category" FROM "product" GROUP BY "product"."category"
Asegúrese de que el campo agrupado también esté seleccionado. La agrupación es especialmente útil para contar la ocurrencia, en este caso, de las categories
.
Product.select(:category).group(:category).count
Como muestra la consulta, utilizará la base de datos para el conteo, que es mucho más eficiente, que recuperar todo el registro primero y hacer el conteo en el código:
SELECT COUNT("products"."category") AS count_categories, "products"."category" AS products_category FROM "products" GROUP BY "products"."category"
.distinto (o .uniq)
Si desea eliminar duplicados de un resultado, puede usar .distinct()
:
Customers.select(:country).distinct
Esto consulta la base de datos de la siguiente manera:
SELECT DISTINCT "customers"."country" FROM "customers"
.uniq()
tiene el mismo efecto. Con Rails 5.0 quedó en desuso y se eliminará de Rails con la versión 5.1. La razón es que la palabra unique
no tiene el mismo significado que distinta y puede ser engañosa. Además distinct
está más cerca de la sintaxis de SQL.
Se une
joins()
permite unir tablas a su modelo actual. Por ej.
User.joins(:posts)
producirá la siguiente consulta SQL:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""
Teniendo mesa unida, tendrás acceso a ella:
User.joins(:posts).where(posts: { title: "Hello world" })
Preste atención en forma plural. Si su relación es :has_many
, entonces el argumento joins()
debe estar pluralizado. De lo contrario, utilice singular.
Anidada joins
:
User.joins(posts: :images).where(images: { caption: 'First post' })
que producirá:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id""
Incluye
ActiveRecord con includes
garantiza que todas las asociaciones especificadas se carguen utilizando el número mínimo posible de consultas. Por lo tanto, al consultar datos de una tabla con una tabla asociada, ambas tablas se cargan en la 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 } )
cargará solo a los autores con condiciones en la memoria sin cargar libros . Utilice joins
cuando no se requiera información adicional sobre asociaciones anidadas.
@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
Límite y compensación
Puede usar el limit
para indicar el número de registros que se van a recuperar, y usar la offset
para indicar el número de registros que se deben omitir antes de comenzar a devolver los registros.
Por ejemplo
User.limit(3) #returns first three records
Se generará la siguiente consulta sql.
"SELECT `users`.* FROM `users` LIMIT 3"
Como el desplazamiento no se menciona en la consulta anterior, devolverá los primeros tres registros.
User.limit(5).offset(30) #returns 5 records starting from 31th i.e from 31 to 35
Se generará la siguiente consulta sql.
"SELECT `users`.* FROM `users` LIMIT 5 OFFSET 30"