Modellgestützte Suchen in Rails. Ein krasses Beispiel:
## 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 >
Im obigen Beispiel:
PostSearch
Klasse übernimmt die Verantwortung für die Suche nach Post
-Modellen. Es kann eine beliebige Kombination seiner definierten Kriterien anwenden und fehlende, leere oder ungültige Eingabewerte automatisch ignorieren. Die Ergebnisse können auch nach einer der definierten Reihenfolgen sortiert werden, entweder in aufsteigender oder absteigender Richtung.PostsController#index
verwendet den model_search
Helfer, um eine PostSearch
Instanz zu erstellen, und weist sie der @search
Variablen zur späteren Verwendung in der Ansicht zu. Die Suchergebnisse werden außerdem einer Variablen zur Verwendung in der Ansicht zugewiesen.@search
Variablen zu erstellen. Der link_to_search
Helfer wird verwendet, um Links im Tabellenkopf zu erstellen, die die Ergebnisse sortieren. Beachten Sie, dass die hier verwendete Methode toggle_order
ein neues Suchobjekt zurückgibt und @search
unverändert lässt.Eine ausführliche Erläuterung der in diesem Beispiel verwendeten Methoden finden Sie in der API-Dokumentation.
Sie können den talent_scout:search
-Generator verwenden, um eine Modellsuchklassendefinition zu generieren. Zum Beispiel,
$ rails generate talent_scout:search post
Erzeugt eine Datei „app/searches/post_search.rb“, die Folgendes enthält:
class PostSearch < TalentScout :: ModelSearch
end
Suchklassen erben von TalentScout::ModelSearch
. Ihre Zielmodellklasse wird aus dem Namen der Suchklasse abgeleitet. PostSearch
sucht beispielsweise standardmäßig nach Post
Modellen. Um diese Schlussfolgerung zu überschreiben, verwenden Sie ModelSearch::model_class=
:
class EmployeeSearch < TalentScout :: ModelSearch
self . model_class = Person # search for Person models instead of `Employee`
end
Suchkriterien werden mit der Methode ModelSearch::criteria
definiert. Kriteriendefinitionen können auf drei Arten angegeben werden: mit einer impliziten where-Klausel, mit einem expliziten Abfrageblock oder mit einem Modellbereichsverweis. Zur Veranschaulichung: Die folgenden drei :title
-Kriterien sind alle gleichwertig:
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
Beachten Sie, dass explizite Abfrageblöcke im Kontext der ActiveRecord::Relation
des Modells ausgewertet werden, genau wie Active Record- scope
.
Eine Kriteriendefinition kann einen Datentyp angeben, der dazu führt, dass sein Eingabewert typisiert wird, bevor er an den Abfrageblock oder -bereich übergeben wird. Als Beispiel:
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" )
Hier wird die an den PostSearch
Konstruktor übergebene Zeichenfolge "Dec 31, 1999"
in ein Date
umgewandelt, bevor sie an den Abfrageblock übergeben wird.
Der Standardkriterientyp ist :string
, was bedeutet, dass standardmäßig alle Eingabewerte in Zeichenfolgen umgewandelt werden. Diese Standardeinstellung (im Gegensatz zur Standardeinstellung ohne Typumwandlung) stellt ein konsistentes Verhalten sicher, unabhängig davon, wie das Suchobjekt aufgebaut ist, sei es aus stark typisierten Werten oder aus Suchformular-Anforderungsparametern.
Die verfügbaren Kriterientypen sind die gleichen wie für Attribute des aktiven Modells: :big_integer
, :boolean
, :date
, :datetime
, :decimal
, :float
, :integer
, :string
, :time
sowie alle von Ihnen definierten benutzerdefinierten Typen.
Ein zusätzlicher Convenience-Typ ist ebenfalls verfügbar: :void
. Der :void
führt Typumwandlungen wie :boolean
aus, verhindert jedoch, dass die Kriterien angewendet werden, wenn der typumwandelte Wert falsch ist. Zum Beispiel:
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 : "" )
Anstatt einen Typ anzugeben, kann eine Kriteriendefinition Auswahlmöglichkeiten festlegen. Auswahlmöglichkeiten definieren eine Reihe von Werten, die an den Abfrageblock übergeben werden können.
Auswahlmöglichkeiten können entweder als Array benutzerfreundlicher Werte angegeben werden:
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ] do | name |
where ( category : name . downcase )
end
end
...Oder als Hash mit menschenfreundlichen Schlüsseln:
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
Der an den Abfrageblock übergebene Wert ist einer der Werte im Array oder einer der Werte im Hash. Das Suchobjekt kann mit jedem der Array-Werte oder Hash-Schlüssel oder Hash-Werte erstellt werden:
PostSearch . new ( category : "Math" )
PostSearch . new ( within : "Last 24 hours" )
PostSearch . new ( within : 24 . hours )
Wenn jedoch eine ungültige Auswahl angegeben wird, werden die entsprechenden Kriterien nicht angewendet:
# The following will skip the criteria, but will not raise an error:
PostSearch . new ( category : "Marketing" )
PostSearch . new ( within : 12 . hours )
Eine Kriteriendefinition kann einen Standardwert angeben, der an den Abfrageblock übergeben wird, wenn der Eingabewert fehlt. Standardwerte werden auch in Suchformularen angezeigt.
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 )
Ein Kriterium wird nicht angewendet, wenn eine der folgenden Bedingungen zutrifft:
Der Eingabewert für das Kriterium fehlt und es wurde kein Standardwert angegeben.
Das Suchobjekt wurde mit einem ActionController::Parameters
(anstelle eines Hash) erstellt und der Kriterieneingabewert ist blank?
, und es wurde kein Standardwert angegeben. (Dieses Verhalten verhindert, dass leere Suchformularfelder die Suchergebnisse beeinflussen.)
Die Typumwandlung des Kriterieneingabewerts schlägt fehl. Zum Beispiel:
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" )
Die Kriteriendefinition gibt den Typ :void
an und der typisierte Eingabewert ist falsch.
Die Kriteriendefinition gibt Auswahlmöglichkeiten an und der Eingabewert ist keine gültige Auswahl.
Der Kriterienabfrageblock gibt nil
zurück. Zum Beispiel:
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 )
Suchergebnisreihenfolgen werden mit der ModelSearch::order
-Methode definiert:
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 )
Es kann jeweils nur eine Bestellung angewendet werden, eine Bestellung kann jedoch mehrere Spalten umfassen:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category, title":
PostSearch . new ( order : :category )
Dieses eingeschränkte Design wurde gewählt, weil es kuratierte mehrspaltige Sortierungen mit einfacheren Benutzeroberflächen für die einspaltige Sortierung ermöglicht und weil es Ad-hoc-mehrspaltige Sortierungen verhindert, die möglicherweise nicht durch einen Datenbankindex unterstützt werden.
Eine Reihenfolge kann in aufsteigender oder absteigender Richtung erfolgen. Die ModelSearch#toggle_order
-Methode wendet eine Reihenfolge in aufsteigender Richtung an oder ändert die Richtung einer angewendeten Reihenfolge von aufsteigend in absteigend:
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 )
Beachten Sie, dass die Methode toggle_order
das vorhandene Suchobjekt nicht ändert. Stattdessen wird ein neues Suchobjekt mit der neuen Reihenfolge und den Kriterienwerten des vorherigen Suchobjekts erstellt.
Wenn eine mehrspaltige Reihenfolge in absteigender Richtung angewendet wird, sind alle Spalten betroffen:
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 )
Um dieses Verhalten zu umgehen und stattdessen eine Spalte in einer statischen Richtung zu fixieren, hängen Sie " ASC"
oder " DESC"
an den Spaltennamen an:
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 )
Eine Reihenfolge kann direkt in aufsteigender oder absteigender Richtung angewendet werden, ohne toggle_order
zu verwenden, indem ein entsprechendes Suffix angehängt wird:
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" )
Die Standardsuffixe sind, wie im obigen Beispiel zu sehen ist, ".asc"
und ".desc"
. Diese wurden aufgrund ihrer I18n-Freundlichkeit ausgewählt. Sie können im Rahmen der Auftragsdefinition überschrieben werden:
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)" )
Eine Bestellung kann als Standardbestellung festgelegt werden, was dazu führt, dass diese Bestellung angewendet wird, wenn keine andere Bestellung angegeben ist:
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 )
Beachten Sie, dass die Standardreihenfolge entweder aufsteigend oder absteigend sein kann, indem Sie default: :asc
bzw. default: :desc
angeben. Ebenso wie jeweils nur eine Bestellung angewendet werden kann, kann auch nur eine Bestellung als Standard festgelegt werden.
Ein Standardsuchbereich kann mit ModelSearch::default_scope
definiert werden:
class PostSearch < TalentScout :: ModelSearch
default_scope { where ( published : true ) }
end
Der Standardumfang wird unabhängig von den Kriterien oder Auftragseingabewerten angewendet.
Controller können die Hilfsmethode model_search
verwenden, um ein Suchobjekt mit den Abfrageparametern der aktuellen Anfrage zu erstellen:
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
Im obigen Beispiel erstellt model_search
ein PostSearch
Objekt. Die Modellsuchklasse wird automatisch aus dem Controller-Klassennamen abgeleitet. Um die Modellsuchklasse zu überschreiben, verwenden Sie ::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
In diesen Beispielen wird das Suchobjekt zur Verwendung in der Ansicht in @search
gespeichert. Beachten Sie, dass @search.results
ein ActiveRecord::Relation
zurückgibt, sodass alle zusätzlichen Bereiche, wie z. B. Paginierung, angewendet werden können.
Suchformulare können mit dem Form Builder von Rails und einem Suchobjekt gerendert werden:
<%= 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 %>
Beachten Sie die method: :get
argument to form_with
. Dies ist erforderlich.
Formularfelder werden mit den gleichnamigen Kriterieneingabewerten (oder Standardwerten) von @search
gefüllt. Es können typgerechte Formularfelder verwendet werden, z. B. date_field
für Typ :date
, check_box
für Typ :boolean
und :void
usw.
Standardmäßig wird das Formular an die Indexaktion des Controllers übermittelt, der der model_class
des Suchobjekts entspricht. PostSearch.model_class
ist beispielsweise Post
, sodass ein Formular mit einer Instanz von PostSearch
an PostsController#index
gesendet wird. Um zu ändern, wohin das Formular gesendet wird, verwenden Sie die Option :url
von form_with
.
Suchlinks können mit der Ansichtshilfsmethode link_to_search
gerendert werden:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
Der Link verweist automatisch auf den aktuellen Controller und die aktuelle Aktion mit Abfrageparametern aus dem angegebenen Suchobjekt. Um eine Verknüpfung zu einem anderen Controller oder einer anderen Aktion herzustellen, übergeben Sie anstelle des Suchobjekts einen Options-Hash:
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
search: @search.toggle_order(:title, :asc) } %>
Der link_to_search
Helfer akzeptiert auch dieselben HTML-Optionen wie der link_to
Helfer von Rails:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
id: "title-sort-link", class: "sort-link" %>
...Sowie einen Inhaltsblock:
<%= link_to_search @search.toggle_order(:title, :asc) do %>
Sort by title <%= img_tag "sort_icon.png" %>
<% end %>
ModelSearch
Hilfsmethoden Die ModelSearch
-Klasse stellt mehrere Methoden bereit, die beim Rendern der Ansicht hilfreich sind.
Eine solche Methode ist ModelSearch#toggle_order
, die in vorherigen Beispielen gezeigt wurde. Denken Sie daran, dass toggle_order
eine Builder-Methode ist, die das Suchobjekt nicht ändert. Stattdessen dupliziert es das Suchobjekt und legt die Reihenfolge für das neue Objekt fest. Ein solches Verhalten eignet sich zum Generieren von Links zu mehreren Varianten einer Suche, beispielsweise Sortierlinks in Tabellenspaltenüberschriften.
ModelSearch#with
und ModelSearch#without
Zwei weitere Methoden im Builder-Stil sind ModelSearch#with
und ModelSearch#without
. Wie toggle_order
geben beide Methoden ein neues Suchobjekt zurück und lassen das ursprüngliche Suchobjekt unverändert. Die Methode with
akzeptiert einen Hash von Kriterieneingabewerten, der mit dem ursprünglichen Satz von Kriterieneingabewerten zusammengeführt wird:
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 )
Die Methode without
“ akzeptiert eine Liste von auszuschließenden Kriterieneingabewerten (die Standardkriterienwerte gelten weiterhin):
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
Eine weitere hilfreiche Methode ist ModelSearch#each_choice
, die die definierten Auswahlmöglichkeiten für ein bestimmtes Kriterium durchläuft. Damit lassen sich Links zu Varianten einer Suche generieren:
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 %>
Beachten Sie, dass, wenn der an each_choice
übergebene Block zwei Argumente akzeptiert, das zweite Argument angibt, ob die Auswahl derzeit ausgewählt ist.
Wenn kein Block an each_choice
übergeben wird, wird ein Enumerator
zurückgegeben. Dies kann verwendet werden, um Optionen für eine Auswahlbox zu generieren:
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :category, @search.each_choice(:category) %>
<%= form.submit %>
<% end %>
Die Methode each_choice
kann auch mit :order
aufgerufen werden. Dadurch wird jede Richtung jeder definierten Reihenfolge durchlaufen, wodurch die entsprechenden Beschriftungen einschließlich des Richtungssuffixes entstehen:
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 %>
Das Auswahlfeld im obigen Formular listet vier Optionen auf: „Titel (AZ)“, „Titel (ZA)“, „Zeit (älteste zuerst)“, „Zeit (neueste zuerst)“.
ModelSearch#order_directions
Schließlich gibt die Hilfsmethode ModelSearch#order_directions
einen HashWithIndifferentAccess
zurück, der die aktuell angewendete Richtung jeder definierten Bestellung widerspiegelt. Es enthält einen Schlüssel für jede definierte Reihenfolge und ordnet jedem Schlüssel entweder :asc
, :desc
oder nil
zu.
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 >
Denken Sie daran, dass jeweils nur eine Reihenfolge angewendet werden kann, sodass höchstens ein Wert im Hash ungleich nil
sein wird.
Fügen Sie den Edelstein zu Ihrer Gemfile hinzu:
$ bundle add talent_scout
Und führen Sie den Installationsgenerator aus:
$ rails generate talent_scout:install
Führen Sie bin/test
aus, um die Tests auszuführen.
MIT-Lizenz