Поиск на основе модели в Rails. Яркий пример:
## 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 >
В приведенном выше примере:
PostSearch
берет на себя ответственность за поиск моделей Post
. Он может применять любую комбинацию определенных критериев, автоматически игнорируя отсутствующие, пустые или недопустимые входные значения. Он также может упорядочить результаты по одному из определенных порядков: по возрастанию или по убыванию.PostsController#index
использует помощник model_search
для создания экземпляра PostSearch
и присваивает его переменной @search
для последующего использования в представлении. Результаты поиска также присваиваются переменной для использования в представлении.@search
. Помощник link_to_search
используется для создания ссылок в заголовке таблицы, которые сортируют результаты. Обратите внимание, что используемый здесь метод toggle_order
возвращает новый объект поиска, оставляя @search
неизмененным.Подробное объяснение методов, используемых в этом примере, см. в документации API.
Вы можете использовать генератор talent_scout:search
для создания определения класса поиска модели. Например,
$ rails generate talent_scout:search post
Создаст файл «app/searches/post_search.rb», содержащий:
class PostSearch < TalentScout :: ModelSearch
end
Классы поиска наследуются от TalentScout::ModelSearch
. Их целевой класс модели выводится из имени класса поиска. Например, PostSearch
по умолчанию будет искать модели Post
. Чтобы переопределить этот вывод, используйте ModelSearch::model_class=
:
class EmployeeSearch < TalentScout :: ModelSearch
self . model_class = Person # search for Person models instead of `Employee`
end
Критерии поиска определяются с помощью метода ModelSearch::criteria
. Определения критериев можно указать одним из трех способов: с помощью неявного предложенияwhere, с помощью явного блока запроса или с помощью ссылки на область модели. Для иллюстрации: следующие три критерия :title
эквивалентны:
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
Обратите внимание, что блоки явных запросов оцениваются в контексте ActiveRecord::Relation
модели, так же, как и блоки scope
Active Record.
Определение критерия может указывать тип данных, что приводит к приведению его входного значения к типу перед передачей в блок или область запроса. В качестве примера:
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" )
Здесь строка "Dec 31, 1999"
, переданная конструктору PostSearch
приводится к типу Date
перед передачей в блок запроса.
Тип критерия по умолчанию — :string
, что означает, что по умолчанию все входные значения будут преобразованы в строки. Это значение по умолчанию (в отличие от значения по умолчанию, при котором отсутствует приведение типов) обеспечивает согласованное поведение независимо от того, как создан объект поиска, будь то на основе строго типизированных значений или на основе параметров запроса формы поиска.
Доступные типы критериев такие же, как и для атрибутов активной модели: :big_integer
, :boolean
, :date
, :datetime
, :decimal
, :float
, :integer
, :string
, :time
, а также любые пользовательские типы, которые вы определяете.
Также доступен дополнительный удобный тип: :void
. Тип :void
приводит к приведению типов, как :boolean
, но предотвращает применение критериев, когда приведенное к типу значение имеет значение false. Например:
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 : "" )
Вместо указания типа определение критерия может указывать варианты выбора. Варианты выбора определяют набор значений, которые могут быть переданы в блок запроса.
Варианты выбора могут быть указаны как массив понятных человеку значений:
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ] do | name |
where ( category : name . downcase )
end
end
... Или как хэш с удобными для человека ключами:
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
Значение, передаваемое в блок запроса, будет одним из значений массива или одним из значений хеша. Объект поиска может быть создан с использованием любого из значений массива, ключей хеша или значений хеша:
PostSearch . new ( category : "Math" )
PostSearch . new ( within : "Last 24 hours" )
PostSearch . new ( within : 24 . hours )
Но если указан неверный выбор, соответствующие критерии не будут применены:
# The following will skip the criteria, but will not raise an error:
PostSearch . new ( category : "Marketing" )
PostSearch . new ( within : 12 . hours )
В определении критерия можно указать значение по умолчанию, которое будет передано в блок запроса, если входное значение отсутствует. Значения по умолчанию также будут отображаться в формах поиска.
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 )
Критерий не будет применяться, если выполняется любое из следующих условий:
Входное значение критерия отсутствует, и значение по умолчанию не указано.
Объект поиска был создан с помощью ActionController::Parameters
(вместо хеша), а входное значение критерия blank?
, и значение по умолчанию не указано. (Такое поведение предотвращает влияние пустых полей формы поиска на результаты поиска.)
Приведение типа входного значения критерия завершается неудачно. Например:
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" )
В определении критерия указан тип :void
, а приведенное к типу входное значение — falsey.
Определение критерия указывает варианты выбора, а входное значение не является допустимым выбором.
Блок запроса критериев возвращает nil
. Например:
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 )
Порядок результатов поиска определяется с помощью метода 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 )
Одновременно можно применить только один заказ, но заказ может состоять из нескольких столбцов:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category, title":
PostSearch . new ( order : :category )
Этот ограниченный дизайн был выбран потому, что он позволяет выполнять курируемую сортировку по нескольким столбцам с помощью более простых пользовательских интерфейсов сортировки по одному столбцу, а также потому, что он предотвращает специальные сортировки по нескольким столбцам, которые могут не поддерживаться индексом базы данных.
Ордер может быть применен в восходящем или нисходящем направлении. Метод ModelSearch#toggle_order
применит порядок в восходящем направлении или изменит направление примененного ордера с восходящего на нисходящее:
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 )
Обратите внимание, что метод toggle_order
не изменяет существующий объект поиска. Вместо этого он создает новый объект поиска с новым порядком и значениями критериев предыдущего объекта поиска.
Когда порядок нескольких столбцов применяется в нисходящем направлении, это затрагивает все столбцы:
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 )
Чтобы обойти это поведение и вместо этого зафиксировать столбец в статическом направлении, добавьте к имени столбца " ASC"
или " DESC"
:
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 )
Порядок можно применить в восходящем или нисходящем направлении напрямую, без использования toggle_order
, добавив соответствующий суффикс:
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" )
Суффиксы по умолчанию, как показано в приведенном выше примере, — ".asc"
и ".desc"
. Они были выбраны из-за их дружелюбия к I18n. Их можно переопределить как часть определения порядка:
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)" )
Ордер можно обозначить как порядок по умолчанию, что приведет к его применению, если не указано иное:
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 )
Обратите внимание, что направление порядка по умолчанию может быть как по возрастанию, так и по убыванию, если указать default: :asc
или default: :desc
соответственно. Кроме того, так же, как одновременно может быть применен только один ордер, только один ордер может быть назначен по умолчанию.
Область поиска по умолчанию можно определить с помощью ModelSearch::default_scope
:
class PostSearch < TalentScout :: ModelSearch
default_scope { where ( published : true ) }
end
Область по умолчанию будет применяться независимо от критериев или входных значений заказа.
Контроллеры могут использовать вспомогательный метод model_search
для создания объекта поиска с параметрами запроса текущего запроса:
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
В приведенном выше примере model_search
создает объект PostSearch
. Класс поиска модели автоматически создается на основе имени класса контроллера. Чтобы переопределить класс поиска модели, используйте ::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
В этих примерах объект поиска сохраняется в @search
для использования в представлении. Обратите внимание, что @search.results
возвращает ActiveRecord::Relation
, поэтому можно применить любую дополнительную область видимости, например разбиение на страницы.
Формы поиска можно визуализировать с помощью конструктора форм Rails и объекта поиска:
<%= 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 %>
Обратите внимание на method: :get
аргумент form_with
. Это необходимо.
Поля формы будут заполнены входными значениями критериев (или значениями по умолчанию) с тем же именем из @search
. Можно использовать поля формы, соответствующие типу, например, date_field
для типа :date
, check_box
для типов :boolean
и :void
и т. д.
По умолчанию форма будет отправлена на действие index контроллера, соответствующее model_class
объекта поиска. Например, PostSearch.model_class
— это Post
, поэтому форма с экземпляром PostSearch
будет отправлена в PostsController#index
. Чтобы изменить место отправки формы, используйте опцию :url
в form_with
.
Поисковые ссылки можно визуализировать с помощью вспомогательного метода представления link_to_search
:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
Ссылка будет автоматически указывать на текущий контроллер и текущее действие с параметрами запроса из данного объекта поиска. Чтобы создать ссылку на другой контроллер или действие, передайте хэш параметров вместо объекта поиска:
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
search: @search.toggle_order(:title, :asc) } %>
Помощник link_to_search
также принимает те же параметры HTML, что и помощник link_to
в Rails:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
id: "title-sort-link", class: "sort-link" %>
...А также блок контента:
<%= link_to_search @search.toggle_order(:title, :asc) do %>
Sort by title <%= img_tag "sort_icon.png" %>
<% end %>
ModelSearch
Класс ModelSearch
предоставляет несколько методов, которые полезны при отрисовке представления.
Одним из таких методов является ModelSearch#toggle_order
, который был показан в предыдущих примерах. Помните, что toggle_order
— это метод в стиле компоновщика, который не изменяет объект поиска. Вместо этого он дублирует объект поиска и устанавливает порядок нового объекта. Такое поведение подходит для создания ссылок на несколько вариантов поиска, например ссылок сортировки в заголовках столбцов таблицы.
ModelSearch#with
и ModelSearch#without
Два дополнительных метода в стиле компоновщика — это ModelSearch#with
и ModelSearch#without
. Как и toggle_order
, оба этих метода возвращают новый объект поиска, оставляя исходный объект поиска неизмененным. Метод with
принимает хеш входных значений критериев для объединения поверх исходного набора входных значений критериев:
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 )
Метод without
принимает список входных значений критериев, которые необходимо исключить (значения критериев по умолчанию все еще применяются):
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
Еще один полезный метод — ModelSearch#each_choice
, который будет перебирать определенные варианты выбора для заданных критериев. Это можно использовать для создания ссылок на варианты поиска:
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 %>
Обратите внимание: если блок, передаваемый each_choice
принимает два аргумента, второй аргумент будет указывать, выбран ли этот вариант в данный момент.
Если в each_choice
не передан ни один блок, он вернет Enumerator
. Это можно использовать для создания опций для поля выбора:
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :category, @search.each_choice(:category) %>
<%= form.submit %>
<% end %>
each_choice
также можно вызвать с помощью :order
. Это приведет к повторению каждого направления каждого определенного порядка, давая соответствующие метки, включая суффикс направления:
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 %>
В поле выбора в приведенной выше форме будут перечислены четыре параметра: «Название (AZ)», «Название (ZA)», «Время (сначала самые старые)», «Время (сначала самые новые)».
ModelSearch#order_directions
Наконец, вспомогательный метод ModelSearch#order_directions
возвращает HashWithIndifferentAccess
, отражающий текущее примененное направление каждого определенного заказа. Он содержит ключ для каждого определенного порядка и связывает каждый ключ с :asc
, :desc
или 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 >
Помните, что одновременно может быть применен только один заказ, поэтому не более одного значения в хеше будет отличным от nil
.
Добавьте драгоценный камень в свой Gemfile:
$ bundle add talent_scout
И запускаем генератор установки:
$ rails generate talent_scout:install
Запустите bin/test
чтобы запустить тесты.
Лицензия MIT