Voici la suite du précédent tutoriel sur Ruby on Rails publié sur ce même blog ou nous avons mis en place une application de gestion de contacts rudimentaire.
Migration de schéma de base de données
Imaginons que nous voulions gérer une liste de sociétés, et lier chacun de nos contacts à sa société... Il va nous falloir modifier notre base de données en ajoutant une table companies et une clé company_id dans la table contacts. Cela peut s'avérer compliqué puisqu'elle contient déjà des données... même si en l'occurence il ne s'agit pour l'heure que de données de test.
Rails et plus particulièrement ActiveRecord proposent un outil de gestion des évolutions du modèle de données intelligent, les migrations. Au même titre que nous avions créé le fichier db/migrate/001_contacts_development.rb pour définir le schéma initial lors du tutoriel précédent, nous allons maintenant créer son évolution incrémentale dans le fichier db/migrate/002_contacts_development.rb :
class ContactsDevelopment < ActiveRecord::Migration def self.up # On renomme le champs name en first_name (ne devra contenir que le prénom) rename_column :contacts, "name", "first_name" # Ajout d'un champs pour stocker le nom de famille add_column :contacts, "last_name", :string # Ajout d'un champs pour stocker la société du contact add_column :contacts, "company_id", :integer # Création de la table des sociétés create_table "companies", :force => true do |t| t.column "name", :string t.column "description", :text t.column "url", :string end end def self.down # Rétablissement de la colonne name rename_column :contacts, "first_name", "name" # Destruction de la colonne last_name remove_column :contacts, :last_name # Destruction de la colonne contenant la référence à une société remove_column :contacts, :company # Destruction de la table contenant les sociétés drop_table :companies end end
Dans chaque classe de migration de schéma, une méthode up appliquera des modifications au modèles tandis qu'une méthode down permettra un rollback des modifications vers une version antérieure. Pour le détail des opérations effectuées par le script de migration ci-dessus, je crois que les commentaires parlent d'eux-mêmes
(j'en ai profité pour répartir le patronyme sur deux champs, nom et prénom)
La mise à jour effective du modèle dans la base de données s'effectue grâce à la commande :
$ rake db:migrate (in /home/niko/ww2/rails/contacts) == ContactsDevelopment: migrating ============================================= -- rename_column(:contacts, "name", "first_name") -> 0.0750s -- add_column(:contacts, "last_name", :string) -> 0.0153s -- add_column(:contacts, "company_id", :integer) -> 0.0343s -- create_table("companies", {:force=>true}) -> 0.0304s == ContactsDevelopment: migrated (0.1694s) ====================================
Nous n'avons pas d'outils d'administration pour notre nouvelle table companies, créons-les :
$ ruby script/generate scaffold Company
Il faut maintenant mettre à jour notre jeu de données ; on édite d'abord le nouveau fichier ./test/fixtures/companies.yml :
Nanonical: id: 1 name: Nanonical description: On vous préfère libres url: http://www.nanonical.com Crimosoft: id: 2 name: Crimosoft description: Notre monopole, c'est trop lol url: http://www.crimosoft.com
Toute ressemblance avec des sociétés existantes ou ayant existé serait un sacré coup de pot
Puis, dans le fichier ./test/fixtures/contacts.yml :
John: id: 1 first_name: John last_name: Doe email: john@doe.com address: 12, rue des champs zip: 75009 city: Paris country: France company_id: 1 # John -> Nanonical Bob: id: 2 first_name: Bob last_name: Doe email: bob@doe.com address: 1, place du pré zip: 35000 city: Rennes country: France company_id: 2 # Bob -> Crimosoft
On réinsère nos nouvelles fixtures :
$ rake db:fixtures:load
On peut se rendre successivement aux adresses http://0.0.0.0:3000/contacts et http://0.0.0.0:3000/companies afin de s'assurer que tout va bien.

Mise à jour des classes modèles
Pour que Rails prenne en compte les modifications structurelles de notre schéma, nous allons éditer les fichiers des modèles Contact et Company, notamment pour spécifier leur type de relation grace aux méthodes de classe ActiveRecord.
Le fichier ./apps/models/contact.rb :
class Contact < ActiveRecord::Base # Chaque employé appartient à une compagnie belongs_to :company end
Et le fichier ./apps/models/company.rb :
class Company < ActiveRecord::Base # Une société possède plusieurs employés has_many :contact end
Mise à jour du contrôleur
On récupère la liste des sociétés depuis le contrôleur gérant les contacts (situé dans le fichier ./app/controllers/contacts_controller.rb), d'abord pour la création :
class ContactsController < ApplicationController [...] def new @contact = Contact.new @companies = Company.find_all end
Puis pour l'édition:
class ContactsController < ApplicationController [...] def edit @contact = Contact.find(params[:id]) @companies = Company.find_all end
Mise à jour de la vue
Enfin, modifions notre formulaire d'édition/création de contacts, situé dans app/views/contacts/_form.rhtml, comme suit :
<%= error_messages_for 'contact' %>
<!--[form:contact]-->
<p><label for="contact_company_id">Company</label>
<%= select 'contact', 'company_id', @companies.collect {|c| [c.name, c.id]} %></p>
<p><label for="contact_name">First name</label><br/>
<%= text_field 'contact', 'first_name' %></p>
<p><label for="contact_name">Last name</label><br/>
<%= text_field 'contact', 'last_name' %></p>
<p><label for="contact_email">Email</label><br/>
<%= text_field 'contact', 'email' %></p>
<p><label for="contact_address">Address</label><br/>
<%= text_field 'contact', 'address' %></p>
<p><label for="contact_city">City</label><br/>
<%= text_field 'contact', 'city' %></p>
<p><label for="contact_zip">Zip</label><br/>
<%= text_field 'contact', 'zip' %></p>
<p><label for="contact_country">Country</label><br/>
<%= text_field 'contact', 'country' %></p>
<!--[eoform:contact]-->
Ce qui donne à peu-près ceci :

Et le template d'affichage de notre liste de contacts, dans le fichier ./app/views/contacts/list.rhtml pour y ajouter la colonne affichant le nom de la société pour chaque contact :
<h1>Listing contacts</h1>
<table>
<tr>
<% for column in Contact.content_columns %>
<th><%= column.human_name %></th>
<% end %>
<th>Société</th>
</tr>
<% for contact in @contacts %>
<tr>
<% for column in Contact.content_columns %>
<td><%=h contact.send(column.name) %></td>
<% end %>
<td><%= h contact.company.name %></td>
<td><%= link_to 'Show', :action => 'show', :id => contact %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => contact %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => contact }, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</table>
<%= link_to 'Previous page', { :page => @contact_pages.current.previous } if @contact_pages.current.previous %>
<%= link_to 'Next page', { :page => @contact_pages.current.next } if @contact_pages.current.next %>
<br />
<%= link_to 'New contact', :action => 'new' %>
Ce qui donne au final quelque chose comme ceci :

To be continued
Dans un prochain tutoriel, nous aborderons la validation de formulaires.







J'ai entre les mains l'un des petits derniers des éditions
Je découvre 













