Retrouvez cet article dans : Linux Magazine Hors série 33
1. Présentation de Ruby on Rails
Si nous regardons comment ont été développés et mis au point la majorité des frameworks, nous remarquons souvent qu’il s’agit d’un développement qui n’est, à la base, pas centré sur un besoin spécifique, mais sur l’envie d’un ou plusieurs développeurs de fournir une nouvelle solution à des problématiques génériques. Ce n’est pas le cas de Ruby on Rails. À l’origine, l’idée n’était pas de fournir un framework, mais de créer un site de gestion de projets : Basecamp [1]. Ce développement a été réalisé par David Heinemeier Hansson pour la société 37signals. Ce n’est que plus tard qu’il a été décidé d’extraire de Basecamp les principes mis au point pour son développement et de les rendre indépendants dans un framework. Ce travail a abouti à la publication de la première release publique, sous licence libre, de Ruby on Rails en juillet 2004. Il aura par la suite fallu un an et demi de travail pour mettre au point la version 1.0 poussé par une communauté grandissante et très rapidement convaincue par la puissance du framework. Nous sommes aujourd’hui à la version 1.2.4 qui préfigure la version 2.0. Ruby on Rails est donc né avec un passif basé sur des besoins réels et non en extrapolant les besoins auxquels il pourrait répondre et les utilisations qui en découleraient. Si certains pouvaient alors penser que Ruby on Rails a été développé pour répondre à des besoins spécifiques, il s’avère en fait que le framework est suffisamment bien pensé pour pouvoir répondre à tous les besoins d’une application Web moderne, voire bien plus... Bien plus, parce qu’il est constitué d’un ensemble de composants que le découpage rend utilisables bien au-delà du simple développement Web, et ayant chacun un rôle bien précis. Il y a six grands composants dans Rails. Les deux premiers ne sont absolument pas spécifiques au développement Web :ActiveRecord. Ce module regroupe tout un ensemble de classes permettant de faire de l’ORM (Object Relational Mapping). Ainsi, avecActiveRecord, vous pouvez oublier le SQL et ne penser à vos données qu’en qualité d’objet. L’ORM n’est pas l’apanage des développements Web, et, vous pouvez, sans rougir, utiliserActiveRecorden dehors de Rails. RubyCocoa, par exemple, utilise ce module depuis peu afin de permettre aux développeurs Mac de mimer une utilisation de type CoreData... Passons ;)
ActiveSupportpropose, quant à lui, tout un ensemble d’extensions pour la bibliothèque standard du langage. Nous y trouverons des extensions pour la manipulation des tableaux, des hachages, des entiers, des chaînes de caractères... Là encore, vous pouvez en faire bénéficier tous vos développements sans restriction. Par exemple, si vous souhaitez savoir quelle heure il était il y a 16 minutes, vous pouvez faire ceci :
require ‘active_support’ 16.minutes.ago # => Fri Oct 05 23:36:00 0200 2007 puts Time::now # => Fri Oct 05 23:52:15 +0200 2007Les trois autres modules sont eux intimement liés à Rails et sont en fait constitués d’un ensemble de classes servant à simplifier l’écriture de code dans une application Rails.
ActionViewpropose tout un ensemble de helpers. Nous explorerons certains d’entre eux dans cet article. Sachez que si vous voulez faire de l’AJAX, c’est dans la documentation d’ActionViewqu’il faudra piocher !ActionControllernous simplifiera énormément la vie quand nous créerons nos contrôleurs.ActionMailerpermet de faciliter l’envoi et la réception de mails.ActionWebServicenous permet de créer des WebServices de façon ultra simple.
2. Installation
Tout au long de cet article, nous allons nous amuser à développer une application de gestion de contacts. Nous allons, en fait, nous limiter, dans un premier temps, à créer un " catalogue " de personnes avec, pour chacune, son nom, son prénom, son adresse mail et son numéro de téléphone. Dans sa première version, notre application sera relativement spartiate, ce qui ne veut pas dire qu’elle ne devra pas rendre les services minimums que sont l’ajout, la modification et la suppression de contacts, plus un moyen d’en afficher la liste complète. Avant de commencer, il faut bien entendu installer Rails. Pour cela, je vous conseille d’utiliser RubyGems en précisant que vous souhaitez installer les dépendances :$ sudo gem install rails -y Bulk updating Gem source index for: http://gems.rubyforge.org Successfully installed rails-1.2.4 Successfully installed activesupport-1.4.3 Successfully installed activerecord-1.15.4 Successfully installed actionpack-1.13.4 Successfully installed actionmailer-1.3.4 Successfully installed actionwebservice-1.2.4 Installing ri documentation for activesupport-1.4.3... Installing ri documentation for activerecord-1.15.4... Installing ri documentation for actionpack-1.13.4... Installing ri documentation for actionmailer-1.3.4... Installing ri documentation for actionwebservice-1.2.4... Installing RDoc documentation for activesupport-1.4.3... Installing RDoc documentation for activerecord-1.15.4... Installing RDoc documentation for actionpack-1.13.4... Installing RDoc documentation for actionmailer-1.3.4... Installing RDoc documentation for actionwebservice-1.2.4... $Comme vous pouvez le voir, l’installation de rails a entraîné l’installation des packages
$ rails address_book
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
...
create log/server.log
create log/production.log
create log/development.log
create log/test.log
$
Nous reviendrons un peu plus tard sur les fichiers générés et leur organisation. Pour le moment, nous allons nous concentrer sur l’essentiel.
3. Base de données et migration
La première chose que nous devons faire maintenant consiste à créer le schéma de notre base de données. Ceci implique de savoir quelle base nous voulons utiliser. Rails est très ouvert sur le sujet, et vous avez, en standard, le choix entre MySQL, PostgreSQL, Oracle ou SQLite (2 ou 3). Mais si vous préférez SQL Server (sic), Informix, DB2, Firebird, Sybase ou toute autre base, il faudra installer l’adaptateur correspondant. Vous trouverez toutes les informations nécessaires sur le site [2] de Ruby on Rails. Dans cette présentation, nous ferons le choix d’SQLite3. Cette base est minimaliste, mais couvre largement nos besoins. Le paramétrage de la base de données se fait au moyen du fichier# MySQL (default setup). Versions 4.1 and 5.0 are recommended. # development: adapter: mysql database: address_book_development username: root password: host: localhost # Warning: The database defined as ‘test’ will be erased and # re-generated from your development database when you run ‘rake’. # Do not set this db to the same as development or production. test: adapter: mysql database: address_book_test username: root password: host: localhost production: adapter: mysql database: address_book_production username: root password: host: localhostComme vous pouvez le voir, ce fichier est en YAML [3]. Je vous laisse consulter la documentation de ce langage. Mais ne perdez pas trop de temps, car c’est la seule fois que nous allons voir un tel fichier et il est suffisamment lisible pour ne pas nous imposer de devenir un spécialiste du domaine. Rails nous a préparé une configuration pour MySQL. C’est en effet la base " par défaut ". Nous allons donc modifier le fichier en précisant que nous utilisons l’adaptateur sqlite3. Nous allons également indiquer quels sont les fichiers de base de données. Voici à quoi doit ressembler ce fichier après modification.
development: adapter: sqlite3 database: db/address_book_development.sqlite3 test: adapter: sqlite3 database: db/address_book_test.sqlite3 production: adapter: sqlite3 database: db/address_book_production.sqlite3Sachez que nous aurions très bien pu demander à Rails de créer directement un fichier de paramétrage pour sqlite3. Il suffit pour cela d’utiliser l’option
rails address_book -d sqlite3Nous aurions ainsi économisé l’édition et la modification de ce fichier. Parfois, chaque seconde compte ;) Maintenant que nous avons précisé quelle base nous voulons utiliser, nous pouvons créer le schéma. Nous avons besoin pour le moment d’une seule table dans laquelle nous allons stocker nos contacts. Habituellement, pour créer un schéma de base de données, nous devons avoir les notions de SQL nécessaires à l’écriture d’un script de création de nos tables. Avec Ruby on Rails, nous allons nous contenter de code Ruby. De plus, comme je l’ai signalé un peu plus haut, Rails utilise les principes de l’ORM. Il nous faut donc également un objet faisant le mapping avec notre table. Pour créer ces différents éléments, nous allons utiliser la commande
$ ruby script/generate model contact
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/contact.rb
create test/unit/contact_test.rb
create test/fixtures/contacts.yml
create db/migrate
create db/migrate/001_create_contacts.rb
$
Le seul fichier que nous allons regarder pour le moment est class CreateContacts < ActiveRecord::Migration
def self.up
create_table :contacts do |t|
end
end
def self.down
drop_table :contacts
end
end
Il n’y a pour le moment pas grand-chose dans ce fichier. Il contient en fait une déclaration de classe ($ rake db:migrate (in /Users/greg/Desktop/address_book) == CreateContacts: migrating ================================== -- create_table(:contacts) -> 0.0871s == CreateContacts: migrated (0.0872s) ========================= $Voilà, nous venons de créer notre table contacts dans la base SQL. Vous pouvez le vérifier :
$ sqlite3 db/address_book_development.sqlite3 <<< .dump
BEGIN TRANSACTION;
CREATE TABLE schema_info (version integer);
INSERT INTO "schema_info" VALUES(1);
CREATE TABLE contacts ("id" INTEGER PRIMARY KEY NOT NULL);
COMMIT;
$
Conformément à ce que nous avons, ou plutôt ce que nous n’avons pas, écrit dans le fichier class CreateContacts < ActiveRecord::Migration
def self.up
create_table :contacts do |t|
t.column :nom, :string
t.column :prenom, :string
t.column :email, :string
t.column :telephone, :string
end
end
def self.down
drop_table :contacts
end
end
L’ajout des colonnes se fait en utilisant la méthode $ rake db:migrate
(in /Users/greg/Desktop/address_book)
$ sqlite3 db/address_book_development.sqlite3 <<< .dump
BEGIN TRANSACTION;
CREATE TABLE schema_info (version integer);
INSERT INTO "schema_info" VALUES(1);
CREATE TABLE contacts ("id" INTEGER PRIMARY KEY NOT NULL);
COMMIT;
$
Rien ! Pourquoi ?
Simplement parce que nous avions déjà une table contacts. En effet, Rails utilise un principe de migration qui permet de " versionner " le schéma de la base de données. C’est le sens de la table $ ruby script/generate migration contact_informations
exists db/migrate
create db/migrate/002_contact_informations.rb
$
Le paramètre passé après class ContactInformations < ActiveRecord::Migration
def self.up
create_table :contacts do |t|
t.column :nom, :string
t.column :prenom, :string
t.column :email, :string
t.column :telephone, :string
end
end
def self.down
end
end
Relançons la demande de migration :
$ rake db:migrate
(in /Users/greg/Desktop/address_book)
== ContactInformations: migrating =============================================
-- create_table(:contacts)
rake aborted!
SQLite3::SQLException: table contacts already exists: CREATE TABLE contacts ("id" INTEGER PRIMARY KEY NOT NULL, "nom" varchar(255) DEFAULT NULL, "prenom" varchar(255) DEFAULT NULL, "email" varchar(255) DEFAULT NULL, "telephone" varchar(255) DEFAULT NULL)
(See full trace by running task with –trace)
$
Ca plante ! Et c’est tout à fait normal, car la table class ContactInformations < ActiveRecord::Migration
def self.up
create_table :contacts, :force => true do |t|
t.column :nom, :string
t.column :prenom, :string
t.column :email, :string
t.column :telephone, :string
end
end
def self.down
end
end
Prenez la bonne habitude d’ajouter systématiquement cette option. D’ailleurs, modifiez tout de suite le fichier $ rake db:migrate
(in /Users/greg/Desktop/address_book)
== ContactInformations: migrating =============================================
-- create_table(:contacts, {:force=>true})
-> 0.5288s
== ContactInformations: migrated (0.5289s) ====================================
$ sqlite3 db/address_book_development.sqlite3 <<< .dump
BEGIN TRANSACTION;
CREATE TABLE schema_info (version integer);
INSERT INTO "schema_info" VALUES(2);
CREATE TABLE contacts ("id" INTEGER PRIMARY KEY NOT NULL, "nom" varchar(255) DEFAULT NULL, "prenom" varchar(255) DEFAULT NULL, "email" varchar(255) DEFAULT NULL, "telephone" varchar(255) DEFAULT NULL);
COMMIT;
$
Vous avez certainement compris que les noms des fichiers contenus dans le répertoire class ContactInformations < ActiveRecord::Migration
def self.up
create_table :contacts, :force => true do |t|
t.column :nom, :string
t.column :prenom, :string
t.column :email, :string
t.column :telephone, :string
end
end
def self.down
remove_column :contacts, :nom
remove_column :contacts, :prenom
remove_column :contacts, :email
remove_column :contacts, :telephone
end
end
Si nous voulons maintenant repasser notre schéma en version 1, il suffit de faire la chose suivante :
$ rake db:migrate VERSION=1
(in /Users/greg/Desktop/address_book)
== ContactInformations: reverting =============================================
-- remove_column(:contacts, :nom)
-> 0.1785s
-- remove_column(:contacts, :prenom)
-> 0.1243s
-- remove_column(:contacts, :email)
-> 0.2462s
-- remove_column(:contacts, :telephone)
-> 0.1576s
== ContactInformations: reverted (0.7078s) ====================================
$ sqlite3 db/address_book_development.sqlite3 <<< .dump
BEGIN TRANSACTION;
CREATE TABLE schema_info (version integer);
INSERT INTO "schema_info" VALUES(1);
CREATE TABLE contacts ("id" INTEGER PRIMARY KEY NOT NULL);
COMMIT;
C’est gagné ! Je vous laisse repasser seul en version 2.
4. ActiveRecord
Passons maintenant au fichier$ ruby script/console Loading development environment. >>Nous nous retrouvons sous un shell Ruby dans lequel tous les objets Rails sont accessibles, y compris ceux que nous avons créés pour notre application. Nous pouvons donc utiliser la classe
>> >> c = Contact.new(
?> :nom => "Lejeune",
?> :prenom => "Gregoire",
?> :telephone => "0123456789",
?> :email => "gregoire.lejeune@free.fr"
>> )
=> #<Contact:0x34aee8c @new_record=true, @attributes={"nom"=>"Lejeune", "prenom"=>"Gregoire", "telephone"=>"0123456789", "email"=>"gregoire.lejeune@free.fr"}
>>
Attention
Ne vous méprenez pas, nous n’avons encore rien modifié au niveau de la base de données. Nous nous sommes contentés de créer un nouvel objet >> c.save => true >>Maintenant, nous avons ajouté notre enregistrement dans la table. Nous pouvons vérifier cela en demandant à retrouver tous les enregistrements de la table
>> c2 = Contact.find( :all )
=> [#<Contact:0x34a3ca8 @attributes={"nom"=>"Lejeune", "prenom"=>"Gregoire", "id"=>"1", "telephone"=>"0123456789", "email"=>"gregoire.lejeune@free.fr"}]
>>
La méthode de classe >> puts c2[0][:prenom] Gregoire => nilAvec
>> Contact.find( :all, :conditions => "nom like ‘lej%’" )
=> [#<Contact:0x349e168 @attributes={“nom”=>”Lejeune”, “prenom”=>”Gregoire”, “id”=>”1”, “telephone”=>”0123456789”, “email”=>”gregoire.lejeune@free.fr”}]
>>
Comme vous pouvez le voir, l’écriture des conditions utilise une syntaxe SQL. Il existe de nombreuses autres options qui vous permettront d’être aussi fin que vous en avez besoin. Cependant, dans certains cas, vous pourrez avoir besoin de faire des requêtes complexes avec jointures et autres plaisirs. Bien entendu, vous pouvez vous amuser à trier, croiser... vous-même les données issues de plusieurs recherches simples. Mais, vous pouvez faire plus simple grâce à la méthode >> Contact.update( 1, { :telephone => "9876543210", :email => "gregoire.lejeune@gmail.com" } )
=> #<Contact:0x3487b70 @errors=#<ActiveRecord::Errors:0x34872d8 @errors={}, @base=#<Contact:0x3487b70 ...>, attributes{"nom"=>"Lejeune", "prenom"=>"Gregoire", "id"=>"1", "telephone"=>"9876543210", "email"=>"gregoire.lejeune@gmail.com"}
>> c2 = Contact.find( :all )
=> [#<Contact:0x347b1a4 @attributes={"nom"=>"Lejeune", "prenom"=>"Gregoire", "id"=>"1", "telephone"=>"9876543210", "email"=>"gregoire.lejeune@gmail.com"}]
>>
Dans cet exemple, nous avons considéré ne pas avoir d’objet >> c = Contact.find( :first )
=> #<Contact:0x3492674 @attributes={"nom"=>"Lejeune", "prenom"=>"Gregoire", "id"=>"1", "telephone"=>"0123456789", "email"=>"gregoire.lejeune@free.fr"}
>> c.email = "gregoire.lejeune@gmail.com"
=> "gregoire.lejeune@gmail.com"
>> c.save
=> true
>> c = Contact.find( :first )
=> #<Contact:0x3485514 @attributes={“nom”=>”Lejeune”, “prenom”=>”Gregoire”, “id”=>”1”, “telephone”=>”0123456789”, “email”=>”gregoire.lejeune@gmail.com”}
>>
Enfin, pour supprimer un enregistrement, il faut appeler la méthode de classe >> Contact.delete( 1 ) => 1 >> Contact.find( :all ) => [] >>Il est, bien entendu, possible de supprimer directement un enregistrement déjà en notre possession en lui appliquant la méthode
>> c = Contact.find( 1 )
=> #<Contact:0x347c0cc @attributes={"nom"=>"Lejeune", "prenom"=>"Gregoire", "id"=>"1", "telephone"=>"0123456789", "email"=>"gregoire.lejeune@gmail.com"}
>> c.destroy
=> #<Contact:0x347c0cc @attributes={“nom”=>”Lejeune”, “prenom”=>”Gregoire”, “id”=>”1”, “telephone”=>”0123456789”, “email”=>”gregoire.lejeune@gmail.com”}
>>
Soyez absolument certain que ce que nous venons de voir ne représente qu’une toute petite partie des possibilités offertes par 5. Scaffolding
Revenons à notre application. Nous avons un modèle, mais toujours aucune interface humainement utilisable ! Nous avons déterminé que nous voulions pouvoir ajouter, modifier, supprimer et lister des contacts. En restant classique, et donc en oubliant le Web 2.0 pour le moment, nous pouvons donc dire qu’il nous faut autant de pages. Et encore, la suppression ne nécessite pas une interface à elle toute seule. Si nous savons que chaque page est décrite dans une vue, il faut donc créer 3 vues. Nous avons également besoin d’un contrôleur dans lequel placer la logique de notre application. Tout ceci peut être fait en utilisant le script ruby script/generate scaffold contact
exists app/controllers/
exists app/helpers/
create app/views/contacts
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/contact.rb
identical test/unit/contact_test.rb
identical test/fixtures/contacts.yml
create app/views/contacts/_form.rhtml
create app/views/contacts/list.rhtml
create app/views/contacts/show.rhtml
create app/views/contacts/new.rhtml
create app/views/contacts/edit.rhtml
create app/controllers/contacts_controller.rb
create test/functional/contacts_controller_test.rb
create app/helpers/contacts_helper.rb
create app/views/layouts/contacts.rhtml
create public/stylesheets/scaffold.css
$
Si vous regardez ce qui vient de se passer, vous noterez que Rails a généré un contrôleur (app/views/contacts/list.rhtmlprésente la liste des contacts enregistrés dans notre base.app/views/contacts/new.rhtmlpermet de créer un nouveau contact.app/views/contacts/edit.rhtmlpermet d’éditer un contact existant.app/views/contacts/show.rhtmlpermet de visualiser les informations d’un contact.
$ ruby script/server => Booting Mongrel (use ‘script/server webrick’ to force WEBrick) => Rails application starting on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server ** Starting Mongrel listening at 0.0.0.0:3000 ** Starting Rails with development environment... ** Rails loaded. ** Loading any Rails specific GemPlugins ** Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart). ** Rails signals registered. HUP => reload (without restart). It might not work well. ** Mongrel available at 0.0.0.0:3000 ** Use CTRL-C to stop.Dans votre navigateur, connectez-vous à l’adresse http://localhost:3000/contacts et réjouissez-vous. Si vous comptez bien, vous venez de faire votre première application avec Ruby on Rails en tapant 4 commandes, en modifiant 3 fichiers et en écrivant 10 lignes de code. Les explications sur les migrations ne comptent pas ;) Remarquez pourtant que le résultat n’est pas très beau. En effet, le scaffolding traduit en français, c’est du prototypage ! Donc, nous venons de prouver que le modèle est correct, que cela fonctionne comme on l’espérait, mais il faut maintenant rendre tout cela joli. C’est sans doute la raison pour laquelle on a inventé les web designers me direz-vous... Oui et non. En effet, dans des cas très simples, comme c’est le cas avec notre application, un peu de travail de CSS et nous pouvons avoir quelque chose de tout à fait acceptable. Certes, très proche de ce que l’on faisait il y a encore 5 ans, mais utilisable tout de même. Dans la " vraie " vie, il ne faut pas abuser du scaffolding et ne l’utiliser qu’à des fins de validation des concepts. Vous pouvez à la rigueur vous en servir comme base, mais vous constaterez souvent que votre code final sera très loin de ce qu’il était à l’origine. Reprenons donc tout depuis le début et faisons une belle application.
6. ActionPack
Nous avons besoin de trois pages : une pour ajouter un contact, une pour modifier un contact et une affichant le contenu de notre carnet d’adresses. Pour la partie " intelligente " de l’application, nous utiliserons un contrôleur. Pour mettre en place le squelette de ces éléments, nous allons encore une fois utiliser le scriptlistpour gérer la liste des contacts ;addpour l’ajout d’un contact ;modifypour la modification d’un contact ;deletepour la suppression d’un contact.
$ ruby script/generate controler gestion_contacts list add modify delete
exists app/controllers/
exists app/helpers/
create app/views/gestion_contacts
exists test/functional/
create app/controllers/gestion_contacts_controller.rb
create test/functional/gestion_contacts_controller_test.rb
create app/helpers/gestion_contacts_helper.rb
create app/views/gestion_contacts/list.rhtml
create app/views/gestion_contacts/add.rhtml
create app/views/gestion_contacts/modify.rhtml
create app/views/gestion_contacts/delete.rhtml
$
Nous nous retrouvons avec cinq fichiers essentiels :
app/controllers/gestion_contacts_controller.rbqui correspond au contrôleur lui-même ;app/views/gestion_contacts/list.rhtmlest le fichier de rendu, la vue associée à l’actionlistdu contrôleur ;app/views/gestion_contacts/add.rhtmlest la vue associée à l’actionadd;app/views/gestion_contacts/modify.rhtmlest la vue associée à l’actionmodify;app/views/gestion_contacts/delete.rhtmlest la vue associée à l’actiondelete.
http://<mon_serveur>:<mon_port>/<controller_name>/<action>Dans nos exemples, le serveur étant local et le port étant celui par défaut dans Rails, nous utiliserons des adresses du type :
http://localhost:3000/<controller_name>/<action>Comme nous l’avons remarqué, si
class GestionContactsController < ApplicationController def list end def add end def modify end endVous l’avez compris, le contrôleur est une classe héritant de
alias_method :index, :listNous allons être confrontés à un nouveau problème... En effet, il nous manque la vue correspondante ; et il serait dommage de devoir dupliquer le fichier
def index list render :action => "list" endNous venons simplement ici, d’appeler l’action
def index redirect_to :action => "list" endLa distinction entre ces deux solutions se comprend quand on sait que la méthode
redirect_to :controller => "controleur", :action => "action"Vous pouvez même omettre de préciser l’action, la redirection se fera alors automatiquement vers
class GestionContactsController < ApplicationController
def list
@contacts = Contact.find( :all )
end
# ...
end
Nous récupérons un tableau d’objets contacts dans la variable d’instance <html>
<head>
<title>Liste des contacts</title>
</head>
<body>
<h1>Liste des contacts</h1>
<table>
<tr><th>Nom</th><th>Prénom</th><th>eMail</th><th>Téléphone</th>
<th> </th></tr>
<% @contacts.each do |contact| %>
<tr>
<td><%= contact.nom %></td>
<td><%= contact.prenom %></td>
<td><%= contact.email %></td>
<td><%= contact.telephone %></td>
<td>
<%= link_to "Modifier", :action => "modify", :id => contact %>
<%= link_to "Supprimer", :action => "delete", :id => contact %>
</td>
</tr>
<% end %>
</table>
<%= link_to "Ajouter un contact", :action => "add" %>
</body>
</html>
La seule nouveauté ici, à moins bien entendu que vous n’ayez pas lu les premiers articles de ce magazine, est l’utilisation de <%= link_to "Modifier", :action => "modify", :id => contact %>a été transformée en :
<a href="/gestion_contacts/modify/1">Modifier</a>Pour l’enregistrement d’ID 1. Ajoutez, via la console Rails, quelques contacts dans la base et regardez le résultat. Je vous propose d’ajouter une CSS pour améliorer le décor. La CSS est un fichier statique. Dans une application Rails, ce type de fichier est placé dans le répertoire
body {
width: 40em;
margin: auto;
font-family: sans-serif;
text-align: justify;
font-size: 1em;
color: #000000;
}
table {
border: 1px solid black;
cell-spacing: 0;
border-spacing: 0;
padding: 0;
}
th {
background-color: red;
color: white;
font-weight: bolder;
padding: 5px;
}
td {
padding: 5px;
}
tr.odd {
background-color: #eeeeee;
}
tr.even {}
Je n’entrerai pas dans les détails. Vous noterez simplement que nous avons déclaré deux styles pour le tag TD. En effet, il peut être assez sympathique de présenter les lignes de la table alternativement blanches (style ...
<% @contacts.each do |contact| %>
<tr class="<%= cycle(‘even’, ‘odd’) %>">
<td><%= contact.nom %></td>
...
Il faut aussi ajouter le chargement de la CSS. Pour cela, nous allons utiliser la méthode...
<title>Liste des contacts</title>
<%= stylesheet_link_tag "style" %>
</head>
...
Cette méthode, d’<link href="/stylesheets/style.css?1191874047" media="screen" rel="Stylesheet" type="text/css" />Maintenant que nous pouvons lister nos contacts, voyons comment en ajouter un. Commençons par la vue. Nous avons besoin d’un formulaire permettant de saisir les informations. Là encore, nous allons utiliser différentes méthodes d’
<html>
<head>
<title>Ajouter un contact</title>
<%= stylesheet_link_tag "style" %>
</head>
<body>
<h1>Ajouter un contact</h1>
<% form_tag :action => ‘add’ do %>
<p>Nom : <%= text_field ‘contact’, ‘nom’ %></p>
<p>Prénom : <%= text_field ‘contact’, ‘prenom’ %></p>
<p>Email : <%= text_field ‘contact’, ‘email’ %></p>
<p>Téléphone : <%= text_field ‘contact’, ‘telephone’ %></p>
<%= submit_tag “Ajouter” %>
<% end %>
</body>
</html>
Le code est clair, les données saisies dans le formulaire seront envoyées vers l’action <input id="contact_nom" name="contact[nom]" size="30" type="text" />Je sais, nous ne sommes pas plus avancé. En fait, il faut savoir que dans
{"commit"=>"Ajouter",
"contact"=>
{"nom"=>"Dupond", "prenom"=>"Pierre", "telephone"=>"0101010101", "email"=>"pierre.dupond@labas.com"},
"action"=>"add",
"controller"=>"gestion_contacts"}
Comme vous pouvez le voir, nous avons également une clé nouveau_contact = Contact.new( params[:contact] )Mais avant cela, il faut gérer le fait que nous utilisons la même action pour appeler le formulaire et pour envoyer les données saisies. En fait, si nous appelons le formulaire sans paramètre (
Codons l’action :
def add
if params.has_key?( :contact )
@contact = Contact.new(params[:contact])
if @contact.save
redirect_to :action => ‘list’
else
render :action => ‘add’
end
end
end
Et la reprise des données par le formulaire me direz-vous... Eh bien, ne modifiez rien... Vous allez voir, c’est de la magie ! Et pour vous en rendre compte, passons à la modification.
Pour la vue, nous allons (pour le moment) nous contenter de reprendre le code de la vue <html>
<head>
<title>Modifier un contact</title>
<%= stylesheet_link_tag "style" %>
</head>
<body>
<h1>Modifier un contact</h1>
<% form_tag :action => ‘add’, :id => @contact do %>
<p>Nom : <%= text_field ‘contact’, ‘nom’ %></p>
<p>Prénom : <%= text_field ‘contact’, ‘prenom’ %></p>
<p>Email : <%= text_field ‘contact’, ‘email’ %></p>
<p>Téléphone : <%= text_field ‘contact’, ‘telephone’ %></p>
<%= submit_tag “Modifier” %>
<% end %>
</body>
</html>
Pour l’action, nous avons besoin de vérifier si def modify
@contact = Contact.find(params[:id])
if params.has_key?( :contact )
and @contact.update_attributes(params[:contact])
redirect_to :action => ‘list’
end
end
Vous remarquerez que nous n’avons rien ajouté au niveau des text_field ‘contact’, ‘prenom’Rails a ajouté la valeur de l’attribut
<p>Nom : <%= text_field ‘contact’, ‘nom’ %></p> <p>Prénom : <%= text_field ‘contact’, ‘prenom’ %></p> <p>Email : <%= text_field ‘contact’, ‘email’ %></p> <p>Téléphone : <%= text_field ‘contact’, ‘telephone’ %></p>Nous pouvons maintenant remplacer le code équivalent, dans
<%= render :partial => ‘form’ %>À partir de maintenant, toute modification de
<html>
<head>
<title> ... TITRE ...</title>
<%= stylesheet_link_tag "style" %>
</head>
<body>
# Code spécifique
</body>
</html>
Le seul élément " gênant " est le titre. Nous lui jetterons un sort plus tard. Sachez que là encore, nous pouvons mutualiser. Il suffit de créer un layout pour le contrôleur. Un layout est un fichier rhtml ayant le même nom qu’un contrôleur et que l’on place dans le répertoire <html>
<head>
<title><%= @page_title %></title>
<%= stylesheet_link_tag "style" %>
</head>
<body>
<%= yield %>
</body>
</html>
L’instruction def delete
Contact.find(params[:id]).destroy
redirect_to :action => ‘list’
end
Simple, efficace, mais dangereux. En effet, si l’utilisateur clique par erreur, il n’y a aucun garde-fou ! Il serait en effet sympathique de demander à l’utilisateur de confirmer sa demande. Inutile de créer une vue pour cela. Nous allons simplement modifier l’affichage du lien " Supprimer " de la liste en demandant à Rails d’ajouter une boite de confirmation. Voici le bout de code modifié dans ...
<%= link_to "Modifier", :action => "modify", :id => contact %>
<%= link_to "Supprimer",
{ :action => ‘delete’, :id => contact },
:confirm => ‘Etes vous sure de vous ?’,
:method => :post %>
</td>
...
Et voici le code HTML généré correspondant :
<a href="/gestion_contacts/delete/1" onclick="if (confirm(‘Etes vous sure de vouloir effacer ce contact ?’)) { var f = document.createElement(‘form’); f.style.display = ‘none’; this.parentNode.appendChild(f); f.method = ‘POST’; f.action = this.href;f.submit(); };return false;">Supprimer</a>
Je ne vous avais pas menti, Rails vous simplifie la vie !
7. Bonne Route !
Tout à l’heure, certains se sont demandés pourquoi, et surtout comment, nous pouvons utiliser une URL simple pour accéder à notre application. En effet, il serait beaucoup plus agréable de se connecter via l’URL http://localhost:3000 plutôt que via http://localhost:3000/<contrôleur>/<action>. Pire, si vous essayez de vous connecter à votre application sans préciser (au minimum) le contrôleur, vous arrivez sur une page vous signalant que vous êtes sur un site Rails, sans plus. Si vous fouillez les sources de notre application, vous retrouverez ce fichier sous le nommap.connect ‘’, :controller => "gestion_contacts"Vous l’aurez compris, la définition d’une route est similaire à celle d’une redirection (avec
8. Pagination
Si vous revenez sur le code généré avec9. Retour sur ActiveRecord
L’application que nous venons de mettre en place est très simple. Trop simple pour nous permettre d’aborder toutes les possibilités que nous offre Rails. Il y a cependant un domaine que je ne peux pas passer sous silence et qui concerne
$ ruby script/generate model address
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/address.rb
create test/unit/address_test.rb
create test/fixtures/addresses.yml
exists db/migrate
create db/migrate/003_create_addresses.rb
$
Il nous suffit ensuite de modifier le fichier de migration 003_create_addresses.rb :
class CreateAddresses < ActiveRecord::Migration
def self.up
create_table :addresses, :force => true do |t|
t.column :contact_id, :integer
t.column :rue, :string
t.column :cp, :string
t.column :ville, :string
end
end
def self.down
drop_table :addresses
end
end
N’oubliez pas de faire la migration avec un rake db:migrate !
Nous avons donc deux modèles dans notre application : Contact et Address. Pour décrire la relation existant entre ces modèles, nous les modifierons de la façon suivante :
contact.rb : class Contact < ActiveRecord::Base has_one :address end address.rb : class Address < ActiveRecord::Base belongs_to :contact endNous venons simplement de préciser qu’un contact a une adresse (
na = Address.new( ... ) c = Contact.new( ... ) c.address = naSi, par la suite, nous récupérons un contact, nous pouvons récupérer son adresse, sous forme d’objet
c = Contact.find( :first ) addresse_du_contact = c.addressL’inverse est également possible. Si vous recherchez une adresse, vous pouvez retrouver le contact qui y habite de la façon suivante :
a = Address.find( :first ) contact_a_cette_adresse = a.contactC’en est presque trop simple ! Comme nous l’avons dit, il arrive qu’une personne ait plusieurs adresses. Nous allons donc transformer notre relation 1:1 en relation 1:N dans ce sens. Dans ce cas, la présence de la clé étrangère dans la table addresses reste parfaitement logique. En effet, une adresse est bien liée à une et une seule personne, mais une personne peut avoir plusieurs adresses. Voici le schéma correspondant :

class Contact < ActiveRecord::Base has_many :addresses end
Notez que nous avons pluralisé address. En haut, le has_one :address est devenu has_many :addresses. C’est logique, puisqu’il y en a plusieurs !
Si maintenant vous recherchez les adresses d’un contact, alors qu’avec la relation 1:1 vous n’aviez qu’un objet de type Address pour un contact, vous obtiendrez maintenant un tableau d’objets Address :
c = Contast.find( :first )
c.addresses
# => [#<Address:0xNNNNNNN @attributes={ ... }, ...]
Pour ajouter une adresse à un contact, il suffit d’ajouter une nouvelle entrée dans sa table d’adresses :
c = Contact.find( :first ) nouvelle_adresse = Address.new( ... ) c.addresses << nouvelle_address c.saveEncore une fois, c’est enfantin ! Pour être, enfin, tout à fait exhaustif sur les cas possibles, nous devons considérer que non seulement une personne peut avoir plusieurs adresses, mais qu’il peut y avoir plusieurs personnes à une même adresse. Nous devons donc mettre en place une relation de type N:N entre nos deux tables. Ce type de relation ne peut pas se faire sans une table de jointure pour aboutir au schéma suivant :

Nous devons donc ajouter une table contacts_addresses. Pour faire cela, nous allons encore faire une migration :
$ ruby script/generate migration lien_nn_contacts_addresses
exists db/migrate
create db/migrate/004_lien_nn_contacts_addresses.rb
$
Dans le fichier class LienNnContactsAddresses < ActiveRecord::Migration
def self.up
create_table :contacts_addresses, :id => false, :force => true do |t|
t.column :contact_id, :integer
t.column :address_id, :integer
end
remove_column :addresses, :contact_id
end
def self.down
drop_table :contacts_addresses
add_column :addresses, :contact_id
end
end
La seule chose importante à noter ici est la présence de l’option :class Contact < ActiveRecord::Base has_and_belongs_to_many :addresses end class Address < ActiveRecord::Base has_and_belongs_to_many :contact endNous pouvons maintenant ajouter plusieurs adresses à un contact et inversement, l’attribut
Conclusion
Vous avez certainement remarqué que je n’ai absolument pas parlé de MVC. J’ai certes cité les termes " Modèle ", " Vue " et " Contrôleur ", mais sans approfondir les concepts. Eh bien, je n’en dirai pas plus. En fait, il n’est absolument pas nécessaire d’être un expert MVC pour pouvoir développer avec Rails. Il suffit simplement de savoir que les modèles sont définis dansRéférences:
- [1] http://www.basecamphq.com
- [2] http://rubyonrails.org
- [3] http://www.yaml.org
- [4] http://api.rubyonrails.org
- [5] http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/
- [6] http://railscasts.com/episodes/51
Retrouvez cet article dans : Linux Magazine Hors série 33





Donnez votre avis
Vous devez avoir ouvert une session pour écrire un commentaire.