Interfaz de consultas de Active Record

Anderson Sánchez
Academia Hack
Published in
8 min readJun 26, 2019

Agiliza tu tiempo de desarrollo con queries eficientes y naturales.

Bien sea que haz realizado consultas sencillas con lenguaje SQL para obtener registros en alguna base de datos relacional o tienes experiencia como administrador de base de datos complejas, al iniciar podría parecer extremadamente difícil aprender la sintaxis para seleccionar solo lo que requerías, o que tabla unir con cual para realizar la consulta necesaria.

En este artículo vamos a conocer la magia que realiza Ruby junto a su gema “ActiveRecord” para automatizar y facilitar el manejo de los registros en una base de datos relacional.

Image by Hawksky from Pixabay

Cuando ActiveRecord realiza el mapping de las clases que hereden de la clase ActiveRecord::Base, construye métodos de consulta comunes en simples métodos de clase. Los mismos queries SQL son realizados por ActiveRecord, pero los abstrae a métodos tales como find(), group(), select(), entre otros, en los cuales nosotros como desarrolladores no vemos lo que está sucediendo.

Te darás cuenta que podrás realizar las mismas operaciones de una manera mas rápida y con una sintaxis mucho mas natural. Yo mismo, sentí que era mucho mas fácil ya que requería escribir menos código además que dichos métodos eran nombrados de una manera mas intuitiva. Por ejemplo, si queremos obtener el libro con el id: 10, en lenguaje SQL seria así:

SELECT * FROM books WHERE (books.id = 10) LIMIT 1

Con la sintaxis del método find() de ActiveRecord seria:

Book.find(10)=> #<Book id: 10, title: "Civil Brand", author: "Sauncho Uglow">

El resultado es exactamente el mismo, obtienes el registro del libro con el id solicitado, con la ventaja que la sintaxis es mucho mas corta y fácil de entender.

Acabamos de utilizar un método de ActiveRecord para realizar un query sencillo que encuentre un registro en la tabla “Books” y con ello pudimos ver un poco de la magia en acción, sin embargo no se observó en el terminal el query que se ejecutó para obtener ese registro.

En el día a día como desarrollador web y sobretodo al trabajar con base de datos complejas y con tablas con muchas relaciones se hace necesario mostrar el query en la terminal. ¿Así que como podemos ver que esta sucediendo realmente?.

Clase Logger de ActiveRecord::Base

Para ayudar a los desarrolladores a ver los queries que realmente se están realizando fue creada la clase Logger. La utilizas de esta manera:

ActiveRecord::Base.logger = Logger.new(STDOUT)

Es importante siempre tratar de pensar en cuál sería el query SQL que se está realizando ya que esto ayuda a reforzar las habilidades SQL y te hace pensar en la consulta paso por paso.

Practicando consultas con ActiveRecord y Logger

Además de obtener solo un registro, al método find le puedes enviar un arreglo de ids, veamos el ejemplo y su SQL equivalente:

Book.find([1, 5])=> # SELECT "books".* FROM "books" WHERE "books"."id" IN (?, ?)  [["id", 1], ["id", 5]]=> [#<Book id: 1, title: "Chelsea on the Rocks", author: "Ellerey Woolacott">, #<Book id: 5, title: "French Fried Vacation 3", author: "Quint Gantzer">]

Como podemos ver, al ejecutar el método find se muestra el query SQL que se ejecutó, además del resultado obtenido, en esta ocasión, un Array con los 2 registros de la tabla “Books” que requeríamos.

Métodos esenciales para buscar registros en tu base de datos

  • take: Retorna un registro sin ningún orden implícito.
Book.take=> # SELECT * FROM books LIMIT 1=> #<Book:
id: 0,
title: "Forever Yours (Ikuisesti sinun)",
author: "Anabal Firbanks">
  • first: Retorna el primer registro ordenado por su clave primaria.
Book.first=> # SELECT * FROM books ORDER BY books.id ASC LIMIT 1=> #<Book:
id: 0,
title: "Forever Yours (Ikuisesti sinun)",
author: "Anabal Firbanks">
  • last: Retorna el ultimo registro ordenado por su clave primaria(por defecto).
Book.last=> # SELECT * FROM books ORDER BY books.id DESC LIMIT 1=> #<Book: id: 50, title: "Alien Abduction", author: "Kerry Bensen">

En una colección que este ordenada utilizando el método order, last va a retornar el ultimo registro ordenado por el atributo especificado.

Book.order(:title).last=> # SELECT * FROM clients ORDER BY clients.first_name DESC LIMIT 1=> #<Book:
id: 20,
title: "Vive L'Amour (Ai qing wan sui)",
author: "Orton Trever">
  • find_by: Retorna el primer registro encontrado que cumpla cierta condición.
Book.find_by(author: 'Susana Jeanesson')=> SELECT * FROM books WHERE (books.first_name = 'Susana Jeanesson') LIMIT 1=> #<Book: id: 2, title: "People, Places, Things", author: "Susana Jeanesson"

Búsquedas condicionales

El método where te permite especificar condiciones para limitar los registros retornados. Este método representa la parte WHERE de un query SQL.

Photo by Bruno Wolff on Unsplash
Book.where("title = ?", "Black Sheep")=> # SELECT "books".* FROM "books" WHERE (title = 'Black Sheep')=> [#<Book: id: 3, title: "Black Sheep", author: "Jourdan Leckenby"

Si quieres especificar múltiples condiciones

Detail.where("quantity > ? AND price > ?", 49, 10 )=> # SELECT "details".* FROM "details" WHERE (quantity > 47 AND price > 10)=> # [#<Detail: id: 41, quantity: 50, book_id: 15, price: 194,7>,      #<Detail: id: 45, quantity: 50, book_id: 39, price: 180,6>,      #<Detail: id: 50, quantity: 50, book_id: 6, price: 137,4>]

Para hacerlo incluso mas entendible, puedes utilizar las condiciones con un hash y el resultado será exactamente el mismo

Detail.where(quantity: 49, price: 10)

Al método where se le puede enviar un rango como condición

Detail.where(quantity: 25..29)=> # [#<Detail: id: 10, quantity: 29, book_id: 42, invoice_id: 22, price: 0.1294e2>, #<Detail: id: 24, quantity: 26, book_id: 1, invoice_id: 22, price: 0.1007e2>]

Búsquedas ordenadas

Como vimos anteriormente podemos utilizar el método order, te permite ordenar los registros, basándose en un parámetro indicado.

Photo by Héctor J. Rivas on Unsplash
Invoice.order(:date)=> # SELECT "invoices".* FROM "invoices" ORDER BY "invoices"."date" ASC=> #[#<Invoice: id: 42, date: Sat, 23 Jun 2018, is_paid: true, customer_id: 1>, #<Invoice: id: 18, date: Tue, 10 Jul 2018, is_paid: false, customer_id: 48>, #<Invoice: id: 39, date: Sat, 28 Jul 2018, is_paid: true, customer_id: 4>,

Seleccionando un atributo

Al realizar una búsqueda puedes indicar los atributos específicos que necesitas con el método select.

Photo by Artificial Photography on Unsplash
Invoice.select("date")=> # SELECT "invoices"."date" FROM "invoices"[#<Invoice: id: 0, date: Thu, 06 Sep 2018>, #<Invoice: id: 1, date: Sat, 19 Jan 2019>, #<Invoice: id: 2, date: Sun, 25 Nov 2018>]

Limitando la búsqueda

Con limit y offset puedes indicar un límite máximo de registros, así como indicar el numero de registros a ignorar antes de iniciar la búsqueda.

Photo by Ludovic Charlet on Unsplash
Country.limit(3).offset(10)=> SELECT  "countries".* FROM "countries" LIMIT ? OFFSET ?  [["LIMIT", 3], ["OFFSET", 10]=> [#<Country: id: 11, name: "Jamaica">,    #<Country: id: 12, name: "Brazil">,    #<Country: id: 13, name: "Chad">]

Agrupar y contar los registros

Para aplicar las clausulas SQL GROUP BY ó COUNT podemos utilizar los métodos group y count de ActiveRecord.

Photo by Perry Grone on Unsplash
Book.group(:status).count=>#  SELECT COUNT(*) AS count_all, “books”.”status” AS books_status FROM “books” GROUP BY “books”.”status”=> # {“available”=>18, “unavailable”=>15, “promotion”=>18}

Scopes

Los scopes te permiten especificar consultas de uso muy común, a las que se puede hacer referencia al invocarse. En ellos se pueden utilizar todos los métodos mencionados anteriormente.

Photo by Danny Avila on Unsplash
class Book < ActiveRecord::Base  scope :expensive, -> { where(price: 90..100) }end

Al realizar la invocación de ese scope definido en el modelo Book, sucede lo siguiente:

Book.expensive=> # SELECT "books".* FROM "books" WHERE "books"."price" BETWEEN ? AND ?  [["price", 90], ["price", 100]]=> # [#<Book: id: 3, title: "Black Sheep", author: "Jourdan Leckenby", price: 95>, #<Book: id: 6, title: "10 Mountains 10 Years", author: "Rheta Esposi", price: 97>, #<Book: id: 26, title: "Little Big Man", author: "Alberik Willerstone", price: 93>

Los scopes pueden recibir parámetros que modifiquen su funcionamiento

class Customer < ActiveRecord::Base  scope :defaulter, ->(debt) { where("debt > ?", debt) }end

Al realizar la invocación de ese scope definido en el modelo , sucede lo siguiente:

Customer.defaulter(950)=> # SELECT "customers".* FROM "customers" WHERE (debt > 950)[#<Customer:
id: 2,
name: "Patton Aynsley",
debt: 968>,
#<Customer:
id: 30,
name: "Lotti Blint",
debt: 995>,
#<Customer:
id: 31,
name: "Etty Tremblet",
debt: 989>

Enums

Los “macros” enum asigna a una columna entera de una tabla, un conjunto de valores enteros.

class Book < ActiveRecord::Base  enum status: [:available, :unavailable, :promotion]end

Automáticamente esto definirá algunos scopes correspondientes a cada una de las opciones del enum. Veamos su funcionamiento:

# Consultamos por el último libro registradobook = Book.last=> #<Book: id: 50, title: "Alien Abduction", author: "Kerry Bensen", price: 0.86e2, status: "available", publisher_id: 39># Podemos consultar si el libro esta en status: :available, al utilizar el scope que te crea el enum por defecto.book.available?=> true# Además podemos cambiar el status a cualquiera de las opciones del enum invocando una de las opciones con el "bang". A dicho libro se le actualizará su :status.book.unavailable!Book Update (0.3ms)  UPDATE "books" SET "status" = ? WHERE "books"."id" = ?  [["status", 2], ["id", 50]]=> true# Si volvemos a preguntar si posee el status: :available, obtenemos un false, ya que se actualizó previamentebook.available?=> false

Buscando por SQL

Si deseas utilizar tus propios queries SQL para encontrar registros de una tabla de tu base de datos, puedes utilizar el método find_by_sql.

Photo by Caspar Camille Rubin on Unsplash
Customer.find_by_sql("SELECT * FROM customers WHERE (customers.country_id = 40)")=> [#<Customer:
id: 2,
name: "Patton Aynsley",
country_id: 40>,
#<Customer:
id: 25,
name: "Eva Gullis",
country_id: 40>

Recomendaciones finales

Luego de conocer algunos de los métodos mas utilizados para realizar consultas a la base de datos con ActiveRecord te puedo recomendar que realices muchas prácticas para que logres reducir el tiempo de desarrollo de una aplicación además de consultar la documentación de Active Record Query Interface, donde encontrarás la utilidad, la sintaxis adecuada, y los argumentos de cada método de consulta.

--

--