Dans un article précédant, je vous expliquais comment faciliter vos recherches avec Ransack. J’aimerai aujourd’hui vous montrer comment étendre les possibilités de recherche de Ransack en créant vos propres prédicats.
Les prédicats proposés de base par Ransack ne répondent pas à tous les besoins. J’ai notamment dû rechercher des enregistrements par date de création, la subtilité étant que seul le mois et l’année étaient donnés. Il fallait en resortir une liste d’enregistrements ayant été créé pendant ce mois.
Ransack ne propose pas de moyen simple pour faire ça, disons plutôt pas de moyen qui me convenait. Il aurait été possible de générer toutes les dates possibles pour le couple mois / année et passer ces dates à la requête SQL dans un IN
avec autant de valeurs que de dates dans le mois (ne parlons même pas du cas où on travaille sur un datetime). Cette solution implique de traiter le paramètre en question dans le controller pour générer l’intervalle de dates. Je trouve cette solution très disgracieuse, j’ai donc cherché un moyen de répondre à ce besoin de manière plus élégante.
Fort heureusement, l’auteur de Ransack a eu la bonne idée de proposer la possibilité d’ajouter des prédicats personnalisés. Pour ne rien gâcher, c’est très simple et rapide à mettre en place.
Une méthode de configuration nommée add_predicate
permet d’ajouter un prédicat personnalisé à Ransack. Il faut donc créer un initializer qui utilisera cette méthode pour étendre Ransack.
On peut par exemple choisir de déclarer ses prédicats maison dans config/initializers/ransack_predicates.rb
comme suit :
Ransack.configure do |config|
config.add_predicate 'positive_equals'
arel_predicate: 'eq',
formatter: proc { |v| v.gsub(",", ".") },
validator: proc { |v| v > 0 },
compounds: true,
type: :float
end
Le premier paramètre permet de définir le nom de votre nouveau prédicat, le second paramètre est un hash d’options :
arel_predicate
définit le prédicat ARel à utiliser pour effectuer la comparaison entre les valeursformatter
permet, si besoin est, de formatter la valeur entrée et donc de la préparer pour la recherchevalidator
est un proc appelé avant recherche pour vérifier que la valeur passée est éligible pour une recherche. Si le proc ne renvoie pas true
alors la valeur sera purement et simplement ignorée dans la rechechecompounds
permet de générer automatiquement les versions _any
et _all
de votre nouveau prédicat pour pouvoir rechercher respectivement sur l’une ou l’ensemble des valeurs passéestype
permet de forcer le type pour le transtypage de la valeur passée, par défaut le type de la colonne correspondante est utiliséeDans cet exemple sans réel intérêt, on a donc créé un prédicat positive_equals
qui recherchera une valeur flottante identique à celle passée, mais uniquement si la valeur passée est positive.
Traitons le cas dont je parlais en introduction qui consiste à retourner tous les enregistrements ayant une date de création incluse dans le couple mois / année donné.
Dans l’initializer, ajoutons le prédicat :
Ransack.configure do |config|
config.add_predicate 'in_month_year',
arel_predicate: 'matches',
:formatter => proc {|v| [v.strftime("%Y-%m"), "%"].join},
:validator => proc {|v| v.present?},
:compounds => false,
:type => :date
end
On définit donc un prédicat in_month_year
utilisant le prédicat ARel matches
qui permet de faire des recherches par regexp.
La valeur d’entrée est une date avec un jour à un par défaut. En effet les helpers de date utilisent un jour par défaut quand vous décidez de n’afficher que le mois et l’année. Cette date est traitée via un strftime
pour n’avoir que l’année en premier, un tiret et le mois (ie: format des dates en base).
On ajoute également un “%” à la fin de ce couple année / mois pour que la regexp puisse trouver tous les enregistrements commençant par cette année et ce mois.
Le validator
vérifie simplement que la valeur est présente ce qui est d’ailleurs le comportement par défaut.
Le compounds
est à false
n’en n’ayant pas l’utilité dans mon application.
Pour s’assurer que tout fonctionne correctement, on force le type :date
via l’option type
.
Il ne nous reste plus qu’à utiliser ce prédicat fraichement créé dans notre moteur de recherche :
<%= search_form_for @query do |f| %>
<%= f.label :created_at_in_month_year %>
<%= f.date_select :created_at_in_month_year, include_blank: true, discard_day: true %>
<%= f.submit %>
<% end %>
On a donc nos deux listes déroulantes nous permettant de choisir le mois et l’année voulue.
Cette valeur sera traitée par notre prédicat et seuls les enregistrements créés sur le mois demandé seront retournés.
Mission accomplie !
Pour conclure pensez aux prédicats personnalisés lorsque vous utilisez Ransack. Ces ajouts de prédicats vous permettront dans bien des cas de continuer à utiliser Ransack de façon transparente plutôt que de devoir ajouter du code gérant des cas particuliers.
Si vous avez aussi développé vos propre prédicats, n’hésitez pas à les partager ici. Je suis toujours intéressé par ce genre d’extensions et je n’ai aucun doute sur le fait que ça servira à d’autres lecteurs.
L’équipe Synbioz.
Libres d’être ensemble.