Blog tech

Étendre les prédicats de Ransack

Rédigé par Nicolas Cavigneaux | 14 novembre 2012

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 personnalisés

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.

Ajouter un prédicat

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 valeurs
  • formatter permet, si besoin est, de formatter la valeur entrée et donc de la préparer pour la recherche
  • validator 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 recheche
  • compounds 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ées
  • type permet de forcer le type pour le transtypage de la valeur passée, par défaut le type de la colonne correspondante est utilisée

Dans 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.

Cas pratique : recherche sur le mois et l’année

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é.

Définition du prédicat

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.

Utilisation dans le formulaire de recherche

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 !

Le mot de la fin

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.