Tutoriel Ruby on Rails (ROR)…

Installation

L'installation de Ruby On Rails se fait très simplement. Une fois Ruby installé, il suffit de taper la commande suivante dans la console :

gem install -y rails

Leopard (la toute nouvelle version de Mac OS X) inclu dejà Rails, il n'y a rien à faire. Cependant, sous Tiger, la version de Ruby n'inclue pas RubyGems et il sera donc préférable d'utiliser Locomotive pour développer avec Ruby On Rails.

Maintenant que Rails est installé, on va créer une nouvelle application. Nous voulons faire un gestionnaire de contzcts. Créons une application Rails qui aura pour nom mycontacts. Pour cela il faut aller en ligne de commande et taper la commande suivante :

rails mycontacts

Rails va créer toute une arborescence, c'est le squelette de l'application. Nous avons, entre autres, le répertoire app où sera stocké les modèles, les vues et les contrôleurs de notre application, le répertoire config pour la configuration et public pour tout ce qui est des fichiers statiques comme les images, les feuilles de styles, le Javascript...

Comme on va utiliser Rails 2.0 mais qu'il n'est pas encore sorti, il va falloir l'ajouter à notre application. Pour cela on va se placer à la racine du dossier créé et le télécharger :

cd mycontacts
rake rails:freeze:edge

L'application intègre un mini serveur web se nommant WEBrick. Pour le lancer le serveur :

ruby script/server

Le serveur est lancé sur le port 3000, on peut maintenant accéder au site avec l'URL suivante dans le navigateur : http://localhost:3000.

Configuration

La configuration est réduite à son minimum, la seule chose à faire est de configurer la connexion à la base de données. Rails est compatible avec un nombre important de SGBD : MySQL, PostgreSQL, SQLite, SQLServer, Oracle pour ne citer que les principaux. Le fichier pour cette configuration se nomme database.yml et se trouve dans le répertoire config. En ouvrant ce fichier, nous voyons 3 blocs : development, test, production. Ce sont les 3 différents modes de lancement de l'application. Le mode development étant celui qu'on va utiliser pour développer, test pour faire les tests et production sera à utiliser quand l'application sera mise en production sur le serveur. Ici nous n'utiliserons que le mode development avec MySQL :

development:
  adapter: mysql
  database: mycontacts
  username: root
  password:
  host: localhost
  encoding: utf8

Nous avons créé une base de données qui se nomme mycontacts sur le serveur MySQL. L'ajout de l'encodage en UTF-8 permet de rentrer les données dans la base de données dans cet encodage qui doit être utilisé pour les caractères spéciaux (accents et autres). En France, l'encodage par défaut est ISO-8859-1 mais il est recommandé maintenant d'utiliser UTF-8 pour une compatibilité optimale.

Modèle avec ActiveRecord

Maintenant que la base de données est configurée, il va falloir créer les tables. Nous voulons faire une gestion de contacts, on va créer une table pour les groupes et une table pour les contacts. Pour nommer nos tables et nos champs, il va falloir respecter une convention de nommage, cela nous évitera une configuration fastidieuse. Voici les principales conventions :

  • Le nom donné à la table doit être au pluriel
  • La clé primaire se nomme id
  • Une clé étrangère commence par le nom de la table, vers laquelle la clé pointe, au singulier se termine par _id
  • Un champ se finissant par _at est rempli automatiquement par une date et une heure

Maintenant que ces règles sont acquises, passons à nos tables. La nommage se fait de préférence en anglais (car sinon on perd l'avantage du convention over configuration). La première table va se nommer groups et la seconde contacts. Un groupe a un nom (name) et du texte (text) pour le décrire. Le contact a un nom, un numéro de téléphone, une adresse et une date de création (created_at). Le contact est relié à un groupe par une clé étrangère (group_id). On peut maintenant créer les modèles. Pour cela, toujours dans le répertoire de l'application :

ruby script/generate model group
ruby script/generate model contact

Les modèles ont comme nom le singulier du nom de la table. Ces modèles se trouvent dans le répertoire app/models. Voici le code du modèle Group :

class Group < ActiveRecord::Base
end

Cette classe fait la relation avec la table groups. C'est le principe ORM (Object/Relational Mapping). Il y a un mapping entre la table (groups) et la classe (Group). Les champs de la table sont des propriétés de la classe... Le modèle pour les éléments est du même ordre :

class Contact < ActiveRecord::Base
end

Nous avons donc nos 2 modèles et nous voulons les associer car nous avons mis une clé étrangère mais l'association doit être indiqué dans nos modèles. L'association est telle que : un groupe peut avoir plusieurs contacts et un contact appartient à un seule groupe. Pour coder cette association nous allons utiliser les méthodes de ActiveRecord : has_many et belongs_to :

class Group < ActiveRecord::Base
  has_many :contacts, :dependent => :destroy
end

Nous avons indiqué qu'une liste peut avoir plusieurs éléments et que si la liste est supprimée, les éléments de cette liste seront eux aussi supprimés. Passons maintenant au modèle Item :

class Contact < ActiveRecord::Base
  belongs_to :group
end

Et voilà, l'association est faite. Il existe d'autres types d'associations, mais nous n'en avons pas besoin ici. Mais au fait, nos tables ne sont toujours pas créées. A la création de nos modèles des fichiers dans le répertoire db/migrate ont été ajoutés. Ces fichiers vont nous permettre d'ajouter les tables à la base de données. Notre fichier 001_create_groups.rb sera comme cela :

class CreateGroups < ActiveRecord::Migration
  def self.up
    create_table :groups do |t|
      t.string :name
			t.string :text
			t.datetime :created_at
    end
  end

  def self.down
    drop_table :groups
  end
end

Et voici pour le fichier 002_create_contacts.rb :

class CreateContacts < ActiveRecord::Migration
  def self.up
    create_table :contacts do |t|
      t.integer :group_id
      t.string :name
			t.string :phone_number
			t.string :address
    end
  end

  def self.down
    drop_table :contacts
  end
end

Les fichiers de migration vont permettre de mettre à jour facilement notre base de données durant la phase de développement. A chaque ajout, suppression, modification de colonne ou même de table, un fichier de migration devra être créé et il suffira ensuite d'une petite ligne de commande pour mettre à jour la base de données :

rake db:migrate

Nos tables ont été créées ! C'est très pratique pour versionner sa base de données, on peut revenir en arrière, avoir une version différente en développement et en production... A utiliser absolument !

Contrôleurs et routes

Tout d'abord réfléchissons un peu sur notre application. Il faudra avoir une page avec tous les groupes et avoir la possibilité d'en ajouter, de les modifier et de les supprimer. Et quand on clique sur un groupe, cela mènera vers les éléments de cette liste et on pourra en ajouter, les modifier et terminer les éléments.

Nos modèles sont faits, nous allons maintenant faire nos différentes actions. Nous aurons 2 contrôleurs : groups pour les actions sur les groupes et contacts pour les actions sur les contacts. Pour les créer, c'est comme pour les modèles :

ruby script/generate controller groups
ruby script/generate controller contacts

Nos 2 contrôleurs se trouvent dans app/controllers. Rails gère automatiquement l'URL Rewriting, pour y accéder, on aura juste à taper l'URL avec le nom du contrôleur à la fin. Par exemple : http://localhost:3000/lists mènera à l'action index (qui n'existe pas encore) de notre contrôleur groups. Dans Rails 1.2, une nouveauté très intéressante a été intégrée : Simply RESTful. Ca nous permet de réaliser très facilement une application respectant l'approche REST (ressource identifiable par une URI, importance des méthodes, opération sans état et utilisation des standards) et le pattern CRUD (Create, Read, Update, Delete) et ce en quelques lignes. L'utilisation de ces principes est maintenant assez répandue dans la communauté Rails mais ils ne sont pas forcément idéals pour tous les cas de figure. Pour celui-là c'est plutôt recommandé. La première chose à faire est d'éditer nos routes dans le fichier config/routes.rb :

ActionController::Routing::Routes.draw do |map|
  map.resources :groups, :has_many => [ :contacts ]
end

Voici nos routes faites. Nous créons les routes des ressources en rapport avec groups puis celles en rapport avec contacts. On remarquera le has_many comme pour le modèles. Cela sert ici à accéder à une action du contrôleur contacts en passant en paramètre l'ID du groupe auquel il appartient. Cela permet d'avoir une système d'URL cohérent. Les URL auxquelles on peut accéder sont les suivantes :

  • /groups
  • /groups/1
  • /groups/new
  • /groups/1/edit
  • /groups/1/contacts
  • /groups/1/contacts/3
  • /groups/1/contacts/new
  • /groups/1/contacts/1/edit

Les URL sont explicites, /groups permet d'avoir tous les groupes, /groups/new permet d'avoir le formulaire pour ajouter un groupe, /groups/1/edit permet d'avoir le formulaire pour éditer le groupe qui a pour ID 1, /groups/1/contacts pour avoir la liste des contacts du groupe qui a pour ID 1... Un système CRUD très logique en somme. En fonction de la méthode utilisée pour la requête (GET, POST, PUT ou DELETE) les URL /groups/1 et /groups/1/contacts/3 réagiront différemment. GET permet la récupération de la ressource, POST l'ajout d'une ressource (dans ce cas, il n'y a pas d'ID à mettre), PUT la mise à jour d'une ressource et DELETE la suppression d'une ressource. Au lieu d'avoir 4 URL différentes, nous en avons donc une seule.

Les routes sont faites, mais impossible encore d'y accéder car les actions ne sont pas faites. Les actions sont juste les méthodes des contrôleurs. Une action hello dans le contrôleur groups sera accessible par l'URL /groups/hello et la vue affichée sera hello.erb.html dans le répertoire app/views/groups. Dirigeons nous donc maintenant dans les contrôleurs. Il y a en tout 7 actions pour nos URLs :

  • index (/groups avec GET)
  • show (/groups/1 avec GET)
  • new (/groups/new)
  • create (/groups avec POST)
  • edit (/groups/1/edit)
  • update (/groups/1 avec PUT)
  • destroy (/groups/1 avec DELETE)

Pour appeler ces URLs dans notre contrôleur ou nos vues, Rails génère automatiquement plusieurs méthodes pour ne pas à avoir à écrire les URLs à la main :

  • groups_path
  • group_path(1)
  • new_group_path
  • edit_group_path

Je pense que c'est explicite alors passons à notre contrôleur. Voici à quoi va ressembler le contrôleur groups :

class GroupsController < ApplicationController
  def index
    @groups = Group.find :all, :order => "name ASC"
  end
 
  def new
    @group = Group.new
  end
 
  def create
    @group = Group.new params[:list]
    if @group.save
      flash[:notice] = "Le groupe a été créé avec succès"
     
      redirect_to groups_path
    else
      render :action => "new"
    end
  end
 
  def edit
    @group = Group.find params[:id]
  end
 
  def update
    @group = Group.find params[:id]
   
    if @group.update_attributes params[:group]
      flash[:notice] = "Le groupe a été modifié avec succès"
     
      redirect_to groups_path
    else
      render :action => "edit"
    end
  end
 
  def destroy
    @group = Group.find params[:id]
    @group.destroy
   
    redirect_to groups_path
  end
end

Les différentes actions sont créées. Notons que l'action show n'a pas été faite car elle n'est pas nécessaire. Nous utilisons ici le modèle Group. Voici quelques méthodes de ActiveRecord utilisées :

  • find pour récupérer un ou plusieurs enregistrements
  • new pour créer un nouvel objet
  • save pour enregistrer dans la base de données ce nouvel objet
  • update_attributes pour mettre à jour les champs
  • destroy pour détruire l'enregistrement

Ces méthodes vont envoyer la requête SQL correspondante au serveur. Plus besoin de connaitre le SQL, tout est Ruby ! A noter que le champ created_at sera automatiquement rempli à la création d'un enregistrement dans la base de données. Ce qui est très pratique car on devrait sinon le faire par une ligne de code supplémentaire dans l'action create de notre contrôleur :

def create
  @group = Group.new params[:list]
	@group.created_at = Time.now
  if @group.save
    flash[:notice] = "Le groupe a été créé avec succès"
   
    redirect_to groups_path
  else
    render :action => "new"
  end
end

C'est un peu inutile quand c'est toujours comme ça. Encore une fois, Rails emploi le principe convention over configuration et permet d'automatiser l'ajout des dates pour la création (created_at) et la modification (updated_at) d'un enregistrement si les champs existent dans la table.

La variable params stocke les informations qui sont envoyées. C'est avec cette variable que l'on pourra récupérer les informations envoyées par un formulaire ou celles qui sont passées à l'URL.

flash[:notice] permet de stocker un message dans une session pour pouvoir l'afficher sur la page d'après (puisque l'on fait une redirection, ce sera sur la page de redirection).

La méthode render permet d'afficher une vue spécifique. S'il n'y a aucun rendu avant la fin de l'action, la vue ayant le nom de l'action sera affichée.

Le contrôleur contacts aura la même structure à la différence qu'on utilisera le modèle Contact et non Group. De plus, il faudra lui ajouter une méthode pour récupérer le bon groupe à chaque appel de ce contrôleur (pour récupérer ensuite les contacts attachés au groupe).

class ContactController < ApplicationController
  before_filter :find_group
 
  def index
    @contacts = Contact.find :all, :order => "name ASC"
  end
 
  def new
    @contact = Contact.new
  end
 
  def create
    @contact = Contact.new params[:item]
    @contact.group = @group
    if @contact.save
      flash[:notice] = "Le contact a été créé avec succès"
     
      redirect_to contacts_path(@group)
    else
      render :action => "new"
    end
  end
 
  def edit
    @contact = Contact.find params[:id]
  end
 
  def update
    @contact = Contact.find params[:id]
   
    if @contact.update_attributes params[:contact]
      flash[:notice] = "Le contact a été modifié avec succès"
     
      redirect_to contacts_path(@contact)
    else
      render :action => "edit"
    end
  end

	def destroy
    @contact = Contact.find params[:id]
    @contact.destroy
   
    redirect_to contacts_path
  end
 
  private
 
  def find_group
    @group = Group.find params[:group_id]
  end
end

Pour appeler une méthode à chaque chargement de la classe, on utilise la méthode before_filter avec le nom de la méthode que l'on veut appeler. Nous appelons la méthode find_group et comme cette méthode n'est pas une action, il faudra modifier sa visibilité.

Vues et Helpers

Les vues sont stockées dans app/views. Il y a trois répertoires : groups, contacts, layouts. groups correspond aux vues qui serviront à l'affichage des actions du contrôleur groups, et c'est la même histoire pour contacts. Le répertoire layouts permet de stocker le squelette des pages que l'on va faire. Ce squelette permettra de ne pas répéter dans chaque vue, les essentiels de la page : la section , une barre latérale si on veut en mettre... Nous devons appeler notre layout application.erb.html pour ne pas à avoir à le configurer ensuite dans les contrôleurs (toujours ces conventions) :

<!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" xml:lang="fr" lang="fr">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Gestion de mes contacts</title>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield %>

</body>
</html>

L'écriture est similaire à ce qu'on peut voir dans les autres langages. Nous avons le HTML et les tags <% %> et <%= %> servant à exécuter du Ruby. <%= flash[:notice] %> permet l'affichage du message que l'on a mis dans nos contrôleurs et <%= yield %> représente la vue qui sera insérée dans ce squelette. Passons aux vues de groups. Il y en a 3 : index.erb.html, new.erb.html, edit.erb.html. L'extension des vues HTML est .erb.html (le système de template ERb puis le type de rendu, ici HTML). new.erb.html et edit.erb.html auront les même champs pour ce qui est du formulaire. Pour éviter d'écrire 2 fois le formulaire, nous allons le mettre dans un partiel _form.erb.html. Il ne peut être appelé qu'avec render, ça ne peut pas être la vue d'une action. Les partiels sont préfixés par _. Commençons donc par construire les 2 formulaires :

<% form_for @group do |f| %>
    <%= render :partial => "form", :object => f %>
    <%= submit_tag 'Ajouter' %>
<% end %>
<% form_for @group do |f| %>
    <%= render :partial => "form", :object => f %>
    <%= submit_tag 'Editer' %>
<% end %>

Le premier code est le formulaire de new.erb.html et le deuxième code le formulaire de edit.erb.html. Nous créons un bloc générant un formulaire en y indiquant la variable de notre contrôleur qui créera directement tout le nécessaire pour le formulaire. Ensuite nous insérons le bouton de validation avec le texte adéquat. Enfin, nous appelons le partiel qui contiendra les champs qui sont communs aux 2 formulaires. Nous passons la variable f à notre partiel pour pouvoir l'exploiter :

<p>Nom du groupe : <%= form.text_field :name %></p>
<p>Description : <%= form.text_field :text %></p>

L'objet f prend le nom form dans le partiel. Il permet de générer les champs avec la convention de nommage nécessaire pour manipuler les données du formulaire dans le contrôleur. Prenons la vue new.erb.html, le code HTML résultant est le suivant :

<form action="/groups" method="post">
    <p>Nom du groupe : <input id="group_name" name="group[name]" size="30" type="text" /></p>
		<p>Texte : <input id="group_name" name="group[text]" size="30" type="text" /></p>
    <input name="commit" type="submit" value="Ajouter" />
</form>

Tout se génère correctement avec un code valide. Pour ce qui est de la vue index.erb.html, ce n'est qu'une simple liste :

<h1>Mes groupes</h1>
<ul>
<% @groups.each do |group| %>
    <li><%= link_to group.name, groups_path(group) %> - <%= link_to "Editer", edit_group_path(group) %> - <%= link_to "Supprimer", group_path(group), :method => :delete %></li>
<% end %>
</ul>
<%= link_to "Ajouter un groupe", new_group_path %>

Nous affichons tous les groupes avec un lien pour éditer le groupe et un pour le supprimer (il ne faut pas oublier de spécifier la bonne méthode pour ce lien). En cliquant sur le groupe, on arrive sur la liste des éléments. Passons maintenant aux vues de contacts. Il y aura les mêmes vues : index.erb.html, new.erb.html, edit.erb.html et le partiel _form.erb.html. Je ne détaillerais ici que la vue index.erb.html car toutes les vues sont similaires à celles du contrôleur groups.

<h1>Mes contacts du groupe "<%= @group.name %>"</h1>
<ul>
<% @contacts.each do |contact| %>
    <li><%= link_to contact_info(contact), edit_contact_path(@group, contact) %> - <%= link_to "Supprimer", contact_path(contact), :method => :delete %></li>
<% end %>
</ul>
<%= link_to "Ajouter un contact au groupe", new_contact_path %>

Nous avons donc ici les contacts d'un groupe avec la possibilité d'en ajouter un et d'éditer celui que l'on veut. Petite particularité, la méthode contact_info. Cette méthode permet d'afficher les différentes informations du contact. Elle n'a pas été générée par Rails, mais doit être créée en tant que helper. Les helpers se trouvent dans le répertoire app/helpers. Nous sommes dans les vues correspondant au contrôleur contacts, le fichier de helper est donc contacts_helper.rb et il faudra ajouter la méthode suivante :

module ContactsHelper
  def contact_info contact
    "#{contact.name} (#{contact.phone_number} - #{contact.address})" 
  end
end

Mettre toutes ces informations directement dans la vue dégraderait un peu la lisibilité du code, il est donc préférable dans ce cas de le mettre dans un helper.

Conclusion

On dirait pas comme ça et pourtant l'application est déjà terminée. Il aura fallu à peine 150 lignes de code (en comptant les vues !) ! Et avec quelques techniques avancées on pourrait arriver à même pas 100 lignes, mais pour rester clair j'ai préféré en rester aux bases. C'est là qu'on se rend compte que Ruby On Rails permet de faire les choses très rapidement. On comprend vite les mécanismes et la progression est très rapide. Tout est en fait simple à comprendre et à mettre en application. Le code reste très lisible et la maintenance est donc facilitée. De nos jours, il est indispensable d'avoir des outils permettant d'avoir une grande productivité tout en étant solide et RoR rempli ce rôle à merveille.

L'article n'aborde ici que les bases du framework, il y a bien d'autres choses à voir comme l'intégration des web services, du développement par tests, de l'Ajax et du système de plugins permettant d'étendre les possibilités de Rails. J'ai estimé que je mettais au minimum 3 à 4 fois moins de temps à développer un site avec Ruby On Rails qu'avec PHP et les librairies que j'avais développé pour mon utilisation depuis des années. C'est dû à 2 choses, Rails intègre beaucoup plus de choses que ce que je pourrais créer en PHP et Ruby est un langage plus simple à retenir et à écrire que PHP (ce qui est mon propre point de vue).

Pour plus d'informations sur Ruby On Rails et sur le développement Web en général, vous pouvez consulter mon blog, Boldr.