Ruby on Rails 1.2.2, les migrations
Par NiKo le samedi 3 mars 2007, 22:19 - Ruby
- Lien permanent -
14 commentaires -
Tags :
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 :
ruby
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 :
bash
$ 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 :
yaml 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 :
yaml 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 :
ruby class Contact < ActiveRecord::Base # Chaque employé appartient à une compagnie belongs_to :company end
Et le fichier ./apps/models/company.rb :
ruby 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 :
ruby
class ContactsController < ApplicationController
[...]
def new
@contact = Contact.new
@companies = Company.find_all
end
Puis pour l'édition:
ruby
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 :
rhtml
<%= 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 :
rhtml
<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.
14 commentaires (Ajouter un commentaire)
non car c'est la convention de ROR.
Gilgam
Petite question bête, au niveau du modèle company, il ne faut pas mettre
has_many :contactS
plutôt que
has_many :contact
puisqu'une compagnie peut avoir plusieurs contacts ?!
Bonjour
Merci pour votre petit guide, cela correspond tout à fait à ce que je suis en train de faire. Loin d'être développeur pro je connaissais surtout le php et m'intéresse à ROR depuis environ un an, comme ça de loin, quand j'avais le temps. Et avec l'envie de créer une application de gestion de cabinet médical en GPL (de type web app).
)
et grâce à vous je commence ce jour (et sous ubuntu en plus
merci
Gilgam
zyegfryed : merci pour la confirmation - pour les fixtures, j'ai vu arriver ça récemment mais pas encore eu le temps d'y jeter un oeil...
Si tu retrouves le snippets en question, je suis intéressé
@NiCoS: c'est bien comme cela qu'agit syncdb avec Django (cf http://www.djangoproject.com/docume...) : la commande ne crée que les tables inexistantes. Donc pour mettre à jour un modèle, il faut supprimer la table, pour ensuite appliquer syncdb. Il me semblait avoir vu un snippet réalisant cela, mais je n'arrive plus à y mettre la main dessus. Par contre, petit truc que je viens de remarquer sur la documentation : la possibilité de charger des données via une "fixture" nommée "initial_data".
Bon, j'ai testé streamlined, alors soit je suis un boulet, soit ce truc est truffé de bugs (ou les deux.)
Bruno> Je me suis permis de reformater un peu ton commentaire qui avait pas mal souffert de sa transformation html, j'éspère que tu ne m'en tiendras pas rigueur
@NiCoS > Rails ne peut pas faire le mapping seul mais si c'est le fait de chercher l'id manuellement qui est ennuyeux, on peut aider Rails en mettant :
company_id:
<%= Company.find_by_name('Nanonical').id %>@Christophe > Plutôt d'accord concernant le scaffold et complètement d'accord pour REST-ful.
En 1.2, par exemple pour l'article précédent :
génère le script de migration, un
map.resourcesdansroutes.rbqui permet de bénéficier d'un controlleurcontacts_controller.rbREST-ful.http://localhost:3000/contacts.xmlouhttp://localhost:3000/contacts/1.xmlpour un tout petit exemple pas très significatif de l'étendue des possibilités offertes (mais exemple visuel ;)).Ca n'enlève rien à la qualité des articles que je lis avec plaisir.
Christophe> J'avais bien prévu les choses dans cet ordre. Pour le scaffolding de Rails, je suis assez d'accord, il est à la limite du scandaleux, surtout en regard de ce qui existe chez Django ou Symfony. Par contre des projets comme Streamlined me font baver, va falloir que je teste ça
Damien : ben si par ex tu mets à jour ton fichier views.py et fait un simple python manage syncdb, ben ta bdd n'est pas mise à jour. Tu dois la vider pour la réinitialiser (donc les données entrées sont perdues - à moins peut être d'avoir un fichier qui charge des données...)
Mais bon, je commence aussi donc j'ai peut être raté qqc - faudrait une confirmation de David (Biologeek) ou autre djangonaute avisé
Le problème c'est que c'est scaffold ça ne sert à rien
Et je le dis bien en tant que rails-user : dès que tu veux te faire une interface digne de ce nom, tu trouves le scaffold tout de suite obsolète.
Alors oui c'est bien comme ça, vite fait pour dépanner dans l'urgence, sinon l'intérêt est vraiment limité.
Autre sujet : Puisque tu sembles vouloir présenter rails à tes lecteurs depuis le début, présente leur tout de suite le RESTful Rails, ça en intéressera plus d'un et fera gagner un temps fou (quoique l'on fait que ça avec rails)
Nicos tu entend quoi par c'est pas possible avec django ?
Sa se fait de modifier le schema de base de donnée non ? il suffit pas de faire un manage syncdb ? (je me lance a peine avec django donc ...)
Ben je pensais mais ça marche pas (chez moi) :/
Sympa !
ça, par contre, c'est un truc qui manque encore dans Django mais j'ai lu que c'était dans le pipe (et puis django est pas encore sorti en version 1.0 contairement à Rails...)
Par contre, tu peux pas faire :
company: Nanonical
au lieu de :
company_id: 1 # John -> Nanonical
et rails se débrouille pour faire le mapping ?