I. Qu'est ce qu'ActiveRecord ?

ActiveRecord est le composant qui permet la manipulation des données dans les applications Ruby on Rails. ActiveRecord est aussi et avant tout un design pattern pour lier les données d'une base de données à des modèles de programmation. Voici la définition très juste de ce design pattern trouvée sur Wikipedia : « En génie logiciel le patron de conception (design pattern) active record (enregistrement actif en anglais) est une approche pour lire les données d'une base de données. Les attributs d'une table ou d'une vue sont encapsulés dans une classe. Ainsi, l'objet instance de la classe est lié à un tuple de la base. Après l'instanciation d'un objet, un nouveau tuple est ajouté à la base au moment de l'enregistrement. Chaque objet récupère ses données depuis la base; quand un objet est mis à jour, le tuple auquel il est lié l'est aussi. La classe implémente des accesseurs pour chaque attribut. » Voici un exemple concret : Si nous avons une table « contacts » dans la base de données de notre application Rails, alors nous avons forcément une classe Contact qui peut être manipulée via ActiveRecord. Voici un exemple de code :

 
Sélectionnez
  c = Contact.new(:first_name => "Dubois", :last_name => "Vincent")
  c.save   # création du tuple

II. Anatomie d'une migration

Avant de rentrer dans le détail des migrations Active Record, voyons un exemple de migration que nous avons créé dans le tutoriel précédent :

db/migrate/20090422185716_create_contacts.rb
Sélectionnez
  class CreateContacts < ActiveRecord::Migration
    def self.up
      create_table :contacts do |t|
        t.string :name
        t.string :email

        t.timestamps
      end
    end

    def self.down
      drop_table :contacts
    end
  end

Cette migration ajoute une table « contacts », possédant deux colonnes « name » et « email » de type texte. La méthode « timestamps » quant à elle, ajoute des colonnes d'informations sur les dates de mises à jour des enregistrements. Nous détaillerons tout cela un peu plus tard.

II-A. Les migrations sont des classes

Une migration est une sous-classe de la classe ActiveRecord::Migration. A ce titre, elle définit deux méthodes : une méthode « up », qui permet d'ajouter une modification à la base de données, et une méthode« down », qui permet de retirer cette modification de la base de données. Active Record fournit des méthodes qui permettent de définir des tâches de modification de la structure de la base de données : Cette migration ajoute une table « contacts », possédant deux colonnes « name » et « email » de type texte. La méthode « timestamps » quant à elle, ajoute des colonnes d'informations sur les dates de mises à jour des enregistrements. Nous détaillerons tout cela un peu plus tard.

  • create_table
  • change_table
  • drop_table
  • add_column
  • change_column
  • rename_column
  • remove_column
  • add_index
  • remove_index

Les migrations sont effectuées lors de l'exécution de la commande « rake db:migrate ». Si une opération ne se déroule pas correctement, la migration qui a échoué fait l'objet d'un rollback. Les noms des fichiers de migrations sont du type YYYYMMDDHHMMSS_action_entite.rb. Dans le cas de ce tutoriel, le nom du fichier est : 20090418171815_create_contacts.rb. Le nom du fichier est sûrement différent chez vous, il dépend de l'heure de la création de l'entité « Contact ». Tous les fichiers de migrations se situent dans le répertoire : db/migrate/. La date de chaque nouveau fichier de migration étant postérieure à celle du dernier, chaque migration sera toujours exécutée dans l'ordre que vous aurez pré-établi et « rollbackée » dans l'ordre inverse. Si vous souhaitez effectuer un « rollback » sur la base de données, il existe une commande qui opère le contraire de «rake db:migrate ». C'est la commande « rake db:rollback ». N'hésitez pas à l'utiliser en cas de besoin.

III. Créer une migration

III-A. Créer un modèle

Lors de la création d'un modèle, la migration adéquate est automatiquement créée. Par exemple, si on exécute la commande (ne le faites pas, car l'entité « Contact » existe déjà) :

 
Sélectionnez
ruby script/generate model Contact name:string email:string

Alors, automatiquement, un fichier [yyyymmddhhmmss]_create_contacts.rb est créé (cf. §1). Vous pouvez ajouter à la main autant de colonnes que vous le souhaitez au sein du bloc create_table.

III-B. Créer une migration seule

Vous pouvez si vous le souhaitez créer seulement une migration, hors du cadre de la création d'une entité ou d'un « scaffold ». Procédons par exemple à l'ajout d'un attribut « first_name » correspondant au prénom du contact et changeons l'attribut « name » en « last_name » à cette occasion.

 
Sélectionnez
ruby script/generate migration AddFirstNameToContacts

Voici le contenu du fichier de migration par défaut :

 
Sélectionnez
class AddFirstNameToContacts < ActiveRecord::Migration
    def self.up
    end

    def self.down
    end
  end

Evidemment, ça ne fait pas grand chose pour l'instant. Précisons tout d'abord que les méthodes « up » et « down » opèrent chacune respectivement une montée et une descente de version de migration. Dans ce but, nous allons donc prévoir de quoi revenir sur nos pas, c'est à dire envisager la suppression de la colonne « first_name » et le renommage de la colonne « last_name » en « name ». Commençons par la méthode « up » :

 
Sélectionnez
def self.up
    add_column :contacts, :first_name, :string
    rename_column :contacts, :name, :last_name
  end

Observons le code. Nous avons tout d'abord ajouté la colonne « first_name » grâce à la méthode « add_column » qui prend en paramètre le nom de la table, le nom de la colonne, le type de colonne et d'autres paramètres éventuels non précisés ici. Ensuite, la méthode « rename_column » nous permet de renommer la colonne « name ». Nous devons maintenant implémenter l'opération inverse dans la méthode « down » :

 
Sélectionnez
def self.down
    remove_column :contacts, :first_name
    rename_column :contacts, :last_name, :name
  end

Facile, n'est ce pas ? Si nous avions seulement voulu ajouter la colonne « first_name », nous aurions même pu le faire en tapant :

 
Sélectionnez
ruby script/generate migration AddFirstNameToContacts first_name:string

Et Rails aurait généré pour nous les lignes contenant « add_column » et « remove_column ». A l'inverse, une migration nommée « RemoveFirstNameToContacts » aurait généré une méthode « remove_column » dans la méthode « up » et une méthode « add_column » dans la méthode « down ». Le principe de « convention over configuration » est poussé ici à son paroxisme. Nous pouvons d'ailleurs aller un peu plus loin si nous le souhaitons en ajoutant plusieurs descriptions de colonnes à la commande : elles seront toutes ajoutées à la migration.

IV. Ecriture d'une migration

IV-A. Créer une table

L'écriture d'une table passe par le bloc de code create_table. Voici un exemple de code typique :

 
Sélectionnez
create_table :contacts, :force => true do |t|
    t.string :name
  end

L'objet table « t » manipulé par le bloc nous permet de créer des colonnes dans la table. Notons au passage l'usage d'un deuxième paramètre nommé « :force », qui permet de recréer la table dans le cas où elle existe déjà. Il y a deux façons de créer des colonnes. Il y a l'ancienne façon (à l'origine dans les versions antérieures de Rails) :

 
Sélectionnez
t.column :name, :string, :null => false

Et il y a la nouvelle manière introduite depuis peu dans Rails, du moins de façon officielle. Les migrations qui l'utilisent sont surnommées « sexy migrations ». C'est celle que nous appliquerons par la suite :

 
Sélectionnez
t.string :name, :null => false

Il est intéressant de préciser que nous n'avons pas de colonne d'identifiant à créer. En effet, Rails crée pour nous automatiquement une colonne de nom « id » qui joue le rôle de clé primaire dans la table. Cela dit, il existe des moyens d'outrepasser ceci, mais là encore, il vaut mieux suivre le principe « convention over configuration ». Les types supportés par Active Record lors de la création d'une colonne sont :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary et :boolean. Il est possible d'utiliser d'autres types de données non supportés de la façon suivante :

 
Sélectionnez
t.column :name, 'autre_type', :null => false

Mais le type ne sera supporté probablement que par la base de données que vous utiliserez. C'est donc à utiliser avec précaution.

IV-B. Modifier une table

La méthode change_table, proche cousin de la méthode create_table, est utilisée pour les tables déjà existantes. L'objet manipulé par le bloc de code dispose de plus de méthodes. En voici un exemple :

 
Sélectionnez

  change_table :contacts do |t|
    t.remove :email, :country
    t.string :address
    t.index :first_name
    t.rename :first_name, :firstname
  end

Toutes ces méthodes sont des raccourcis. Si nous avions dû créer ceci de façon standard, nous aurions fait :

 
Sélectionnez

  remove_column :contacts, :email
  remove_column :contacts, :country
  add_column :contacts, :address, :string
  add_index :contacts, :first_name
  rename_column :contacts, :first_name, :firstname

Tout cela nous permet de ne pas réécrire plusieurs fois le même code et donc de respecter le principe « DRY » de Rails.

IV-C. Helpers spéciaux

Rails fournit un certain nombre de méthodes «helpers » spéciales qui permettent de manipuler la structure des tables ou encore les relations entre les tables. La première, que nous avons déjà vu plus haut dans ce tutoriel, est la méthode « timestamps ». Cette méthode déclare automatiquement deux colonnes, nommées « created_at » et « updated_at», qui permettent respectivement de tracer les dates de création et de mise à jour des enregistrements dans la table. La deuxième méthode que nous allons voir et qui est très importante est la méthode « references ». C'est cette méthode qui permet à Rails de structurer les tables afin d'établir le lien entre deux d'entre elles. Voici un exemple d'utilisation :

 
Sélectionnez
t.references :category

L'exemple ci-dessus permet de générer automatiquement le lien entre la table actuelle et la table « category » via un champ « category_id » qui va être automatiquement créé.

V. Exécuter des migrations

Rails nous fournit un certain nombre de tâches Rake qui permettent de manipuler les migrations. Celle que nous utiliserons le plus souvent est la commande « rake db:migrate ». Elle peut être agrémentée du numéro de version de migration à atteindre. Dans ce cas, les migrations suivantes ne sont pas effectuées. Par exemple :

 
Sélectionnez
rake db:migrate VERSION=20090420203025

Dans l'exemple ci-dessus, la version correspond au préfixe du fichier de migration. Nous pouvons également exécuter l'opération inverse, c'est-à-dire décrémenter le niveau des migrations. Pour cela, Rails met à notre disposition la méthode « rake db:rollback ». Seule, la commande permet de descendre d'un niveau de migration à chaque exécution. On peut, comme pour la commande « migrate », préciser « VERSION=... ». Dans ce cas, on descend le niveau de migration jusqu'à la version donnée en paramètre. Le mieux pour l'opération rollback, est quand même de préciser le paramètre « STEP » pour descendre plusieurs niveaux de migrations à la fois. Par exemple :

 
Sélectionnez
rake db:rollback STEP=3

Lorsque vous invoquez les commandes « rake db:migrate » ou « rake db:rollback », la tâche « b » est exécutée automatiquement et met à jour le fichier schema.rb qui se situe dans le répertoire db. Ce fichier reflète alors la structure de la base de données. Il est possible de recharger la structure de la base de données à partir du fichier schema.rb à l'aide de la commande « rake db:schema:load »

VI. Modéliser l'application « Mes Contacts »

Voici le diagramme de classes de notre application de gestion des contacts : nous souhaitons qu'un utilisateur puisse gérer ses propres contacts. Chaque contact pourra avoir plusieurs numéros de téléphone, messageries instantanées, sites web, adresses postales, et adresses email.

Image non disponible

Avant de créer les migrations qui vont nous permettre de modéliser la base de données, nous devons supprimer les migrations que nous avons ajouté précédemment :

 
Sélectionnez
rake db:rollback STEP=2
  rm db/migrate/*

Maintenant que la base de données est vierge, nous pouvons créer nos entités. Il est néanmoins temps de faire un point sur le modèle. Certaines choses peuvent être mutualisées. C'est le cas par exemple de la gestion des utilisateurs. Plutôt que de créer de zéro une gestion des utilisateurs, nous allons appliquer le principe « DRY » et utiliser le plugin « restful_authentication ». Ce plugin va nous permettre de gérer tous les enregistrements et connexions des utilisateurs à l'application. Commençons par l'installer :

 
Sélectionnez
ruby script/plugin install git://github.com/technoweenie/restful-authentication

Le plugin est maintenant installé dans notre répertoire vendor/plugins. Pour créer l'entité « User » et la structure des tables utilisateurs en base de données, lançons la commande :

 
Sélectionnez
ruby script/generate authenticated user sessions --include-activation

Cette commande crée beaucoup de choses, notamment l'entité « User » (fichier app/models/user.rb) et le fichier de migration correspondant db/migrate/[VERSION]_create_users.rb. Sachez, pour finir, que le plugin génère des contrôleurs et vues qui permettent de gérer les utilisateurs et les sessions d'authentification. Il rajoute au fichier routes.rb des routes qui facilitent l'utilisation de ces contrôleurs (exemples : /signup, /register, /login, /logout). Nous verrons en détail ce plugin dans un autre chapitre quand nous en saurons plus sur les contrôleurs. Le but ici était d'initialiser l'entité « User ». Concentrons-nous maintenant sur la création des autres entités. A cette fin, lançons les commandes suivantes :

 
Sélectionnez

  ruby script/generate model Phone phone_type:integer phone_number:string is_main:boolean

  ruby script/generate model InstantMessaging protocol:integer address:string is_main:boolean

  ruby script/generate model Website url:string is_main:boolean

  ruby script/generate model Address address_type:integer address_street:string address_postcode:string
  address_city:string address_region:string address_country:string is_main:boolean

  ruby script/generate model Email email_type:integer email_address:string is_main:boolean

  ruby script/generate model Contact first_name:string last_name:string nickname:string birthdate:date
  photo:string company_name:string company_department:string company_position:string notes:text

Nous devons ensuite rajouter à la main les liens entre les différentes entités. Pour cela, nous allons utiliser la méthode «references » que nous avons étudié dans le paragraphe 3.3. Ajoutons la ligne :

 
Sélectionnez
t.references :contact

dans les méthodes create_table des fichiers de migrations correspondant aux entités « Phone », « InstantMessaging », « Website », « Address » et « Email » pour faire le lien entre ces entités et l'entité « Contact ». Le lien entre les deux entités « Phone » et « Contact » se lit : un contact possède de zéro à plusieurs numéros de téléphone. Pour finir, il reste à faire le lien entre les utilisateurs et les contacts. Dans le fichier de migration de l'entité « Contact », ajoutons :

 
Sélectionnez
t.references :user

Une dernière chose maintenant. Si vous êtes observateur, vous vous êtes probablement aperçu qu'il manque un attribut à l'ensemble du modèle. Il y a un attribut « tag » appartenant à l'entité « Contact ». Cet attribut veut seulement dire que l'on doit pouvoir « tagger » un contact avec un à plusieurs mots. On pourra générer par exemple avec ce genre de données un « nuage de tags » des contacts. Cette fonctionnalité existe déjà dans Rails par l'intermédiaire de plugins. ActsAsTaggableOnSteroids en est un. Nous l'étudierons plus en détails dans un autre chapitre. Nous allons l'installer et l'utiliser pour le raccorder à notre modèle :

 
Sélectionnez
ruby script/plugin install http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids

Ensuite nous devons créer la migration correspondante à l'aide de la commande :

 
Sélectionnez
ruby script/generate acts_as_taggable_migration

La dernière chose à faire est de rajouter la fonctionnalité de « tagging » aux contacts. Nous devons ouvrir le fichier app/models/contact.rb et le compléter comme suit :

 
Sélectionnez
class Contact < ActiveRecord::Base
    acts_as_taggable
  end

Voilà, tous nos modèles sont décrits, toutes les migrations correspondantes sont écrites et l'apport de certains plugins a permis de nous pré-mâcher beaucoup de travail. N'oublions pas de mettre à jour la base de données avant de passer à la suite :

 
Sélectionnez
rake db:migrate

VII. Conclusion

ActiveRecord est LE composant de Rails qu'on se doit de connaître sur le bout des doigts. Une bonne conception de la base de données passera forcément par une écriture correcte des migrations. Les migrations sont un outil puissant car elles permettent de gagner un temps fou lors de l'initialisation d'un projet Rails. On ne doit pas les négliger. Dans la prochaine partie, nous aborderons le développement par les tests dans Ruby on Rails. Nous verrons quel est leur rôle et comment en tirer un maximum de bénéfice. Vous pouvez retrouver le source de ce tutoriel ici.

VIII. Remerciements

J'adresse tous mes remerciements à l'équipe de développement web pour leurs relectures et leurs conseils.