Advertisement
  1. Code
  2. Ruby on Rails

Consultas en Rails, Parte 3

by
Read Time:14 minsLanguages:

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

En este último artículo, vamos a profundizar un poco más en las consultas y jugar con algunos escenarios más avanzados. Cubriremos las relaciones de los modelos de Active Record un poco más en este artículo, pero me mantendré alejado de los ejemplos que podrían ser demasiado confusos para los novatos en programación. Antes de avanzar, cosas como el ejemplo de abajo no deben causar ninguna confusión:

Si eres nuevo en las consultas de Active Record y en SQL, te recomiendo que eches un vistazo a mis dos artículos anteriores antes de continuar. Este podría ser difícil de tragar sin los conocimientos que estaba construyendo hasta ahora. Depende de ti, por supuesto. Por otro lado, este artículo no va a ser tan largo como los otros si solo quieres ver estos casos de uso ligeramente avanzados. ¡Vamos a profundizar!

Temas

  • Ámbitos y asociaciones
  • Las uniones más delgadas
  • Fusionar
  • has_many (tiene_muchos)
  • Uniones personalizadas

Ámbitos y asociaciones

Repitámoslo. Podemos consultar los modelos de Active Record de inmediato, pero las asociaciones también se pueden consultar, y podemos encadenar todas estas cosas. Hasta aquí todo bien. Podemos empaquetar los buscadores en ámbitos ordenados y reutilizables en sus modelos también, y he mencionado brevemente su similitud con los métodos de clase.

Rieles

Así que también puedes empaquetarlos en tus propios métodos de clase y acabar con ello. Los ámbitos no son dudosos ni nada, creo, aunque la gente los menciona como algo mágico aquí y allá, pero como los métodos de clase logran lo mismo, yo optaría por eso.

Rieles

Estos métodos de clase se leen igual, y no necesitas apuñalar a nadie con un lambda. Lo que sea que funcione mejor para ti o para tu equipo; depende de ti qué API quieres usar. Pero no los mezcles y los combines, quédate con una sola opción. Ambas versiones te permiten encadenar fácilmente estos métodos dentro de otro método de la clase, por ejemplo:

Rieles

Rieles

Vamos a dar un pequeño paso más allá, quédate conmigo. Podemos utilizar un lambda en las propias asociaciones para definir un determinado ámbito. Parece un poco raro al principio, pero pueden ser muy útiles. Esto hace posible llamar a estas lambdas directamente en tus asociaciones.

Esto es muy bonito y muy legible con un encadenamiento de métodos más corto. Sin embargo, hay que tener cuidado con el acoplamiento de estos modelos.

Rieles

Dime que esto no es genial de alguna manera. No es para el uso diario, pero supongo que es bueno tenerlo. Así que aquí la Mission puede "solicitar" solo agentes que tengan la licencia para matar.

Unas palabras sobre la sintaxis, ya que nos alejamos de las convenciones de nomenclatura y utilizamos algo más expresivo como double_o_agents. Tenemos que mencionar el nombre de la clase para no confundir a Rails, que de otro modo podría esperar buscar una clase DoubleOAgent. Por supuesto, podemos tener ambas asociaciones de Agent, la habitual y la personalizada, y Rails no se quejará.

Rieles

Uniones más delgadas

Cuando consultas la base de datos en busca de registros y no necesitas todos los datos, puedes optar por especificar qué es exactamente lo que quieres que se te devuelva. ¿Por qué? Porque los datos devueltos a Active Record acabarán siendo incorporados a nuevos objetos Ruby. Veamos una estrategia sencilla para evitar la sobrecarga de memoria en tu aplicación Rails:

Rieles

Rieles

SQL

Así, esta consulta devuelve una lista de agentes con una misión de la base de datos a Active Record, que a su vez se dispone a construir objetos Ruby a partir de ella. Los datos de mission están disponibles ya que los datos de estas filas se unen a las filas de los datos del agente. Esto significa que los datos unidos están disponibles durante la consulta, pero no serán devueltos a Active Record. Por lo tanto, tendrá estos datos para realizar cálculos, por ejemplo.

Es especialmente interesante porque puedes hacer uso de datos que no se envían de vuelta a tu aplicación. Menos atributos que deban incorporarse a los objetos Ruby, que ocupan memoria, pueden ser una gran ventaja. En general, piensa en devolver solo las filas y columnas absolutamente necesarias que necesites. De esta manera, se puede evitar bastante el bloat.

Rieles

Solo un pequeño apunte sobre la sintaxis aquí: como no estamos consultando la tabla Agent a través de where, sino la tabla :mission unida, tenemos que especificar que estamos buscando missions específicas en nuestra cláusula WHERE.

SQL

El uso de includes aquí también devolvería las misiones a Active Record para la carga ansiosa y ocuparía memoria construyendo objetos Ruby.

Fusionar

Una fusión resulta útil, por ejemplo, cuando queremos combinar una consulta sobre agentes y sus misiones asociadas que tienen un alcance específico definido por usted. Podemos tomar dos objetos ActiveRecord::Relation y fusionar sus condiciones. Claro, no es nada del otro mundo, pero la merge es útil si quieres usar un determinado ámbito mientras usas una asociación.

En otras palabras, lo que podemos hacer con merge es filtrar por un ámbito con nombre en el modelo unido. En uno de los ejemplos anteriores, utilizamos métodos de clase para definir nosotros mismos dichos ámbitos con nombre.

Rieles

Rieles

SQL

Cuando encapsulamos lo que es una misión peligrosa dentro del modelo Mission, podemos meterlo en un join vía merge de esta manera. Así que mover la lógica de tales condiciones al modelo relevante donde pertenece es, por un lado, una buena técnica para lograr un acoplamiento más suelto, ya que no queremos que nuestros modelos de Active Record conozcan muchos detalles sobre los demás, y por otro lado, te da una buena API en tus uniones sin que te explote en la cara. El ejemplo de abajo sin fusión no funcionaría sin un error:

Rieles

SQL

Cuando ahora fusionamos un objeto ActiveRecord::Relation para nuestras misiones en nuestros agentes, la base de datos no sabe de qué misiones estamos hablando. Tenemos que dejar claro qué asociación necesitamos y unir primero los datos de la misión, o SQL se confundirá. Una última guinda. Podemos encapsular esto aún mejor involucrando también a los agentes:

Rieles

Rieles

SQL

En mi opinión, eso es una dulce cereza. Encapsulación, OOP adecuado, y gran legibilidad. ¡Genial!

has_many

Arriba hemos visto mucho la asociación belongs_to en acción. Echemos un vistazo a esto desde otra perspectiva e introduzcamos las secciones de servicios secretos en la mezcla:

Rieles

Así que en este escenario, los agentes no solo tendrían un mission_id sino también un section_id. Hasta aquí todo bien. Busquemos todas las secciones con agentes con una misión específica, es decir, las secciones que tienen algún tipo de misión.

Rieles

SQL

¿Te has dado cuenta de algo? Un pequeño detalle es diferente. Las claves extranjeras están invertidas. Aquí estamos solicitando una lista de secciones pero utilizamos claves foráneas como esta "agents". "section_id" = " sections. "id". En otras palabras, estamos buscando una clave foránea de una tabla que estamos uniendo.

Rieles

SQL

Anteriormente nuestras uniones a través de una asociación belongs_to tenían este aspecto: las claves foráneas se reflejaban ("missions". "id" = "agents". "mission_id") y buscaban la clave foránea de la tabla que estamos iniciando.

Volviendo a tu escenario has_many, ahora obtendríamos una lista de secciones que se repiten porque tienen varios agentes en cada sección, por supuesto. Así que para cada columna de agente que se une, obtenemos una fila para esta sección o section_id, en resumen, estamos duplicando filas básicamente. Para hacer esto aún más vertiginoso, vamos a traer las misiones en la mezcla también.

Rieles

SQL

Mira las dos partes de INNER JOIN. ¿Sigues conmigo? Estamos "llegando" a través de los agentes a sus misiones desde la sección de agentes. Sí, cosas para los dolores de cabeza de la diversión, lo sé. Lo que obtenemos son misiones que indirectamente se asocian a una determinada sección.

Como resultado, obtenemos nuevas columnas unidas, pero el número de filas sigue siendo el mismo que devuelve esta consulta. Lo que se devuelve a Active Record, que da lugar a la creación de nuevos objetos Ruby, sigue siendo la lista de secciones. Así que cuando tenemos varias misiones en marcha con varios agentes, obtendríamos de nuevo filas duplicadas para nuestra sección. Vamos a filtrar esto un poco más:

Rieles

SQL

Ahora solo se devuelven las secciones que participan en misiones en las que Ernst Stavro Blofeld es el enemigo implicado. Cosmopolitas como algunos supervillanos podrían pensar de sí mismos, podrían operar en más de una sección, por ejemplo la sección A y la C, los Estados Unidos y Canadá respectivamente.

Si tenemos varios agentes en una sección determinada que están trabajando en la misma misión para detener a Blofeld o lo que sea, tendríamos de nuevo filas repetidas devueltas en Active Record. Seamos un poco más claros al respecto:

Rieles

SQL

Lo que esto nos da es el número de secciones en las que opera Blofeld, que se conocen, y que tienen agentes activos en misiones con él como enemigo. Como paso final, vamos a hacer algo de refactorización de nuevo. Extraemos esto en un bonito "pequeño" método de clase en class Section:

Rieles

Puedes refactorizar esto aún más y dividir las responsabilidades para lograr un acoplamiento más flojo, pero sigamos por ahora.

Uniones personalizadas

La mayor parte del tiempo puedes confiar en que Active Record escriba el SQL que quieras por ti. Eso significa que te quedas en la tierra de Ruby y no necesitas preocuparte demasiado por los detalles de la base de datos. Pero a veces es necesario hacer un agujero en el terreno de SQL y hacer lo propio. Por ejemplo, si necesitas utilizar un LEFT join y salirte del comportamiento habitual de Active Record de hacer un INNER join por defecto. joins es una pequeña ventana para escribir tu propio SQL personalizado si es necesario. La abres, introduces tu código de consulta personalizado, cierras la "ventana" y puedes seguir añadiendo métodos de consulta de Active Record.

Demostrémoslo con un ejemplo que tiene que ver con los gadgets. Digamos que un agente típico suele has_many artilugios, y queremos encontrar agentes que no estén equipados con ningún artilugio de lujo que les ayude en el campo. Un join habitual no daría buenos resultados, ya que en realidad nos interesan los valores nil, o null en lenguaje SQL, de estos juguetes espía.

Rieles

Cuando hacemos una operación de joins, obtendremos sólo los agentes devueltos que ya están equipados con gadgets porque el agent_id de estos gadgets no es nulo. Este es el comportamiento esperado de un inner join por defecto. El inner join se basa en una coincidencia en ambos lados y devuelve solo las filas de datos que coinciden con esta condición. Un gadget inexistente con un valor nil para un agente que no lleva ningún gadget no coincide con ese criterio.

Rieles

SQL

En cambio, nosotros buscamos a los agentes de la chusma que necesitan urgentemente un poco de amor del intendente. Tu primera suposición podría ser la siguiente:

Rieles

SQL

No está mal, pero como se puede ver en la salida de SQL, no sigue el juego y sigue insistiendo en el INNER JOIN por defecto. Este es un escenario en el que necesitamos un OUTER join, porque falta un lado de nuestra "ecuación", por así decirlo. Estamos buscando resultados para aparatos que no existen; más exactamente, para agentes sin aparatos.

Hasta ahora, cuando hemos pasado un símbolo a Active Record en un join, éste esperaba una asociación. En cambio, si le pasamos una cadena, espera que sea un fragmento real de código SQL, una parte de la consulta.

Rieles

SQL

O, si tienes curiosidad por los agentes perezosos sin misiones, que posiblemente estén en Barbados o donde sea, nuestra unión personalizada tendría este aspecto:

Rieles

SQL

La unión externa es la versión de unión más inclusiva, ya que coincidirá con todos los registros de las tablas unidas, incluso si algunas de estas relaciones aún no existen. Debido a que este enfoque no es tan exclusivo como las uniones internas, obtendrás un montón de nils aquí y allá. Esto puede ser informativo en algunos casos, por supuesto, pero las uniones internas suelen ser lo que buscamos. Rails 5 nos permitirá utilizar un método especializado llamado left_outer_joins para estos casos. ¡Por último!

Una pequeña cosa para el camino: mantén estos agujeros de espionaje en la tierra de SQL tan pequeños como sea posible. Le harás un gran favor a todo el mundo, incluido tu futuro yo.

Reflexiones finales

Conseguir que Active Record escriba un SQL eficiente para ti es una de las principales habilidades que debes sacar de esta miniserie para principiantes. De esta manera, también obtendrás un código que es compatible con cualquier base de datos que admita, lo que significa que las consultas serán estables en todas las bases de datos. Es necesario que entiendas no solo cómo jugar con Active Record sino también el SQL subyacente, que es de igual importancia.

Sí, SQL puede ser aburrido, tedioso de leer y no tener un aspecto elegante, pero no hay que olvidar que Rails envuelve a Active Record con SQL y no hay que descuidar la comprensión de esta pieza vital de la tecnología, sólo porque Rails hace que sea muy fácil no preocuparse la mayor parte del tiempo. La eficiencia es crucial para las consultas a la base de datos, especialmente si construyes algo para grandes audiencias con mucho tráfico.

¡Ahora vete a internet y busca más material sobre SQL para sacártelo de encima de una vez por todas!

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.