Advertisement
  1. Code
  2. Ruby on Rails

Consultas en Rails, parte 2

by
Read Time:14 minsLanguages:

Spanish (Español) translation by Esther (you can also view the original English article)

En este segundo artículo profundizaremos un poco más en las consultas de Active Record en Rails. En caso de que aún no conozcas el lenguaje SQL, añadiré ejemplos lo suficientemente sencillos como para que puedas ir aprendiendo la sintaxis conforme avancemos.

Dicho esto, definitivamente ayudaría si repasaras un rápido tutorial de SQL antes de volver a seguir leyendo. De lo contrario, tómate tu tiempo para entender las consultas SQL que utilizamos, y espero que al final de esta serie ya no te sientas intimidado.

La mayor parte es muy sencilla, pero la sintaxis es un poco extraña si acabas de empezar a codificar, especialmente en Ruby. Aguanta, ¡no es ninguna ciencia espacial!

Temas

  • Includes & Eager Loading
  • Unión de Tablas
  • Carga rápida
  • Indicadores
  • Agregados
  • Buscadores dinámicos
  • Campos específicos
  • SQL personalizado

Includes & Eager Loading

Estas consultas incluyen más de una tabla de la base de datos con la que trabajar y podrían ser las más importantes para sacar de este artículo. Se reduce a lo siguiente: en lugar de hacer múltiples consultas para la información que está repartida en varias tablas, includes trata de mantenerlas al mínimo. El concepto clave detrás de esto se llama "eager loading" y significa que estamos cargando objetos asociados cuando hacemos una búsqueda.

Si lo hiciéramos iterando sobre una colección de objetos y luego intentando acceder a sus registros asociados desde otra tabla, nos encontraríamos con un problema que se denomina "problema de la consulta N + 1". Por ejemplo, para cada agent.handler en una colección de agentes, lanzaríamos consultas separadas para ambos agentes y sus handlers. Eso es lo que tenemos que evitar, ya que esto no es escalable en absoluto. En su lugar, hacemos lo siguiente:

Rieles

Si ahora iteramos sobre dicha colección de agentes, descontando que por ahora no hemos limitado el número de registros devueltos, terminaremos con dos consultas en lugar de posiblemente un gazillón.

SQL

Este agente de la lista tiene dos manejadores, y cuando ahora pedimos al objeto agente sus manejadores, no es necesario lanzar ninguna consulta adicional a la base de datos. Podemos llevar esto un paso más allá, por supuesto, y cargar ansiosamente múltiples registros de tablas asociadas. Si necesitáramos cargar no solo los manejadores sino también las misiones asociadas del agente por cualquier razón, podríamos usar includes como este.

Rieles

¡Es muy sencillo! Solo hay que tener cuidado con el uso de las versiones singular y plural para los includes. Dependen de las asociaciones de tu modelo. Una asociación has_many utiliza el plural, mientras que una belongs_to o una has_one necesita la versión singular, por supuesto. Si lo necesitas, también puedes meter una cláusula where para especificar condiciones adicionales, pero la forma preferida de especificar condiciones para las tablas asociadas que se cargan de forma ansiosa es utilizando joins en su lugar.

Una cosa a tener en cuenta sobre la carga ansiosa es que los datos que se añadirán serán enviados de vuelta en su totalidad a Active Record, que a su vez construye objetos Ruby incluyendo estos atributos. Esto contrasta con la unión "simple" de los datos, en la que obtendrás un resultado virtual que podrás utilizar para realizar cálculos, por ejemplo, y que consumirá menos memoria que los includes.

Tablas de unión

La unión de tablas es otra herramienta que permite evitar el envío de demasiadas consultas innecesarias. Un escenario común es unir dos tablas con una sola consulta que devuelva algún tipo de registro combinado. joins es otro método de búsqueda de Active Record que te permite, en términos de SQL, UNIR tablas. Estas consultas pueden devolver registros combinados de varias tablas, y se obtiene una tabla virtual que combina registros de estas tablas. Esto es bastante radical cuando se compara con el lanzamiento de todo tipo de consultas para cada tabla en su lugar. Hay varios tipos de superposición de datos que se pueden obtener con este enfoque.

La unión interna es el modus operandi por defecto para las uniones. Coincide con todos los resultados que coinciden con un determinado id y su representación como clave ajena de otro objeto o tabla. En el ejemplo siguiente, dicho de forma sencilla: dame todas las misiones en las que el id de la misión aparezca como mission_id en la tabla de un agente. "agentes". "mission_id" = "misiones". "id". Las uniones internas excluyen las relaciones que no existen.

Rieles

SQL

Así que estamos emparejando las misiones y los agentes que las acompañan, ¡en una sola consulta! Claro, podríamos obtener primero las misiones, iterar sobre ellas una por una y preguntar por sus agentes. Pero entonces volveríamos a nuestro terrible "problema de la consulta N + 1". ¡No, gracias!

Lo bueno de este enfoque es que no obtendremos ningún caso nulo con las uniones internas; solo obtendremos los registros devueltos que coincidan con sus identificadores en las claves externas de las tablas asociadas. Si necesitamos encontrar misiones, por ejemplo, que carezcan de agentes, necesitaríamos un outer join en su lugar. Como esto implica escribir tu propio SQL OUTER JOIN, lo veremos en el último artículo. Volviendo a las uniones estándar, también puede unir múltiples tablas asociadas, por supuesto.

Rieles

Y puedes añadir a éstas algunas cláusulas where para especificar aún más tus buscadores. A continuación, estamos buscando solo las misiones que son ejecutadas por James Bond y solo los agentes que pertenecen a la misión 'Moonraker' en el segundo ejemplo.

SQL

Rieles

SQL

Con las joins, también hay que prestar atención al uso singular y plural de las asociaciones de los modelos. Como mi clase Mission has_many :agents, podemos usar el plural. Por otro lado, para la clase Agent belongs_to :mission, solo la versión singular funciona sin reventar. Pequeño detalle importante: la parte del where es más simple. Dado que estás buscando varias filas en la tabla que cumplan una determinada condición, la forma plural siempre tiene sentido.

Ámbitos

Los ámbitos son una forma práctica de extraer las necesidades comunes de consulta en métodos propios bien nombrados. De esta manera son un poco más fáciles de pasar y también posiblemente más fácil de entender si otros tienen que trabajar con tu código o si necesitas volver a ciertas consultas en el futuro. Puedes definirlos para los modelos individuales, pero utilízalos también para sus asociaciones.

El cielo es el límite realmente: ¡las joins, los includes y los where son todo un juego! Como los ámbitos también devuelven objetos ActiveRecord::Relations, puedes encadenarlos y llamar a otros ámbitos sobre ellos sin dudarlo. Extraer ámbitos así y encadenarlos para realizar consultas más complejas es muy útil y hace que las más largas sean más legibles. Los ámbitos se definen mediante la sintaxis "stabby lambda":

Rieles

SQL

Como puedes ver en el ejemplo anterior, encontrar a James Bond es mucho más agradable cuando puedes encadenar ámbitos. De esta manera, puedes mezclar y combinar varias consultas y permanecer DRY al mismo tiempo. Si necesitas ámbitos mediante asociaciones, también están a tu disposición:

También puedes redefinir el default_scope para cuando estés mirando algo como Mission.all.

SQL

Agregados

Esta sección no es tan avanzada en términos de la comprensión que implica, pero los necesitará más a menudo en escenarios que pueden considerarse un poco más avanzados que su buscador promedio como .all, .first, .find_by_id o lo que sea. El filtrado basado en cálculos básicos, por ejemplo, es probablemente algo con lo que los novatos no entran en contacto de inmediato. ¿Qué es lo que buscamos exactamente aquí?

  • suma
  • cuenta
  • mínimo
  • máximo
  • promedio

Es fácil, ¿verdad? Lo bueno es que en lugar de recorrer una colección de objetos devueltos para hacer estos cálculos, podemos dejar que Active Record haga todo este trabajo por nosotros y devuelva estos resultados con las consultas, preferiblemente en una sola consulta. Bonito, ¿no?

  • cuenta

Rieles

SQL

  • promedio

Rieles

SQL

Como ahora sabemos cómo podemos hacer uso de las uniones, podemos dar un paso más y pedir solo la media de aparatos que tienen los agentes en una misión concreta, por ejemplo.

Rieles

SQL

La agrupación de este número medio de aparatos por los nombres de las misiones se convierte en algo trivial en ese momento. Ver más sobre la agrupación a continuación:

Rieles

SQL

  • suma

Rieles

SQL

  • máximo

Rieles

SQL

  • mínimo

Rieles

SQL

¡Atención!

Todos estos métodos de agregación no te permiten encadenar otras cosas, son terminales. El orden es importante para hacer los cálculos. No obtenemos un objeto ActiveRecord::Relation de vuelta de estas operaciones, lo que hace que la música se detenga en ese punto, sino que obtenemos un hash o números. Los ejemplos siguientes no funcionarán:

Rieles

Agrupado

Si quieres que los cálculos se desglosen y ordenen en grupos lógicos, debes hacer uso de una cláusula GROUP y no hacer esto en Ruby. Lo que quiero decir es que debes evitar iterar sobre un grupo que produce potencialmente toneladas de consultas.

Rieles

SQL

Este ejemplo encuentra todos los agentes que están agrupados en una misión concreta y devuelve un hash con el número medio calculado de gadgets como valores, ¡en una sola consulta! ¡Sí! Lo mismo ocurre con los demás cálculos, por supuesto. En este caso, realmente tiene más sentido dejar que SQL haga el trabajo. El número de consultas que disparamos para estas agregaciones es demasiado importante.

Buscadores dinámicos

Para cada atributo de tus modelos, por ejemplo, nameemail_addressfavorite_gadget, etc., Active Record te permite utilizar métodos de búsqueda muy legibles que se crean dinámicamente para ti. Suena críptico, lo sé, pero no significa nada más que find_by_id o find_by_favorite_gadget. La parte de find_by es estándar, y Active Record solo pone el nombre del atributo por ti. Incluso puedes llegar a añadir un ! si quieres que el buscador lance un error si no se encuentra nada. Lo mejor es que puedes encadenar estos métodos dinámicos de búsqueda. Así:

Rieles

SQL

Por supuesto que puedes volverte loco con esto, pero creo que pierde su encanto y utilidad si vas más allá de dos atributos:

Rieles

SQL

En este ejemplo, sin embargo, es agradable ver cómo funciona bajo el capó. Cada nuevo _and_ añade un operador SQL AND para unir lógicamente los atributos. En general, la principal ventaja de los buscadores dinámicos es la facilidad de lectura; sin embargo, si se utilizan demasiados atributos dinámicos, esa ventaja se pierde rápidamente. Rara vez lo utilizo, quizá sobre todo cuando juego con la consola, pero sin duda es bueno saber que Rails ofrece este pequeño truco.

Campos específicos

Active Record te da la opción de devolver objetos un poco más centrados en los atributos que llevan. Normalmente, si no se especifica lo contrario, la consulta pedirá todos los campos de una fila mediante * (SELECT "agentes".*), y luego Active Record construye objetos Ruby con el conjunto completo de atributos. Sin embargo, puedes seleccionar solo campos específicos que deben ser devueltos por la consulta y limitar el número de atributos que tus objetos Ruby deben "llevar".

Rieles

SQL

Rieles

SQL

Como puedes ver, los objetos devueltos solo tendrán los atributos seleccionados, además de sus identificadores, por supuesto, lo que se da con cualquier objeto. No importa si se utilizan cadenas, como en el caso anterior, o símbolos: la consulta será la misma.

Rieles

Unas palabras de precaución: Si intentas acceder a atributos del objeto que no has seleccionado en tus consultas, recibirás un MissingAttributeError. Sin embargo, como el id se te proporcionará automáticamente, puedes pedir el id sin seleccionarlo.

SQL personalizado

Por último, pero no menos importante, puedes escribir tu propio SQL personalizado a través de find_by_sql. Si tienes suficiente confianza en tu propio SQL-Fu y necesitas algunas llamadas personalizadas a la base de datos, este método puede ser muy útil en ocasiones. Pero esta es otra historia. Simplemente no olvides comprobar primero los métodos de envoltura de Active Record y evitar reinventar la rueda donde Rails trata de encontrarte más de la mitad del camino.

Rieles

Como es lógico, esto se traduce en:

SQL

Dado que los ámbitos y los métodos de tu propia clase pueden usarse indistintamente para tus necesidades de buscador personalizado, podemos llevar esto un paso más allá para consultas SQL más complejas.

Rieles

Podemos escribir métodos de clase que encapsulen el SQL dentro de un documento Here. Esto nos permite escribir cadenas de varias líneas de una manera muy legible y luego almacenar esa cadena SQL dentro de una variable que podemos reutilizar y pasar a find_by_sql. De esta manera no se revisten toneladas de código de consulta dentro de la llamada al método. Si tienes más de un lugar para usar esta consulta, también es DRY.

Como se supone que esto es un tutorial para novatos y no un tutorial de SQL en sí, he mantenido el ejemplo muy minimalista por una razón. Sin embargo, la técnica para consultas mucho más complejas es la misma. Es fácil imaginar una consulta SQL personalizada que se extienda más allá de diez líneas de código.

Vuélvete todo lo loco que necesites, ¡razonablemente! Puede ser un salvavidas. Unas palabras sobre la sintaxis. La parte SQL es solo un identificador para marcar el principio y el final de la cadena. Apuesto a que no necesitarás mucho este método, ¡esperemos! Definitivamente tiene su lugar, y la tierra de Rails no sería lo mismo sin él, en los raros casos en los que querrás afinar tu propio SQL con él.

Reflexiones finales

Espero que te sientas un poco más cómodo escribiendo consultas y leyendo el temido SQL en bruto. La mayoría de los temas que cubrimos en este artículo son esenciales para escribir consultas que tratan con una lógica de negocio más compleja. Tómate tu tiempo para entenderlos y juega un poco con las consultas en la consola.

Estoy bastante seguro de que cuando dejes atrás la tierra de los tutoriales, tarde o temprano tu credibilidad en Rails aumentará significativamente si trabajas en tus primeros proyectos de la vida real y necesitas elaborar tus propias consultas personalizadas. Si todavía te da un poco de reparo el tema, te diría que simplemente te diviertas con él, ¡no es ninguna ciencia espacial!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.