Le blog de la CT2C

Starter App - Partie 4 - Enrichissons nos formulaires - Simple Form

Par Régis Millet aka Kulgar , publié le 20 Février 2013

Vers la partie précédente

Nous voici dans la quatrième partie de cette suite de tutoriels. Nous allons découvrir ici comment nous faciliter la création de formulaires en Ruby On Rails, notamment à l'aide de la gem simple_form. Pour ne pas avoir une partie trop longue, je vais la découper en deux. Dans celle-ci, nous verrons Simple Form sous toutes les coutures. Dans la suivante, nous verrons d'autres gems très utiles pour les formulaires : nested_form, client_side_validations + quelques petits bonus.


Les formulaires en Ruby On Rails

Petit rappel, comment crée-t-on un formulaire en Ruby On Rails ? A l'aide d'une belle panoplies de methodes (helpers) préfaites.

Lorsque j'ai découvert Ruby On Rails, je fus tout bonnement impressionné de la facilité avec laquelle il était possible de créer des formulaires. Je trouvais leur création simple, rapide et le code HTML généré était tout bonnement parfait. Alors pourquoi utiliser une gem, alors qu'il est déjà si simple de créer des formulaires en ROR ?

Hé bien tout simplement parce qu'avec Ruby On Rails, elle pourrait, elle devrait être encore plus simple. En effet, de base, les méthodes fournies ne prennent absolument pas en compte le type des données définies au sein du model, font fi de toutes les validations et manquent cruellement de flexibilité et d'options pour modifier l'aspect du code html généré, ou tout simplement pour ajouter quelques informations bien utiles...

Les gems Formtastic et Simple Form (les plus connues) ont été créées pour encore plus faciliter la création de formulaires en ROR, tout en ajoutant une très chouette panoplie d'options pour personnaliser son formulaire. Ajoutez à cela certaines gems comme "ClientSideValidations" ou "Nested Form" (que nous verrons également) qui s'intègrent parfaitement à Simple Form/Formtastic, et vous rendrez très vite compte que vous ne pourrez plus jamais vous passer de gems pour la création de vos formulaires. :)

Simple Form

Dépôt GitHub
Pourquoi utiliser Simple Form plutôt que Formtastic ? Pour une fois, ce n'est pas qu'une question de goût !

La raison principale, c'est que les développeurs de Simple Form sont également ceux de la gem Devise. A priori, cela ne vous apporte rien, mais en réalité si ! Il se trouve que ces superbes développeurs ont fait en sorte que Devise détecte la présence de la gem Simple Form. Si cette gem est effectivement présente, la gem Devise générera alors ses formulaires en y intégrant la syntaxe et les méthodes de Simple Form ! Et ça, c'est plutôt pratique.

Deuxième raison, Simple Form accepte une option lors de son installation pour que le code html généré par la gem soit dors et déjà compatible avec Foundation (ou Twitter Bootstrap). Et ça aussi, c'est plutôt pratique. Tandis que pour Formtastic, il faudra chercher d'autres gems, et il n'en existe même pas pour Foundation (vous allez me dire, et celle-ci ? Je vous répondrais que son développement n'est même pas au stade embryonnaire, elle ne fonctionne pas.)

Troisième raison, je préfère de loin le code html généré par Simple Form que celui généré par Formtastic. Le code de Formtastic transforme ceci :

<%= semantic_form_for @article do |f| %>
<%= f.inputs :name => "Basic" do %>
<%= f.input :title %>
<%= f.input :body %>
<% end %>
<%= f.submit %>
<% end %>

En ceci :
<form ...>
<fieldset>
<ol>
<li ...><label ...>Title</label><input ...></li>
<li ...><label ...>Body</label><input ...></li>
</ol>
</fieldset>
<input type="submit" ...>
</form>

Vous voyez le 'ol' 'li' ? Dans Formtastic, il est possible de personnaliser la balise fieldset, les balises li entourant chaque couple label / input, le label et l'input mais pour une raison que j'ignore, il n'est PAS possible de modifier la balise ol (ou si c'est le cas, pas d'une façon triviale).

Cela ne pose aucun problème, sauf à partir du moment où l'on souhaite organiser notre site en grille pour le responsive design... J'ai essayé, et je peux vous dire que modifier le code HTML généré par Formtastic pour que ça colle bien au format attendu par Foundation ou Twitter Bootstrap, c'est tout bonnement impossible. D'ailleurs, la gem formtastic bootstrap qui propose cette compatibilité casse totalement la manière dont le code est généré par Formtastic...

Autre petit point négatif, Formtastic utilise automatiquement la class CSS label pour tous les label. Or cette class a une totale autre fonction dans les framework front-end comme Foundation et Twitter bootstrap avec du CSS déjà bien fourni. Qui plus est, il est encore une fois impossible de modifier la class CSS de la balise label dans formtastic. Et ça, je n'aime pas du tout. La class est tout bonnement ajoutée en dur dans le code de formtastic (j'ai été me balader dedans pour voir), et la méthode ajoutant cette class n'accepte aucune option pour la modifier. Ce n'est tout bonnement pas prévu.

De fait, je trouve Formtastic trop rigide dans le code html généré et absolument pas conforme à un code html responsive design. A l'ère des IPad / IPhone, c'est fortement dommage et je déconseille son utilisation tant qu'elle n'aura pas changé sa façon de gérer le code HTML généré.

Tandis que les développeurs de Simple Form ont axé leurs efforts sur la flexibilité :
SimpleForm aims to be as flexible as possible while helping you with powerful components to create your forms.
En résumé, SimpleForm c'est toute la puissance de Formtastic (dont ils se sont beaucoup inspirés) avec toute la flexibilité qu'on attend d'une telle gem.

Installation de la gem et préparation de l'application
Ceci étant dit, passons aux choses sérieuses. Commençons par installer la gem. Chose curieuse, la gem semble ne plus être mise à jour sur RubyGems depuis quelques temps. Qu'à cela ne tienne, nous allons la récupérer depuis GitHub pour profiter de ses dernières mises à jour (notamment l'installation personnalisée pour Foundation). Ajoutez donc ceci à votre Gemfile :

gem 'simple_form', '2.1.0.dev', :git => "git://github.com/plataformatec/simple_form.git", :branch => "v2.1"

Puis faites votre bundle install habituel. Je sais, la branche est encore en développement. Mais bon, nous allons faire avec ! (Évidemment, si depuis une nouvelle version est sortie, modifiez la ligne comme il faut.) Nous ne prenons pas la V3.0 car celle-ci est faite pour être compatible avec Ruby 2.0 et Rails 4.0 qui sont encore en grand chantier.

A présent, nous pouvons utiliser leur commande pour installer les fichiers de configuration :

$ rails generate simple_form:install --foundation

Cette commande vous crée quelques fichiers dont deux fichiers de configuration dans config/initializers et un fichier dans lib/templates/erb/scaffold. Les deux fichiers de configuration sont les plus importants. Le premier concerne la configuration de simple_form proprement dite. Ouvrez donc ce fichier pour le parcourir et découvrir les différentes options. Tout est bien commenté, aussi ne vais-je pas expliquer les options, pour ne pas alourdir le tutoriel, à moins que vous n'en fassiez la demande.

A noter: Les options définies dans le fichier simple_form_foundation.rb permettent d'écraser les options définies dans simple_form.rb. Cela vous permettra ainsi de modifier les options à votre guise pour l'intégration de Foundation.

Ouvrons le fichier simple_form_foundation.rb. Nous allons modifier quelques lignes :

SimpleForm.setup do |config|
config.wrappers :foundation, :class => :input, :hint_class => :field_with_hint, :error_class => :error do |b|
b.use :html5
b.use :placeholder
b.use :maxlength
b.use :pattern
b.use :min_max
b.use :readonly
b.use :label_input
b.use :error, :wrap_with => { :tag => :small }

b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
end

...
end

J'ai simplement demandé à utiliser par défaut les extensions maxlength, pattern, min_max, readonly et hint.
- maxlength : récupère la taille maximale d'un champ de texte depuis votre validation :length => { :within => 15..500 } et l'intègre dans l'attribut maxlength de l'input. Nous l'enrichirons plus tard avec un petit décompte javascript.
- min_max : la même chose que maxlength, mais pour les nombres et correspondant à la validation :numericality => {:greater_than_or_equals_to => 5, :lower_than_or_equals_to => 10}
- pattern : utile surtout pour les nouveaux navigateurs qui valident les champs en les confrontant à un regex fourni. Cela correspond à la validation :format => { :with => phone_regex } où phone_regex serait : /\(?([0-9]+)\)?([ .-]?)([0-9]+)\2([0-9]+)/ (pour valider les numéros de téléphone).
- readonly : évite qu'un champ non accessible (i.e. non spécifié dans la liste des attr_accessible) apparaisse comme modifiable dans votre formulaire. En principe, il aura l'attribute disbaled="disabled" et sera grisé.

J'ai également enlevé le commentaire pour pouvoir utiliser les hint. Il nous faudra faire le css, mais tout viendra en temps voulu !

Quant au fichier lib/templates/erb/scaffold/_form.html.erb, en principe, vous n'aurez pas à y toucher. C'est le code utilisé pour générer les formulaires lorsque vous utilisez la commande rails rails generate scaffold Model attribute_1:string attribute_2:integer attribute_3:text .... Ce fichier permet tout simplement d'utiliser simple_form à la place du classique form_for de Ruby On Rails. :)

Maintenant que nous avons débroussaillé la configuration et l'installation de simple_form, nous allons voir comment l'utiliser. Mais avant, il nous faut créer un nouveau model pour voir en long, en large et en travers, toutes les possibilités offertes par Simple Form.


De quoi expérimenter Simple Form


Pour les raisons du tutoriel, nous ne créerons pas encore de model User dans notre application StarterApp. Toutefois, rien ne nous empêche de créer le classique model Address très souvent (toujours ?) associé au model User pour stocker les adresses de nos utilisateurs. Et c'est ce que nous allons faire, car le model Address contient suffisamment d'attributs pour nous amuser avec Simple Form. Nous ajouterons également quelques attributs qui ne seront utilisés que pour des raisons pédagogiques. Nous créerons aussi le model UserDatasheet qui contiendra d'autres informations comme le numéro de téléphone, le titre de la personne (M. Mme...), sa date de naissance, etc.

Allez, zouh, lancez-moi ces commandes depuis votre terminal :
$ rails g scaffold Address street_number:integer postal_code:string street:string city:string country:string area:string other_information:text building:string company:boolean company_name:string
$ rails g scaffold UserDatasheet phone:string mobile:string title:string birthdate:date firstname:string lastname:string nickname:string

Vous avez donc deux nouveaux models. Comme nous aimons tout bien ranger, nous créerons un nouveau dossier model/users_related dans lequel nous insérons ces deux models. Nous ajoutons ensuite des restrictions au niveau de nos données dans nos migrations. Voici donc le fichier migration pour les adresses :

create_table :addresses do |t|
t.integer :street_number, :null => false
t.string :postal_code, :null => false, :limit => 50
t.string :street, :null => false
t.string :city, :null => false, :limit => 60
t.string :country, :null => false, :limit => 80
t.string :area
t.text :other_information
t.string :building
t.boolean :company, :null => false, :default => false
t.string :company_name
t.timestamps
end

Pour rappel, :null => false oblige que cette donnée à être remplie. Si elle ne l'est pas, la sauvegarde ne peut pas avoir lieu.
:limit => 60 précise une taille limite pour la donnée. S'il s'agit d'une chaîne de caractères, cela traduit le nombre maximum de caractères autorisés. S'il s'agit d'un nombre, c'est la limite en bytes.
Enfin, :default indique la valeur par défaut à utiliser pour cette donnée. Elle est particulièrement utile lorsque vous ajoutez une nouvelle colonne ne devant pas être null dans une table non vide.

Faisons de même pour la migration user_datas :

create_table :user_datasheets do |t|
t.string :phone, :limit => 40
t.string :mobile, :limit => 40
t.string :title, :null => false
t.date :birthdate
t.string :firstname, :limit => 40
t.string :lastname, :limit => 40
t.string :nickname, :null => false, :limit => 80
t.timestamps
end

Il est bon d'utiliser :limit lorsque c'est possible. Par défaut, une donnée string est limitée à 255 caractères. La plupart du temps vous n'aurez pas besoin d'autant de caractères. Prévoyez tout de même un peu large histoire d'être paré à toutes possibilités exotiques. En général, je m'amuse à chercher sur le web les "Noms les plus longs" ou "Noms des villes les plus longs" du monde et j'arrondis la taille de ces noms exotiques à la dizaine supérieure. Cela laisse de la marge à votre application et évitera que le nom de certains utilisateurs soit refusé tout en optimisant votre application. (Et puis, cela ajoute également un tantinet de sécurité, car vous laissez moins de marge de manœuvre aux pirates pour insérer du code malveillant dans votre base de données.)

Évidemment, n'oubliez jamais de répercuter les restrictions définies dans vos fichiers migrations au sein de validations dans vos models, pour que vos utilisateurs n'aient pas de mauvaises surprises.


Grâce à la commande scaffold, nous avons déjà tout ce qu'il nous faut pour bien expérimenter simple_form. Il nous reste juste à exécuter...

$ rake db:migrate
$ annotate
$ magic_encoding

A la découverte de Simple Form

Lançons notre serveur et rendons-nous à la page http://localhost:3000/addresses/new.

Et là, patatrac ! Une erreur... Rassurez-vous, rien de grave. C'est juste que nous avons un attribut nommé country et Simple Form étant bourré de détections automatiques, il va chercher la présence d'une méthode country_select qui est fournie par... la gem country_select. Son installation est absolument abominable, il nous faut l'ajouter à notre gemfile puis exécuter bundle install :

# Gemfile
gem country_select

$ bundle install

Ce fut difficile ! Relancez votre serveur et rafraîchissez votre page. Par miracle, il n'y a plus d'erreurs, et en plus, on a déjà un champ pour sélectionner son pays: la grande classe, à ce petit détail près... Tout est en anglais et ce n'est pas très beau.
Pour le pas très beau, ce n'est pas très grave, et on y viendra lorsqu'on installera Foundation. Quant à l'anglais, nous allons y remédier de suite.

Les gems comme Simple Form et Formtastic vont chercher les traductions des attributs dans les fichiers .yml. Concernant le nom des attributs, il vaut mieux les traduire au niveau du model, comme ceci :

# fichier locales/fr/models.fr.yml
fr:
activerecord:
models:
address:
one: "Adresse"
other: "Adresses"
attributes:
address:
country: "Pays"
street_number: "Numéro de rue"
street: "Rue"
city: "Ville"
area: "Zone et/ou Département"
other_information: "Autres informations"
building: "Bâtiment"
company: "S'agit-il d'une société ?"
company_name: "Nom de la société"

Faites de même pour UserDatasheet, et n'oubliez pas de relancer votre serveur pour qu'il charge le nouveau fichier yml et de rafraîchir votre page. Désormais, vos attributs sont en français. Notez que ces traductions seront réutilisées en de nombreux endroits et par d'autres gems, comme active_admin.

Si vous souhaitez effectuer des traductions propres à vos formulaires Simple Form, vous avez un exemple dans le fichier qui avait été créé automatiquement locales/simple_form.en.yml. Copiez/collez ce fichier dans le dossier locales/fr et remplacez "en" par "fr" pour effectuer vos propres traductions en français. Par exemple :

# fichier locales/fr/simple_form.fr.yml
fr:
simple_form:
"yes": 'Oui'
"no": 'Non'
required:
text: 'requis'
mark: '*'
html: '<abbr title="requis">**</abbr>'
error_notification:
default_message: "Merci de corriger les erreurs signalées:"
labels:
defaults:
country: "Votre pays de résidence"
building: "Code bâtiment"
address:
street_number: "N°"
building: "Code de votre bâtiment"
hints:
defaults:
building: 'Exemple: Bâtiment 2A'
street_number: 'Uniquement le numéro de rue'

Et effectivement, les traductions écrasent bien celles dans notre fichier models.fr.yml, grâce à la configuration qu'on avait déjà faite en partie 1 (Mise en français d'une application rails).

Simple Form accepte des traductions par défaut, définissables à l'aide de la clef defaults et qui s'appliqueront à TOUS les formulaires Simple Form. Si vous souhaitez insérer une traduction pour un formulaire spécifique, c'est toujours possible en ciblant le model concerné, comme je l'ai fait ci-dessus pour le model address. Lorsque nous rafraîchissons la page, nous voyons qu'effectivement, la traduction finale pour le label de l'attribut building est Code de votre bâtiment.

Avant de passer à la personnalisation des champs, aux validations, jetons d'abord un œil au code généré. Vous l'avez peut-être déjà remarqué mais le code ROR généré pour le formulaire par le scaffold correspond bien à ce que nous attendions, i.e. correspond bien à ce qui est présent dans le fichier lib/templates/erb/scaffold/_form.html.erb.


Voyons maintenant le code HTML généré :

<form id="new_address" class="simple_form new_address" novalidate="novalidate" method="post" action="/addresses" accept-charset="UTF-8">
<div style="margin:0;padding:0;display:inline">...</div>
<div class="form-inputs">
<div class="input integer optional address_street_number field_with_hint">
<label class="integer optional control-label" for="address_street_number">N°</label>
<input id="address_street_number" class="numeric integer optional" type="number" step="1" name="address[street_number]">
<span class="hint">Uniquement le numéro de rue</span>
</div>
<div class="input string optional address_postal_code">...</div>
<div class="input string optional address_street">...</div>
<div class="input string optional address_city">...</div>
<div class="input country optional address_country">...</div>
<div class="input string optional address_area">...</div>
<label class="string optional control-label" for="address_area">Zone et/ou Département</label>
<input id="address_area" class="string optional" type="text" size="50" name="address[area]" maxlength="255">
</div>
</div>
<div class="form-actions">
<input class="button" type="submit" value="Create Adresse" name="commit">
</div>
</form>

Que remarquons-nous ?

- Comme le spécifie la doc de Simple Form, chaque couple label/input est inclus dans un div. Il est tout à fait possible de modifier ce div, comme nous le verrons dans le chapitre suivant. Aussi, ce découpage en div est tout simplement parfait pour le responsive_design et cela nous facilitera la tâche pour rendre les formulaires compatibles avec la grille de Foundation.
- Ensuite, les class CSS des div sont différentes selon le type de donnée. Vous n'auriez jamais pu avoir un tel résultat avec la méthode form_for de rails.
- Autre chose remarquable: les label ont été générés automatiquement alors que nous n'en avons précisé aucun dans le code ROR du formulaire. Simple Form les a créé automatiquement à partir des attributs en récupérant les traductions de ces attributs depuis nos fichiers yml. Encore une fois, ces labels peuvent tout à fait être modifiés à volonté et on peut même totalement les supprimer.
- Vous noterez également que Simple Form récupère chaque type de données : string, country, integer... et place intelligemment un textarea dès lors qu'il rencontre une donnée de type text. Nous pouvons évidemment écraser chacun de ces types de données à l'aide d'options que nous verrons aussi.
- Enfin, notez que Simple Form a déjà mis quelques attributs dans les inputs: des maxlength définis à 255 lorsqu'il s'agit de données de type string, des attributs rows, cols pour le textarea... Bref, plein de petites choses utiles qui vous montrent encore une fois combien cette gem est indispensable !

Avant de continuer, ajoutons un attribut dans le model Address, comme ceci :

# Fichier address.rb
attr_accessible :building_password, [...]attr_accessor :building_password

Et ajoutez cette ligne de code dans le formulaire :

<%= f.input :building_password %>

Si vous rafraîchissez votre page et que vous essayez de remplir le nouveau champ généré par Simple Form, vous verrez que le contenu du champ est masqué. Ceci est possible grâce à la présence du mot clef password dans l'attribut. Simple Form détecte qu'il s'agit là d'une donnée sensible et applique donc le type password au champ input, comme vous pouvez le constater dans le code HTML généré :

<div class="input password optional address_building_password">
<label class="password optional control-label" for="address_building_password">Building password</label>
<input id="address_building_password" class="password optional" type="password" size="50" name="address[building_password]">
</div>

Évidemment, je vous ai fait mettre building_password en tant qu'attr_accessor (i.e. un attribut qui ne sera pas stocké dans votre base de données). A moins que vous n'ayez une TRES bonne raison de le faire, n'enregistrez JAMAIS de telles données sensibles dans votre base de données !

Allez, je vous sens trépigner d'impatience d'apprendre comment personnaliser tout ceci et modeler le formulaire comme nous le souhaitons .

Apprenons à utiliser Simple Form


Modification du champ :

Pour modifier le label d'un champ, rien de plus simple, il suffit d'utiliser les options label et label_html, comme ceci :
<%= f.input :postal_code, :label => "Code postal", :label_html => { :style => "font-weight:bold;"} %>
Par exemple, ici nous modifions le label de l'attribut postal_code et le mettons en gras. Plutôt que d'utiliser le style, vous pouvez définir dans le label_html une class, comme ceci :

<%= f.input :postal_code, :label => "Code postal", :label_html => { :class => "bold"} %>

Si vous souhaiter désactiver un label pour un champ, il suffit de lui coller l'option :label => false:
<%= f.input :city, :label => false, :placeholder => "Votre ville" %>
<%= f.input :country, :label => false, :hint => "Votre pays" %>

Dans ce cas-là, n'oubliez pas de mettre une information quelque part pour indiquer à l'utilisateur ce qu'attend ce champ comme information à l'aide des options hint ou placeholder.

Pour modifier l'input, le tag de l'erreur généré ou le div englobant le tout, vous avez respectivement les options : :input_html, :error_html, :wrapper_html fonctionnant exactement de la même façon que :label_html. Ces différentes options attendent un hash rempli d'attributs HTML, comme ceci :

<%= f.input :street_number, :hint => false,
:input_html => { :maxlength => 5, :size => 3 },
:wrapper_html => { :style => "float: left;"} %>
<%= f.input :street, :label => false, :hint => "Libellé de votre rue, ex: Chemin des bois du loup",
:error_html => { :style => "border: 2px red solid;" } %>

Comme je le disais, en principe, utilisez plutôt des class CSS que directement du style. Mais vous comprenez l'idée.

Vous pouvez également modifier la valeur d'un champ, par exemple :

<%= f.input :area, :input_html => {:value => "Quelque part dans le monde" }, :disabled => true %>
<%= f.input :company, :input_html => {:checked => true} %>

Je vous ai même ajouté de quoi désactiver le champ ou cocher une case par défaut.

Ajoutons des validations :

Voyons un peu plus l'intérêt d'une gem comme Simple Form en ajoutant quelques validations. Grâce à annotate, nous avons des commentaires bien utiles dans nos models. Ajoutons donc un maximum de validations à nos models Address et UserDatasheet :

# Model UserDatasheet
TITLES = ["M.", "Mme.", "Mlle."]
phone_regex = /\(?([0-9]+)\)?([ .-]?)([0-9]+)\2([0-9]+)/

validates :phone, :format => { :with => phone_regex, :message => "n'est pas un numéro de téléphone valide" }
validates :mobile, :format => { :with => phone_regex, :message => "n'est pas un numéro de téléphone valide" }
validates :nickname, :length => {:maximum => 80 }
validates :firstname, :length => { :within => 2..40 }
validates :lastname, :length => {:within => 2..40}
validates :title, :inclusion => {:in => TITLES }

# Model Address
validates :street_number, :presence => true, :numericality => { :greater_than_or_equal_to => 1 }
validates :street, :presence => true, :length => { :within => 5..255 }
validates :postal_code, :presence => true, :length => { :within => 2..50 }
validates :city, :presence => true, :length => { :within => 2..60 }
validates :other_information, :length => { :maximum => 500 }
validates :country, :presence => true, :length => { :maximum => 60 }
validates :area, :presence => true, :length => { :within => 2..255 }

Rechargez maintenant le formulaire pour l'adresse. Vous constaterez quelques différences:
- la présence d'étoiles (*) à côté des labels: elle correspond aux champs requis, i.e. ceux possédant la validation :presence => true.
- si vous regardez le code de l'input pour renseigner la ville, par exemple, vous remarquerez que maxlength correspond désormais à 60, cela respecte bien à la validation :length que nous avons définie.
- si vous explorez le code concernant le champ pour le numéro de rue, vous verrez un nouvel attribut min égal à 1, et correspond à présent à notre validation numericality.

Comme attendu, les extensions que nous avions activées par défaut lors de la configuration de Simple Form (hint, maxlenght, etc.) s'appliquent correctement. Et moi je dis, c'est magique !
Si maintenant vous cliquez sur le bouton "Créer", les erreurs apparaîtront à côté de chaque champ, et par la même occasion, vous verrez celle que nous avons encadrée en rouge tantôt.

Passons maintenant à l'autre model et rendez-vous sur http://localhost:3000/user_datasheets/new. Si vous avez une erreur concernant le champ de la date de naissance, et même si vous ne l'avez pas, modifiez le code du formulaire comme ceci :

<%= f.input :birthdate, :as => :date, start_year: Date.today.year - 90,
end_year: Date.today.year - 12,
order: [:day, :month, :year] %>

Vous remarquez par la même occasion deux choses. La première, c'est que nous avons modifié volontairement le type du champ, en utilisant :as => :date. De cette façon, Simple Form sait qu'il doit utiliser un ensemble complexe de champs pour sélectionner une date (i.e. un jour, un mois et une année). Les options suivantes, je les ai récupérées directement de la documentation de Simple Form où ils expliquent qu'il est tout à fait possible d'utiliser les options des helpers rails déjà définis (ici il s'agit de date_select ).

Concernant les types des champs disponibles, vous avez toute une liste dans la doc de Simple Form.

Remarque : Une fois la page rafraîchie, vous avez peut-être des options bizarres pour le choix des mois. Si c'est le cas, veuillez m'excuser, c'est que j'ai oublié de vous faire installer les traductions de Ruby On Rails par défaut. Pour les utiliser, rien de plus simple, ajoutez la gem rails-i18n à votre Gemfile, installez-la avec bundle install et relancez votre serveur. Et voilà, vous avez les mois en français !


Modifions également le champ title, comme ceci :

<%= f.input :title, :collection => UserDatasheet::TITLES, :as => :radio_buttons %>

Ici, nous utilisons une collection de valeurs que Simple Form transforme par défaut en un champ select. Mais pour ce genre de choses, nous préférerons utiliser des radio buttons, aussi utilisons-nous ce type de champ grâce à l'option :as. Voilà, rafraîchissez la page et vous aurez bien des radio buttons contenant les différents titres définis en tant que constante au sein du modèle UserDatasheet. Il ne vous reste plus qu'à vérifier que le pattern a bien été généré pour les champs concernant les numéros de téléphone :

<input type="tel" size="40" pattern="\(?([0-9]+)\)?([ .-]?)([0-9]+)\2([0-9]+)" name="user_datasheet[phone]" maxlength="40" id="user_datasheet_phone" class="string tel optional">

Hé oui, il y est et correspond bien à notre regex ! Ceci termine la partie sur Simple Form. Je fais volontairement l'impasse sur les associations, nous les verrons en détail en même temps que la gem nested_form dans la partie suivante. La lecture de ce tutoriel ne vous dispense pas de l'entière lecture de la documentation de Simple Form qui, pour les points non abordés ici, vous permettra sûrement d'aller encore plus loin. En tout cas, j'ai été conquis par Simple Form et j'espère que ce tutoriel vous aura conquis aussi !



Petits bonus


Une alternative à Country Select
Dépôt Github
La gem country_select fonctionne a merveille avec Simple Form. Oui mais ! Car il y a toujours un mais... Les noms des pays sont en anglais et la gem country_select n'offre aucun moyen de traduire ces noms dans une autre langue. Je vais donc vous proposer une alternative qui ne nécessitera que quelques efforts de votre part.

Il s'agit de la gem i18n country select (vous avez le lien vers son dépôt github, ci-dessus). La similitude du nom avec country_select est voulue, et la gem fonctionne de la même façon, à ceci près qu'elle stocke dans votre base de données non pas le nom des pays, mais leur code (exemple FR pour France). Cette gem en utilise une autre 18n_country_translations qui lui permet de traduire la liste des pays dans une multitude de langues différentes. Ce sont ces deux gems que nous allons utiliser pour pallier le gros manque de country_select (à savoir une traduction des noms des pays dans de nombreuses langues).

Ajoutons-les à notre Gemfile:
gem "i18n_country_select" # https://github.com/onomojo/i18n_country_select
gem 'i18n-country-translations' # https://github.com/onomojo/i18n-country-translations

Faites le classique bundle install et nous sommes parés ! i18n_country_select ne possède qu'une seule méthode : country_code_select qui prend en paramètres le model sous forme de symbole ( ex: :user ) et l'attribut concerné (ex: :country). Nous allons utiliser la méthode préconisée par SimpleForm pour écraser la détection automatique de country_select et remplacer ça par country_code_select. (Hé oui, quand je vous disais que nous verrions Simple Form sous toutes ses coutures !)

Alors... La doc de Simple Form nous dit qu'écraser les inputs de Simple Form ou en créer de nouveaux est simple. Suivons donc leurs indications et commençons par créer un nouveau dossier dans app nommé inputs. Créons-y le fichier priority_input.rb et modifions-le.

La class PriorityInput est utilisée par SimpleForm pour deux champs particuliers contenant le mot country ou time_zone. Ces deux champs s'utilisent conjointement avec les configurations config.time_zone_priority et config.country_priority permettant de mettre en avant un certain pays ou une certaine zone de temps au sein des listes de choix. Nous allons respecter cette logique et éviter de tout casser en utilisant un simple test.


# app/inputs/priority_input.rb
class PriorityInput < SimpleForm::Inputs::priorityInput
def input
if input_type.to_s == "country"
@builder.send(:"#{input_type}_code_select", attribute_name, input_priority,
input_options, input_html_options)
else
super
end
end
end

Comme je le disais, nous effectuons un test pour voir qu'il s'agit bien d'un champ de type country. Histoire de ne pas comparer des choux avec des carottes, nous utilisons la méthode to_s pour transformer la valeur de input_type en chaîne de caractères. La méthode suivante, @builder.send est identique à celle utilisée par défaut par Simple Form, à ceci près que j'ai rajouté _code dans :"#{input_type}_code_select" pour faire appel à la méthode de la gem i18n_country_select.
S'il ne s'agit pas d'un champ de type country, il suffit de faire un simple appel à la méthode input de la class PriorityInput de Simple Form, et le tour est joué ! Si vous rafraîchissez votre page, les pays sont désormais en français.

Comme avec country_select, il nous est possible de mettre quelques pays en avant :

<% # Au sein de notre formulaire pour l'adresse : %>
<%= f.input :country, :priority => [[ 'United States', 'US' ], [ 'Canada', 'CA' ], ['France', 'FR']] %>

# Ou directement dans le fichier de configuration de simple_form.rb : 
config.country_priority = [[ 'United States', 'US' ], [ 'Canada', 'CA' ], ['France', 'FR']]

Ensuite, pour afficher le pays dans la langue de navigation de l'utilisateur, il ne vous reste plus qu'à utiliser la méthode fournie par la gem 18n_country_translations dans une de vos view:

<%= t(@address.country, :scope => :countries) %>


Les validators
Avez-vous déjà créé vos propres validators ? Peut-être que lorsque vous avez rédigé les validations pour les numéros de téléphone, vous avez trouvé cela dommage de vous répéter pour le numéro de fixe et le mobile... Eh bien, vous auriez eu raison ! Je vais donc vous apprendre à créer vos propres validators .

Pour ce faire, commencez par créer un nouveau dossier dans app que vous nommerez validators. Créez-y un fichier que vous nommerez phone_validator.rb et éditez-le comme ceci :

# -*- encoding : utf-8 -*-
# Validation des numéros de téléphone
class PhoneValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\(?([0-9]+)\)?([ .-]?)([0-9]+)\2([0-9]+)/
record.errors.add(attribute, :not_a_phone)
end
end
end

C'est l'écriture classique d'un validateur personnel en ruby on rails. Il doit hériter de la class ActiveModel::EachValidator. Il doit posséder au minimum la méthode validate_each qui est appelée automatiquement lors d'une validation et qui doit prendre obligatoirement 3 paramètres : la donnée que l'on tente d'enregistrer (record), l'attribut devant être validé (attribute), et la valeur qu'on tente de lui assigner (value).

Ensuite, il suffit de confronter la valeur au regex que nous avions écrit tantôt pour les numéros de téléphone. Et si cela ne correspond pas, nous renvoyons une erreur et l'ajoutons à la liste des erreurs associées à l'attribut pour la donnée concernée, à l'aide de la méthode : record.errors.add qui prend en paramètre l'attribut concerné et le message d'erreur à renvoyer.

Ici, nous avons utilisé un symbole :not_a_phone en guise de message d'erreur, ce qui nous permet de facilement traduire le message d'erreur en le définissant dans un fichier .yml, comme ceci :

fr:
errors:
messages:
not_a_phone: "n'est pas un numéro de téléphone valide"

Ensuite, pour utiliser ce validator personnel au sein de votre model, vous n'avez plus qu'à faire ceci :

  validates :phone,   :phone => true
validates :mobile, :phone => true

Rafraîchissez votre page, essayez de créer une nouvelle donnée UserDatasheet et vous aurez bien votre message n'est pas un numéro de téléphone valide. Super, non ?! :)

Et parce que je suis gentil, je vous donne gratuitement deux autres validators que j'utilise beaucoup :

# -*- encoding : utf-8 -*-
# fichier email_validator.rb, pour valider les emails sans utiliser de regex, grâce à la gem mail
# Je l'ai récupéré depuis un Ghist sur GitHub, je ne saurais plus donner le lien par contre...
require 'mail'

# Code récupéré depuis un blog, très efficace pour tester les adresses mails,
# Egalement non basé sur le regex donc plus probant
class EmailValidator < ActiveModel::EachValidator
def validate_each(record,attribute,value)
begin
m = Mail::Address.new(value)
# We must check that value contains a domain and that value is an email address
r = m.domain && m.address == value
t = m.__send__(:tree)
# We need to dig into treetop
# A valid domain must have dot_atom_text elements size > 1
# user@localhost is excluded
# treetop must respond to domain
# We exclude valid email values like <user@localhost.com>
# Hence we use m.__send__(tree).domain
r &&= (t.domain.dot_atom_text.elements.size > 1)
rescue Exception => e
r = false
end
record.errors[attribute] << (options[:message] || "is invalid") unless r
end
end

# -*- encoding : utf-8 -*-
# fichier word_validator.rb
# Permet de valider un mot, regardant d'abord s'il s'agit bien d'un mot (i.e. ne contenant aucun caractère alpha numérique)
# puis confrontant ce mot à un dictionnaire français enregistré dans la base de données si l'option : :dictionnary => true est activée
# Par exemple : validates :french_word, :word => { :dictionnary => true, :language => "fr" }

class WordValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A[[:alpha:]'-]+\z/i
record.errors.add(attribute, :not_a_word)
end
if options[:dictionnary] == true && !options[:language].blank?
if Dictionnary.where(:language => options[:language]).find_by_word(value.clean_word).nil?
record.errors.add(:attribute, :not_a_dictionnary_word)
end
end
end
end

Et voilà ! Je vous dis à très bientôt pour la suite de ce beau tutoriel avec la découverte d'autres gems pour enrichir encore plus nos formulaires !


Index -- --

  • 3 Commentaire


  • J'avoue avoir été très pris ces derniers temps ! J'ai heureuseument pu récupérer le gros tuto que j'avais commencé sur le site du zéro et je vais pouvoir me remettre à la rédaction de celui-ci, mais il ne m'est pas possible pour l'heure de faire les deux à la fois. ;)


    Par Kulgar, le 27 Août 2013

  • Je repasse ici après quelques mois et pas de suite?
    dommage!


    Par LarryGolade, le 26 Août 2013

  • Très bonnes séries d'articles trouvés un peu au hasard du Net!
    J'attends la suite avec impatience :)


    Par LarryGolade, le 19 Mars 2013
Insérez votre commentaire
  1. Min: 50 caractères, Max: 800. Actuellement: 0 caractères

  2. ne pas remplir