Tutoriel - Partie II / Mars 2006

Créez votre blog avec Rails !

 

Création des relations entre les modèles et validation des données

Pour commencer nous allons définir explicitement la relation existant entre le modèle Commentaire et le modèle Article, à savoir qu'un commentaire "appartient" à un article.

Pour cela, éditez le fichier app/models/commentaire.rb et recopiez ceci :


  • class Commentaire < ActiveRecord::Base
  •  belongs_to :article, :counter_cache => true
  •  validates_presence_of :texte
  • end

La déclaration belongs_to signifie que la table qui contient la clef étrangère (commentaires) "appartient" à la table qu'elle référence (articles). En pratique cela signifie pour Rails que la table commentaires contient une colonne nommée article_id qui référence la colonne id dans la table articles. Le nom de la clef étrangère (article_id) est fixé par convention et peut, comme toujours, être modifié en cas de besoin.

Le fait de rajouter :counter_cache => true indique à ActiveRecord de maintenir un compteur pour le nombre de commentaires associés à chaque article, c'est le champ commentaires_count de la table articles qui sera utilisé à cet effet. Si vous ajoutez un commentaire le compteur sera incrémenté, si vous supprimez un commentaire le compteur sera alors décrémenté, et le tout de manière totalement automatique.

Enfin la ligne validates_presence_of :texte demande à Rails de vérifier que le champ nommé "texte" est bien renseigné avant de sauvegarder le commentaire dans la base de données. Dans le cas contraire le formulaire de saisie sera réaffiché avec un message d'erreur.

En pratique un article peut avoir plusieurs commentaires qui lui sont associés, modifions donc le fichier app/models/article.rb en conséquence :

  • class Article < ActiveRecord::Base
  •  has_many :commentaires, :dependent => true
  •  validates_presence_of :titre, :texte
  • end

Nous avons donc rajouté la déclaration has_many :commentaires dont l'option :dependent => true signifie que les commentaires ne peuvent exister indépendamment de l'article auquel ils font référence. En l'occurence la destruction d'un article entraînera automatiquement la destruction des commentaires associés.

De la même manière que pour les commentaires, le fait d'ajouter validates_presence_of :titre, :texte nous assure que Rails vérifiera que ces champs sont renseignés avant qu'un article puisse être sauvegardé. Voici ce qui apparaîtra si vous essayez de sauver un article sans avoir correctement renseigné les champs obligatoires :

blog

Figure 4 : Erreur de saisie

 

Et voilà, nous pouvons maintenant passer à l'étape suivante.

Modification de la mise en page de notre application

Il faut reconnaître que l'habillage de notre application est pour le moment plutôt austère et nous allons y remédier immédiatement.
Dans l'archive contenant le code source de l'application finale vous trouverez dans public/images toutes les images utilisées (copiez les dans votre répertoire demoblog/public/images) ainsi que la feuille de style public/stylesheets/blog.css à copier dans votre répertoire demoblog/public/stylesheets/ en remplacement de scaffold.css que nous n'utiliserons pas.

Pour commencer nous allons revoir la mise en page générale de notre application. Il existe plusieurs moyens de définir des mises en page dans Rails mais sachez que si vous placez dans le répertoire app/views/layouts/ un fichier portant le même nom que le contrôleur alors toutes les vues générées par ce contrôleur utiliseront par défaut cette mise en page.
Un fichier de mise en page app/views/layouts/blog.rhtml a déjà été créé lors de la génération de l'échafaudage (scaffolding) de notre application. Nous allons maintenant le remplacer par ce qui suit  :

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  • "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html xmlns="http://www.w3.org/1999/xhtml">
  • <head>
  • <meta http-equiv=Content-Type content="text/html; charset=utf-8" />
  • <title>Rails Blog</title>
  • <%= stylesheet_link_tag "blog" %>
  • <%= javascript_include_tag "prototype", "effects" %>
  • </head>
  • <body>
  •  <div id="page">
  •   <div id="top">
  •    <div id="titre_blog">
  •     <a href="/">A la découverte de Ruby on Rails</a>
  •    </div>
  •   </div><!-- fin div top -->
  •   <div id="main">
  •    <div id="content">
  •     <p style="color: green"><%= flash[:notice] %>
  •     <%= @content_for_layout %>
  •    </div><!-- fin div content -->
  •     <div id="footer">propulsé par ReFRESH</div>
  •    </div><!-- fin div page -->
  • </body>
  • </html>

Une fois cette opération effectuée voici ce que vous devriez voir apparaître dans votre navigateur :

blog

 

blog

Figure 5a & 5b : Notre nouvelle mise en page

Habillage des pages d'ajout et d'édition d'articles

Modifions maintenant les vues app/views/blog/new.rhtml et app/views/blog/edit.rhtml, remplacez leur contenu par celui-ci :

new.rhtml

  • <h2 class="new">Ecrire un nouvel article</h2>
  •  <div id="form-content">
  •   <%= start_form_tag :action => 'create' %>
  •   <%= render :partial => 'form' %>
  •   <%= submit_tag "Envoyer" %>
  •   <%= end_form_tag %>
  • </div>
  • <%= link_to image_tag('retour.gif'), :action => 'list' %>

edit.rhtml

  • <h2 class="edit">Editer l'article</h2>
  •  <div id="form-content">
  •   <%= start_form_tag :action => 'update', :id => @article %>
  •   <%= render :partial => 'form' %>
  •   <%= submit_tag 'Editer' %>
  •   <%= end_form_tag %>
  • </div>
  • <%= link_to image_tag('retour.gif'), :action => 'list' %>

Notez qu'afin d'éviter la duplication de code nos deux vues new.rhtml et edit.rhtml utilisent un même fichier (_form.rhtml) pour afficher le formulaire de saisie. Il s'agit d'un fichier partiel ("partial" en anglais) dont le contenu est inséré à l'endroit où il est appelé. Il est impératif que le nom du fichier contenant le partiel commence par un tiret bas (_), même si ce tiret ne doit pas apparaître lors de l'appel du partiel avec la méthode render (render :partial => 'form').

Nous allons modifier le fichier partiel app/views/blog/_form.rhtml afin de supprimer l'affichage des champs created_at et updated_at qui seront mis à jour automatiquement par Rails lors de la création et de la modification d'enregistrements.

_form.rhtml

  • <%= error_messages_for 'article' %>
  • <!--[form:article]-->
  • <p><label for="article_titre">Titre</label><br/>
  • <%= text_field 'article', 'titre' %></p>
  • <p><label for="article_texte">Texte</label><br/>
  • <%= text_area 'article', 'texte' %></p>
  • <!--[eoform:article]-->

Affichage de la liste des articles

Passons maintenant à l'affichage de la liste des articles. Voici le contenu à copier dans le fichier app/views/blog/list.rhtml :

  • <p id="nouvel-article"><%= link_to image_tag('article.gif'), :action => 'new' %></p>
  • <%= render :partial => 'article', :collection => @articles %>
  • <div id="page_infos">
  •  <% if @article_pages.length > 1 %>
  •  <div id="page_num">
  •   <page <%= @article_pages.current.to_i %> / <%= @article_pages.length %>
  •  </div>
  • <% end %>
  •  <div id="liens-pagination">
  •   <%= link_to(image_tag('prev.gif', :alt => 'page précédente'), { :page => @article_pages.current.previous },
  •   { "id" => "link-precedent"}) if @article_pages.current.previous %>
  •  &nbsp;
  •   <%= link_to(image_tag('next.gif', :alt => 'page suivante'),
  •   { :page => @article_pages.current.next },
  •   { "id" => "link-suivant"}) if @article_pages.current.next %>
  •  </div>
  • </div>

Nous allons créér un nouveau fichier partiel app/views/blog/_article.rhtml que nous utiliserons à la fois pour l'affichage d'une liste d'articles et d'un article en particulier. Voici le contenu à copier dans ce fichier :

  • <div class="article">
  • <div class="article_top"></div>
  •  <div class="article-message">
  •   <h2 class="article-titre">
  •   <%= link_to h(article.titre), :action => 'show', :id => article %>
  •   </h2>
  •  <div class="article-etat">
  •  <div class="article-info">
  •  posté le <%= article.created_at.strftime("%d/%m/%Y à %H:%M:%S") %>
  •  </div>
  •  <div class="article-edit">
  •   <%= link_to image_tag('editer.gif', :alt => 'editer cet article'),
  •   {:action => 'edit' , :id => article} %>
  •   <%= link_to image_tag('supprimer.gif', :alt => 'supprimer cet article'),
  •   {:action => 'destroy' , :id => article},
  •   :confirm => "supprimer cet article ?"%>
  •  </div>
  •  </div>
  •  <div class="article-texte"><%= textilize article.texte %></div>
  •  <div class="article-commentaire">
  •  <%= link_to 'commentaires',
  •  :action => 'show',
  •  :id => article %> (<%= article.commentaires_count.to_s %>)
  •  <%= link_to image_tag('comment.jpg', :alt => 'afficher les commentaires'),
  •  :action => 'show',
  •  :id => article %>
  •  </div>
  • </div>
  • <div class="article_bas"></div>
  • </div>

Notez que cette simple ligne <%= textilize article.texte %> indique à Rails de transformer les marqueurs au format Textile en code HTML, ce qui est très pratique pour formater le corps de nos articles (pour que cela fonctionne il faut néanmoins que RedCloth soit installé).

Affichage d'un article et ajout de commentaires

Modifions le fichier app/views/blog/show.rhtml afin d'afficher un article et ses commentaires :

show.rhtml

  • <%= link_to image_tag('retour.gif', :alt => "retour à la liste"),
  • :action => 'list' %>
  • <br /><br />
  • <%= render :partial => 'article' %>
  • <br />
  • <div id="commentaires-list">
  • <%= render :partial => "commentaire",
  • :collection => @article.commentaires %>
  • </div>
  • <div id="commentaire_infos">
  •  <div id="commentaire_spinner">
  •   <%= image_tag('spinner.gif',
  •   :style => 'display:none',
  •   :id => 'spinner') %>
  •  </div>
  •  <div id="commentaires-erreur"></div>
  •  <div id="commentaire_action">
  •   <a href="#" onclick="Effect.Appear('form-content'); new Effect.ScrollTo('form-content');return false;"><%= image_tag 'commenter.gif', :alt => 'ajouter un commentaire' %></a>
  •  </div>
  • </div>
  • <br /><br />
  • <%= render :partial => 'form_commentaire' %>
  • <div id="bottom"></div>

Si vous regardez attentivement dans le fichier de mise en page de notre application (app/views/layouts/blog.rhtml) vous remarquerez la présence de cette ligne qui va charger les bibliothèques JavaScript prototype et effects : <%= javascript_include_tag "prototype", "effects" %>

Nous aurons ensuite accès sans effort aux méthodes AJAX et à une ribambelle d'effets visuels, qui nous permettront par exemple de faire apparaître en douceur le formulaire de saisie juste en dessous du dernier commentaire affiché, le tout dans le plus pur style "web 2.0"...

Il est temps maintenant de créer deux nouveaux fichiers partiels, l'un app/views/blog/_commentaire.rhtml est utilisé pour l'affichage de la liste des commentaires et l'autre app/views/blog/_form_commentaire.rhtml pour l'affichage du formulaire d'ajout de commentaire. Voici le contenu de ces deux fichiers :

_commentaire.rhtml:

  • <div class="commentaire_glob">
  •  <div class="commentaire_top"></div>
  •  <div class="commentaire-message">
  •   <div class="commentaire-info">
  •   <posté le <%= commentaire.created_at.strftime("%d/%m/%Y à %H:%M:%S") %>
  •   </div>
  •   <div class="commentaire-texte">
  •   <%= textilize commentaire.texte %>
  •   </div>
  •  </div>
  •  <div class="commentaire_bas"></div>
  • </div>

_form_commentaire.rhtml

  • <div id="form-content" style="display: none;">
  •  <h3>Ajoutez vos commentaires !</h3>
  •  <%= form_remote_tag(:update => {:success => 'commentaires-list',
  •  :failure => 'commentaires-erreur'},
  •  :url => {:action => 'ajout_commentaire',
  •  :id => @article },
  •  ;:loading => "Effect.Appear('spinner')",
  •  :after => "Effect.DropOut('form-content')",
  •  :complete => "$('spinner').style.display = 'none';$('commentaire_texte').value=''; new
  •  Effect.ScrollTo('bottom');return false;",
  •  :position => 'bottom') %>
  •  <%= text_area('commentaire', 'texte', 'cols' => 40, 'rows' => 10 ) %>
  •  <br /><br />
  •  <%= submit_tag 'Envoyer' %>
  •  <%= end_form_tag %>
  • </div>

Comme vous pouvez le constater nous utilisons AJAX pour ajouter des commentaires, ainsi que des effets visuels pour faire disparaître le formulaire de saisie et faire défiler la page... et tout ceci fait partie intégrante de Rails.
Il ne nous reste plus qu'à définir la méthode ajout_commentaire dans notre contrôleur. Voici le code que vous pouvez copier dans app/controllers/blog_controller.rb, par exemple juste après la méthode destroy :

  • def ajout_commentaire
  • # le commentaire ne sera pas créé si il est vide
  • unless (params[:commentaire][:texte]).empty?
  •   c = Article.find(params[:id]).commentaires.create(params[:commentaire])
  •   render :partial => "commentaire", :locals => { :commentaire => c }
  • else
  •   render :nothing => true
  • end
  • end

Réglons quelques petits détails

Vous avez sans doute remarqué que dans la liste les articles sont affichés chronologiquement, les plus anciens en premier alors que le contraire serait tout de même plus pratique. Qu'à cela ne tienne, éditez le fichier app/controllers/blog_controller.rb et modifiez la méthode list comme ci-dessous :

  • def list
  •  @article_pages, @articles = paginate :articles,
  •   :per_page => 10,
  •   :order => 'id DESC'
  • end

Profitons-en aussi pour modifier le routage afin d'arriver par défaut sur la page de liste des articles, pour cela supprimez le fichier public/index.html, puis éditez le fichier config/routes.rb, décommentez la ligne # map.connect '', :controller => "welcome" et remplacez la par : map.connect '', :controller => "blog".

Et voila, c'est terminé !

C'est la fin de la première partie de ce tutoriel, certes notre application n'est pas encore totalement opérationnelle, en grande partie parce-que l'ajout et la suppression d'articles ne sont pas protégés mais nous verrons dans le prochain tutoriel comment mettre rapidement en place cette fonctionnalité, et bien d'autres encore.

En attendant, j'espère que cela vous aura donné envie d'aller plus loin avec Ruby on Rails. Mais avant de vous quitter je ne peux résister à l'envie de vous montrer une copie d'écran de la commande rake stats qui nous donne toutes sortes d'informations statistiques sur le code que nous venons de produire.

La ligne qui nous intéresse ici est Code LOC: 62 (LOC signifie Lines Of Code), il s'agit du nombre de lignes de code réel dans l'application (sans tenir compte des commentaires ou du code de test) et sur ces 62 lignes nous n'avons pas du en écrire plus d'une dizaine nous mêmes... C'est cette incroyable productivité, alliée à l'expressivité du langage Ruby qui ont fait le succès de Ruby on Rails.

En attendant la suite de ce tutoriel n'hésitez pas à vous rendre sur le portail RailsFrance et sur le site RubyFR où vous trouverez tout plein d'informations concernant Rails et Ruby.

blog

 

Captures d'écran du blog version finale :

blog

 

Retour

Propos recueillis par Laetitia Maraninchi.


A découvrir

 Le livre :

 L'interview de Laurent Julliard sur :

 Les ressources en ligne :

  • http://www.rubyonrails.org/ - le site officiel (en anglais).
  • http://www.railsfrance.org/ - le portail de la communauté francophone des utilisateurs de Ruby on Rails.
  • http://rubyfr.org - le site francophone dédié au développement avec le langage Ruby.
  • http://questionnaire.journaldunet.com/fiche/1492/3/index.html - un sondage sur RoR

A venir dans ce tutoriel

  • Protéger l'ajout d'article et les fonctions d'édition et suppression
  • Modifier les messages par défaut affichés en cas d'erreur de saisie
  • Rajouter des catégories et utiliser les migrations
  • Rajouter un moteur de recherche live avec AJAX
  • Tester les différents composants de notre application

Déjà en ligne

Téléchargements


mentions légales | conditions générales de vente | copyright © 2012
(1) livraison gratuite à partir de 49 € en France métropolitaine