#219 Active Model
- Download:
- source codeProject Files in Zip (92.6 KB)
- mp4Full Size H.264 Video (12.7 MB)
- m4vSmaller H.264 Video (8.03 MB)
- webmFull Size VP8 Video (20.9 MB)
- ogvFull Size Theora Video (18.2 MB)
L'épisode 193 [regarder, lire] était centré sur les modèles non liés à une table. Dans cet épisode, nous avons créé un modèle qui propose certaines fonctionnalités d'ActiveRecord mais qui ne correspond à aucune table en base de données. La technique utilisée était plutôt un hack parce qu'ActiveRecord n'était pas prévu pour faire ça. Avec Rails 3.0 cependant, nous avons une nouvelle fonctionnalité appelée ActiveModel qui facilite vraiment ce mécanisme.
Avant de voir ActiveModel en détail, nous allons décrire la partie de l'application que nous allons modifier pour utiliser ce dernier.
Le screenshot ci-dessus montre un formulaire de contact créé grâce au scaffold de Rails. L'application dispose d'un modèle nommé Message
qui est un objet ActiveRecord, ce qui signifie que les messages passent par la base de données. Nous allons changer le fonctionnement de ce formulaire afin qu'il envoie simplement les messages sans les stocker en base de données.
Lorsque l'on s'apprête à faire quelque chose comme ça, il est toujours bon de prendre le temps de voir quels sont vos besoins et de vous assurer que vous n'avez pas besoin de les stocker en base de données, cela peut parfois avoir de bon côtés. Le stockage en base permet la sauvegarde ou rend plus facile la délégation de l'envoi de messages dans une queue pour un processus en arrière plan. Pour notre exemple toutefois, nous n'avons besoin d'aucune de ces fonctionnalités, nous pouvons donc créer un modèle sans table.
Le code de la classe Message
ressemble à ceci :
class Message < ActiveRecord::Base validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
Message
hérite de ActiveRecord::Base
comme tout modèle mais, comme nous ne voulons pas de stockage en base, nous allons supprimer cet héritage. Une fois cela fait, notre formulaire ne fonctionnera plus étant donné que les validations sont fournies par ActiveRecord. Heureusement, nous pouvons retrouver ces fonctionnalités dans ActiveModel.
Si nous regardons le code de Rails 3, nous pouvons voir qu'il existe deux dossiers, activerecord
et activemodel
. L'équipe de développement de Rails a pris tout ce qui, dans ActiveRecord, n'était pas directement lié à la base de données et l'a mis dans ActiveModel. ActiveRecord se base fortement sur ActiveModel pour tout ce qui ne concerne pas la base de données et, comme ActiveModel est complet et bien testé, il est très pratique à utiliser en dehors d'ActiveRecord.
Si nous jetons un œil dans le dossier qui contient le code d'ActiveModel, nous pouvons voir les fonctionnalités qu'il fournit.
Nous pouvons voir qu'ActiveModel inclut, entre autres, le code de gestion des callbacks, du ditry tracking, de la sérialisation et des validations. Ce sont les validations qui nous intéressent.
Le code des validations contient, vers le début du fichier, le commentaire suivant. Nous pouvons en retenir qu'il est très facile d'ajouter les validations à un modèle. Tout ce que nous avons besoin de faire, c'est inclure le module Validations
et fournir des accesseurs pour les attributs sur lesquels nous appelons les validations.
# == Active Model Validations # # Fournit à vos objets un framework de validation complet. # # Une implémentation minimale pourrait être: # # class Person # include ActiveModel::Validations # # attr_accessor :first_name, :last_name # # validates_each :first_name, :last_name do |record, attr, value| # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end #
Maintenant que nous savons ceci, nous pouvons l'appliquer sur notre modèle Message.
class Message include ActiveModel::Validations attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 end
Ce n'est pas suffisant pour que notre modèle se comporte comme le contrôleur s'y attend. Il y a deux problèmes dans la méthode create
. Tout d'abord, l'appel à Message.new
ne fonctionnera plus puisque le constructeur de notre modèle ne prend plus en paramètre un hash d'attributs. Ensuite, save
ne fonctionnera pas puisque nous n'avons pas de table en base dans laquelle stocker notre nouveau message.
class MessagesController < ApplicationController def new @message = Message.new end def create @message = Message.new(params[:message]) if @message.save # TODO send message here flash[:notice] = "Message sent! Thank you for contacting us." redirect_to root_url else render :action => 'new' end end end
Nous allons résoudre le deuxième problème en premier. Bien que nous ne puissions sauvegarder le message, nous pouvons vérifier sa validité, nous allons donc remplacer @message.save
par @message.valid?
.
def create @message = Message.new(params[:message]) if @message.valid? # TODO send message here flash[:notice] = "Message sent! Thank you for contacting us." redirect_to root_url else render :action => 'new' end end
Nous pouvons résoudre le deuxième problème en implémentant, dans le modèle Message
, la méthode initialize
qui prend un hash en paramètre. Cette méthode va boucler sur le hash et assigner sa valeur à chaque attribut du message grâce à la méthode send
.
class Message include ActiveModel::Validations attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end end
Si nous rechargeons le formulaire, nous voyons que ce n'est pas encore tout à fait ça.
Cette fois, l'erreur est causée par la méthode to_key
qui est manquante dans le modèle Message
. L'erreur est retournée par la méthode form_for
. Cela signifie que Rails lui-même attend une fonctionnalité que notre modèle ne supporte pas encore. Ajoutons cette fonctionnalité.
Plutôt que d'essayer de deviner tout ce que Rails attend de notre modèle, il existe un sympathique test de compatibilité inclus dans ActiveModel qui permet de vérifier si notre modèle se comporte comme Rails s'y attend. Si nous incluons le module ActiveModel::Lint::Tests
dans les tests du modèle, cela va automatiquement vérifier que le modèle à les fonctionnalités requises.
Le code source du module Lint::Tests
montre les méthodes auxquelles le modèle doit répondre pour que tout fonctionne comme il faut, y compris to_key
. Nous pouvons faire marcher notre modèle en incluant quelques modules d'ActiveRecord. Le premier est Conversion
, qui fournit, entre autres, cette méthode to_key. L'autre module est Naming
, mais cette fois nous allons utiliser extend
dans notre modèle étant donné que ce module inclut des méthodes de classe.
En plus d'inclure le module Conversion
, nous devons définir une méthode persisted?
dans notre modèle, qui doit retourner false
puisque notre modèle n'a pas de persistance en base. Avec ces changements, notre modèle Message
devrait maintenant ressembler à ceci :
class Message include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming attr_accessor :name, :email, :content validates_presence_of :name validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i validates_length_of :content, :maximum => 500 def initialize(attributes = {}) attributes.each do |name, value| send("#{name}=", value) end end def persisted? false end end
Si nous rechargeons le formulaire, il marche de nouveau. Cela signifie que le modèle Message
satisfait les attentes de Rails 3. Si nous essayons de soumettre le formulaire, nous voyons que les validations fonctionnent également.
Dans cet épisode, nous avons seulement effleuré la surface de ce que peut faire ActiveModel mais cela devrait être suffisant pour vous mettre en appétit et vous donner une raison de regarder plus avant le code pour voir ce que l'on peut faire. Le code est bien documenté et structuré. Si vous voyez quelque chose qui peut vous être utile, vous devriez trouver assez d'informations dans les commentaires de ce fichier pour que vous puissiez vous en servir.