spacer ASCIIcasts

Tags

219: Active Model 

(view original Railscast)

Other translations: spacer spacer spacer spacer

Written by Simon Courtois

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.

spacer

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 :

/app/models/message.rb

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.

spacer

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.

/app/models/message.rb

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.

/apps/controllers/messages_controller.rb

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?.

/app/controllers/messages_controllers.rb

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.

/app/models/message.rb

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.

spacer

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 :

/app/models/message.rb

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.

spacer

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.

Previous episode Next episode
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.