Статьи

Расширенный поиск с Ransack

Поиск плоский значок, кнопка Рождества

В этом уроке мы рассмотрим, как добавить сложные функции поиска в ваше приложение Rails. Эта задача будет облегчена с помощью драгоценного камня Ransack Ruby. Ransack предоставляет отличных помощников и строителей для расширенного поиска ваших моделей. Он имеет некоторые действительно мощные функции, доступные из коробки без написания большого количества кода, такие как сортировка или условный поиск.

Мы будем использовать то же приложение, что и в серии Braintree . Пожалуйста, обратитесь к этому посту, чтобы настроить и оформить основное приложение. Вот форк репозитория, который мы будем использовать для нашего приложения Ransack — MovieStore .

Цель

Вот список функций, которые мы собираемся реализовать:

  • Поиск фильмов по названию
  • Поиск фильмов по ценовым диапазонам
  • Сортировать по названию, цене, году выпуска, по возрастанию или по убыванию
  • Поиск фильмов по любому из его (модельных) атрибутов с такими параметрами, как равно, больше, меньше, и т. Д.
  • Сортировать по любому атрибуту модели

Вы можете просмотреть рабочую демонстрацию этого приложения, развернутого на Heroku.

Исследование Ransack предоставлено демо

ActiveRecordHackery имеет собственное демонстрационное приложение, использующее гем Ransack здесь . Он обеспечивает два режима поиска: базовый и расширенный. В базовом режиме вы можете осуществлять поиск только по имени, фамилии, электронной почте или названию поста. В расширенном режиме у вас есть много вариантов для настройки поиска. Испытайте их, чтобы найти силу, которую Ransack предоставляет в сложном поиске.

инструменты

  • Ruby (2.1.0)
  • Rails (4.1.1)
  • Foundation 5 — интерфейсный CSS-фреймворк
  • Рансак — Рубиновый камень
  • Devise — аутентификация пользователя

Шаг 1: Добавление рансака

Первый шаг — добавить драгоценный камень Ransack в ваш Gemfile:

 gem 'ransack'

Обязательно запустите bundle install

Шаг 2: Добавить поисковый объект

В наиболее распространенном случае метод search

 @search = Movie.search(params[:q])

Результат может быть сохранен в переменной экземпляра @movies

 @movies = @search.result

Все действие индекса выглядит так:

 def index
  @search = Movie.search(params[:q])
  @movies = @search.result
end

Шаг 3: Добавить форму поиска

Давайте создадим форму для обработки поиска. Для формы просто нужно текстовое поле (для названия фильма) и кнопка отправки. Форму можно оставить прямо под заголовком сайта.

Добавьте выражение yield Содержание для этого yield

Откройте файл layouts / application.html.erb и добавьте:

 <%= yield(:search) %>

в разделе заголовка. Затем на странице указателя добавьте простую форму поиска следующим образом:

 <% content_for :search do %>
  <div class="large-8 small-9 columns">
  <%= search_form_for @search do |f| %>
    <%= f.text_field :title_cont, class: "radius-left expand", placeholder: "Movie title" %>
  </div>
    <div class="large-4 small-3 columns">
      <%= f.submit "Search", class: "radius-right button" %>
    </div>
  <% end %>
<% end %>

Как видите, мы использовали вспомогательный метод search_form_forsearch Он содержит text_field:title_contсодержащий некоторое значение. Продолжение здесь называется предикатом, и именно так Ransack определяет, какой информации сопоставить.

В качестве простого рефакторинга я переместил этот контент в отдельный фрагмент ( _title_search_box.html.erb ), чтобы его можно было повторно использовать. Добавьте строку на страницу индекса, чтобы отобразить этот фрагмент:

 <%= render "title_search_box" %>

Примечание: у Ransack также есть помощник для меток, но он нам не нужен в нашей форме.

((screenshot))

Шаг 4 — 1: Добавить диапазон цен

На предыдущем шаге мы создали простой поисковый функционал. Давайте попробуем что-то более продвинутое, например, поиск диапазонов цен на фильмы, указав минимальное и максимальное значение.

Вот чего мы хотим достичь:

((screenshot-2))

((screenshot-3))

Мы начнем со стилизации окна поиска, заимствуя некоторые классы CSS из приложения MovieStore. Классы form-container и glassy-bg (находятся в layout.css.scss и helpers.scss соответственно) добавляют в окно поиска стеклянный фон. Я создал новый div со column Коробка изначально скрыта.

 <div class="column">
  <div class="advanced-search hide form-container glassy-bg columns">
    <a class="close-advanced-search fi-x"></a>
  </div>
</div>

Добавьте кнопку, которая при нажатии открывает окно расширенного поиска следующим образом:

 <div class="column">
  <h5>
    <a class="show-advanced-search">Advanced Search <span class="fi-plus"/></a>
  </h5>
</div>

Теперь, в movies.js.coffee , добавьте событие click

 $ ->
  $('.show-advanced-search').click ->
    $('.advanced-search').show() // which removes the hide class
    $(this).hide()

Также обработать событие click

 $('.close-advanced-search').click ->
    $('.advanced-search').hide()
    $('.show-advanced-search').show()

Шаг 4 — 2: Добавление диапазона цен

Время добавить диапазон цен в форму поиска. Мы будем использовать тот же метод search_form_for@search

В приведенном ниже коде символы :price_gteqprice_lteqцену, которая больше или равна, и цену, меньшую или равную соответственно. Посмотреть список доступных предикатов здесь

 <h4>Advanced Search</h4>
<%= search_form_for @search do |f| %>
  <div class="large-5 small-4 columns">
    <%= f.label :price_gteq, class: "movie-label" %>
    <%= f.text_field :price_gteq, class: "radius", placeholder: "Minumum Price" %>
  </div>
  <h6 class="large-2 small-4 columns center"><span>And</span></h6>
  <div class="large-5 small-4 columns">
    <%= f.label :price_lteq, class: "movie-label" %>
    <%= f.text_field :price_lteq, class: "radius", placeholder: "Maximum Price" %>
  </div>
  <div class="column">
    <%= f.submit "Search", class: " radius button" %>
  </div>
<% end %>

((screenshot-4))

Шаг 5: Добавить сортировку

Ransack имеет конструктор форм, который предоставляет ссылки для сортировки столбцов в порядке возрастания или убывания. Этот строитель является sort_link Его очень просто использовать, просто передайте объект поиска и имя столбца (атрибут), который вы хотите отсортировать, и заполнитель для него.

Я переопределил три метки Foundation в файле foundation_and_overrides.scss .

 <h4 class="column">Featured Movies</h4>
<div class="row right padm">
  <div class="column">
    <div class="filter-label red">
      <%= sort_link @search, :title, "Title" %>
    </div>
    <div class="filter-label dark-golden-rod">
      <%= sort_link @search, :price, "Price" %>
    </div>
    <div class="filter-label dark-slate-gray">
      <%= sort_link @search, :release_year, "Release Year" %>
    </div>
  </div>
</div>

((screenshot-5))

Шаг 6: Добавить условный поиск

На этом шаге давайте рассмотрим, как добавить условные поля поиска. Сделайте блок выбора со всеми атрибутами фильма, а другой — со всеми предикатами. Окончательным вводом будет текстовое поле для значения, которое будет использоваться при поиске. Ransack имеет несколько конструкторов форм для этого сценария, как показано в приведенном ниже коде.

Ради интереса я добавил некоторые функции сортировки, но в форме выбора. Один раскрывающийся список предназначен для атрибутов модели, а другой — для порядка (по возрастанию или по убыванию):

 <h4>Advanced Search</h4>
<%= search_form_for @search do |f| %>
  <%= f.condition_fields do |c| %>
    <div class="large-4 small-4 columns">
      <%= c.attribute_fields do |a| %>
        <%= a.attribute_select nil, class: "radius" %>
      <% end %>
    </div>
    <div class="large-4 small-4 columns">
      <%= c.predicate_select Hash.new, class: "radius" %>
    </div>
    <div class="large-4 small-4 columns">
      <%= c.value_fields do |v| %>
        <%= v.text_field :value, class: "radius" %>
      <% end %>
    </div>
  <% end %>
  <h5>Sort</h5>
  <div class="column">
  <%= f.sort_fields do |s| %>
    <%= s.sort_select Hash.new, class: "large-5 small-4 columns mrs radius" %>
  <% end %>
  </div>
  <%= f.submit "Search", class: "radius button" %>
<% end %>

((screenshot-6))

Шаг 7: Рефакторинг поиска

Хорошей идеей будет переместить наш код в часть с именем condition_fields :

_condition_fields.html.erb

 <div class="field">
  <div class="large-4 small-4 columns">
    <%= f.attribute_fields do |a| %>
      <%= a.attribute_select nil, class: "radius" %>
    <% end %>
  </div>
  <div class="large-4 small-4 columns">
    <%= f.predicate_select Hash.new, class: "radius" %>
  </div>
  <div class="large-4 small-4 columns">
    <%= f.value_fields do |v| %>
      <%= v.text_field :value, class: "radius" %>
    <% end %>
    <%= link_to "remove", "#", class: "remove_fields" %>
  </div>
</div>

Шаг 8: Ссылка для добавления критериев

Давайте добавим ссылку в поле расширенного поиска, чтобы добавить условия. В index.html.erb добавьте ссылку для добавления критериев, например:

 <%= f.condition_fields do |c| %>
  <%= render "condition_fields", f: c %>
<% end %>
<p><%= link_to_add_fields "Add Conditions", f, :condition %></p>

Сначала создайте вспомогательный метод link_to_add_fields

 def link_to_add_fields(name, f, type)
  new_object = f.object.send "build_#{type}"
  id = "new_#{type}"
  fields = f.send("#{type}_fields", new_object, child_index: id) do |builder|
    render(type.to_s + "_fields", f: builder)
  end
  link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end

В movies.js.coffee обработайте событие clickadd_fields

 $('form').on 'click', '.add_fields', (event) ->
  time = new Date().getTime()
  regexp = new RegExp($(this).data('id'), 'g')
  $(this).before($(this).data('fields').replace(regexp, time))
  event.preventDefault()

Шаг 9: Ссылка для удаления критериев

Целесообразно также добавить ссылку для удаления условий. Добавьте его в частичку condition_fields , сопровождая каждое условие:

 <div class="large-4 small-4 columns">
  <%= f.value_fields do |v| %>
    <%= v.text_field :value, class: "radius" %>
  <% end %>
  <%= link_to "", "#", class: "remove_fields fi-x" %>
</div>

Вернитесь в movies.js.coffee , необходимый обработчик события click

 $('form').on 'click', '.remove_fields', (event) ->
  $(this).closest('.field').remove()
  event.preventDefault()

Шаг 10: отправить запрос

Если мы добавим слишком много условий, число параметров может достичь предела, разрешенного запросами GET. Этого можно избежать, используя вместо этого POST.

В файле config / rout.rb добавьте:

 resources :movies, only: [:show, :index] do
  match :search, to: 'movies#index', via: :post, on: :collection
end

Затем в index.html.erb измените форму поиска, чтобы использовать новый путь поиска, например:

 <%= search_form_for @search, url: search_movies_path, method: :post do |f| %>

Резюме

В этом руководстве мы использовали то же самое приложение онлайн-магазина фильмов, которое использовалось в разделе « Создание онлайн-магазина с помощью Rails » для добавления сложных функций поиска. Благодаря использованию драгоценного камня Ransack приложение для просмотра фильмов теперь имеет приятный интерфейс для простого и сложного поиска.