Filterameter bietet deklarative Filter für Rails-Controller, um den Boilerplate-Code zu reduzieren und die Lesbarkeit zu verbessern. Wie oft haben Sie diese Controller-Aktion gesehen (oder geschrieben)?
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
Es handelt sich um redundanten Code, dessen Schreiben und Warten etwas mühsam ist. Ganz zu schweigen davon, was RuboCop dazu sagen wird. Wäre es nicht schön, wenn Sie einfach die Filter deklarieren könnten, die der Controller akzeptiert?
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
Vereinfachen und beschleunigen Sie die Entwicklung von Rails-Controllern, indem Sie Filterparameter mit Filterameter deklarativ machen.
Dieses Juwel erfordert Rails 6.1+ und funktioniert mit ActiveRecord.
Fügen Sie diese Zeile zur Gemfile Ihrer Anwendung hinzu:
gem 'filterameter'
Und dann ausführen:
$ bundle install
Oder installieren Sie es selbst als:
$ gem install filterameter
Fügen Sie das Modul Filterameter::DeclarativeFilters
in den Controller ein, um den Filter DSL bereitzustellen. Es kann in den ApplicationController
integriert werden, um die Funktionalität allen Controllern zur Verfügung zu stellen, oder es kann von Fall zu Fall eingemischt werden.
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 } } ]
Filter ohne Optionen können alle auf einmal mit filters
deklariert werden:
filters :color ,
:size ,
:name
Für jeden Filter können die folgenden Optionen angegeben werden.
Wenn sich der Name des Parameters vom Namen des Attributs oder Bereichs unterscheidet, verwenden Sie den Namensparameter, um den Namen des Attributs oder Bereichs anzugeben. Wenn der Attributname beispielsweise current_status
lautet, der Filter jedoch einfach als status
angezeigt wird, verwenden Sie Folgendes:
filter :status , name : :current_status
Diese Option kann auch bei verschachtelten Filtern hilfreich sein, sodass dem Abfrageparameter der Modellname vorangestellt werden kann. Ein Beispiel finden Sie in der association
.
Wenn das Attribut oder der Bereich verschachtelt ist, kann durch Benennung der Zuordnung darauf verwiesen werden. Wenn sich das Attribut manager_id beispielsweise im Abteilungsdatensatz eines Mitarbeiters befindet, verwenden Sie Folgendes:
filter :manager_id , association : :department
Das Attribut oder der Bereich kann auf mehr als einer Ebene verschachtelt sein. Deklarieren Sie den Filter mit einem Array, das die Zuordnungen der Reihe nach angibt. Wenn beispielsweise ein Mitarbeiter zu einer Abteilung und eine Abteilung zu einer Geschäftseinheit gehört, verwenden Sie Folgendes, um den Namen der Geschäftseinheit abzufragen:
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
Wenn eine Assoziation ein has_many
ist, wird die eindeutige Methode für die Abfrage aufgerufen.
Einschränkung: Wenn es mehr als eine Assoziation zu derselben Tabelle gibt und beide Assoziationen Teil der Abfrage sein können, können Sie einen verschachtelten Filter nicht direkt verwenden. Erstellen Sie stattdessen einen Bereich, der die Zuordnungen eindeutig macht, und erstellen Sie dann einen Filter für diesen Bereich.
Wenn der Filterwert validiert werden soll, verwenden Sie die Option validates
zusammen mit ActiveModel-Validierungen. Hier ist ein Beispiel für die Verwendung des Einschlussvalidators zur Größenbeschränkung:
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
Der inclusion
wurde überschrieben, um die zusätzliche allow_multiple_values
bereitzustellen. Bei „true“ kann der Wert ein Array sein und jeder Eintrag im Array wird validiert. Verwenden Sie dies, wenn der Filter einen oder mehrere Werte angeben kann.
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
Geben Sie die Option „partial“ an, wenn der Filter eine Teilsuche durchführen soll (SQLs LIKE
). Die Teiloption akzeptiert einen Hash, um das Suchverhalten anzugeben. Hier sind die verfügbaren Optionen:
Es gibt zwei Abkürzungen: : Die Teiloption kann mit true
deklariert werden, wodurch nur die Standardwerte verwendet werden. Alternativ kann die Option „partial“ direkt mit der Option „match“ deklariert werden, z. B. partial: :from_start
.
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
Die match
definieren, wo Sie suchen (was dann steuert, wo die Platzhalter angezeigt werden):
Geben Sie die Bereichsoption an, um die Suche nach Bereichen, Mindestwerten oder Höchstwerten zu ermöglichen. (Alle diese Angaben sind inklusive. Eine Suche nach einem Mindestwert von 10,00 $ würde alle Artikel mit einem Preis von 10,00 $ einschließen.)
Hier sind die verfügbaren Optionen:
Die Verwendung der Bereichsoption bedeutet, dass zusätzlich zum Attributfilter auch minimale und maximale Abfrageparameter angegeben werden können. Die Parameternamen bestehen aus dem Attributnamen plus dem Suffix _min oder _max .
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
Im ersten Beispiel könnten die Abfrageparameter „price“ , „price_min“ und „price_max“ umfassen.
Standardmäßig sind die meisten Filter sortierbar. Um zu verhindern, dass ein Attributfilter sortierbar ist, legen Sie die Option auf „false“ fest.
filter :price , sortable : false
Die folgenden Filter sind nicht sortierbar:
Für Bereiche, die keine Argumente annehmen, sollte der Filter einen booleschen Wert bereitstellen, der angibt, ob der Bereich aufgerufen werden soll oder nicht. Stellen Sie sich beispielsweise einen Bereich namens high_priority
mit Kriterien vor, der Datensätze mit hoher Priorität identifiziert. Der Bereich würde durch die Abfrageparameter high_priority=true
aufgerufen.
Durch die Übergabe von high_priority=false
wird der Bereich nicht aufgerufen. Dies erleichtert das Einbinden eines Filters in eine Kontrollkästchen-Benutzeroberfläche.
Bereiche, die Argumente annehmen, müssen als Klassenmethoden und nicht als Inline-Bereiche geschrieben werden. Stellen Sie sich zum Beispiel einen Bereich mit dem Namen recent
“ vor, der als Argument ein „Stand“-Datum verwendet. So könnte das aussehen:
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
Wie oben erwähnt, sind die meisten Attributfilter standardmäßig sortierbar. Wenn für ein Attribut kein Filter deklariert wurde, kann die sort
verwendet werden. Verwenden Sie nach Bedarf denselben name
und association
Zuordnungsoptionen.
Die folgende Deklaration könnte beispielsweise auf einem Aktivitätscontroller verwendet werden, um die Sortierung von Aktivitäten nach erstelltem Projekt zu ermöglichen.
sort :project_created_at , name : :created_at , association : :project
Sortierungen ohne Optionen können alle auf einmal mit sorts
deklariert werden:
sorts :created_at ,
:updated_at ,
:description
Bereiche können zum Sortieren verwendet werden, müssen jedoch mit sort
(oder sorts
) deklariert werden. Wenn ein Modell beispielsweise einen Bereich namens by_created_at
enthält, können Sie dem Controller Folgendes hinzufügen, um ihn verfügbar zu machen.
sort :by_created_at
Es können auch die name
und association
genutzt werden. Wenn sich der Bereich beispielsweise auf dem Projektmodell befände, könnte er mithilfe der association
auch auf einem untergeordneten Aktivitätscontroller verwendet werden:
sort :by_created_at , association : :project
Für die Sortierung sind nur singuläre Assoziationen gültig. Eine Sammlungszuordnung könnte mehrere Werte zurückgeben, wodurch die Sortierung unbestimmt wäre.
Ein zum Sortieren verwendeter Bereich muss ein einzelnes Argument akzeptieren. Abhängig vom Parameter wird entweder :asc
oder :desc
übergeben.
Der obige Beispielbereich könnte wie folgt definiert werden:
def self . by_created_at ( dir )
order ( created_at : dir )
end
Eine Standardsortierung kann mit default_sort
deklariert werden. Das/die Argument(e) sollten einen oder mehrere der deklarierten Sortierungen oder sortierbaren Filter nach Namen angeben. Standardmäßig ist die Reihenfolge aufsteigend. Wenn Sie eine absteigende Reihenfolge wünschen, können Sie das Spaltennamensymbol :desc zuordnen.
default_sort updated_at : :desc , :description
Um konsistente Ergebnisse zu liefern, wird immer eine Sortierung angewendet. Wenn kein Standardwert angegeben ist, wird der Primärschlüssel in absteigender Reihenfolge verwendet.
Es gibt zwei Möglichkeiten, die Filter anzuwenden und die Abfrage zu erstellen, je nachdem, wie viel Kontrolle und/oder Sichtbarkeit gewünscht wird:
build_filtered_query
vor dem Aktionsrückrufbuild_query_from_filters
manuell auf build_filtered_query
vor dem Aktionsrückruf Fügen Sie vor dem Aktionsrückruf build_filtered_query
für Controller-Aktionen hinzu, die die Abfrage erstellen sollen. Dies kann entweder im ApplicationController
oder fallweise erfolgen.
Bei Verwendung des Rückrufs ist der Variablenname der pluralisierte Modellname. Das Fotomodell verwendet beispielsweise die Variable @photos
um die Abfrage zu speichern. Der Variablenname kann explizit mit filter_query_var_name
angegeben werden. Wenn die Abfrage beispielsweise als @data
gespeichert ist, verwenden Sie Folgendes:
filter_query_var_name :data
Darüber hinaus benötigt der Befehl filter_model
einen optionalen zweiten Parameter, um den Variablennamen anzugeben. Mit dieser Abkürzung können sowohl das Modell als auch der Variablenname angegeben werden. Um beispielsweise das Picture-Modell zu verwenden und die Ergebnisse als @data
zu speichern, verwenden Sie Folgendes:
filter_model 'Picture' , :data
Im glücklichen Pfad stellt der WidgetsController Widgets bereit und kann nach Größe und Farbe filtern. So könnte der Controller aussehen:
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
manuell auf Um die Abfrage manuell zu generieren, können Sie build_query_from_filters
direkt aufrufen, anstatt den Callback zu verwenden .
Hier ist noch einmal der Widgets-Controller, diesmal wird die Abfrage manuell erstellt:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
Diese Methode akzeptiert optional eine Startabfrage. Wenn es einen Controller für aktive Widgets gäbe, der nur aktive Widgets zurückgeben sollte, könnte Folgendes als Ausgangspunkt an die Methode übergeben werden:
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
Die Startabfrage ist auch ein guter Ort, um alle Includes bereitzustellen, um Eager Loading zu ermöglichen:
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
Beachten Sie, dass die Startabfrage das Modell bereitstellt, sodass das Modell nicht nachgeschlagen wird und die Deklaration model_name
nicht erforderlich ist.
Rails-Konventionen werden verwendet, um das Modell des Controllers zu bestimmen. Beispielsweise erstellt der PhotosController eine Abfrage für das Photo-Modell. Wenn ein Controller einen Namensraum hat, wird das Modell zuerst ohne Namensraum und dann mit Namensraum gesucht.
Wenn die Konventionen nicht das richtige Modell vorsehen , kann das Modell wie folgt explizit benannt werden:
filter_model 'Picture'
Wichtig: Wenn die filter_model
Deklaration verwendet wird, muss sie vor allen Filter- oder Sortierdeklarationen stehen.
Es gibt drei Konfigurationsmöglichkeiten:
Die Konfigurationsoptionen können in einem Initialisierer, einer Umgebungsdatei oder in application.rb
festgelegt werden.
Die Optionen können direkt eingestellt werden...
Filterameter.configuration.action_on_undeclared_parameters = :log
...oder die Konfiguration kann wie folgt ermittelt werden:
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
Tritt auf, wenn der Filterparameter Schlüssel enthält, die nicht definiert sind. Gültige Aktionen sind :log
, :raise
und false
(keine Aktion ausführen). Standardmäßig führt die Entwicklung eine Protokollierung durch, der Test führt eine Erhöhung durch und die Produktion führt nichts aus.
Tritt auf, wenn die Validierung eines Filterparameters fehlschlägt. Gültige Aktionen sind :log
, :raise
und false
(keine Aktion ausführen). Standardmäßig führt die Entwicklung eine Protokollierung durch, der Test führt eine Erhöhung durch und die Produktion führt nichts aus.
Standardmäßig sind die Filterparameter unter dem Schlüssel :filter
verschachtelt. Verwenden Sie diese Einstellung, um den Schlüssel zu überschreiben.
Wenn die Filterparameter NICHT verschachtelt sind, setzen Sie dies auf „false“. Dadurch werden die Filterparameter nur auf diejenigen beschränkt, die deklariert wurden, was bedeutet, dass nicht deklarierte Parameter ignoriert werden (und die Konfigurationsoption action_on_undeclared_parameters nicht ins Spiel kommt).
Die Deklarationen können für jeden Controller getestet werden, um Tippfehler, falsch definierte Bereiche oder andere Probleme zu erkennen. Die Methode declarations_validator
wird zu jedem Controller hinzugefügt, und ein einzelner Controller-Test kann hinzugefügt werden, um alle Deklarationen für diesen Controller zu validieren.
Ein RSpec-Test könnte so aussehen:
expect ( WidgetsController . declarations_validator ) . to be_valid
Im Minitest könnte es so aussehen:
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
Die Filterparameter werden aus den Controller-Parametern abgerufen und unter dem filter
verschachtelt (standardmäßig; siehe Konfiguration zum Ändern des Filterschlüssels). Beispielsweise könnte eine Anfrage für große, blaue Widgets die folgenden Abfrageparameter in der URL enthalten:
?filter[size]=large&filter[color]=blue
In einem generischen Suchformular verwendet der Formularhelfer form_with
die Option scope
, mit der Parameter gruppiert werden können:
<%= 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 %>
Die Sortierung ist auch unter dem Filterschlüssel verschachtelt:
/widgets?filter[sort]=size
Verwenden Sie ein Array, um mehrere Sortierungen zu übergeben. Die Reihenfolge der Parameter ist die Reihenfolge, in der die Sortierungen angewendet werden. Im Folgenden wird beispielsweise zuerst nach Größe und dann nach Farbe sortiert:
/widgets?filter[sort]=size&filter[sort]=color
Die Sortierung erfolgt standardmäßig aufsteigend, es kann jedoch ein Präfix hinzugefügt werden, um die Sortierung zu steuern:
+
aufsteigend (Standardeinstellung)-
absteigendDie folgende Sortierung erfolgt beispielsweise absteigend nach Größe:
/widgets?filter[sort]=-size
Feedback, Funktionswünsche und Änderungsvorschläge sind willkommen. Bitte nutzen Sie den Issue-Tracker für Feedback und Funktionsanfragen. Um eine Änderung direkt vorzuschlagen, forken Sie bitte das Repo und öffnen Sie eine Pull-Anfrage. Behalten Sie die Maßnahmen im Auge, um sicherzustellen, dass die Tests und Rubocop bestanden werden. Code Climate wird auch manuell zur Bewertung der Codeline verwendet.
Um einen Fehler zu melden, verwenden Sie bitte den Issue-Tracker und geben Sie die folgenden Informationen an:
Goldene Sterne werden vergeben, wenn Sie das Problem mit einem Test reproduzieren können.
Tests werden in RSpec geschrieben und die Dummy-App verwendet eine Docker-Datenbank. Das Skript bin/start_db.sh
startet und bereitet die Testdatenbank vor. Dies ist ein einmaliger Schritt vor der Durchführung der Tests.
bin/start_db.rb
bundle exec rspec
Mithilfe der Bewertung können die Tests auch für alle Ruby- und Rails-Kombinationen durchgeführt werden. Auch die Installation ist ein einmaliger Schritt.
bundle exec appraisal install
bundle exec appraisal rspec
Das Juwel ist als Open Source unter den Bedingungen der MIT-Lizenz verfügbar.