Editer plusieurs modèles avec fields_for

Publié le 23/01/08 par Jean-Baptiste Escoyez | 0 commentaires

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.


class Post < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :posts
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) :


<p> <b>Title</b><br /> <%= f.text_field :title %> </p>

<p> <b>Content</b><br /> <%= f.text_area :content %> </p>

<p> <b>Name</b><br /> <%= text_field_tag :name, @post.author.name %> </p>

<p> <b>Email</b><br /> <%= text_field_tag :email, @post.author.email %> </p>

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


def create
  @post = Post.new(params[:post])
  @post.author = Author.new(:name => params[:name], :email => params[:email])

  respond_to do |format|
    if @post.save
      # response code
    end
  end
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):


<p> <b>Title</b><br /> <%= f.text_field :title %> </p>

<p> <b>Content</b><br /> <%= f.text_area :content %> </p>

<% fields_for :author, @post.author do |af| %>
  <p> <b>Name</b><br /> <%= af.text_field :name %> </p>

  <p> <b>Email</b><br /> <%= af.text_field :email %> </p>
<% end %>

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


def create
  @post = Post.new(params[:post])
  @post.author = Author.new(params[:author])

  respond_to do |format|
    if @post.save
      # response code
    end
  end
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:


class Post < ActiveRecord::Base
  belongs_to :author
  alias author_attributes=  build_author
end

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


def create
  @post = Post.new(params[:post])
  @post.author = Author.new(params[:author])

  respond_to do |format|
    if @post.save
      # response code
    end
  end
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.