Il n’est pas toujours aisé de traiter les données envoyées par un formulaire. Souvent, les contrôleurs grossissent lorsque les formulaires se complexifient. Avec fields_for, il est possible de transférer tout ce traitement dans le modèle. Nos contrôleurs restent alors parfaitement DRY.

Le problème

Prenons l’exemple habituel du blog.

   1  class Post < ActiveRecord::Base
   2    belongs_to :author
   3  end
   4    
   5  class Author < ActiveRecord::Base
   6    has_many :posts
   7  end

Chaque Post est écrit par un Author. Dans le formulaire de création de Post, les champs name et email de l’auteur sont inclus.
Nous voudrions créer à la volée un enregistrement Author dans la base de données au moment de la création de Post.
Pour le moment, notre application ressemble à ceci.

Vue du formulaire (app/view/posts/_form.html.erb) :

   1  <p> <b>Title</b><br /> <%= f.text_field :title %> </p>
   2  
   3  <p> <b>Content</b><br /> <%= f.text_area :content %> </p>
   4   
   5  <p> <b>Name</b><br /> <%= text_field_tag :name, @post.author.name %> </p>
   6  
   7  <p> <b>Email</b><br /> <%= text_field_tag :email, @post.author.email %> </p>

Action create dans PostsController (app/controllers/posts_controller.rb) :

   1  def create
   2    @post = Post.new(params[:post])
   3    @post.author = Author.new(:name => params[:name], :email => params[:email])
   4  
   5    respond_to do |format|
   6      if @post.save
   7        # response code
   8      end
   9    end
  10  end

La solution

Nous désirons simplifier la vue et le contrôleur. La principale cause de désagrément est l’organisation des attributs envoyés par le formulaire :

  { "name"=>"Jean-Baptiste Escoyez",
    "email"=>"jbe at belighted point com",
    "post"=>{
      "title"=>"Editer plusieurs modèles avec fields_for", 
      "content"=>"Contenu..."}}

Nous voyons que name et email ne sont pas associés à post. De ce fait, nous sommes obligés de créer un Author dans le contrôleur.

Nous utilisons fields_for pour nous aider à organiser les arguments.

Vue du formulaire (app/view/posts/_form.html.erb):

   1  <p> <b>Title</b><br /> <%= f.text_field :title %> </p>
   2  
   3  <p> <b>Content</b><br /> <%= f.text_area :content %> </p>
   4    
   5  <% fields_for :author, @post.author do |af| %>
   6    <p> <b>Name</b><br /> <%= af.text_field :name %> </p>
   7  
   8    <p> <b>Email</b><br /> <%= af.text_field :email %> </p>
   9  <% end %>

Action create dans PostsController (app/controllers/posts_controller.rb):

   1  def create
   2    @post = Post.new(params[:post])
   3    @post.author = Author.new(params[:author])
   4  
   5    respond_to do |format|
   6      if @post.save
   7        # response code
   8      end
   9    end
  10  end

Remarquez le second argument de fields_for. Il s’agit de l’objet qui donnera les valeurs aux champs name et email au moment de l’édition du post. Le contrôleur a également changé car les arguments envoyés par le formulaire sont maintenant organisés différemment.

  { "author"=>{
      "name"=>"Jean-Baptiste Escoyez",
      "email"=>"jbe at belighted point com"},
    "post"=>{
      "title"=>"Editer plusieurs modèles avec fields_for", 
      "content"=>"Contenu..."} }

Encore mieux !

Nous pouvons aller encore plus loin dans notre refactorisation. En changeant fields_for par f.fields_for, vous allez imbriquer les attributs des deux objets.

  { "post"=>{
      "name"=>"Jean-Baptiste Escoyez",
      "email"=>"jbe at belighted point com",
      "author"=>{
        "title"=>"Editer plusieurs modèles avec fields_for", 
        "content"=>"Contenu..."} } }

Comme une des clefs du hash est author, la méthode author= de post sera appelée lorsque vous appellerez Post.new(params[:post]). Malheureusement, la méthode author n’est pas prévue pour prendre un hash en paramètre mais un objet Author. Il faut donc remplacer f.fields_for :author par f.fields_for :author_attributes et créer une méthode author_attributes= dans le modèle Post ; comme la méthode author_attributes= est semblable à build_author, un alias de méthode suffit:

   1  class Post < ActiveRecord::Base
   2    belongs_to :author
   3    alias author_attributes=  build_author
   4  end

Vous pouvez dès lors supprimer la ligne post.author = Author.new(params[:author]) dans votre contrôleur.

   1  def create
   2    @post = Post.new(params[:post])
   3    @post.author = Author.new(params[:author])
   4  
   5    respond_to do |format|
   6      if @post.save
   7        # response code
   8      end
   9    end
  10  end

fields_for peut être utilisé de manière imbriquée si vous avez plusieurs modèles à créer/éditer. N’oubliez pas le f. avant fields_for si vous voulez imbriquer les attributs.

fields_for peut également être utilisé sans modèle correspondant, juste pour structurer l’information envoyée par un formulaire. Depuis que j’utilise fields_for, j’ai complètement laissé tomber les FormTagHelper.

0 Commentaires

Ajouter un commentaire

Vous devez être identifié pour poster un commentaire. Identifiez-vous, ou inscrivez-vous si ce n'est déjà fait.