Este post va sobre mi gema activerecord-preload_query y otras maneras de hacer lo mismo.
Activerecord nos provee los métodos preload
, eager_load
e includes
para precargar
relaciones y evitar hacer N+1 queries a la base de datos cuando queremos acceder
a esta. También nos provee de counter_cache
, el cual está muy bien, pero está
muy imitado.
Pero que pasa si tenemos algunos cálculos mas complicados? Imaginemos que tenemos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 |
|
Gracias a counter_cache
podemos hacer algo así:
1 2 3 |
|
Y solo se hará una sola llamada.
Pero y si queremos saber por alguna razón el stock total de cada categoría?
Imaginemos que Product
tiene el atributo stock
.
En la categoría podremos añadir un método products_stock
como este:
1 2 3 |
|
De esta manera, tendremos de nuevo el problema del N+1.
Solución con joins
Para evitarlo podríamos optar por algo como:
1
|
|
Esto soluciona el problema del N+1, pero estamos sobrescribiendo el select
,
entonces a @categories
no podremos enviarle muchos métodos de activerecord
porque no estarán disponibles los campos que el espera.
Un ejemplo sencillo es con @categories.count
y si en vez de ser Category
lo que precede a select
fuese un “scope” mucho más
complicado como podría ser en el caso del scope
producido por una búsqueda
compleja seguramente acabaríamos rompiendo la query
.
Además que incluso con este sencillo caso, @categories.count
acaba dando un
error al no estar presentes los campos que se esperaban, porque hemos sobreescrito el
select
.
Otro problema a tener en cuenta, pero no el principal es hacer joins
de tablas
muy grandes.
Consulta a parte
Para evitar los problemas de sobreescribir el select
, lo mejor es hacer la consulta
a parte, por ejemplo:
1 2 3 4 5 6 7 |
|
Con preload_query
Con preload_query
es básicamente igual, pero interceptando el método de
ActiveRecord que se encarga de hacer la consulta, así no llamaremos a la base de
datos hasta el momento en el que sea necesario.
Ejemplo de uso básico:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
A la clase Category
puede que nos interese implementarle el método sum_stock
para cuando no se hace un preload.
Por ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Me gustaría pensar un DSL para implementar este último ejemplo de forma mas sencilla, pero de momento no se me ocurre :P
Gemas similares
Estas gemas hace algo parecido, pero solo se encargan de los counts
, quizás
para ti sea suficiente.