[WB//Rails4]

Wyszukiwanie pełnotekstowe z PostgreSQL

Instalacja MySQl, PostgreSQL... w Fedorze 15

… czyli do full text search użyjemy gemów:

Dopisujemy gem pg oraz powyżej wspomniane gemy do pliku Gemfile:

Gemfile
gem 'pg'
gem 'texticle', '~> 2.0', require: 'texticle/rails'
gem 'pg_search'

i instalujemy je:

bundle install

Musimy też zmienić konfigurację bazy w pliku database.yml:

development:
  adapter: postgresql
  encoding: unicode
  database: fortune_responders_development
  pool: 5
  username: wbzyl
  password:

test:
  adapter: postgresql
  encoding: unicode
  database: fortune_responders_test
  pool: 5
  username: wbzyl
  password:

Pozostało utworzyć bazy i wykonać jeszcze raz migracje:

rake db:create:all
rake db:migrate

Full text search with PostgreSQL

Zaczynamy od wyszukiwania za pomocą operatora LIKE (lub iLIKE):

app/models/fortune.rb
def self.text_search(query)
  if query.present?
    # SQLite i PostgreSQL
    where('quotation like :q or source like :q', q: "%#{query}%")
    # tylko PostgreSQL; i – ignore case
    # where("quotation ilike :q or source ilike :q", q: "%#{query}%")
  else
    scoped
  end
end

Możemy też użyć operatora @@ (tylko PostgreSQL):

app/models/fortune.rb
def self.text_search(query)
  if query.present?
    where("quotation @@ :q or source @@ :q", q: query)
  else
    scoped
  end
end

Zapytanie z operatorrem @@ wyszukuje wszystkie rekordy zawierające wszystkie wpisane słowa, na przykład:

late bird

PostgreSQL & Polish

Zaawansowane wyszukiwanie

Zaczynamy od wpisania i wykonania kilku przykładów na konsoli DB:

select 'ala has a cat' @@ 'cats';
select to_tsvector('ala has a cat') @@ plainto_tsquery('cats');
-- stemming
select to_tsvector('english', 'ala has a cat') @@ plainto_tsquery('english', 'cats');
-- without stemming
select to_tsvector('simple', 'ala has a cat') @@ plainto_tsquery('simple', 'cats');
-- one word
select to_tsvector('simple', 'ala has a cat') @@ to_tsquery('simple', 'cat');
-- and
select to_tsvector('simple', 'ala has a cat') @@ to_tsquery('simple', 'cat & dog');
-- or
select to_tsvector('simple', 'ala has a cat') @@ to_tsquery('simple', 'cat | dog');
-- not
select to_tsvector('simple', 'ala has a cat') @@ to_tsquery('simple', 'cat & !dog');

Poprawki w modelu:

app/models/fortune.rb
def self.text_search(query)
  if query.present?
    # where("quotation @@ :q or source @@ :q", q: query)
    orquery = <<-ORQUERY
      to_tsvector('english', quotation) @@ plainto_tsquery('english', #{sanitize(query)})
        or
      to_tsvector('english', source) @@ plainto_tsquery('english', #{sanitize(query)})
    ORQUERY
    where(orquery)
  else
    scoped
  end
end

Texticle

Ta sama funkcjonalność co wyżej (z rank?), ale kod metody text_search dużo prostszy:

app/models/fortune.rb
def self.text_search(query)
  if query.present?
    search(query)    # metoda zdefiniowana w Texticle
  else
    scoped
  end
end

PG_search

Ten gem implementuje wyszukiwanie pełnotekstowe w jednym modelu (pg_search_scope) albo — w kilku modelach (multisearch).

pg_search_scope

Dopisujemy w modelu (nie używa słownika 'english', nie działa stemming):

include PgSearch

# definiujemy metodę `fortunes_search`
pg_search_scope :fortunes_search, against: [:quotation, :source],
    using: {tsearch: {dictionary: "english"}}

def self.text_search(query)
  if query.present?
    fortunes_search(query)    # metoda zdefiniowana powyżej
  else
    scoped
  end
end

Wyniki posortowane malejąco względem „ranking search results”.

multisearch

…to wyszukiwanie w wielu modelach:

rake pg_search:migration:multisearch
rake db:setup

do każdego modelu dodajemy dwie linijki kodu, na przykład w modelu Fortune dopisujemy:

include PgSearch
multisearchable :against => [:quotation, :source]

Następnie wykonujemy zadanie rake:

rake pg_search:multisearch:rebuild MODEL=Fortune

Zrobione! Wchodzimy na konsolę, gdzie zadajemy kilka zapytań:

PgSearch.multisearch('bird bush')
PgSearch.multisearch('bird bush').each { |doc| puts doc.content }

Koniec wykładu 13.05.2012

git commit -m "... Wyszukiwanie z PostgreSQL ..."
... rebase ...
git tag v0.0.3

Ajaxujemy wyszukiwanie

Jest następujacy problem: Added option :ajax for remote page links. Oznacza to, że nie można zajaksować pagination links. Można to obejść za pomocą jednej linijki kodu JavaScript:

app/assets/javascripts/application.js
$('.digg_pagination a').data('remote', true)

Dalej postępujemy tak jak w rozdziale „Remote links”.

Takie obejście nie jest konieczne jeśli do paginacji użyliśmy gemu Kaminari. Wystarczy dopisać w widoku:

app/views/fortunes/index.html.erb
<div id="paginator">
  <%= paginate @fortunes, :remote => true %>
</div>