Le blog de la CT2C

Starter App - Partie 3 - Faker, Bullet et requêtes N+1

Par Régis Millet aka Kulgar , publié le 1 Février 2013

Vers la partie précédente
Vers la partie suivante

Dans cette partie nous allons découvrir les gems :
- Faker : qui nous permettra de créer facilement un bon nombre de données
- Bullet : qui facilitera la détection des requêtes N+1 vers notre base de données.

Nous continuons toujours à intégrer ces gems dans notre application "StarterApp".



Définissons quelques associations


Avant de continuer, pour faciliter un peu la création de nos données, nous allons définir quelques associations. Tout d'abord, comme nous avons bien configuré notre application dans la première partie, nous allons déplacer nos models déjà créés dans un sous-dossier que nous nommerons client_pages_related. Vous devriez avoir l'architecture suivante dans le dossier models :

Répertoire models/client_pages related contient :
- client_page.rb
- client_menu_button.rb
- client_page_content.rb

Si tel n'est pas le cas, je vous invite à revenir sur les précédentes parties de ce tutoriel. Concernant les associations, elles sont simples :


Une page possède autant de contenu que souhaité client_page has_many client_page_contents et possède un bouton de menu client_page has_one client_menu_button. En ROR, cela se traduit ainsi :

# -*- encoding : utf-8 -*-
# fichier client_page.rb
class ClientPage < ActiveRecord::Base
# Associations
has_many :contents, :inverse_of => :page,
:foreign_key => "page_id", :class_name => "ClientPageContent" #, :dependent => :destroy
has_one :menu_button, :inverse_of => :page,
:foreign_key => "page_id", :class_name => "ClientMenuButton"

# Attributs accessibles
attr_accessible :home, :name, :published, :title, :webdescription, :webtitle
end

Notez l'usage de foreign_key: j'ai en effet préféré utiliser la clé page_id plutôt que celle par défaut client_page_id, qui est plus courte à écrire. J'ai également utilisé :class_name, pour modifier un peu le nom des méthodes qui seront générées (il sera plus agréable d'écrire page.contents que page.client_page_contents...)

J'ai également précisé la relation inverse avec inverse_of. Elle n'est pas obligatoire mais conseillée pour éviter quelques problèmes liés aux associations bi-directionnelles. L'explication dépasserait le cadre de ce tutoriel, aussi je vous laisse lire ce chapitre des guides Rails où l'utilité de inverse_of est très bien expliquée.

Concernant l'option :dependent => :destroy, c'est à vous de voir si vous souhaitez l'activer ou pas pour le contenu des pages. Si vous ne le faites pas, vous devrez supprimer manuellement le contenu des pages après avoir supprimé la page associée. Si vous l'activez, vous n'aurez pas à le faire, en revanche, vous devrez faire attention si vous souhaitez déplacer le contenu d'une page vers une autre à ne pas supprimer la page d'origine avant d'avoir terminer le déplacement de son contenu.

Profitez-en pour vérifier vos attributs accessibles et modifiables depuis le site avec la méthode attr_accessible
Rien de bien sorcier concernant les deux autres fichiers :

# -*- encoding : utf-8 -*-
# Fichier client_page_content.rb
class ClientPageContent < ActiveRecord::Base
# Associations
belongs_to :page, :inverse_of => :contents,
:foreign_key => "page_id", :class_name => "ClientPage"

# Attributs accessibles
attr_accessible :body, :css_class, :priority, :title
end

# -*- encoding : utf-8 -*-
# client_menu_button.rb
class ClientMenuButton < ActiveRecord::Base
# Associations
belongs_to :page, :inverse_of => :menu_button,
:foreign_key => "page_id", :class_name => "ClientPage"

# Attributs accessibles
attr_accessible :name, :priority
end

Ceci étant fait, nous pouvons passer à l'utilisation de la gem Faker.


Gem Faker


Dépôt GitHub:
Comme nous allons avoir besoin de données pour bien comprendre l'utilité de Bullet, je nous fais d'abord intégrer Faker, qui les créera pour nous. Cette petite gem permet de générer des données aléatoires. Très utile pour la création d'utillisateurs lambda, elle possède également de quoi créer nos pages de façon aléatoire et remplies de "lorem" et "d'ipsum".

Première chose, nous allons l'ajouter à notre GemFile, comme nous allons nous en servir qu'en développement, on va la placer dans ce groupe :
group :development do
# ...
gem "faker" # https://github.com/stympy/faker
end

N'oubliez pas ensuite de faire l'installation :
$ bundle install

Dès qu'il s'agit de modifier la base de données sans avoir à passer par le site, on songe tout de suite à une tâche rake. C'est en effet ce qui est le plus commode (bien plus commode que de le faire en mode console). Nous allons donc créer une tâche rake. Toutes les tâches rake doivent être placées dans le dossier lib/tasks et avoir l'extension de fichier .rake. Allons-y, créons le fichier lib/tasks/db_populate.rake et ajoutons-y le code suivant :
# -*- encoding : utf-8 -*-
# Nous devons inclure Faker pour pouvoir l'utiliser ensuite
require 'faker'

namespace :db do

desc "Insère un bon paquet de données dans notre base de données pour nos pages"
task :sample_datas => :environment do
# On arrête cette tâche si nous ne sommes pas en environnement de développement
unless Rails.env == "development"
puts "Vous n'êtes pas en environnement de développement"
return
end

# Facultatif, si vous souhaitez supprimer toutes les données précédemment créées
# Le problème c'est que toutes les données d etoutes les tables seront supprimées
# Rake::Task['db:reset'].invoke

# Moi je préfère cibler les tables en particulier :
ClientPage.delete_all
ClientPageContent.delete_all
ClientMenuButton.delete_all

# On précise à Faker que l'on souhaite des données françaises.
# Dans notre cas, cela ne va rien changer, mais pour la création d'utilisateur, ça change le format
# de l'adresse, du numéro de téléphone, etc.
Faker::Config.locale = :fr

# Nous allons exécuter ce bout de code 5 fois histoire de ne pas avoir trop de pages
5.times do |n|
# Nous allons commencer par la création d'une page.
page_name = Faker::Lorem.word # Génère un mot aléatoirement
title = Faker::Lorem.sentence # Génère une phrase aléatoirement
webtitle = Faker::Lorem.words # Génère une série de mots aléatoires
webdescription = Faker::Lorem.paragraph # Génère un paragraphe rempli de mots aléatoires
published = (n == 1) ? false : true # On ne publie pas la 1ère page
home = (n == 4) ? true : false # La 4ème page sera notre page d'accueil

# On crée la page avec ces données :
client_page = ClientPage.create(
:title => title,
:webtitle => webtitle,
:webdescription => webdescription,
:name => page_name,
:published => published,
:home => home)

# Si la page a une erreur, on l'affiche et on arrête la boucle
if client_page.errors.any?
puts client_page.errors.full_messages
return
else
# Ca ne fait jamais de mal de savoir ce qu'il se passe :
puts "Page créée : nom = #{client_page.name}"
end

# Maintenant qu'on a notre page, on peut continuer avec les autres éléments :
# Continuons avec le bouton :
button_priority = n # Le bouton aura la priorité égale à la valeur actuelle de n, on ne se casse pas la tête
button_name = page_name # Ou un nouveau mot aléatoire, mais bon, autant que le bouton ait le même nom que la page

# Et on crée le bouton :
page_button = client_page.build_menu_button(
:priority => button_priority,
:name => button_name
)

unless page_button.save
# Si la sauvegarde échoue, idem, on affiche les erreurs et on quitte
puts page_button.errors.full_messages
return
else
puts "Bouton de la page #{client_page.name} créé : nom = #{page_button.name}"
end

# Enfin on va ajouter du contenu aléatoire à nos pages
# Pour cela on va tirer un nombre aléatoire qui déterminera le nombre de 'contenus' que notre page contiendra
nb_content = rand(1..6)

nb_content.times do |c|
content_title = Faker::Lorem.sentence
content_priority = c
content_body = Faker::Lorem.paragraphs # Génère un nombre de paragraphes aléatoires remplis de mots aléatoires
# On laisse css_class vide

page_content = client_page.contents.new(
:title => content_title,
:priority => content_priority,
:body => content_body
)

# Idem que précédemment, en cas d'erreur, on quitte
unless page_content.save
puts page_content.errors.full_messages
return
else
puts "Contenu de la page #{client_page.name} créé : titre = #{page_content.title}"
end
end
end
# And voila ! Avec ça nous avons créé 5 pages remplies de contenu aléatoire !
end

end

Je vous ai bien commenté le code et je ne pense pas qu'il soit nécessaire d'en remettre une couche.

Concernant les méthodes Faker utilisées. Je vous rassure, je ne les ai pas devinées ni sorties de derrière mon chapeau. Je vous l'accorde, il y a peu de documentation concernant cette gem. Il nous faut donc explorer les sources sur GitHub. Fort heureusement, il n'y a pas 36000 méthodes non plus et toutes celles qui nous intéressent se trouvent dans le dossier lib/faker. Explorez les différents fichiers de ce dossier pour voir toutes les méthodes fournies par Faker.


Il ne nous reste plus qu'à exécuter cette tâche rake :
$ rake db:sample_datas

Et à admirer le contenu aléatoire qu'elle génère. Avec tout ça, nous voilà parés pour utiliser et comprendre la gem Bullet !

Nous réutiliserons la gem Faker lorsque nous aurons besoin d'utilisateurs lambdas après avoir intégré la gem Devise. Gardez-donc la dans un coin de votre tête, elle est tout de même pratique et nous évite d'inscrire un bon nombre de données à la main !


Gem Bullet


Les requêtes N+1 : le problème :
Pour bien comprendre le problème des N+1 requêtes, prenons un petit exemple. Maintenant que nous avons des boutons pour notre menu, associés à des pages, nous souhaiterions créer un menu. Dans le fichier application.html.erb, nous ajoutons donc naturellement le code suivant :
<nav>
<ul>
<% ClientMenuButton.order("priority DESC").all.each do |button| %>
<li><%= link_to button.name, client_page_path(button.page.id) %></li>
<%# le .id est facultatif %>
<% end %>
</ul>
</nav>

Pour que ce code fonctionne, nous créons la route :
# fichier routes.rb
#...
get "pages/:page_id" => "client_pages#show", :as => :client_page
#...

Ainsi que l'action correspondante dans le controller (j'en ai profité pour modifier l'action home maintenant que nous avons défini une page d'accueil) :
# -*- encoding : utf-8 -*-
class ClientPagesController < ApplicationController

# route_path, on récupère la page d'accueil et on l'affiche si elle existe. Sinon on affiche une page d'accueil par défaut.
def home
@page = ClientPage.where(:home => true).first

respond_to do |format|
if @page.nil?
format.html { render :home }
else
format.html { render :show }
end
end
end

# GET "pages/:page_id"
def show
@page = ClientPage.find(params[:page_id].to_i)

respond_to do |format|
format.html
end
end
end

Et sans oublier la création du fichier views/client_pages/show.html.erb que nous laisserons vide pour l'instant. Si vous lancez le serveur et accédez à la page d'accueil, tout se passe pour le mieux. Oui mais, le souci - invisible - réside dans ce bout de code :

<% ClientMenuButton.order("priority DESC").all.each do |button| %>
<li><%= link_to button.name, client_page_path(button.page.id) %></li>
<% end %>

En effet, ce bout de code va effectuer N+1 requêtes vers la base de données : 1 pour récupérer tous les boutons et N (ici le nombre de boutons) pour récupérer chaque page associée à chaque bouton. Pour le cas présent, ce n'est pas gênant car nous avons peu de boutons et de pages. La preuve dans les logs du serveur development.log :

  Rendered client_pages/show.html.erb within layouts/application (0.1ms)
ClientMenuButton Load (0.2ms) SELECT `client_menu_buttons`.* FROM `client_menu_buttons` ORDER BY priority DESC
ClientPage Load (0.3ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` = 6 LIMIT 1
ClientPage Load (0.3ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` = 5 LIMIT 1
ClientPage Load (0.2ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` = 4 LIMIT 1
ClientPage Load (0.2ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` = 3 LIMIT 1
ClientPage Load (0.1ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` = 2 LIMIT 1
Completed 200 OK in 61ms (Views: 38.2ms | ActiveRecord: 5.6ms)

Mais imaginez dans le cas d'une boutique en ligne où les produits sont triés par catégorie et que vous avez plus de 1000 produits par catégorie... Et que, du côté de la partie d'administration, vous vous amusez (parce que le client vous l'a demandé) à afficher toutes les catégories avec les 1000 produits qu'elles contiennent sur une même page. Faites le compte, vous arriverez très vite à plus de 10 000 requêtes vers la base de données et ça ralentira (croyez-moi) énormément le chargement de la page ! Pour peu qu'en plus vos produits possèdent une image d'illustration stockée dans une autre table, là vous êtes bons pour attendre 5 heures le rafraîchissement de votre page.


Les requêtes N+1 : la solution
Ruby On Rails est très bien fichu et il nous le prouve encore une fois. Nous pouvons en effet anticiper ces requêtes N+1 en pré-sélectionnant les ids des associations qui vont être utilisées. Pour ce faire, il existe la méthode includes. Dans cette méthode, nous précisons le nom des associations devant être pré-sélectionnées, et ce, au niveau de la requête d'origine. (i.e. celle récupérant les données à partir desquels nous ferons appel aux associations).

Dans notre cas, nous aurions simplement à modifier nos lignes de code comme ceci (Ne l'ajoutez pas tout de suite) :
<% ClientMenuButton.includes(:page).order("priority DESC").all.each do |button| %>
<li><%= link_to button.name, client_page_path(button.page.id) %></li>
<% end %>

Dès lors, notre application ne fait plus N+1 requête vers la base de données mais seulement 2 (logs tirés de development.log) :

  Rendered client_pages/show.html.erb within layouts/application (0.0ms)
ClientMenuButton Load (0.2ms) SELECT `client_menu_buttons`.* FROM `client_menu_buttons` ORDER BY priority DESC
ClientPage Load (0.2ms) SELECT `client_pages`.* FROM `client_pages` WHERE `client_pages`.`id` IN (6, 5, 4, 3, 2)
Completed 200 OK in 13ms (Views: 11.2ms | ActiveRecord: 0.7ms)

Nous sommes déjà passés de 5.6ms à 0.7ms pour la durée d'exécution des requêtes au niveau de la base de données. Je vous laisse imaginer le gain de vitesse obtenu lorsqu'il s'agit de milliers de données.

Quelques explications Comme vous pouvez le constater le mécanisme est tout simple. En utilisant includes(:page), Ruby On Rails sait qu'il doit récupérer toutes les pages dont les ids correspondent aux valeurs de la clé de l'association (dans notre cas :page_id). Pour ce faire, comme Rails a déjà chargé la table des boutons, il ne fait que sélectionner toutes les valeurs de la colonne page_id. Il réinjecte ensuite ces valeurs dans une condition WHERE dès que nous souhaitons récupérer la première association. Pour l'association suivante, plus besoin de requête vers la base de données, Rails a déjà récupéré la donnée et l'a stocké dans sa mémoire cache, il la récupère donc de là et c'est beaucoup plus rapide !


En guise de bonus, grâce à cette pré-sélection, nous pouvons ajouter une condition sur les pages associées aux boutons lorsqu'on récupère les boutons. Plutôt pratique dans notre cas puisque nous aimerions bien ne récupérer que les boutons associés aux pages publiées. Pour cela, ce ne sera pas plus compliqué que ceci :

<% ClientMenuButton.includes(:page).where("client_pages.published = ? ", true).order("priority DESC").all.each do |button| %>
<li><%= link_to button.name, client_page_path(button.page.id) %></li>
<% end %>

Au niveau de la condition, vous devez utiliser le nom de la table client_pages et non le nom de l'association page.

Pour aller un peu plus loin. Comment faire pour pré-sélectionner de la même façon l'association d'une association ? Par exemple, nous souhaiterions afficher les contenus de toutes les pages ainsi que leurs illustrations, qui sont stockées dans une autre table avec les associations suivantes: page :has_many => :contents et content :has_many => :pictures.

Et bien c'est tout à fait possible, lorsque nous récupérons les pages, il suffit de préciser à Ruby On Rails d'inclure également la deuxième association. (On parle de nested association car c'est une association dans une autre) :

@pages = Page.includes(:contents => [ :pictures])


Et vous pouvez aller très loin comme ceci. Dans les guides Rails, ils fournissent un exemple de ce type :
Category.includes(:posts => [{:comments => :users}, :tags], :articles).all

Ici on sélectionne les posts et articles associés aux catégories mais aussi les commentaires associés aux posts ainsi que les utilisateurs ayant posté ces commentaires, avec en prime les tags associés aux posts. Bref... Quand vous arrivez à un tel bric à brac, c'est que vous avez tout compris sur le problème des requêtes N+1 !

Notez également que les méthodes belongs_to, has_many, etc. ont également l'option :include, qui permet également de faire la même chose que ci-dessus. Par exemple, si à chaque fois que vous récupérez les commentaires depuis les posts, vous récupérez également les utilisateurs, vous pourriez mettre ceci en option au niveau du model Post :
 has_many :comments, :include => [:users] 



Les requêtes N+1 : détection grâce à la gem Bullet
Dépôt GitHub
Le rôle de la gem Bullet ce n'est ni plus ni moins que de détecter ces requêtes N+1 et de vous avertir lorsqu'une pré-sélection (l'usage d'un includes) serait nécessaire. Pour l'installer, ajoutons-la d'abord à notre gemfile :

group :development do
# ...
gem "bullet" # https://github.com/flyerhzm/bullet
end

Installez-la avec bundle install. Nous devons ensuite ajouter ces quelques lignes à notre fichier config/environments/development.rb :

# Configuration de bullet
config.after_initialize do
Bullet.enable = true # Active bullet
Bullet.alert = false # Active un pop up javascript lorsqu'une requête N+1 est détectée
Bullet.bullet_logger = true # Crée un fichier log/bullet.log et y store l'historique des requêtes N+1 détectées, c'est mon option favorite
Bullet.console = true # Affiche une notification javascript dans la console de votre navigateur (via la commande javascript console.log)
Bullet.growl = false # Affiche une notification dans Growl, si vous êtes sur Mac. Cf dépôt github
# Bullet.xmpp # envoie des notifications xmpp / jabber au compte spécifié, cf dépôt github
Bullet.rails_logger = false # Ajoute des notifications dans les journaux du serveur. Pas vraiment nécessaire si vous avez activé bullet_logger.
Bullet.airbrake = false # Ajoute des notifications à airbrake
Bullet.disable_browser_cache = false # Désactive le cache du browser. Mettez 'true' si vous faites face à des évènements bizarres au niveau de votre navigateur (javascript non chargé en totalité par exemple)
end

Arrêtez votre serveur et relancez-le. Ouvrez Firebug dans l'onglet console (ou la console javascript de votre navigateur). Rendez-vous sur notre page d'accueil et si vous n'avez pas mis le includes de tout à l'heure, vous devriez y voir quelque chose comme ceci :

Uniform Notifier 192.168.1.5:33
user: machin
N+1 Query detected
ClientMenuButton => [:page] Add to your finder: :include => [:page]N+1 Query method call stack
path_to_your_app/StarterApp/app/views/layouts/application.html.erb:14:in `block in _app_views_layouts_application_html_erb__598491937_76627910'
path_to_your_app//StarterApp/app/views/layouts/application.html.erb:13:in `each'
path_to_your_app//StarterApp/app/views/layouts/application.html.erb:13:in `_app_views_layouts_application_html_erb__598491937_76627910'
path_to_your_app//StarterApp/app/controllers/client_pages_controller.rb:12:in `block (2 levels) in home'
path_to_your_app//StarterApp/app/controllers/client_pages_controller.rb:8:in `home'

Bullet nous signale bien une détection de requêtes N+1. Il nous indique en plus l'association concernée, ce qu'il faut faire et où il a détecté ça. Moi qui utilise beaucoup cette gem, je peux vous dire que ces informations sont de l'or et vous permettent de traquer et réparer facilement les problèmes de requêtes N+1 pour optimiser grandement la vitesse de votre application Web. Vous remarquerez que Rails a également bien généré le fichier bullet.log contenant les mêmes informations.

Maintenant, si nous mettons l'instruction includes :
<% ClientMenuButton.includes(:page).where("client_pages.published = ? ", true).order("priority DESC").all.each do |button| %>
<li><%= link_to button.name, client_page_path(button.page.id) %></li>
<% end %>

Et que nous rafraîchissons, Bullet ne nous dit plus rien. Nous avons donc correctement résolu ce souci de requêtes N+1.

Bullet indique également lorsqu'une instruction includes est superflue. Attention toutefois, car dans ce cas là, il n'indique pas où il l'a détectée dans votre code (malheureusement). De plus, il peut vous afficher cette notification même si l'include est nécessaire. Cela arriverait notamment si nous affichions un bouton auquel aucune page n'est associée. En effet, dans ce cas là, Bullet détecterait qu'il n'y a pas de page associée et donc que l'includes ne serait pas nécessaire. Oui... mais il l'est pour tous les autres boutons.

Bullet indique également lorsque vous auriez besoin d'ajouter un counter_cache. Pour faire bref, un counter_cache est nécessaire chaque fois que vous utilisez la méthode count pour compter des données, par exemple : page.contents.count. En Ruby On Rails, il serait alors nécessaire de créer une nouvelle colonne dans la table pages nommée : contents_count puis de préciser l'option :counter_cache => true au niveau de l'association belongs_to du model Content. Ce qui donnerait :

belongs_to :page, :counter_cache => true

Ceci étant fait, cela permettrait de s'affranchir de la requête vers la base de données pour effectuer le comptage.

Oui mais ! Il existe de nombreux bugs dans Ruby On Rails 3. La colonne censée tenir le compte du nombre d'objets associés n'est pas toujours correctement mise à jour, notamment lors de la destruction d'objets... Comme l'en atteste la création de cette gem : counter_culture nous ne pouvons pas réellement nous fier à Rails pour ce qui concerne du counter_cache. D'ailleurs, je vous conseille vivement l'utilisation de la gem counter_culture si vous souhaitez utiliser cette méthode de counter_cache. Mais même là, les développeurs de la gem précisent qu'il faut faire un petit rafraîchissement de tous les compteurs chaque semaine... Bref, du coup, moi je préfère garder mon instruction count.



Petits bonus de fin


Faker a fait du bon boulot !
Premier petit bonus, très rapide. Modifions la page client_pages/show.html.erb en ajoutant ce code :

<% @page.contents.each do |content| %>
<h1><%= content.title %></h1>
<%= simple_format content.body %>
<% end %>

Et celui-ci à notre petit menu :

<% ClientMenuButton.includes(:page).where("client_pages.published = ? ", true).order("priority DESC").all.each do |button| %>
<li>
<%= link_to button.name, client_page_path(button.page.id) %>
<%= "- Actuel" if @page == button.page %>
</li>
<% end %>

Simplement pour vérifier que Faker a fait du bon boulot ! Rafraîchissez votre page et vous verrez que nous n'avons que du contenu différent sur chaque page, comme attendu. Le petit "- actuel" c'est rien que pour vous, pour vous montrer qu'avec une telle application (aux pages un peu dynamiques), il est facile de faire un super menu.


Rails Panel :
Dépôt GitHub
J'ai découvert cette gem très récemment et je pense que je vais être amené à beaucoup l'utiliser. Comme Bullet, elle est surtout orientée vers l'optimisation de votre application. Pour la mettre en place, rien de plus simple, ajoutez-la au Gemfile puis faites un petit bundle install :

group :development do
# ...
gem 'meta_request', '0.2.1' # https://github.com/dejan/rails_panel
end

Ensuite, installez Chrome (si vous ne l'avez pas encore ? Notez que je désapprouverais totalement une négation sur cette question. Nous autres, développeurs Rails, nous nous devons d'avoir tous les navigateurs ! Ne serait-ce que pour tester nos sites. )
Puis rendez-vous sur le Store de Chrome et cherchez rails panel dans les extensions. (Le lien est susceptible de changer, mais pour l'heure, elle se trouve à cette adresse: Rails Panel Extension Chrome). Installez-la, relancez votre serveur, relancez votre navigateur, ouvrez la console Chrome pour les développeurs et vous devriez y voir un nouvel onglet Rails Panel.

Ce panneau vous affiche un bon nombre d'informations utiles :
- Les paramètres (contenus de la variable params)
- Les requêtes effectuées vers la base de données (ça c'est cool !) + la durée de chaque requête (ça c'est encore plus cool !)
- Les vues récupérées pour afficher la page (notez que ça n'inclut pas le layout, mais ça inclut les partials !) et la durée nécessaire pour effectuer leur rendu,
- Les erreurs éventuelles (oui bon, en dehors des erreurs fatales évidemment).

Vous avez également un historique des chargements des pages et surtout de leur temps de chargement total. Très utile lorsque vous souhaitez optimiser la rapidité de votre application, justement à l'aide de la gem Bullet !

Ce petit bonus termine cette partie ! J'espère que ça vous a plu et je vous dis à bientôt pour la suite !


Index -- --

  • 4 Commentaire


  • Bonjour Dragon,

    Au temps pour moi ! Il y a une erreur dans mon code... J'aurais dû vérifier... En effet, j'ai modifié l'association : ':has_one :menu_button, :inverse_of => :page, :foreign_key => "page_id", :class_name => "ClientMenuButton"' dans le model ClientPage après avoir rédigé la tâche rake.

    Il ne faut donc plus appeler 'client_page.build_client_menu_button', mais 'client_page.build_menu_button' qui est une méthode directement générée par l'association ':has_one'. Je vais corriger mon code et voir s'il n'y a pas d'autres erreurs du même type.

    Merci pour ce retour ;)


    Par Kulgar, le 14 Février 2013

  • Bonjour,
    Tout d'abord, merci pour ce génial tuto !
    Je suis newbie et je tombe sur cette erreur quand je lance la commande 'rake db:sample_datas' :
    undefined method `build_client_menu_button' for #.
    Rails le retrace ici : /StarterApp/lib/tasks/db_populate.rake:64:in `block (3 levels) in '.

    Faut-il dénir cette méthode quelque part dans un des fichiers ?

    Merci d'avance.


    Par drag0n, le 14 Février 2013

  • Merci pour ces retours, les petites modifications ont été effectuées. :)


    Par Kulgar, le 11 Février 2013
  • Voir tous les commentaires
    1 de plus
Insérez votre commentaire
  1. Min: 50 caractères, Max: 800. Actuellement: 0 caractères

  2. ne pas remplir