Rails+Sphinx=? Часть I

    Поговорим о поиске в Ruby on Rails?

    Я решил разбить повествование на две части: в первой скучная настройка проекта и простой поиск по одному полю одной модели. Во второй подробнее остановимся на тонкостях и я постараюсь рассказать про все, что может плагин. Кстати в исходниках (ссылка в тексте) проект уже немного изменен для второй части, но проблем это не вызовет.

    Установка


    Устанавливаем Rails не ниже 2.0.2
    Скачиваем сфинкс 0.9.8: www.sphinxsearch.com/downloads.html и собираем самостоятельно, или используем порты/портажи/<вставить нужное>
    $ sudo port install sphinx

    Sphinx поддерживает две субд — MySQL и PostgreSQL, но достаточно легко можно добиться поддержки любой базы данных.
    Проверка после установки:
    Macintosh:sphinx-0.9.8 kronos$ searchd -h
    Sphinx 0.9.8-release (r1371)
    Copyright (c) 2001-2008, Andrew Aksyonoff
    ...

    Путь до searchd и indexer должен быть в переменной окружения path.

    Сфинкс состоит из нескольких утилит, некоторые из них:
    searchd — поисковый демон
    search — консольный аналог searchd для отладки/теста поиска.
    indexer — индексатор.
    Создаем проект:
    $ rails sphinxtest -d mysql
    $ cd sphinxtest/

    Незабудьте отредактировать config/database.yml

    Для удобства для работы со сфинксом будем использовать плагин.
    Я считаю, что адекватных плагина для Rails два — ultrasphinx и Thinking Sphinx (кстати пока писал статью вышел RailsCast про него). Так как последний из-за внутренних именований второй конфликтует с другим плагином «redhill on rails», то я использую первый. Но возможно второй лучше — выбирайте сами. :)
    Установка плагина:
    $ script/plugin install git://github.com/fauna/ultrasphinx.git


    Настройка


    $ mkdir config/ultrasphinx
    cp vendor/plugins/ultrasphinx/examples/default.base config/ultrasphinx/

    default.base — заготовка для конфигурационного файла sphinx-а. В первой части просто настроим пути до логов/пидов/индексов:
    # ...
    searchd
    {
    # ...
    log = /opt/local/var/db/sphinx/log/searchd.log
    query_log = /opt/local/var/db/sphinx/log/query.log
    pid_file = /opt/local/var/db/sphinx/log/searchd.pid
    # ...
    }
    # ...
    index
    {
    # путь где будут лежать индексы
    path = /opt/local/var/db/sphinx/
    # ...
    }
    # ...

    Пишем код


    Для простоты сделаем один контроллер с формой, которая с помощью ajax-а, будет искать скажем… Артистов по имени. Модель артиста будет состоять из одного поля — title:
    $ script/generate controller home index search
    $ script/generate model artist

    Код миграции, пусть у артиста будет только одно поле title(db/migrate/..._create_artists.rb):
    class CreateArtists < ActiveRecord::Migration
      def self.up
        create_table :artists do |t|
          t.string :title, :null => false
          t.timestamps
        end
      end
    
      def self.down
        drop_table :artists
      end
    end
    

    Теперь скажем сфинксу, что искать мы будем по одному полю (app/models/artist.rb):
    class Artist < ActiveRecord::Base
    is_indexed :fields => ['title']
    end

    Запись «is_indexed :fields => ['title']» означает, что индексирование будет происходить по одному полю.

    Ну и создаем базы и выполняем миграции:
    $ rake db:create
    $ rake db:migrate

    Так же стоит настроить роуты в файле config/routes.rb:
    map.root :controller => 'home'
    map.search 'search', :conditions => {:method => :get}, :controller => 'home', :action => 'search'


    Код контроллера(app/controllers/home_controller.rb):
    class HomeController < ApplicationController
      def index
      end
    
      def search
        query = params[:query].split(/'([^']+)'|"([^"]+)"|\s+|\+/).reject{|x| x.empty?}.map{|x| x.inspect }*' && '
        @artists = Ultrasphinx::Search.new(:query => query, 
                                          :sort_mode => 'relevance', 
                                          :class_names => ["Artist"])    
        @artists.run
        respond_to do |format|
          format.js #search.js.erb
        end
      end
    end
    

    Первым регулярным выражением мы разбираем поисковый запрос, разбивая слова по пробелам, игнорируем пустые слова( например ,,) и добавляем ко всем слова кавычки. Операция && означает лишь набор слов, ну например запросу
    «Bleed it out» => 'Bleed' && 'it' && 'out' будет соответствовать и запись «Sell it out» (два слова из трех совпали), т.е. && не диктует список обязательных слов, а лишь перечисляет их (если вам необходимо обязательное наличие всех слов, то нужно использовать AND, но об этом во второй части).
    Коротко пробежимся по параметрам:
    :query — поисковый запрос
    :sort_mode — тип сортировки результатов
    :class_names — массив имен классов моделей которые будут созданы в результате поиска. Sphinx внутри себя хранит каждый документ как набор полей и их значений. В Rails с таким представлением работать не удобно, а куда удобнее с готовым объектом модели. Ultrasphinx сам определит к какой модели относиться найденный документ и создаст его экземпляр, таким образом сам поиск ничем не отличается от Artist.find(...) или Artist.paginate (да, результаты поискового запроса совместимы с will_paginate-ом).
    Команда @artists.run выполняет запрос. Запросы выполняются очень быстро. На семимилионной базе — тысячные секунды.
    Представления(шаблоны) можно посмотреть в готовом проекте

    теперь можно что нибудь добавить в базу:
    $ script/console
    >> Artist.create(:title => 'Tiesto')
    >> Artist.create(:title => 'Armin')
    >> Artist.create(:title => 'ATB')
    >> exit

    Выполним необходимые приготавления для работы плагина (это нужно делать каждый раз когда вы что либо меняете в моделях в описании is_indexed):
    $ rake ultrasphinx:configure
    $ rake ultrasphinx:index
    $ rake ultrasphinx:daemon:start (либо restart если уже запущен)

    Запускаемся и тестируемся)
    $ mongrel_rails -p 3001 -d

    Одно маленькое но


    Индексы отдельно данные в базе отдельно. Когда мы удаляем/изменяем/добавляем в базу индексы не изменяются. Чтобы изменения базы отразились на индексах полную реиндексацию базы:
    $ rake ultrasphinx:index

    Конечно это не очень хорошо. Но могу заверить, что решение проблемы существует и называется delta-индексированием. Об этом в следующей части.

    Резюме


    Sphinx — очень крутая штука :). Open source, бесплатна, шустро ищет и индексирует. Must have!

    Аналоги


    Из аналогов могу отметить acts_as_ferret, для маленьких проектов он подойдет идеально (например мы использовали его на Хакфесте Рамблера), но для больших объемов данных он ведет себя мягко говоря неважно — очень долго индексирует.
    Для постгресткого tsearch2 есть вроде бы не плохой плагин: Acts as tsearch, в бою не применял, не знаю. Еще есть acts_as_solr
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 6

      0
      Форматирование конечно да :(. С этим можно что-нибудь сделать?
        0
        а где thinking sphinx? ;-)
          0
          Я ссылку на рельсокаст кинул врятли я лучше него смогу рассказать)
        0
        Интересно, почитаем-с.
          0
          Спасибо большое, очень интересно
            0
            С Rails 3 ничего не работает!

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое