Я считаю прожектор «сделанным» . У него нет производственных зависимостей, поэтому нет причин, по которым он не должен работать бесконечно. Я также перешел к другим вещам.
Если вы обнаружите ошибку, не стесняйтесь открыть проблему, чтобы другие могли ее найти и обсудить, но я вряд ли отвечу лично. Если Searchlight больше не соответствует вашим потребностям, раскошелитесь! :)
Searchlight — это простой способ создания поиска в базе данных с использованием ORM.
Searchlight может работать с любым ORM или объектом, который может создавать запросы с использованием связанных вызовов методов (например, ActiveRecord .where(...).where(...).limit(...)
или аналогичных цепочек с Sequel, Mongoid , и т. д).
Доступны демо-приложение и код этого приложения, которые помогут вам начать работу.
Основное назначение Searchlight — поддержка форм поиска в веб-приложениях.
Searchlight не пишет за вас запросы. Что он делает:
form_for
в Rails).WHERE first_name =
) Например, если у вас есть класс поиска Searchlight под названием YetiSearch
, и вы создаете его экземпляр следующим образом:
search = YetiSearch . new (
# or params[:yeti_search]
"active" => true , "name" => "Jimmy" , "location_in" => %w[ NY LA ]
)
... вызов search.results
создаст поиск путем вызова методов search_active
, search_name
и search_location_in
в вашем YetiSearch
, предполагая, что вы их определили. (Если вы сделаете это еще раз, но опустите "name"
, поиск search_name
не произойдет.)
Затем метод results
вернет возвращаемое значение последнего метода поиска. Если вы используете ActiveRecord, это будет ActiveRecord::Relation
, и затем вы можете вызвать each
для циклического перебора результатов, to_sql
для получения сгенерированного запроса и т. д.
Класс поиска состоит из двух основных частей: base_query
и некоторых методов search_
. Например:
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
Вызов PersonSearch.new("first_name" => "Gregor", "last_name" => "Mendel").results
приведет к запуску Person.all.where(first_name: "Gregor").where(last_name: "Mendel")
и возврату полученный ActiveRecord::Relation
. Если вы опустили параметр last_name
или указали "last_name" => ""
, второй where
не будет добавлен.
Вот более полный пример класса поиска. Обратите внимание: поскольку Searchlight не пишет за вас запросы, вы можете делать все, что поддерживает ваш ORM . (Чтобы узнать еще больше, см. spec/support/book_search.rb
.)
# 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
Вот несколько примеров поиска.
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
Для каждого определяемого вами метода поиска Searchlight определит соответствующий метод чтения опций. Например, если вы добавите def search_first_name
, ваш класс поиска получит метод .first_name
, который возвращает options["first_name"]
или, если этот ключ не существует, options[:first_name]
. Это полезно в основном при построении форм.
Поскольку ключи "first_name"
и :first_name
считаются взаимозаменяемыми, Searchlight выдаст ошибку, если вы укажете оба.
Searchlight предоставляет некоторые методы для изучения параметров, предоставляемых вашему поиску.
raw_options
содержит именно то, с помощью чего он был создан.options
содержит все непустые raw_options
empty?
. Например, если raw_options
имеет categories: nil, tags: ["a", ""]
, опциями будут tags: ["a"]
.empty?(value)
возвращает true для nil
, строк, содержащих только пробелы, или чего-либо еще, что возвращает true из value.empty?
(например, пустые массивы)checked?(value)
возвращает логическое значение, которое в основном работает как !!value
, но считает 0
, "0"
и "false"
false
Наконец, explain
расскажет вам, как Searchlight интерпретировал ваши варианты. Например, book_search.explain
может вывести:
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"]
Иногда полезно иметь параметры поиска по умолчанию — например, «заказы, которые не были выполнены» или «дома, выставленные в списке за последний месяц».
Это можно сделать, переопределив options
. Например:
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
Вы можете подклассифицировать существующий класс поиска и поддерживать все те же параметры с другим базовым запросом. Это может быть полезно, например, для наследования одной таблицы.
class VillageSearch < CitySearch
def base_query
Village . all
end
end
Или вы можете использовать super
, чтобы получить значение base_query
суперкласса и изменить его:
class SmallTownSearch < CitySearch
def base_query
super . where ( "`cities`.`population` < ?" , 1_000 )
end
end
Вы можете предоставить поиску Searchlight любые параметры, которые вам нравятся; только те, у кого есть соответствующий метод search_
будут определять, какие методы будут запущены. Например, если вы хотите использовать AccountSearch.new("super_user" => true)
для поиска ограниченных результатов, просто убедитесь, что вы отметили options["super_user"]
при построении запроса.
Searchlight прекрасно работает с формами Rails — просто подключите адаптер ActionView
следующим образом:
require "searchlight/adapters/action_view"
class MySearch < Searchlight :: Search
include Searchlight :: Adapters :: ActionView
# ...etc
end
Это позволит использовать Searchlight::Search
с 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 }
Пока ваша форма отправляет параметры, которые понимает ваш поиск, вы можете легко подключить их к своему контроллеру:
# 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
Для любой конкретной версии проверьте .travis.yml
, чтобы узнать, какие версии Ruby мы тестируем на совместимость.
Добавьте эту строку в Gemfile вашего приложения:
gem 'searchlight'
И затем выполните:
$ bundle
Или установите его самостоятельно как:
$ gem install searchlight
rake
запускает тесты; rake mutant
запускает мутационные тесты с использованием Mutant.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)