Filterameter fournit des filtres déclaratifs pour les contrôleurs Rails afin de réduire le code passe-partout et d'augmenter la lisibilité. Combien de fois avez-vous vu (ou écrit) cette action du contrôleur ?
def index
@films = Films . all
@films = @films . where ( name : params [ :name ] ) if params [ :name ]
@films = @films . joins ( :film_locations ) . merge ( FilmLocations . where ( location_id : params [ :location_id ] ) ) if params [ :location_id ]
@films = @films . directed_by ( params [ :director_id ] ) if params [ :director_id ]
@films = @films . written_by ( params [ :writer_id ] ) if params [ :writer_id ]
@films = @films . acted_by ( params [ :actor_id ] ) if params [ :actor_id ]
end
C'est du code redondant et un peu pénible à écrire et à maintenir. Sans parler de ce que RuboCop va en dire. Ne serait-il pas bien si vous pouviez simplement déclarer les filtres acceptés par le contrôleur ?
filter :name , partial : true
filter :location_id , association : :film_locations
filter :director_id , name : :directed_by
filter :writer_id , name : :written_by
filter :actor_id , name : :acted_by
def index
@films = build_query_from_filters
end
Simplifiez et accélérez le développement des contrôleurs Rails en rendant les paramètres de filtre déclaratifs avec Filterameter.
Cette gemme nécessite Rails 6.1+ et fonctionne avec ActiveRecord.
Ajoutez cette ligne au Gemfile de votre application :
gem 'filterameter'
Et puis exécutez :
$ bundle install
Ou installez-le vous-même en tant que :
$ gem install filterameter
Incluez le module Filterameter::DeclarativeFilters
dans le contrôleur pour fournir le filtre DSL. Il peut être inclus dans ApplicationController
pour rendre la fonctionnalité disponible à tous les contrôleurs ou il peut être mélangé au cas par cas.
filter :color
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
filter :brand_name , association : :brand , name : :name
filter :on_sale , association : :price , validates : [ { numericality : { greater_than : 0 } } ,
{ numericality : { less_than : 100 } } ]
Les filtres sans options peuvent être déclarés d'un seul coup avec filters
:
filters :color ,
:size ,
:name
Les options suivantes peuvent être spécifiées pour chaque filtre.
Si le nom du paramètre est différent du nom de l'attribut ou de la portée, utilisez le paramètre name pour spécifier le nom de l'attribut ou de la portée. Par exemple, si le nom de l'attribut est current_status
mais que le filtre est exposé simplement en tant que status
utilisez ce qui suit :
filter :status , name : :current_status
Cette option peut également être utile avec les filtres imbriqués afin que le paramètre de requête puisse être préfixé par le nom du modèle. Voir l'option association
pour un exemple.
Si l'attribut ou la portée est imbriqué, il peut être référencé en nommant l'association. Par exemple, si l'attribut manager_id se trouve dans l'enregistrement du service d'un employé, utilisez ce qui suit :
filter :manager_id , association : :department
L'attribut ou la portée peut être imbriqué sur plusieurs niveaux. Déclarez le filtre avec un tableau spécifiant les associations dans l'ordre. Par exemple, si un employé appartient à un service et qu'un service appartient à une unité commerciale, utilisez ce qui suit pour effectuer une requête sur le nom de l'unité commerciale :
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
Si une association est un has_many
la méthode distincte est appelée sur la requête.
Limitation : S'il existe plusieurs associations à la même table et que les deux associations peuvent faire partie de la requête, vous ne pouvez pas utiliser directement un filtre imbriqué. Au lieu de cela, créez une portée qui lève l'ambiguïté des associations, puis créez un filtre sur cette portée.
Si la valeur du filtre doit être validée, utilisez l'option validates
avec les validations ActiveModel. Voici un exemple d'utilisation du validateur d'inclusion pour restreindre les tailles :
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
Le validateur inclusion
a été remplacé pour fournir l'option supplémentaire allow_multiple_values
. Lorsque c'est vrai, la valeur peut être un tableau et chaque entrée du tableau sera validée. Utilisez-le lorsque le filtre peut spécifier une ou plusieurs valeurs.
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
Spécifiez l'option partielle si le filtre doit effectuer une recherche partielle ( LIKE
de SQL). L'option partielle accepte un hachage pour spécifier le comportement de recherche. Voici les options disponibles :
Il existe deux raccourcis : : l'option partielle peut être déclarée avec true
, qui utilise simplement les valeurs par défaut ; ou l'option partielle peut être déclarée directement avec l'option match, comme partial: :from_start
.
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
Les options match
définissent l'endroit où vous effectuez la recherche (qui contrôle ensuite l'endroit où les caractères génériques apparaissent) :
Spécifiez l'option de plage pour activer les recherches par plages, valeurs minimales ou valeurs maximales. (Tous ces éléments sont inclus. Une recherche d'une valeur minimale de 10,00 $ inclurait tous les articles dont le prix est de 10,00 $.)
Voici les options disponibles :
L'utilisation de l'option range signifie qu'en plus du filtre d'attribut, les paramètres de requête minimum et maximum peuvent également être spécifiés. Les noms de paramètres sont le nom de l'attribut plus le suffixe _min ou _max .
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
Dans le premier exemple, les paramètres de requête peuvent inclure price , price_min et price_max .
Par défaut, la plupart des filtres sont triables. Pour empêcher un filtre d'attribut d'être triable, définissez l'option sur false.
filter :price , sortable : false
Les filtres suivants ne peuvent pas être triés :
Pour les étendues qui n’acceptent pas d’arguments, le filtre doit fournir un booléen indiquant si l’étendue doit être invoquée ou non. Par exemple, imaginez une portée appelée high_priority
avec des critères qui identifient les enregistrements de haute priorité. La portée serait invoquée par les paramètres de requête high_priority=true
.
Passer high_priority=false
n’invoquera pas la portée. Cela facilite l’inclusion d’un filtre avec une interface utilisateur de case à cocher.
Les étendues qui acceptent des arguments doivent être écrites sous forme de méthodes de classe, et non sous forme de portées en ligne. Par exemple, imaginez une étendue appelée recent
qui prend comme argument une date à partir de. Voici à quoi cela pourrait ressembler :
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
Comme indiqué ci-dessus, la plupart des filtres d'attributs peuvent être triés par défaut. Si aucun filtre n'a été déclaré pour un attribut, la déclaration sort
peut être utilisée. Utilisez le même name
et les mêmes options association
selon vos besoins.
Par exemple, la déclaration suivante pourrait être utilisée sur un contrôleur d'activité pour permettre de trier les activités par projet créé à.
sort :project_created_at , name : :created_at , association : :project
Les tris sans options peuvent être déclarés d'un seul coup avec sorts
:
sorts :created_at ,
:updated_at ,
:description
Les étendues peuvent être utilisées pour le tri, mais doivent être déclarées avec sort
(ou sorts
). Par exemple, si un modèle incluait une portée appelée by_created_at
vous pouvez ajouter ce qui suit au contrôleur pour l'exposer.
sort :by_created_at
Les options name
et association
peuvent également être utilisées. Par exemple, si la portée était sur le modèle Project, elle pourrait également être utilisée sur un contrôleur d'activité enfant à l'aide de l'option association
:
sort :by_created_at , association : :project
Seules les associations singulières sont valables pour le tri. Une association de collection peut renvoyer plusieurs valeurs, rendant le tri indéterminé.
Une étendue utilisée pour le tri doit accepter un seul argument. Il sera transmis soit :asc
ou :desc
selon le paramètre.
L'exemple de portée ci-dessus peut être défini comme suit :
def self . by_created_at ( dir )
order ( created_at : dir )
end
Un tri par défaut peut être déclaré en utilisant default_sort
. Le ou les arguments doivent spécifier un ou plusieurs des tris déclarés ou des filtres triables par nom. Par défaut, l'ordre est croissant. Si vous souhaitez un ordre décroissant, vous pouvez mapper le symbole du nom de colonne sur :desc.
default_sort updated_at : :desc , :description
Afin de fournir des résultats cohérents, un tri est toujours appliqué. Si aucune valeur par défaut n'est spécifiée, il utilisera la clé primaire décroissante.
Il existe deux manières d'appliquer les filtres et de créer la requête, en fonction du niveau de contrôle et/ou de visibilité souhaité :
build_filtered_query
avant le rappel d'actionbuild_query_from_filters
build_filtered_query
avant le rappel d'action Ajoutez le rappel d'action build_filtered_query
pour les actions du contrôleur qui doivent générer la requête. Cela peut être fait soit dans ApplicationController
, soit au cas par cas.
Lors de l'utilisation du rappel, le nom de la variable est le nom du modèle au pluriel. Par exemple, le modèle Photo utilisera la variable @photos
pour stocker la requête. Le nom de la variable peut être explicitement spécifié avec filter_query_var_name
. Par exemple, si la requête est stockée sous @data
, utilisez ce qui suit :
filter_query_var_name :data
De plus, la commande filter_model
prend un deuxième paramètre facultatif pour spécifier le nom de la variable. Le modèle et le nom de la variable peuvent être spécifiés avec ce raccourci. Par exemple, pour utiliser le modèle Picture et stocker les résultats sous @data
, utilisez ce qui suit :
filter_model 'Picture' , :data
Dans le chemin heureux, le WidgetsController sert des widgets et peut filtrer selon la taille et la couleur. Voici à quoi pourrait ressembler le contrôleur :
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
before_action :build_filtered_query , only : :index
filter :size
filter :color
def index
render json : @widgets
end
end
build_query_from_filters
Pour générer la requête manuellement, vous pouvez appeler directement build_query_from_filters
au lieu d'utiliser le callback .
Voici à nouveau le contrôleur Widgets, cette fois en créant la requête manuellement :
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
Cette méthode prend éventuellement une requête de départ. S'il existait un contrôleur pour les widgets actifs qui ne devait renvoyer que les widgets actifs, les éléments suivants pourraient être transmis à la méthode comme point de départ :
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
La requête de départ est également un bon endroit pour fournir des inclusions afin de permettre un chargement rapide :
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
Notez que la requête de démarrage fournit le modèle, donc le modèle n'est pas recherché et la déclaration model_name
n'est pas nécessaire.
Les conventions Rails sont utilisées pour déterminer le modèle du contrôleur. Par exemple, PhotosController crée une requête sur le modèle Photo. Si un contrôleur est doté d'un espace de noms, le modèle sera d'abord recherché sans l'espace de noms, puis avec l'espace de noms.
Si les conventions ne fournissent pas le modèle correct , le modèle peut être nommé explicitement avec ce qui suit :
filter_model 'Picture'
Important : Si la déclaration filter_model
est utilisée, elle doit l'être avant toute déclaration de filtre ou de tri.
Il existe trois options de configuration :
Les options de configuration peuvent être définies dans un initialiseur, un fichier d'environnement ou dans application.rb
.
Les options peuvent être définies directement...
Filterameter.configuration.action_on_undeclared_parameters = :log
...ou la configuration peut être obtenue :
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
Se produit lorsque le paramètre de filtre contient des clés qui ne sont pas définies. Les actions valides sont :log
, :raise
et false
(ne pas agir). Par défaut, le développement sera enregistré, le test sera lancé et la production ne fera rien.
Se produit lorsqu'un paramètre de filtre échoue à une validation. Les actions valides sont :log
, :raise
et false
(ne pas agir). Par défaut, le développement sera enregistré, le test sera lancé et la production ne fera rien.
Par défaut, les paramètres du filtre sont imbriqués sous la clé :filter
. Utilisez ce paramètre pour remplacer la clé.
Si les paramètres du filtre ne sont PAS imbriqués, définissez-le sur false. Cela limitera les paramètres de filtre à ceux qui ont été déclarés, ce qui signifie que les paramètres non déclarés sont ignorés (et l'option de configuration action_on_undeclared_parameters n'entre pas en jeu).
Les déclarations peuvent être testées pour chaque contrôleur, en détectant les fautes de frappe, les portées mal définies ou tout autre problème. La méthode declarations_validator
est ajoutée à chaque contrôleur, et un seul test de contrôleur peut être ajouté pour valider toutes les déclarations de ce contrôleur.
Un test RSpec pourrait ressembler à ceci :
expect ( WidgetsController . declarations_validator ) . to be_valid
Dans Minitest, cela pourrait ressembler à ceci :
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
Les paramètres de filtre sont extraits des paramètres du contrôleur, imbriqués sous la clé de filter
(par défaut ; voir Configuration pour modifier la clé de filtre). Par exemple, une requête pour de grands widgets bleus peut avoir les paramètres de requête suivants sur l'URL :
?filter[size]=large&filter[color]=blue
Sur un formulaire de recherche générique, l'assistant de formulaire form_with
prend l'option scope
qui permet de regrouper les paramètres :
<%= form_with url: "/search", scope: :filter, method: :get do |form| %>
<%= form.label :size, "Size:" %>
<%= form.text_field :size %>
<%= form.label :color, "Color:" %>
<%= form.text_field :color %>
<%= form.submit "Search" %>
<% end %>
Le tri est également imbriqué sous la clé de filtre :
/widgets?filter[sort]=size
Utilisez un tableau pour transmettre plusieurs tris. L'ordre des paramètres est l'ordre dans lequel les tris seront appliqués. Par exemple, les éléments suivants sont triés d'abord par taille, puis par couleur :
/widgets?filter[sort]=size&filter[sort]=color
Les tris sont ascendants par défaut, mais un préfixe peut être ajouté pour contrôler le tri :
+
croissant (par défaut)-
descendantPar exemple, les éléments suivants sont triés par taille décroissante :
/widgets?filter[sort]=-size
Les commentaires, les demandes de fonctionnalités et les modifications proposées sont les bienvenus. Veuillez utiliser le suivi des problèmes pour les commentaires et les demandes de fonctionnalités. Pour proposer un changement directement, veuillez forker le dépôt et ouvrir une pull request. Gardez un œil sur les actions pour vous assurer que les tests et Rubocop réussissent. Code Climate est également utilisé manuellement pour évaluer la ligne de code.
Pour signaler un bug, veuillez utiliser l'outil de suivi des problèmes et fournir les informations suivantes :
Des étoiles d'or seront attribuées si vous parvenez à reproduire le problème avec un test.
Les tests sont écrits en RSpec et l'application factice utilise une base de données Docker. Le script bin/start_db.sh
démarre et prépare la base de données de test. Il s'agit d'une étape unique avant d'exécuter les tests.
bin/start_db.rb
bundle exec rspec
Les tests peuvent également être exécutés sur toutes les combinaisons Ruby et Rails à l'aide de l'évaluation. L'installation est également une étape unique.
bundle exec appraisal install
bundle exec appraisal rspec
La gemme est disponible en open source selon les termes de la licence MIT.