Prêt pour une séance d’occultisme ? Dans cet article de la série destinée aux bases de Ruby On Rails nous allons continuer à étudier la magie de notre framework favori partout où elle se trouve !
Aujourd’hui on s’attaque aux formulaires et plus précisément au form_for
.
Rails offre un tas de raccourcis qui aident à développer et le form n’échappe pas à cette règle.
Si vous avez commencé avec le scaffold de Rails vous utilisez peut-être form_for
sans trop vous soucier de ce qui se passe derrière pourtant…
Avant d’entrer dans les détails, voyons voir ce qu’un form_for
génère pour un formulaire minimaliste de création d’article de blog :
views/blogs/_form.html.erb
<%= form_for(@blog) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :content %><br>
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
HTML généré
<form class="new_blog" id="new_blog" action="/blogs" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓" />
<input type="hidden" name="authenticity_token" value="RTzn2pxMSZJB3wS7+H0CXYHghQgfjvhFznarusp4ALR2iR8QxCxL5I1HZgPkQwN26YjaxpWb29x2tkT3i6vjqw==" />
<div class="field">
<label for="blog_title">Title</label><br>
<input type="text" name="blog[title]" id="blog_title" />
</div>
<div class="field">
<label for="blog_content">Content</label><br>
<textarea name="blog[content]" id="blog_content">
</textarea>
</div>
<div class="actions">
<input type="submit" name="commit" value="Create Blog" />
</div>
</form>
À première vue rien d’extraordinaire, mais si on regarde de plus près, on observe plusieurs choses.
À la création de votre formulaire on remarque un input de type hidden
qui a pour valeur une clé d’authentification.
<input type="hidden" name="authenticity_token" value="RTzn2pxMSZJB3wS7+H0CXYHghQgfjvhFznarusp4ALR2iR8QxCxL5I1HZgPkQwN26YjaxpWb29x2tkT3i6vjqw==" />
Celle-ci est créée par le form_for
pour prévenir de la faille CSRF et oblige le visiteur à posséder le jeton généré lors de l’affichage du formulaire.
<div class="field">
<label for="blog_title">Title</label><br>
<input type="text" name="blog[title]" id="blog_title" />
</div>
On remarque aussi que nos inputs ont un name
composé du nom du modèle sur lequel on agit ainsi que de son attribut entre crochets.
Ainsi Rails va nous simplifier la vie lorsqu’on voudra traiter des objets imbriqués au sein d’un même formulaire.
Admettons que vous souhaitiez réaliser un formulaire sans passer par Rails, vous vous y prendriez sans doute comme ceci :
<form action="/blogs" accept-charset="UTF-8" method="post">
<label for="blog_title">Title</label><br>
<input type="text" name="title" id="blog_title" />
<label for="blog_content">Content</label><br>
<textarea name="content" id="blog_content">
</textarea>
<input type="submit" name="commit" value="Créer un article" />
</form>
Plusieurs problèmes se posent alors, d’abord lors de la soumission, plusieurs messages d’erreurs apparaissent :
En effet si vous souhaitez interagir avec la base de données, vous aurez besoin d’ajouter une clé d’authentification fournie une fois de plus par Ruby On Rails.
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
Afin de respecter les conventions de Rails, on nous invite alors à détailler l’attribut name
comme vu précédemment.
Enfin, si je souhaite que mon formulaire HTML représente mon objet actuel j’aurais besoin de préciser l’attribut value
comme ci-dessous :
<input type="text" name="blog[title]" id="blog_title" value="<%= @blog.title %>" />
C’est beau la magie, mais on a envie de comprendre “le truc”, on va donc regarder la méthode form_for
fournit par Rails. Pour cela on se dirige dans l’action_view
qui comme son nom l’indique regroupe ce qui attrait au rendu.
Regardons d’abord la définition de la méthode form_for
:
rails/actionview/lib/action_view/helpers/form_helper.rb
def form_for(record, options = {}, &block)
# Plein de code ruby ...
end
On remarque que celle-ci reçoit trois arguments.
Le record, qui est l’objet qu’on passe au form_for
, précédemment @blog
.
Les options avec lesquelles on pourra modifier la méthode. Par exemple :
<%= form_for(@blogs, remote: true, method: :patch) do |f| %>
Enfin le &block
, qui désigne le contenu du bloc
<%= form_for @blogs do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :content %><br>
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Contenu de la méthode
Les premières lignes sont simples, dans un premier temps la méthode form_for
soulève une erreur si le bloc n’existe pas, puis elle définit html_options
.
def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
html_options = options[:html] ||= {}
# Plein de code ruby ...
end
Ensuite, on vérifie le type de record
pour déterminer les valeurs qu’on passera dans object_name
et dans object
qui seront réutilisés par la suite.
case record
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
La méthode model_name_from_record_or_class
va nous permettre d’extraire le nom de l’objet disponible depuis celui-ci.
model_name_from_record_or_class(object).param_key
Puis on commence à traiter le rendu avec apply_form_for_options
.
apply_form_for_options!(record, object, options)
builder = instantiate_builder(object_name, object, options)
la méthode instantiate_builder
va à la fois instancier la classe souhaitée et construire le formulaire en prenant en compte cette classe et les paramètres passés.
output = capture(builder, &block)
capture
est une méthode qui permet de stocker le template qu’on a constitué jusqu’à présent ainsi que le contenu du bloc dans la variable output
qu’on ré-exploitera par la suite.
form_tag_with_body(html_options, output)
Pour finir on concatène les éléments et on ajoute la fermeture du formulaire ! Voyez vous-même :
def form_tag_with_body(html_options, content)
output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
end
Pourquoi utiliser form_tag
?
Le form_for
est destiné à interagir avec des ressources, pour cette raison il est contenu dans un bloc qui lui permet de gérer très simplement la création et l’édition de données associées à l’objet, ici @blog
.
Cependant, il n’est pas toujours utile d’avoir un formulaire compris dans un bloc, si on souhaite réaliser une barre de recherche un simple GET
suffira. C’est la qu’intervient le form_tag
qui est à mi-chemin entre notre formulaire HTML et le form_for
.
views/blogs/search.html.erb
<%= form_tag(blogs_search_path, :method => "get") do %>
<%= text_field_tag :title, params[:title], placeholder: "Chercher un article de blog" %>
<%= submit_tag "Search", :name => nil %>
<% end %>
<% @blogs.each do |blog| %>
<h1><%= blog.title %></h1>
<p><%= blog.content %></p>
<% end %>
controllers/blogs_controller.rb
# GET /blogs?title=title
def search
if params[:title]
@blogs = Blog.search(params[:title])
else
@blogs = Blog.all
end
end
models/blog.rb
def self.search(title)
where("title like ?", "%#{title}%")
end
Pour finir, voici un petit tableau récapitulatif des avantages des différents formulaires.
Avantages | Formulaire HTML | form_tag | form_for |
---|---|---|---|
Facilite la création de balises HTML | Non | Oui | Oui |
Génère une clé d'authentification | Non | Oui | Oui |
Attributs name : objet[attribut] | Non | Non | Oui |
Facilite la création et l'édition | Non | +/- | Oui |
J’espère que cet article vous aura aidé à mieux comprendre la magie du form_for
.
À très vite pour le prochain article sur la magie de Rails !
L’équipe Synbioz.
Libres d’être ensemble.