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.


