Búsquedas respaldadas por modelos en Rails. Un ejemplo genial:
## 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 >
En el ejemplo anterior:
PostSearch
se encarga de la responsabilidad de buscar modelos Post
. Puede aplicar cualquier combinación de sus criterios definidos, ignorando automáticamente los valores de entrada faltantes, en blanco o no válidos. También puede ordenar los resultados según uno de sus órdenes definidos, ya sea en dirección ascendente o descendente.PostsController#index
utiliza el asistente model_search
para construir una instancia PostSearch
y la asigna a la variable @search
para su uso posterior en la vista. Los resultados de la búsqueda también se asignan a una variable para su uso en la vista.@search
. El asistente link_to_search
se utiliza para crear enlaces en el encabezado de la tabla que ordenan los resultados. Tenga en cuenta que el método toggle_order
utilizado aquí devuelve un nuevo objeto de búsqueda, dejando @search
sin modificar.Para obtener una explicación detallada de los métodos utilizados en este ejemplo, consulte la documentación de la API.
Puede utilizar el generador de talent_scout:search
para generar una definición de clase de búsqueda de modelo. Por ejemplo,
$ rails generate talent_scout:search post
Generará un archivo "app/searches/post_search.rb" que contiene:
class PostSearch < TalentScout :: ModelSearch
end
Las clases de búsqueda heredan de TalentScout::ModelSearch
. Su clase de modelo de destino se infiere del nombre de la clase de búsqueda. Por ejemplo, PostSearch
buscará modelos Post
de forma predeterminada. Para anular esta inferencia, utilice ModelSearch::model_class=
:
class EmployeeSearch < TalentScout :: ModelSearch
self . model_class = Person # search for Person models instead of `Employee`
end
Los criterios de búsqueda se definen con el método ModelSearch::criteria
. Las definiciones de criterios se pueden especificar de tres maneras: con una cláusula donde implícita, con un bloque de consulta explícito o con una referencia de alcance del modelo. A modo de ilustración, los siguientes tres criterios :title
son todos equivalentes:
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
Tenga en cuenta que los bloques de consulta explícitos se evalúan en el contexto de ActiveRecord::Relation
del modelo, al igual que los bloques scope
de Active Record.
Una definición de criterios puede especificar un tipo de datos, lo que hace que su valor de entrada se encasille antes de pasar al bloque de consulta o al alcance. Como ejemplo:
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" )
Aquí, la cadena "Dec 31, 1999"
pasada al constructor PostSearch
se encasilla en una Date
antes de pasarla al bloque de consulta.
El tipo de criterio predeterminado es :string
, lo que significa que, de forma predeterminada, todos los valores de entrada se convertirán en cadenas. Este valor predeterminado (a diferencia de un valor predeterminado de no encasillamiento) garantiza un comportamiento consistente sin importar cómo se construya el objeto de búsqueda, ya sea a partir de valores fuertemente tipados o de parámetros de solicitud de formulario de búsqueda.
Los tipos de criterios disponibles son los mismos que para los atributos del modelo activo: :big_integer
, :boolean
, :date
, :datetime
, :decimal
, :float
, :integer
, :string
, :time
, además de cualquier tipo personalizado que defina.
También está disponible un tipo de conveniencia adicional: :void
. El tipo :void
encasilla como :boolean
, pero evita que se apliquen los criterios cuando el valor encasillado es falso. Por ejemplo:
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 : "" )
En lugar de especificar un tipo, una definición de criterios puede especificar opciones. Las opciones definen un conjunto de valores que se pueden pasar al bloque de consulta.
Las opciones se pueden especificar como una matriz de valores amigables para los humanos:
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ] do | name |
where ( category : name . downcase )
end
end
...O como un Hash con claves amigables para los humanos:
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
El valor pasado al bloque de consulta será uno de los valores del Array o uno de los valores del Hash. El objeto de búsqueda se puede construir con cualquiera de los valores de matriz o claves Hash o valores Hash:
PostSearch . new ( category : "Math" )
PostSearch . new ( within : "Last 24 hours" )
PostSearch . new ( within : 24 . hours )
Pero si se especifica una elección no válida, no se aplicarán los criterios correspondientes:
# The following will skip the criteria, but will not raise an error:
PostSearch . new ( category : "Marketing" )
PostSearch . new ( within : 12 . hours )
Una definición de criterios puede especificar un valor predeterminado, que se pasará al bloque de consulta cuando falte el valor de entrada. Los valores predeterminados también aparecerán en los formularios de búsqueda.
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 )
No se aplicará un criterio si se cumple alguna de las siguientes condiciones:
Falta el valor de entrada de criterios y no se ha especificado ningún valor predeterminado.
¿El objeto de búsqueda se construyó con un ActionController::Parameters
(en lugar de un Hash) y el valor de entrada de los criterios está blank?
y no se ha especificado ningún valor predeterminado. (Este comportamiento evita que los campos vacíos del formulario de búsqueda afecten los resultados de la búsqueda).
La encasillación del valor de entrada de criterios falla. Por ejemplo:
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 definición de criterios especifica el tipo :void
y el valor de entrada encasillado es falso.
La definición de criterios especifica opciones y el valor de entrada no es una opción válida.
El bloque de consulta de criterios devuelve nil
. Por ejemplo:
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 )
Los pedidos de resultados de búsqueda se definen con el método 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 )
Solo se puede aplicar un pedido a la vez, pero un pedido puede comprender varias columnas:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category, title":
PostSearch . new ( order : :category )
Se eligió este diseño restringido porque permite clasificaciones seleccionadas de varias columnas con interfaces de usuario de clasificación de una sola columna más simples y porque evita clasificaciones ad hoc de varias columnas que pueden no estar respaldadas por un índice de base de datos.
Una orden se puede aplicar en dirección ascendente o descendente. El método ModelSearch#toggle_order
aplicará un orden en dirección ascendente o cambiará la dirección de una orden aplicada de ascendente a descendente:
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 )
Tenga en cuenta que el método toggle_order
no modifica el objeto de búsqueda existente. En lugar de ello, crea un nuevo objeto de búsqueda con el nuevo orden y los valores de criterios del objeto de búsqueda anterior.
Cuando se aplica un orden de varias columnas en dirección descendente, todas las columnas se ven afectadas:
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 )
Para evitar este comportamiento y, en su lugar, fijar una columna en una dirección estática, agregue " ASC"
o " DESC"
al nombre de la columna:
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 )
Se puede aplicar una orden en dirección ascendente o descendente directamente, sin usar toggle_order
, agregando un sufijo apropiado:
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" )
Los sufijos predeterminados, como se ve en el ejemplo anterior, son ".asc"
y ".desc"
. Estos fueron elegidos por su simpatía hacia los I18n. Se pueden anular como parte de la definición del pedido:
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)" )
Se puede designar una orden como orden predeterminada, lo que hará que esa orden se aplique cuando no se especifique lo contrario:
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 )
Tenga en cuenta que la dirección del orden predeterminado puede ser ascendente o descendente especificando default: :asc
o default: :desc
, respectivamente. Además, así como solo se puede aplicar una orden a la vez, solo se puede designar una orden predeterminada.
Se puede definir un alcance de búsqueda predeterminado con ModelSearch::default_scope
:
class PostSearch < TalentScout :: ModelSearch
default_scope { where ( published : true ) }
end
El alcance predeterminado se aplicará independientemente de los criterios o valores de entrada del pedido.
Los controladores pueden usar el método auxiliar model_search
para construir un objeto de búsqueda con los parámetros de consulta de la solicitud actual:
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
En el ejemplo anterior, model_search
construye un objeto PostSearch
. La clase de búsqueda del modelo se deriva automáticamente del nombre de la clase del controlador. Para anular la clase de búsqueda de modelo, utilice ::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
En estos ejemplos, el objeto de búsqueda se almacena en @search
para su uso en la vista. Tenga en cuenta que @search.results
devuelve un ActiveRecord::Relation
, por lo que se puede aplicar cualquier alcance adicional, como la paginación.
Los formularios de búsqueda se pueden representar usando el generador de formularios de Rails y un objeto de búsqueda:
<%= 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 %>
Observe el method: :get
argumento para form_with
. Esto es necesario.
Los campos del formulario se completarán con los valores de entrada de criterios (o predeterminados) del mismo nombre de @search
. Se pueden utilizar campos de formulario apropiados para el tipo, por ejemplo, date_field
para el tipo :date
, check_box
para los tipos :boolean
y :void
, etc.
De forma predeterminada, el formulario se enviará a la acción de índice del controlador que corresponde a la model_class
del objeto de búsqueda. Por ejemplo, PostSearch.model_class
es Post
, por lo que un formulario con una instancia de PostSearch
se enviará a PostsController#index
. Para cambiar dónde se envía el formulario, use la opción :url
de form_with
.
Los enlaces de búsqueda se pueden representar utilizando el método auxiliar de vista link_to_search
:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
El enlace apuntará automáticamente al controlador actual y a la acción actual, con parámetros de consulta del objeto de búsqueda dado. Para vincular a un controlador o acción diferente, pase un Hash de opciones en lugar del objeto de búsqueda:
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
search: @search.toggle_order(:title, :asc) } %>
El asistente link_to_search
también acepta las mismas opciones HTML que el asistente link_to
de Rails:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
id: "title-sort-link", class: "sort-link" %>
...Además de un bloque de contenidos:
<%= link_to_search @search.toggle_order(:title, :asc) do %>
Sort by title <%= img_tag "sort_icon.png" %>
<% end %>
ModelSearch
La clase ModelSearch
proporciona varios métodos que son útiles al representar la vista.
Uno de esos métodos es ModelSearch#toggle_order
, que se mostró en ejemplos anteriores. Recuerde que toggle_order
es un método de estilo constructor que no modifica el objeto de búsqueda. En lugar de ello, duplica el objeto de búsqueda y establece el orden del nuevo objeto. Este comportamiento es adecuado para generar vínculos a múltiples variantes de una búsqueda, como ordenar vínculos en los encabezados de las columnas de una tabla.
ModelSearch#with
y ModelSearch#without
Dos métodos adicionales de estilo constructor son ModelSearch#with
y ModelSearch#without
. Al igual que toggle_order
, ambos métodos devuelven un nuevo objeto de búsqueda, dejando el objeto de búsqueda original sin modificar. El método with
acepta un hash de valores de entrada de criterios para fusionarlos sobre el conjunto original de valores de entrada de criterios:
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 )
El método without
acepta una lista de valores de entrada de criterios para excluir (los valores de criterios predeterminados aún se aplican):
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
Otro método útil es ModelSearch#each_choice
, que iterará sobre las opciones definidas para un criterio determinado. Esto se puede utilizar para generar enlaces a variantes de una búsqueda:
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 %>
Observe que si el bloque pasado a each_choice
acepta dos argumentos, el segundo argumento indicará si la opción está elegida actualmente.
Si no se pasa ningún bloque a each_choice
, devolverá un Enumerator
. Esto se puede utilizar para generar opciones para un cuadro de selección:
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :category, @search.each_choice(:category) %>
<%= form.submit %>
<% end %>
El método each_choice
también se puede invocar con :order
. Al hacerlo, se iterará sobre cada dirección de cada orden definido, lo que generará las etiquetas adecuadas, incluido el sufijo de dirección:
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 %>
El cuadro de selección en el formulario anterior enumerará cuatro opciones: "Título (AZ)", "Título (ZA)", "Hora (el más antiguo primero)", "Hora (el más nuevo primero)".
ModelSearch#order_directions
Finalmente, el método auxiliar ModelSearch#order_directions
devuelve un HashWithIndifferentAccess
que refleja la dirección aplicada actualmente de cada orden definida. Contiene una clave para cada orden definido y asocia cada clave con :asc
, :desc
o 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 >
Recuerde que solo se puede aplicar una orden a la vez, por lo que solo un valor en el Hash, como máximo, será distinto de nil
.
Agrega la gema a tu Gemfile:
$ bundle add talent_scout
Y ejecute el generador de instalación:
$ rails generate talent_scout:install
Ejecute bin/test
para ejecutar las pruebas.
Licencia MIT