Considero o holofote "pronto" . Ele não tem dependências de produção, portanto não há razão para não funcionar indefinidamente. Também mudei para outras coisas.
Se você encontrar um bug, sinta-se à vontade para abrir um problema para que outras pessoas possam encontrá-lo e discuti-lo, mas é improvável que eu responda pessoalmente. Se o Searchlight não atender mais às suas necessidades, desembolse! :)
Searchlight é uma maneira pouco mágica de construir pesquisas de banco de dados usando um ORM.
O Searchlight pode funcionar com qualquer ORM ou objeto que possa construir uma consulta usando chamadas de método encadeadas (por exemplo, .where(...).where(...).limit(...)
ActiveRecord ou cadeias semelhantes com Sequel, Mongoid , etc).
Um aplicativo de demonstração e o código desse aplicativo estão disponíveis para ajudá-lo a começar.
O principal uso do Searchlight é oferecer suporte a formulários de pesquisa em aplicativos da web.
O Searchlight não escreve consultas para você. O que ele faz é:
form_for
no Rails)WHERE first_name =
) Por exemplo, se você tiver uma classe de pesquisa Searchlight chamada YetiSearch
e instanciá-la assim:
search = YetiSearch . new (
# or params[:yeti_search]
"active" => true , "name" => "Jimmy" , "location_in" => %w[ NY LA ]
)
... chamar search.results
criará uma pesquisa chamando os métodos search_active
, search_name
e search_location_in
em seu YetiSearch
, assumindo que você os definiu. (Se você fizer isso novamente, mas omitir "name"
, ele não chamará search_name
.)
O método results
retornará então o valor de retorno do último método de pesquisa. Se você estiver usando ActiveRecord, isso seria um ActiveRecord::Relation
, e você poderá chamar each
para percorrer os resultados, to_sql
para obter a consulta gerada, etc.
Uma classe de pesquisa tem duas partes principais: uma base_query
e alguns métodos search_
. Por exemplo:
class PersonSearch < Searchlight :: Search
# This is the starting point for any chaining we do, and it's what
# will be returned if no search options are passed.
# In this case, it's an ActiveRecord model.
def base_query
Person . all # or `.scoped` for ActiveRecord 3
end
# A search method.
def search_first_name
# If `"first_name"` was the first key in the options_hash,
# `query` here will be the base query, namely, `Person.all`.
query . where ( first_name : options [ :first_name ] )
end
# Another search method.
def search_last_name
# If `"last_name"` was the second key in the options_hash,
# `query` here will be whatever `search_first_name` returned.
query . where ( last_name : last_name )
end
end
Chamar PersonSearch.new("first_name" => "Gregor", "last_name" => "Mendel").results
seria executado Person.all.where(first_name: "Gregor").where(last_name: "Mendel")
e retornaria o ActiveRecord::Relation
resultante. Se você omitiu a opção last_name
ou forneceu "last_name" => ""
, o segundo where
não seria adicionado.
Aqui está um exemplo de classe de pesquisa mais completo. Observe que , como o Searchlight não escreve consultas para você, você está livre para fazer qualquer coisa que seu ORM suporte . (Veja spec/support/book_search.rb
para obter ainda mais detalhes.)
# app/searches/city_search.rb
class CitySearch < Searchlight :: Search
# `City` here is an ActiveRecord model
def base_query
City . includes ( :country )
end
# Reach into other tables
def search_continent
query . where ( '`countries`.`continent` = ?' , continent )
end
# Other kinds of queries
def search_country_name_like
query . where ( "`countries`.`name` LIKE ?" , "% #{ country_name_like } %" )
end
# .checked? considers "false", 0 and "0" to be false
def search_is_megacity
query . where ( "`cities`.`population` #{ checked? ( is_megacity ) ? '>=' : '<' } ?" , 10_000_000 )
end
end
Aqui estão alguns exemplos de pesquisas.
CitySearch . new . results . to_sql
# => "SELECT `cities`.* FROM `cities` "
CitySearch . new ( "name" => "Nairobi" ) . results . to_sql
# => "SELECT `cities`.* FROM `cities` WHERE `cities`.`name` = 'Nairobi'"
CitySearch . new ( "country_name_like" => "aust" , "continent" => "Europe" ) . results . count # => 6
non_megas = CitySearch . new ( "is_megacity" => "false" )
non_megas . results . to_sql
# => "SELECT `cities`.* FROM `cities` WHERE (`cities`.`population` < 10000000"
non_megas . results . each do | city |
# ...
end
Para cada método de pesquisa definido, o Searchlight definirá um método de leitor de opção correspondente. Por exemplo, se você adicionar def search_first_name
, sua classe de pesquisa obterá um método .first_name
que retorna options["first_name"]
ou, se essa chave não existir, options[:first_name]
. Isto é útil principalmente ao construir formulários.
Como considera as chaves "first_name"
e :first_name
intercambiáveis, o Searchlight gerará um erro se você fornecer ambas.
O Searchlight fornece alguns métodos para examinar as opções fornecidas para sua pesquisa.
raw_options
contém exatamente o que foi instanciadooptions
contém todos raw_options
que não estavam empty?
. Por exemplo, se raw_options
for categories: nil, tags: ["a", ""]
, as opções serão tags: ["a"]
.empty?(value)
retorna verdadeiro para nil
, strings somente com espaços em branco ou qualquer outra coisa que retorne verdadeiro de value.empty?
(por exemplo, matrizes vazias)checked?(value)
retorna um booleano, que funciona principalmente como !!value
mas considera 0
, "0"
e "false"
como false
Por fim, explain
explicará como o Searchlight interpretou suas opções. Por exemplo, book_search.explain
pode gerar:
Initialized with `raw_options`: ["title_like", "author_name_like", "category_in",
"tags", "book_thickness", "parts_about_lolcats"]
Of those, the non-blank ones are available as `options`: ["title_like",
"author_name_like", "tags", "book_thickness", "in_print"]
Of those, the following have corresponding `search_` methods: ["title_like",
"author_name_like", "in_print"]. These would be used to build the query.
Blank options are: ["category_in", "parts_about_lolcats"]
Non-blank options with no corresponding `search_` method are: ["tags",
"book_thickness"]
Às vezes é útil ter opções de pesquisa padrão - por exemplo, “pedidos que não foram atendidos” ou “casas listadas no último mês”.
Isso pode ser feito substituindo options
. Por exemplo:
class BookSearch < SearchlightSearch
# def base_query...
def options
super . tap { | opts |
opts [ "in_print" ] ||= "either"
}
end
def search_in_print
return query if options [ "in_print" ] . to_s == "either"
query . where ( in_print : checked? ( options [ "in_print" ] ) )
end
end
Você pode subclassificar uma classe de pesquisa existente e oferecer suporte a todas as mesmas opções com uma consulta base diferente. Isto pode ser útil para herança de tabela única, por exemplo.
class VillageSearch < CitySearch
def base_query
Village . all
end
end
Ou você pode usar super
para obter o valor base_query
da superclasse e modificá-lo:
class SmallTownSearch < CitySearch
def base_query
super . where ( "`cities`.`population` < ?" , 1_000 )
end
end
Você pode fornecer uma pesquisa no Searchlight com as opções que desejar; somente aqueles com um método search_
correspondente determinarão quais métodos serão executados. Por exemplo, se você quiser fazer AccountSearch.new("super_user" => true)
para encontrar resultados restritos, apenas certifique-se de marcar options["super_user"]
ao criar sua consulta.
O Searchlight funciona bem com formulários Rails - basta incluir o adaptador ActionView
da seguinte maneira:
require "searchlight/adapters/action_view"
class MySearch < Searchlight :: Search
include Searchlight :: Adapters :: ActionView
# ...etc
end
Isso permitirá o uso de Searchlight::Search
com form_for
:
# app/views/cities/index.html.haml
...
= form_for ( @search , url : search_cities_path ) do | f |
% fieldset
= f . label :name , "Name"
= f . text_field :name
% fieldset
= f . label :country_name_like , "Country Name Like"
= f . text_field :country_name_like
% fieldset
= f . label :is_megacity , "Megacity?"
= f . select :is_megacity , [ [ 'Yes' , true ] , [ 'No' , false ] , [ 'Either' , '' ] ]
% fieldset
= f . label :continent , "Continent"
= f . select :continent , [ 'Africa' , 'Asia' , 'Europe' ] , include_blank : true
= f . submit "Search"
- @results . each do | city |
= render partial : 'city' , locals : { city : city }
Contanto que seu formulário envie opções que sua pesquisa entenda, você pode conectá-lo facilmente ao seu controlador:
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
@search = OrderSearch . new ( search_params ) # For use in a form
@results = @search . results # For display along with form
end
protected
def search_params
# Ensure the user can only browse or search their own orders
( params [ :order_search ] || { } ) . merge ( user_id : current_user . id )
end
end
Para qualquer versão, verifique .travis.yml
para ver quais versões do Ruby estamos testando quanto à compatibilidade.
Adicione esta linha ao Gemfile da sua aplicação:
gem 'searchlight'
E então execute:
$ bundle
Ou instale você mesmo como:
$ gem install searchlight
rake
executa os testes; rake mutant
executa testes de mutação usando mutant.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)