Choses à faire

24h dans une journée, et tant de choses à faire !

La couche Modèle de Rails (Partie 2)

publié le 12 décembre 2009 par Pierre Quillery

Voici la suite du premier article qui parlait de la couche Modèle dans Rails, n’hésitez pas à aller y jeter un œil si vous ne l’avez pas encore lu ! Ce post fait partie d’une série de tutoriels dont vous pouvez trouver la liste sur mon ancien site.

Configuration des base de données dans Rails

Comme vous l’avez sans doute remarqué dans le précédent article, nous avons directement commencé à travailler avec la base de données sans pour autant avoir eu besoin de configurer quoi que ce soit au niveau de l’application. En effet, ainsi que je le décrivais déjà ici, Rails se repose par défaut sur une base Sqlite, ce qui permet de s’affranchir de configuration et rentrer directement dans le vif du sujet.

La configuration concernant les bases de données est placée dans le fichier config/database.yml. Comme vous pouvez le voir, on y trouve 3 zones qui correspondent à la configuration de chaque environnement (dev, preprod et prod). L’intérêt de ce fonctionnement est qu’il est très souple à l’usage : pour développer vous aurez rarement besoin d’une base MySql, alors qu’en production, c’est une nécessité.

# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  # Emplacement du fichier où se trouve la base
  database: db/development.sqlite3
  # Nombre de connexion dans le pool
  pool: 5
  timeout: 5000

# ...

Notons que MySQL n’est pas le seul SGBD supporté par Rails : il en est de même pour PostgreSQL, DB2, SQL Server, Oracle, Sybase, Firebird, etc.

Prenons l’exemple de MySQL, et changeons la configuration pour nous connecter à une base MySQL correspondant à notre application en production :

# ...

production:
  adapter: mysql
  # Encodage du client
  encoding: utf8
  # Nom de la base à laquelle se connecter
  database: monapplication_prod
  pool: 5
  username: monnomdutilisateur
  password: monmotdepasse
  # Si la base se trouve sur la machine locale ...
  socket: /var/run/mysqld/mysql.sock
  # ... Ou si la base se trouve ailleurs
  host: serveurbdd.monapplication.com

Interaction avec les bases de données avec la ligne de commande

Nous avons vu dans le précédent article comment utiliser les migrations pour interagir avec les bases de données via la ligne de commande Rails. Voici quelques autres commandes qui vous permettront de tester tout ça à loisir :

  • rake db:create → Créer la base de donnée
  • rake db:destroy → Détruire la base de données

Notez que ces commandes, comme toutes les opérations en ligne de commande de Rails, sont sensibles au contexte pour le choix de l’environnement à affecter : par défaut, vous travaillez avec l’environnement de développement. Pour changer ce comportement, il vous faut changer la valeur de la variable d’environnement RAILS_ENV en lui donnant le nom de l’environnement de votre choix.

Ainsi vous écrirez par exemple la commande suivante pour supprimer le contenu de la base de production :

RAILS_ENV=production rake db:destroy

Sélection, Modification et Suppression d’élément

Utilisation de la console Rails

Ruby on Rails intègre une console interactive qui est un lieu d’expérimentation privilégié : vous pouvez directement créer des objets persistants en base, tester un bout de code, une fonction, etc. Nous allons l’utiliser pour la suite du tutorial, car cela nous évitera d’avoir à nous soucier des autres couches. Pour la démarrer, rien de plus simple :

script/console

Vous entrez maintenant dans la console interactive, tapez quit pour en sortir à tout moment ! Notez enfin que l’invite de commande commence toujours par les caractères >> alors que les retours de ruby sont préfixés d’une flèche =>.

Mise en place de notre projet de travail

Voici quelques commandes qui vous nous permettre de mettre en place un projet pour tester un peu les possibilités offertes en ce qui concerne les opérations simples de CRUD sur lesquelles nous allons nous attarder aujourd’hui.

rails appli_de_test
cd appli_de_test
script/generate model Utilisateur login:string mot_de_passe:string
script/generate model Article titre:string contenu:text utilisateur_id:integer
script/generate model Tag libelle:string --skip-timestamps
script/generate model Taggable tag_id:integer article_id:integer --skip-timestamps

Nous allons créer, comme vous l’avez compris un blog minimaliste : un Utilisateur possède des Articles. Les Articles partagent un ou plusieurs Tags via la relation Taggable.

Il ne nous reste plus qu’à éditer les fichiers de classes correspondants que vous trouverez dans le répertoire app/models pour exprimer explicitement les relations qui lient ces éléments entre eux :

# app/models/utilisateur.rb
class Utilisateur < ActiveRecord::Base
  has_many :articles
end

# app/models/article.rb
class Article < ActiveRecord::Base
  belongs_to :utilisateur
  has_many :taggables
  has_many :tags, :through => :taggables
end

# app/models/taggable.rb
class Taggable < ActiveRecord::Base
  belongs_to :article
  belongs_to :tag
end

# app/models/tag.rb
class Tag < ActiveRecord::Base
  has_many :taggables
  has_many :articles, :through => :taggables

  # Ce champ est obligatoire, on force donc sa présence
  validates_presence_of :libelle
end

Maintenant, il ne nous reste plus qu’à mettre en place la structure de la base de données, à l’aide de la commande rake db:migrate puis à nous connecter à la console de Rails pour commencer nos essais : script/console

Création d’élément

Précisons en guise de préambule que cet aperçu des possibilités qui vous sont offertes par Rails et Active Record (classe qui s’occupe spécifiquement de la persistence des données en base) est vraiment très superficiel ; je ne saurais trop vous conseiller de vous référer à la documentation d’Active Record pour apprendre réellement à vous en servir ;).

Nous allons tout d’abord créer des Tags à l’aide de la méthode create(). Cette méthode va instancier un objet Tag et créer directement l’enregistrement en base (si la validation s’est bien déroulée).

>> t = Tag.create
=> #<Tag id: nil, libelle: nil>

Ah, nous remarquons le Tag n’a pas d’id (nil signifie null en PHP) ; cela signifie qu’il n’est pas présent en base – or il devrait l’être, puisque c’est ce que nous attendons de la commande create()

Une erreur s’est produite à cause de la validation que nous avons ajouté dans la classe Tag : j’ai volontairement omis de préciser le champ libelle et l’enregistrement n’a pu être effectué. C’est ce mécanisme qui nous permettra de faciliter la validation des formulaires, ainsi que nous le verrons dans un prochain article.

Rails nous indique en fait dans la variable errors de l’objet t la liste des erreurs qui se sont produites et qui empêchent la validation et donc l’enregistrement de l’élément :

>> t = Tag.create
=> #<Tag id: nil, libelle: nil>
>> t.errors
=> #<ActiveRecord::Errors:0x7fd366f44020 @errors=#<OrderedHash 
  {"libelle"=>[#<ActiveRecord::Error:0x7fd366f42f90 @attribute=:libelle, 
  @type=:blank, @options={}, @message=:blank, @base=#<Tag id: nil, 
  libelle: nil>>]}>, @base=#<Tag id: nil, libelle: nil>>

Le message n’est pas forcément très lisible présenté comme ça, mais nous apprenons ainsi qu’il s’agit d’une erreur qui concerne le champ libelle et qu’elle a le type :blank. Le mécanisme de validation de formulaire de Rails rendra ce tableau très facile à exploiter pour nous par la suite, même s’il n’a pas l’air très avenant vu sous cet angle 😉 !

Essayons maintenant de reprendre la saisie des Tags en évitant de provoquer des erreurs :

>> Tag.create :libelle => "Rock"
=> #<Tag id: 1, libelle: "Rock">
>> Tag.create :libelle => "Folk"
=> #<Tag id: 2, libelle: "Folk">
>> Tag.create :libelle => "Soul"
=> #<Tag id: 3, libelle: "Soul">
>> Tag.create :libelle => "Funk"
=> #<Tag id: 4, libelle: "Funk">

Ce coup-ci, il est clair que tout se passe bien : les enregistrements se voient directement attribuer un id numérique, ce qui signifie qu’ils sont maintenant présents en base.

Sélection d’éléments

Maintenant que la base est peuplée de quelques éléments, nous pouvons nous intéresser aux méthodes qui nous permettent de retrouver ces mêmes éléments. Tout passe à vrai dire par la méthode de classe find qui est disponible pour chaque classe de modèle.

Ainsi, pour sélectionner une collection d’éléments, nous utilisons la variante find :all, en voici un exemple :

>> Tag.find :all
=> [#<Tag id: 1, libelle: "Rock">, #<Tag id: 2, libelle: "Folk">, #<Tag id: 3, libelle: "Soul">, #<Tag id: 4, libelle: "Funk">]

Notez que dans le retour qui est fait, Rails nous renvoie bien une collection d’éléments qu’il a placé dans un tableau ([ ... ]) ; comme en Javascript, on emploie en Ruby les crochets pour écrire un tableau.

Essayons maintenant d’ajouter une condition à notre sélection – plusieurs syntaxes sont disponibles, voici celle qui permet de créer une sorte de requête préparée qui prend la forme d’un tableau. Les ? dans la requête SQL sont ainsi remplacés par les valeurs entrées dans les index suivant. Ici nous recherchons par exemple les Tags qui commencent par la lettre F.

>> Tag.find :all, :conditions => [ 'libelle like ?', 'F%' ]
=> [#<Tag id: 2, libelle: "Folk">, #<Tag id: 4, libelle: "Funk">]

Voyons maintenant comment sélectionner un élément unique dans la base ; pour cela, on emploie la version find :first de la méthode :

>> Tag.find :first, 1
=> #<Tag id: 1, libelle: "Rock">

Dans le cas présent, Rails a bien renvoyé un élément unique, et non un tableau, ainsi que c’était le cas précédemment. On aurait éventuellement pu omettre le :first, c’est en fait la méthode utilisée par Rails si on ne précise rien.

Nous allons maintenant voir une autre façon d’écrire les conditions : plutôt que de passer un tableau, il est également possible de passer un simple hash ({ ... }) – il s’agit d’une sorte de tableau associatif :

>> Tag.find :first, :conditions => { :libelle => 'Rock' }
=> #<Tag id: 1, libelle: "Rock">

Voilà les bases de ce qu’il est possible de faire pour sélectionner des éléments ; vous trouverez ici le détail des différentes options supportées par la méthode find, elles sont nombreuses. Vous pouvez également regarder ce screencast qui fait une démonstration des possibilités offertes par cette méthode.

Modification d’un élément

Maintenant que vous savez retrouver un élément, il serait utile de savoir le modifier puis le sauvegarder ; voici comment faire en employant assez logiquement la méthode save :

>> t = Tag.find :first, 1
=> #<Tag id: 1, libelle: "Rock">
>> t.libelle = "Rock'n'Roll"
=> "Rock'n'Roll"
>> t.save
=> true

Je sélectionne ainsi l’élément « Rock », que je désire modifier en « Rock’n’Roll ». Il me suffit de changer la valeur du champ « libelle », puis de sauvegarder. Notez que la méthode save retourne une variable qui va nous permettre de savoir que l’enregistrement a bien été effectué, s’il a correctement passé la validation. Retrouvez ici la documentation concernant cette méthode.

Voici un exemple de modification qui va échouer :

>> t = Tag.find 2
=> #<Tag id: 2, libelle: "Folk">
>> t.libelle = ""
=> ""
>> t.save
=> false
>> t.errors
=> #<ActiveRecord::Errors:0x7f9f6d187d60 @errors=#<OrderedHash 
  {"libelle"=>[#<ActiveRecord::Error:0x7f9f6d185510 @attribute=:libelle, 
  @type=:blank, @options={}, @message=:blank, @base=#<Tag id: 2, 
  libelle: "">>]}>, @base=#<Tag id: 2, libelle: "">>

Comme avec la méthode create que nous voyions tout à l’heure, il est toujours possible d’accéder aux messages de validation. Il nous est toutefois possible de court-circuiter la validation si nous sommes dans un environnement contrôlé, ce qui permet d’accélérer le traitement, grâce à la méthode save!.

Suppression d’un élément

Dernière partie, et non la moins importante de notre passage en revue du CRUD le plus basique en Rails, la destruction d’un élément. Voilà comment s’y prendre, grâce à la méthode destroy :

>> t = Tag.find :first, :conditions => {:id => 1}
=> #<Tag id: 1, libelle: "Rock'n'Roll">
>> t.destroy
=> #<Tag id: 1, libelle: "Rock'n'Roll">
>> Tag.find :first, :conditions => {:id => 1}
=> nil

Il est important de noter que la fonction destroy retourne l’élément supprimé en cas de succès, ce qui s’avère souvent très utile.

Créons des relations entre les objets

Maintenant que nous savons interagir avec nos Modèles, il nous reste à voir comment créer des relations entre eux ; nous allons ainsi créer un Utilisateur et lui ajouter plusieurs Articles, ce qui nous permettra de voir le fonctionnement de la relation 1 à plusieurs. Dans un second temps, nous verrons comment assigner des Tags à un Article donnée en passant par la relation plusieurs à plusieurs.

Utiliser la relation Un à Plusieurs

Voici quelques commandes maintenant connues qui vont nous permettre de créer quelques enregistrements que nous allons utiliser par la suite.

>> util1 = Utilisateur.create :login => "utilisateur1", :mot_de_passe => "mdp1"
=> #<Utilisateur id: 1, login: "utilisateur1", mot_de_passe: "mdp1", 
  created_at: "2009-12-11 17:07:08", updated_at: "2009-12-11 17:07:08">
>> util2 = Utilisateur.create :login => "utilisateur2", :mot_de_passe => "mdp2"
=> #<Utilisateur id: 2, login: "utilisateur2", mot_de_passe: "mdp2", 
  created_at: "2009-12-11 17:07:21", updated_at: "2009-12-11 17:07:21">

>> arti1 = Article.create :titre => "Titre de mon premier article", 
  :contenu => "Pas de contenu pour le moment"
=> #<Article id: 1, titre: "Titre de mon premier article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil, 
  created_at: "2009-12-11 17:08:11", updated_at: "2009-12-11 17:08:11">
>> arti2 = Article.create :titre => "Titre de mon second article", 
  :contenu => "Pas de contenu pour le moment" 
=> #<Article id: 2, titre: "Titre de mon second article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil, 
  created_at: "2009-12-11 17:08:28", updated_at: "2009-12-11 17:08:28">
>> arti3 = Article.create :titre => "Titre de mon troisième article", 
  :contenu => "Pas de contenu pour le moment"
=> #<Article id: 3, titre: "Titre de mon troisième article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil, 
  created_at: "2009-12-11 17:08:42", updated_at: "2009-12-11 17:08:42">

Comment changer le rédacteur du premier Article pour dire qu’il est en fait le premier Utilisateur ? Rien de plus simple :

>> arti1.utilisateur = util1
=> #<Utilisateur id: 1, login: "utilisateur1", mot_de_passe: "mdp1", 
  created_at: "2009-12-11 17:07:08", updated_at: "2009-12-11 17:07:08">
>> arti1.save
=> true
>> arti1
=> #<Article id: 1, titre: "Titre de mon premier article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: 1, 
  created_at: "2009-12-11 17:08:11", updated_at: "2009-12-11 17:10:41">

Si nous avions voulu exploiter la relation dans le sens inverse, il nous aurait fallu énoncer les choses différemment : en effet un Article n’a qu’un seul et unique Utilisateur dans notre exemple – alors qu’un Utilisateur peut avoir écrit un nombre indéfini d’Articles.

Rails nous donne donc accès à un tableau dans le lequel il nous suffit d’ajouter les Articles de notre choix à l’Utilisateur qui est leur auteur. Disons que le deuxième Utilisateur a écrit les deux derniers articles :

>> util2.articles = [ arti2, arti3 ]
=> [#<Article id: 2, titre: "Titre de mon second article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: 2, 
  created_at: "2009-12-11 17:08:28", updated_at: "2009-12-11 17:14:41">, 
  #<Article id: 3, titre: "Titre de mon troisième article", 
  contenu: "Pas de contenu pour le moment", utilisateur_id: 2, 
  created_at: "2009-12-11 17:08:42", updated_at: "2009-12-11 17:14:41">]
>> util2.save
=> true
>> util2
=> #<Utilisateur id: 2, login: "utilisateur2", mot_de_passe: "mdp2", 
  created_at: "2009-12-11 17:07:21", updated_at: "2009-12-11 17:07:21">

Et voilà pour la relation Un à Plusieurs ; comme vous le voyez, le fonctionnement de Rails à ce sujet est assez intuitif et logique.

Utiliser la relation Plusieurs à Plusieurs

Nous allons maintenant ajouter les tags que nous avons déjà employé précédemment pour tagguer nos Articles. Rails fait la plus grosse partie de notre travail grâce à la relation has_many :through que nous avons défini dans les classes Article et Tag : nous pouvons faire abstraction de la classe Taggable avec laquelle notre framework se chargera d’interagir.

Commençons par ajouter deux Tags à notre premier article :

>> arti1.tags << Tag.find(:first, :conditions => { :libelle => "Folk" })
=> [#<Tag id: 2, libelle: "Folk">]
>> arti1.tags << Tag.find(:first, :conditions => { :libelle => "Funk" })
=> [#<Tag id: 2, libelle: "Folk">, #<Tag id: 4, libelle: "Funk">]
>> arti1.tags
=> [#<Tag id: 2, libelle: "Folk">, #<Tag id: 4, libelle: "Funk">]

Notez que j’utilise une syntaxe différente pour interagir avec le tableau des tags de l’Article ; l’opérateur << peut se lire comme : « ajoute l’élément suivant à ce tableau » ; il s’agit d’un équivalent du array_push() de PHP ou du push() de Javascript.

Ajoutons encore quelques Tags à un autre Article et regardons le contenu de la table contenant les Taggables, avec lesquels nous ne sommes pas directement intervenus, pour constater que Rails a fait lui-même les associations nécessaires pour nous permettre de nous abstraire de cette table de jointure.

>> arti2.tags = Tag.find(:all)
=> [#<Tag id: 2, libelle: "Folk">, #<Tag id: 3, libelle: "Soul">, 
  #<Tag id: 4, libelle: "Funk">]
>> Taggable.find :all
=> [#<Taggable id: 1, tag_id: 2, article_id: 1>, 
  #<Taggable id: 2, tag_id: 4, article_id: 1>, 
  #<Taggable id: 3, tag_id: 2, article_id: 2>, 
  #<Taggable id: 4, tag_id: 3, article_id: 2>, 
  #<Taggable id: 5, tag_id: 4, article_id: 2>]

Voilà, c’est tout pour aujourd’hui ! Dans quelques temps, nous verrons la suite (et a priori la fin) de notre programme initial sur la couche Modèle de Rails, avec les garnitures (fixtures) & les filtres. En attendant, n’hésitez pas à essayer de refaire ce tutoriel chez vous, c’est tout son intérêt ;), et laissez un commentaire si vous rencontrez un problème.

Les textes, illustrations et démonstrations présents sur ce site sont la propriété de leurs auteurs respectifs, sauf mention contraire (photo de la bannière).
Chosesafaire.fr, un site propulsé par Wordpress, vous est proposé par Pierre Quillery & Killian Ebel.

Valid XHTML 1.0 Strict