Le Blog du Geek Joyeux

Plus moins vite tu codes, moins plus vite ça plante

size doesn't COUNT

| Commentaires

Requêtes N+1

Lorsque vous développez avec Rails, vous devez bien sûr faire attention aux requêtes qui sont effectuées et surtout éviter les requêtes N+1.

Les requêtes N+1 sont celles qui sont lancées pour chaque élément d’une liste.

Prenons l’exemple suivant :

1
2
3
4
# app/controllers/articles_controller.rb

@articles = Article.all
# SELECT articles.* FROM articles
1
2
3
4
5
6
7
8
<%# app/views/articles/index.html.erb %>

<% @articles.each do |article| %>
  ...
  Nombre de commentaires: <%= articles.comments.count %>
  <%# SELECT COUNT(*) FROM comments WHERE comments.article_id = X %>
  ...
<% end %>

Pour chaque article listé, une requête va être lancée pour compter ses commentaires.

Heureusement pour nous, Rails fournit, dans l’API d’ActiveRecord un moyen d’éviter ces requêtes N+1. Il s’agit de la méthode includes qui s’utilise comme ceci :

1
2
3
4
5
# app/controllers/articles_controller.rb

@articles = Article.includes(:comments).all
# SELECT articles.* FROM articles
# SELECT comments.* FROM comments WHERE comments.article_id IN (1, 2, ..., 42)

On peut donc voir ici que l’ensemble des commentaires est récupéré d’un seul coup. Cela nous permet d’accéder aux informations des commentaires d’un article sans requête supplémentaire.

Size vs. Count

Le souci c’est que ça ne règle pas notre problème de count. En effet, si nous faisons de nouveau appel à count, les requêtes seront tout de même effectuées !

L’astuce est donc d’utiliser size plutôt que count sur l’attribut comments de nos articles, ce qui va simplement retourner la taille du tableau contenant les commentaires associés. Ces derniers étant déjà chargés, le compte est bon !

1
2
3
4
5
# app/controllers/articles_controller.rb

@articles = Article.includes(:comments).all
# SELECT articles.* FROM articles
# SELECT comments.* FROM comments WHERE comments.article_id IN (1, 2, ..., 42)
1
2
3
4
5
6
7
<%# app/views/articles/index.html.erb %>

<% @articles.each do |article| %>
  ...
  Nombre de commentaires: <%= articles.comments.size %>
  ...
<% end %>

Commentaires