Recherches basées sur un modèle dans Rails. Un exemple génial :
## app/searches/post_search.rb
class PostSearch < TalentScout :: ModelSearch
criteria :title_includes do | string |
where ( "title LIKE ?" , "% #{ string } %" )
end
criteria :within , choices : {
"Last 24 hours" => 24 . hours ,
"Past Week" => 1 . week ,
"Past Month" => 1 . month ,
"Past Year" => 1 . year ,
} do | duration |
where ( "created_at >= ?" , duration . ago )
end
criteria :only_published , :boolean , default : true do | only |
where ( "published" ) if only
end
order :created_at , default : :desc
order :title
end
## app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
<!-- app/views/posts/index.html.erb -->
<%= form_with model : @search , local : true , method : :get do | form | %>
<%= form . label :title_includes %>
<%= form . text_field :title_includes %>
<%= form . label :within %>
<%= form . select :within , @search . each_choice ( :within ) , include_blank : true %>
<%= form . label :only_published %>
<%= form . check_box :only_published %>
<%= form . submit %>
<% end %>
< table >
< thead >
< tr >
< th >
<%= link_to_search "Title" , @search . toggle_order ( :title ) %>
<%= img_tag " #{ @search . order_directions [ :title ] || "unsorted" } _icon.png" %>
</ th >
< th >
<%= link_to_search "Time" , @search . toggle_order ( :created_at ) %>
<%= img_tag " #{ @search . order_directions [ :created_at ] || "unsorted" } _icon.png" %>
</ th >
</ tr >
</ thead >
< tbody >
<% @posts . each do | post | %>
< tr >
< td > <%= link_to post . title , post %> </ td >
< td > <%= post . created_at %> </ td >
</ tr >
<% end %>
</ tbody >
</ table >
Dans l'exemple ci-dessus :
PostSearch
assume la responsabilité de la recherche de modèles Post
. Il peut appliquer n'importe quelle combinaison de ses critères définis, ignorant automatiquement les valeurs d'entrée manquantes, vides ou invalides. Il peut également classer les résultats selon l'un de ses ordres définis, dans le sens croissant ou décroissant.PostsController#index
utilise l'assistant model_search
pour construire une instance PostSearch
et l'assigne à la variable @search
pour une utilisation ultérieure dans la vue. Les résultats de la recherche sont également affectés à une variable à utiliser dans la vue.@search
. L'assistant link_to_search
est utilisé pour créer des liens dans l'en-tête du tableau qui trient les résultats. Notez que la méthode toggle_order
utilisée ici renvoie un nouvel objet de recherche, laissant @search
inchangé.Pour une explication détaillée des méthodes utilisées dans cet exemple, consultez la documentation de l'API.
Vous pouvez utiliser le générateur talent_scout:search
pour générer une définition de classe de recherche de modèle. Par exemple,
$ rails generate talent_scout:search post
Générera un fichier "app/searches/post_search.rb" contenant :
class PostSearch < TalentScout :: ModelSearch
end
Les classes de recherche héritent de TalentScout::ModelSearch
. Leur classe de modèle cible est déduite du nom de la classe de recherche. Par exemple, PostSearch
recherchera par défaut les modèles Post
. Pour remplacer cette inférence, utilisez ModelSearch::model_class=
:
class EmployeeSearch < TalentScout :: ModelSearch
self . model_class = Person # search for Person models instead of `Employee`
end
Les critères de recherche sont définis avec la méthode ModelSearch::criteria
. Les définitions de critères peuvent être spécifiées de trois manières : avec une clause Where implicite, avec un bloc de requête explicite ou avec une référence de portée de modèle. À titre d’illustration, les trois critères :title
suivants sont tous équivalents :
class Post < ActiveRecord :: Base
scope :title_equals , -> ( string ) { where ( title : string ) }
end
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :title do | string |
where ( title : string )
end
criteria :title , & :title_equals
end
Notez que les blocs de requête explicites sont évalués dans le contexte de ActiveRecord::Relation
du modèle, tout comme les blocs scope
Active Record.
Une définition de critères peut spécifier un type de données, ce qui entraîne le transtypage de sa valeur d'entrée avant d'être transmise au bloc de requête ou à la portée. A titre d'exemple :
class PostSearch < TalentScout :: ModelSearch
criteria :created_on , :date do | date |
where ( created_at : date . beginning_of_day .. date . end_of_day )
end
end
PostSearch . new ( created_on : "Dec 31, 1999" )
Ici, la chaîne "Dec 31, 1999"
transmise au constructeur PostSearch
est transtypée en Date
avant d'être transmise au bloc de requête.
Le type de critère par défaut est :string
, ce qui signifie que, par défaut, toutes les valeurs d'entrée seront converties en chaînes. Cette valeur par défaut (par opposition à une valeur par défaut sans transtypage) garantit un comportement cohérent quelle que soit la manière dont l'objet de recherche est construit, que ce soit à partir de valeurs fortement typées ou de paramètres de requête du formulaire de recherche.
Les types de critères disponibles sont les mêmes que pour les attributs du modèle actif : :big_integer
, :boolean
, :date
, :datetime
, :decimal
, :float
, :integer
, :string
, :time
, ainsi que tous les types personnalisés que vous définissez.
Un type de commodité supplémentaire est également disponible : :void
. Le type :void
est transtypé comme :boolean
, mais empêche l'application des critères lorsque la valeur transtypée est fausse. Par exemple:
class PostSearch < TalentScout :: ModelSearch
criteria :only_edited , :void do
where ( "modified_at > created_at" )
end
end
# The following will apply `only_edited`:
PostSearch . new ( only_edited : true )
PostSearch . new ( only_edited : "1" )
# The following will skip `only_edited`:
PostSearch . new ( only_edited : false )
PostSearch . new ( only_edited : "0" )
PostSearch . new ( only_edited : "" )
Au lieu de spécifier un type, une définition de critères peut spécifier des choix. Les choix définissent un ensemble de valeurs qui peuvent être transmises au bloc de requête.
Les choix peuvent être spécifiés sous la forme d'un tableau de valeurs respectueuses de l'humain :
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ] do | name |
where ( category : name . downcase )
end
end
...Ou sous forme de hachage avec des clés conviviales :
class PostSearch < TalentScout :: ModelSearch
criteria :within , choices : {
"Last 24 hours" => 24 . hours ,
"Past Week" => 1 . week ,
"Past Month" => 1 . month ,
"Past Year" => 1 . year ,
} do | duration |
where ( "created_at >= ?" , duration . ago )
end
end
La valeur transmise au bloc de requête sera l'une des valeurs du tableau ou l'une des valeurs du hachage. L'objet de recherche peut être construit avec l'une des valeurs de tableau, des clés de hachage ou des valeurs de hachage :
PostSearch . new ( category : "Math" )
PostSearch . new ( within : "Last 24 hours" )
PostSearch . new ( within : 24 . hours )
Mais si un choix invalide est précisé, les critères correspondants ne seront pas appliqués :
# The following will skip the criteria, but will not raise an error:
PostSearch . new ( category : "Marketing" )
PostSearch . new ( within : 12 . hours )
Une définition de critères peut spécifier une valeur par défaut, qui sera transmise au bloc de requête lorsque la valeur d'entrée est manquante. Les valeurs par défaut apparaîtront également dans les formulaires de recherche.
class PostSearch < TalentScout :: ModelSearch
criteria :within_days , :integer , default : 7 do | num |
where ( "created_at >= ?" , num . days . ago )
end
end
# The following are equivalent:
PostSearch . new ( )
PostSearch . new ( within_days : 7 )
Un critère ne sera pas appliqué si l’un des éléments suivants est vrai :
La valeur d'entrée du critère est manquante et aucune valeur par défaut n'a été spécifiée.
L'objet de recherche a été construit avec un ActionController::Parameters
(au lieu d'un hachage) et la valeur d'entrée du critère est blank?
, et aucune valeur par défaut n'a été spécifiée. (Ce comportement empêche les champs vides du formulaire de recherche d'affecter les résultats de la recherche.)
Le transtypage de la valeur d’entrée du critère échoue. Par exemple:
class PostSearch < TalentScout :: ModelSearch
criteria :created_on , :date do | date |
where ( created_at : date . beginning_of_day .. date . end_of_day )
end
end
# The following will skip `created_on`, but will not raise an error:
PostSearch . new ( created_on : "BAD" )
La définition des critères spécifie le type :void
et la valeur d'entrée transtypée est falsey.
La définition des critères spécifie des choix et la valeur d'entrée n'est pas un choix valide.
Le bloc de requête de critères renvoie nil
. Par exemple:
class PostSearch < TalentScout :: ModelSearch
criteria :minimum_upvotes , :integer do | minimum |
where ( "upvotes >= ?" , minimum ) unless minimum <= 0
end
end
# The following will skip the `minimum_upvotes` where clause:
PostSearch . new ( minimum_upvotes : 0 )
Les ordres des résultats de recherche sont définis avec la méthode ModelSearch::order
:
class PostSearch < TalentScout :: ModelSearch
order :created_at
order :title
order :category
end
PostSearch . new ( order : :created_at )
PostSearch . new ( order : :title )
PostSearch . new ( order : :category )
Une seule commande peut être appliquée à la fois, mais une commande peut comprendre plusieurs colonnes :
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category, title":
PostSearch . new ( order : :category )
Cette conception restreinte a été choisie car elle permet des tris multi-colonnes organisés avec des interfaces utilisateur de tri sur une seule colonne plus simples, et parce qu'elle empêche les tris multi-colonnes ad hoc qui ne peuvent pas être soutenus par un index de base de données.
Un ordre peut être appliqué dans le sens ascendant ou descendant. La méthode ModelSearch#toggle_order
appliquera un ordre dans le sens croissant, ou changera le sens d'un ordre appliqué de croissant à décroissant :
class PostSearch < TalentScout :: ModelSearch
order :created_at
order :title
end
# The following will order by "title":
PostSearch . new ( ) . toggle_order ( :title )
PostSearch . new ( order : :created_at ) . toggle_order ( :title )
# The following will order by "title DESC":
PostSearch . new ( order : :title ) . toggle_order ( :title )
Notez que la méthode toggle_order
ne modifie pas l'objet de recherche existant. Au lieu de cela, il crée un nouvel objet de recherche avec le nouvel ordre et les valeurs de critères de l'objet de recherche précédent.
Lorsqu’un ordre multi-colonnes est appliqué dans le sens décroissant, toutes les colonnes sont concernées :
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category DESC, title DESC":
PostSearch . new ( order : :category ) . toggle_order ( :category )
Pour contourner ce problème et corriger une colonne dans une direction statique, ajoutez " ASC"
ou " DESC"
au nom de la colonne :
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , "created_at ASC" ]
end
# The following will order by "category, created_at ASC":
PostSearch . new ( order : :category )
# The following will order by "category DESC, created_at ASC":
PostSearch . new ( order : :category ) . toggle_order ( :category )
Un ordre peut être appliqué directement dans le sens croissant ou décroissant, sans utiliser toggle_order
, en ajoutant un suffixe approprié :
class PostSearch < TalentScout :: ModelSearch
order :title
end
# The following will order by "title":
PostSearch . new ( order : :title )
PostSearch . new ( order : "title.asc" )
# The following will order by "title DESC":
PostSearch . new ( order : "title.desc" )
Les suffixes par défaut, comme le montre l'exemple ci-dessus, sont ".asc"
et ".desc"
. Ceux-ci ont été choisis pour leur convivialité I18n. Ils peuvent être remplacés dans le cadre de la définition de la commande :
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ] , asc_suffix : " (A-Z)" , desc_suffix : " (Z-A)"
end
# The following will order by "title":
PostSearch . new ( order : "Title" )
PostSearch . new ( order : "Title (A-Z)" )
# The following will order by "title DESC":
PostSearch . new ( order : "Title (Z-A)" )
Une commande peut être désignée comme commande par défaut, ce qui entraînera son application lorsqu'aucune commande n'est autrement spécifiée :
class PostSearch < TalentScout :: ModelSearch
order :created_at , default : :desc
order :title
end
# The following will order by "created_at DESC":
PostSearch . new ( )
# The following will order by "created_at":
PostSearch . new ( order : :created_at )
# The following will order by "title":
PostSearch . new ( order : :title )
Notez que le sens de l'ordre par défaut peut être ascendant ou décroissant en spécifiant respectivement default: :asc
ou default: :desc
. De plus, tout comme une seule commande peut être appliquée à la fois, une seule commande peut être désignée par défaut.
Une portée de recherche par défaut peut être définie avec ModelSearch::default_scope
:
class PostSearch < TalentScout :: ModelSearch
default_scope { where ( published : true ) }
end
La portée par défaut sera appliquée quels que soient les critères ou les valeurs d’entrée de commande.
Les contrôleurs peuvent utiliser la méthode d'assistance model_search
pour construire un objet de recherche avec les paramètres de requête de la requête actuelle :
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
Dans l'exemple ci-dessus, model_search
construit un objet PostSearch
. La classe de recherche de modèle est automatiquement dérivée du nom de classe du contrôleur. Pour remplacer la classe de recherche de modèle, utilisez ::model_search_class=
:
class EmployeesController < ApplicationController
self . model_search_class = PersonSearch
def index
@search = model_search # will construct PersonSearch instead of `EmployeeSearch`
@employees = @search . results
end
end
Dans ces exemples, l'objet de recherche est stocké dans @search
pour être utilisé dans la vue. Notez que @search.results
renvoie un ActiveRecord::Relation
, donc toute portée supplémentaire, telle que la pagination, peut être appliquée.
Les formulaires de recherche peuvent être rendus à l'aide du générateur de formulaires de Rails et d'un objet de recherche :
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.label :title_includes %>
<%= form.text_field :title_includes %>
<%= form.label :created_on %>
<%= form.date_field :created_on %>
<%= form.label :only_published %>
<%= form.check_box :only_published %>
<%= form.submit %>
<% end %>
Notez la method: :get
argument to form_with
. Ceci est nécessaire.
Les champs du formulaire seront remplis avec les valeurs de critère d'entrée (ou par défaut) du même nom provenant de @search
. Des champs de formulaire adaptés au type peuvent être utilisés, par exemple date_field
pour le type :date
, check_box
pour les types :boolean
et :void
, etc.
Par défaut, le formulaire sera soumis à l'action d'indexation du contrôleur qui correspond à la model_class
de l'objet de recherche. Par exemple, PostSearch.model_class
est Post
, donc un formulaire avec une instance de PostSearch
sera soumis à PostsController#index
. Pour modifier l'endroit où le formulaire est soumis, utilisez l'option :url
de form_with
.
Les liens de recherche peuvent être rendus à l'aide de la méthode d'assistance de vue link_to_search
:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
Le lien pointera automatiquement vers le contrôleur actuel et l'action en cours, avec les paramètres de requête de l'objet de recherche donné. Pour créer un lien vers un autre contrôleur ou une autre action, transmettez un hachage d'options à la place de l'objet de recherche :
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
search: @search.toggle_order(:title, :asc) } %>
L'assistant link_to_search
accepte également les mêmes options HTML que l'assistant link_to
de Rails :
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
id: "title-sort-link", class: "sort-link" %>
...ainsi qu'un bloc de contenu :
<%= link_to_search @search.toggle_order(:title, :asc) do %>
Sort by title <%= img_tag "sort_icon.png" %>
<% end %>
ModelSearch
La classe ModelSearch
fournit plusieurs méthodes utiles lors du rendu de la vue.
L'une de ces méthodes est ModelSearch#toggle_order
, qui a été présentée dans les exemples précédents. N'oubliez pas que toggle_order
est une méthode de style constructeur qui ne modifie pas l'objet de recherche. Au lieu de cela, il duplique l'objet recherché et définit l'ordre du nouvel objet. Un tel comportement convient à la génération de liens vers plusieurs variantes d'une recherche, tels que des liens de tri dans les en-têtes de colonnes d'un tableau.
ModelSearch#with
et ModelSearch#without
Deux méthodes supplémentaires de type générateur sont ModelSearch#with
et ModelSearch#without
. Comme toggle_order
, ces deux méthodes renvoient un nouvel objet de recherche, laissant l'objet de recherche d'origine inchangé. La méthode with
accepte un hachage de valeurs d'entrée de critères à fusionner par-dessus l'ensemble original de valeurs d'entrée de critères :
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :published , :boolean
end
# The following are equivalent:
PostSearch . new ( title : "Maaaaath!" , published : true )
PostSearch . new ( title : "Maaaaath!" ) . with ( published : true )
PostSearch . new ( title : "Math?" ) . with ( title : "Maaaaath!" , published : true )
La méthode without
accepte une liste de valeurs d'entrée de critères à exclure (les valeurs de critères par défaut s'appliquent toujours) :
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :published , :boolean , default : true
end
# The following are equivalent:
PostSearch . new ( title : "Maaaaath!" )
PostSearch . new ( title : "Maaaaath!" , published : false ) . without ( :published )
ModelSearch#each_choice
Une autre méthode utile est ModelSearch#each_choice
, qui parcourra les choix définis pour un critère donné. Cela peut être utilisé pour générer des liens vers des variantes d'une recherche :
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ]
end
<% @search.each_choice(:category) do |choice, chosen| %>
<%= link_to_search "Category: #{choice}", @search.with(category: choice),
class: ("active" if chosen) %>
<% end %>
Notez que si le bloc passé à each_choice
accepte deux arguments, le 2ème argument indiquera si le choix est actuellement choisi.
Si aucun bloc n'est transmis à each_choice
, il renverra un Enumerator
. Cela peut être utilisé pour générer des options pour une zone de sélection :
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :category, @search.each_choice(:category) %>
<%= form.submit %>
<% end %>
La méthode each_choice
peut également être invoquée avec :order
. Cela parcourra chaque direction de chaque ordre défini, produisant les étiquettes appropriées, y compris le suffixe de direction :
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ] , asc_suffix : " (A-Z)" , desc_suffix : " (Z-A)"
order "Time" , [ :created_at ] , asc_suffix : " (oldest first)" , desc_suffix : " (newest first)"
end
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :order, @search.each_choice(:order) %>
<%= form.submit %>
<% end %>
La case de sélection du formulaire ci-dessus répertorie quatre options : "Titre (AZ)", "Titre (ZA)", "Heure (la plus ancienne en premier)", "Heure (la plus récente en premier)".
ModelSearch#order_directions
Enfin, la méthode d'assistance ModelSearch#order_directions
renvoie un HashWithIndifferentAccess
reflétant la direction actuellement appliquée de chaque commande définie. Il contient une clé pour chaque commande définie et associe chaque clé à :asc
, :desc
ou nil
.
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ]
order "Time" , [ :created_at ]
end
< thead >
< tr >
<% @search . order_directions . each do | order , direction | %>
< th >
<%= link_to_search order , @search . toggle_order ( order ) %>
<%= img_tag " #{ direction || "unsorted" } _icon.png" %>
</ th >
<% end %>
</ tr >
</ thead >
N'oubliez pas qu'un seul ordre peut être appliqué à la fois, donc une seule valeur dans le hachage, au maximum, sera non nil
.
Ajoutez la gemme à votre Gemfile :
$ bundle add talent_scout
Et lancez le générateur d'installation :
$ rails generate talent_scout:install
Exécutez bin/test
pour exécuter les tests.
Licence MIT