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