PgSearch membangun cakupan bernama yang memanfaatkan pencarian teks lengkap PostgreSQL.
Baca postingan blog yang memperkenalkan PgSearch di https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
$ gem install pg_search
atau tambahkan baris ini ke Gemfile Anda:
gem 'pg_search'
Selain menginstal dan memerlukan permata, Anda mungkin ingin menyertakan tugas rake PgSearch di Rakefile Anda. Ini tidak diperlukan untuk proyek Rails, yang mendapatkan tugas Rake melalui Railtie.
load "pg_search/tasks.rb"
Untuk menambahkan PgSearch ke model Rekaman Aktif, cukup sertakan modul PgSearch.
class Shape < ActiveRecord :: Base
include PgSearch :: Model
end
multisearchable
pg_search_scope
:tsearch
(Pencarian Teks Lengkap):prefix
(hanya PostgreSQL 8.4 dan yang lebih baru):negation
:dictionary
:normalization
:any_word
:sort_only
:highlight
:dmetaphone
(Pencarian mirip Metafon Ganda):trigram
(pencarian trigram):threshold
:word_similarity
:ranked_by
(Memilih algoritma pemeringkatan):order_within_rank
(Memutus hubungan)PgSearch#pg_search_rank
(Membaca peringkat rekaman sebagai Float)pg_search mendukung dua teknik berbeda untuk pencarian, multi-pencarian dan cakupan pencarian.
Teknik pertama adalah multi-pencarian, yang mana catatan dari banyak kelas Rekaman Aktif yang berbeda dapat digabungkan menjadi satu indeks pencarian global di seluruh aplikasi Anda. Sebagian besar situs yang ingin mendukung halaman pencarian umum ingin menggunakan fitur ini.
Teknik lainnya adalah cakupan pencarian, yang memungkinkan Anda melakukan pencarian lebih lanjut hanya terhadap satu kelas Rekaman Aktif. Ini lebih berguna untuk membuat hal-hal seperti pelengkapan otomatis atau memfilter daftar item dalam pencarian tersaring.
Sebelum menggunakan multi-penelusuran, Anda harus membuat dan menjalankan migrasi untuk membuat tabel database pg_search_documents.
$ rails g pg_search:migration:multisearch
$ rake db:migrate
Untuk menambahkan model ke indeks pencarian global untuk aplikasi Anda, panggil multisearchable dalam definisi kelasnya.
class EpicPoem < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :title , :author ]
end
class Flower < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : :color
end
Jika model ini sudah memiliki data yang ada, Anda perlu mengindeks ulang model ini untuk memasukkan data yang ada ke dalam tabel pg_search_documents. Lihat tugas pembangunan kembali di bawah.
Setiap kali rekaman dibuat, diperbarui, atau dimusnahkan, panggilan balik Rekaman Aktif akan diaktifkan, yang mengarah ke pembuatan rekaman PgSearch::Document yang sesuai di tabel pg_search_documents. Opsi :against dapat berupa satu atau beberapa metode yang akan dipanggil pada rekaman untuk menghasilkan teks pencariannya.
Anda juga dapat meneruskan nama Proc atau metode untuk dipanggil guna menentukan apakah rekaman tertentu harus disertakan atau tidak.
class Convertible < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :make , :model ] ,
if : :available_in_red?
end
class Jalopy < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :make , :model ] ,
if : lambda { | record | record . model_year > 1970 }
end
Perhatikan bahwa nama Proc atau metode dipanggil dalam hook after_save. Artinya Anda harus berhati-hati saat menggunakan Waktu atau objek lainnya. Dalam contoh berikut, jika rekaman terakhir disimpan sebelum stempel waktu dipublikasikan_at, rekaman tersebut tidak akan tercantum dalam pencarian global sama sekali hingga disentuh lagi setelah stempel waktu.
class AntipatternExample
include PgSearch :: Model
multisearchable against : [ :contents ] ,
if : :published?
def published?
published_at < Time . now
end
end
problematic_record = AntipatternExample . create! (
contents : "Using :if with a timestamp" ,
published_at : 10 . minutes . from_now
)
problematic_record . published? # => false
PgSearch . multisearch ( "timestamp" ) # => No results
sleep 20 . minutes
problematic_record . published? # => true
PgSearch . multisearch ( "timestamp" ) # => No results
problematic_record . save!
problematic_record . published? # => true
PgSearch . multisearch ( "timestamp" ) # => Includes problematic_record
Perbarui pg_search_documents secara kondisional
Anda juga dapat menggunakan opsi :update_if
untuk meneruskan nama Proc atau metode yang akan dipanggil guna menentukan apakah catatan tertentu harus diperbarui atau tidak.
Perhatikan bahwa nama Proc atau metode dipanggil dalam hook after_save
, jadi jika Anda mengandalkan flag kotor ActiveRecord gunakan *_previously_changed?
.
class Message < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :body ] ,
update_if : :body_previously_changed?
end
Tentukan atribut tambahan untuk disimpan pada tabel pg_search_documents
Anda dapat menentukan :additional_attributes
untuk disimpan dalam tabel pg_search_documents
. Misalnya, mungkin Anda mengindeks model buku dan model artikel dan ingin menyertakan author_id.
Pertama, kita perlu menambahkan referensi ke penulis ke migrasi yang membuat tabel pg_search_documents
kita.
create_table :pg_search_documents do | t |
t . text :content
t . references :author , index : true
t . belongs_to :searchable , polymorphic : true , index : true
t . timestamps null : false
end
Kemudian, kita dapat mengirimkan atribut tambahan ini di lambda
multisearchable (
against : [ :title , :body ] ,
additional_attributes : -> ( article ) { { author_id : article . author_id } }
)
Hal ini memungkinkan pencarian lebih cepat tanpa bergabung di kemudian hari dengan melakukan sesuatu seperti:
PgSearch . multisearch ( params [ 'search' ] ) . where ( author_id : 2 )
CATATAN: Saat ini Anda harus memanggil record.update_pg_search_document
secara manual agar atribut tambahan dapat disertakan dalam tabel pg_search_documents
Dua asosiasi dibangun secara otomatis. Pada rekaman asli, terdapat asosiasi has_one :pg_search_document yang menunjuk ke rekaman PgSearch::Document, dan pada rekaman PgSearch::Document terdapat asosiasi polimorfik milik_to :searchable yang menunjuk kembali ke rekaman asli.
odyssey = EpicPoem . create! ( title : "Odyssey" , author : "Homer" )
search_document = odyssey . pg_search_document #=> PgSearch::Document instance
search_document . searchable #=> #<EpicPoem id: 1, title: "Odyssey", author: "Homer">
Untuk mengambil PgSearch::Entri dokumen untuk semua rekaman yang cocok dengan kueri tertentu, gunakan PgSearch.multisearch.
odyssey = EpicPoem . create! ( title : "Odyssey" , author : "Homer" )
rose = Flower . create! ( color : "Red" )
PgSearch . multisearch ( "Homer" ) #=> [#<PgSearch::Document searchable: odyssey>]
PgSearch . multisearch ( "Red" ) #=> [#<PgSearch::Document searchable: rose>]
PgSearch.multisearch mengembalikan ActiveRecord::Relation, seperti halnya cakupan, sehingga Anda dapat merangkai panggilan cakupan hingga akhir. Ini berfungsi dengan permata seperti Kaminari yang menambahkan metode cakupan. Sama seperti cakupan biasa, database hanya akan menerima permintaan SQL bila diperlukan.
PgSearch . multisearch ( "Bertha" ) . limit ( 10 )
PgSearch . multisearch ( "Juggler" ) . where ( searchable_type : "Occupation" )
PgSearch . multisearch ( "Alamo" ) . page ( 3 ) . per ( 30 )
PgSearch . multisearch ( "Diagonal" ) . find_each do | document |
puts document . searchable . updated_at
end
PgSearch . multisearch ( "Moro" ) . reorder ( "" ) . group ( :searchable_type ) . count ( :all )
PgSearch . multisearch ( "Square" ) . includes ( :searchable )
PgSearch.multisearch dapat dikonfigurasi menggunakan opsi yang sama seperti pg_search_scope
(dijelaskan lebih detail di bawah). Cukup atur PgSearch.multisearch_options di penginisialisasi:
PgSearch . multisearch_options = {
using : [ :tsearch , :trigram ] ,
ignoring : :accents
}
Jika Anda mengubah opsi :against pada suatu kelas, menambahkan multisearchable ke kelas yang sudah memiliki catatan dalam database, atau menghapus multisearchable dari suatu kelas untuk menghapusnya dari indeks, Anda akan menemukan bahwa tabel pg_search_documents bisa menjadi out- tidak sinkron dengan catatan aktual di tabel Anda yang lain.
Indeks juga bisa menjadi tidak sinkron jika Anda mengubah rekaman dengan cara yang tidak memicu callback Rekaman Aktif. Misalnya, metode instance #update_attribute dan metode kelas .update_all melewatkan callback dan langsung memodifikasi database.
Untuk menghapus semua dokumen untuk kelas tertentu, Anda cukup menghapus semua catatan PgSearch::Document.
PgSearch :: Document . delete_by ( searchable_type : "Animal" )
Untuk membuat ulang dokumen untuk kelas tertentu, jalankan:
PgSearch :: Multisearch . rebuild ( Product )
Metode rebuild
akan menghapus semua dokumen untuk kelas tertentu sebelum dibuat ulang. Dalam beberapa situasi, hal ini mungkin tidak diinginkan, seperti saat Anda menggunakan pewarisan tabel tunggal dan searchable_type
adalah kelas dasar Anda. Anda dapat mencegah rebuild
menghapus catatan Anda seperti:
PgSearch :: Multisearch . rebuild ( Product , clean_up : false )
rebuild
berjalan di dalam satu transaksi. Untuk menjalankan di luar transaksi, Anda dapat meneruskan transactional: false
seperti:
PgSearch :: Multisearch . rebuild ( Product , transactional : false )
Pembangunan kembali juga tersedia sebagai tugas Rake, untuk kenyamanan.
$ rake pg_search:multisearch:rebuild[BlogPost]
Argumen opsional kedua dapat diberikan untuk menentukan jalur pencarian skema PostgreSQL yang akan digunakan, untuk database multi-penyewa yang memiliki beberapa tabel pg_search_documents. Berikut ini akan menyetel jalur pencarian skema ke "my_schema" sebelum mengindeks ulang.
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
Untuk model yang dapat melakukan banyak penelusuran :against
metode yang secara langsung memetakan ke atribut Rekaman Aktif, pernyataan SQL tunggal yang efisien dijalankan untuk memperbarui tabel pg_search_documents
sekaligus. Namun, jika Anda memanggil metode dinamis apa pun di :against
maka update_pg_search_document
akan dipanggil pada masing-masing catatan yang diindeks dalam batch.
Anda juga dapat menyediakan implementasi khusus untuk membangun kembali dokumen dengan menambahkan metode kelas yang disebut rebuild_pg_search_documents
ke model Anda.
class Movie < ActiveRecord :: Base
belongs_to :director
def director_name
director . name
end
multisearchable against : [ :name , :director_name ]
# Naive approach
def self . rebuild_pg_search_documents
find_each { | record | record . update_pg_search_document }
end
# More sophisticated approach
def self . rebuild_pg_search_documents
connection . execute <<~SQL . squish
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
SELECT 'Movie' AS searchable_type,
movies.id AS searchable_id,
CONCAT_WS(' ', movies.name, directors.name) AS content,
now() AS created_at,
now() AS updated_at
FROM movies
LEFT JOIN directors
ON directors.id = movies.director_id
SQL
end
end
Catatan: Jika menggunakan PostgreSQL sebelum 9.1, ganti pemanggilan fungsi CONCAT_WS()
dengan penggabungan pipa ganda, misalnya. (movies.name || ' ' || directors.name)
. Namun, sekarang ketahuilah bahwa jika salah satu nilai yang digabungkan adalah NULL maka nilai content
akhir juga akan menjadi NULL, sedangkan CONCAT_WS()
akan secara selektif mengabaikan nilai NULL.
Jika Anda memiliki operasi massal yang besar untuk dilakukan, seperti mengimpor banyak rekaman dari sumber eksternal, Anda mungkin ingin mempercepatnya dengan menonaktifkan pengindeksan untuk sementara. Anda kemudian dapat menggunakan salah satu teknik di atas untuk membangun kembali dokumen pencarian secara offline.
PgSearch . disable_multisearch do
Movie . import_from_xml_file ( File . open ( "movies.xml" ) )
end
Anda dapat menggunakan pg_search_scope untuk membangun cakupan pencarian. Parameter pertama adalah nama cakupan, dan parameter kedua adalah hash opsi. Satu-satunya opsi yang diperlukan adalah :against, yang memberi tahu pg_search_scope kolom mana yang akan dicari.
Untuk mencari berdasarkan kolom, berikan simbol sebagai opsi :against.
class BlogPost < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_title , against : :title
end
Kami sekarang memiliki lingkup ActiveRecord bernama search_by_title pada model BlogPost kami. Dibutuhkan satu parameter, string permintaan pencarian.
BlogPost . create! ( title : "Recent Developments in the World of Pastrami" )
BlogPost . create! ( title : "Prosciutto and You: A Retrospective" )
BlogPost . search_by_title ( "pastrami" ) # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]
Lewatkan saja Array jika Anda ingin mencari lebih dari satu kolom.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_full_name , against : [ :first_name , :last_name ]
end
Sekarang permintaan pencarian kita bisa cocok dengan salah satu atau kedua kolom.
person_1 = Person . create! ( first_name : "Grant" , last_name : "Hill" )
person_2 = Person . create! ( first_name : "Hugh" , last_name : "Grant" )
Person . search_by_full_name ( "Grant" ) # => [person_1, person_2]
Person . search_by_full_name ( "Grant Hill" ) # => [person_1]
Sama seperti dengan cakupan bernama Rekaman Aktif, Anda dapat meneruskan objek Proc yang mengembalikan hash opsi. Misalnya, cakupan berikut mengambil parameter yang secara dinamis memilih kolom mana yang akan dicari.
Penting: Hash yang dikembalikan harus menyertakan kunci :query. Nilainya tidak harus dinamis. Anda dapat memilih untuk melakukan hard-code ke nilai tertentu jika Anda mau.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_name , lambda { | name_part , query |
raise ArgumentError unless [ :first , :last ] . include? ( name_part )
{
against : name_part ,
query : query
}
}
end
person_1 = Person . create! ( first_name : "Grant" , last_name : "Hill" )
person_2 = Person . create! ( first_name : "Hugh" , last_name : "Grant" )
Person . search_by_name :first , "Grant" # => [person_1]
Person . search_by_name :last , "Grant" # => [person_2]
Dimungkinkan untuk mencari kolom pada model terkait. Perhatikan bahwa jika Anda melakukan ini, tidak mungkin mempercepat pencarian dengan indeks database. Namun, ini didukung sebagai cara cepat untuk mencoba penelusuran lintas model.
Anda dapat meneruskan Hash ke opsi :associated_against untuk mengatur pencarian melalui asosiasi. Kuncinya adalah nama asosiasi dan nilainya berfungsi seperti opsi :against untuk model lainnya. Saat ini, penelusuran lebih dari satu asosiasi tidak didukung. Anda dapat menyiasatinya dengan menyiapkan serangkaian :through asosiasi untuk menunjukkan semuanya.
class Cracker < ActiveRecord :: Base
has_many :cheeses
end
class Cheese < ActiveRecord :: Base
end
class Salami < ActiveRecord :: Base
include PgSearch :: Model
belongs_to :cracker
has_many :cheeses , through : :cracker
pg_search_scope :tasty_search , associated_against : {
cheeses : [ :kind , :brand ] ,
cracker : :kind
}
end
salami_1 = Salami . create!
salami_2 = Salami . create!
salami_3 = Salami . create!
limburger = Cheese . create! ( kind : "Limburger" )
brie = Cheese . create! ( kind : "Brie" )
pepper_jack = Cheese . create! ( kind : "Pepper Jack" )
Cracker . create! ( kind : "Black Pepper" , cheeses : [ brie ] , salami : salami_1 )
Cracker . create! ( kind : "Ritz" , cheeses : [ limburger , pepper_jack ] , salami : salami_2 )
Cracker . create! ( kind : "Graham" , cheeses : [ limburger ] , salami : salami_3 )
Salami . tasty_search ( "pepper" ) # => [salami_1, salami_2]
Secara default, pg_search_scope menggunakan pencarian teks PostgreSQL bawaan. Jika Anda meneruskan opsi :using ke pg_search_scope, Anda dapat memilih teknik pencarian alternatif.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name , against : :name , using : [ :tsearch , :trigram , :dmetaphone ]
end
Berikut ini contoh jika Anda meneruskan beberapa opsi :using dengan konfigurasi tambahan.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name ,
against : :name ,
using : {
:trigram => { } ,
:dmetaphone => { } ,
:tsearch => { :prefix => true }
}
end
Fitur-fitur yang diterapkan saat ini adalah
Pencarian teks lengkap bawaan PostgreSQL mendukung pembobotan, pencarian awalan, dan stemming dalam berbagai bahasa.
Setiap kolom yang dicari dapat diberi bobot “A”, “B”, “C”, atau “D”. Kolom dengan huruf lebih awal memiliki bobot lebih tinggi dibandingkan kolom dengan huruf belakangan. Jadi, pada contoh berikut, judul adalah yang terpenting, diikuti subjudul, dan terakhir konten.
class NewsArticle < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_full_text , against : {
title : 'A' ,
subtitle : 'B' ,
content : 'C'
}
end
Anda juga dapat meneruskan bobot sebagai array dari array, atau struktur lain apa pun yang merespons #each dan menghasilkan simbol tunggal atau simbol dan bobot. Jika Anda menghilangkan bobotnya, default akan digunakan.
class NewsArticle < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_full_text , against : [
[ :title , 'A' ] ,
[ :subtitle , 'B' ] ,
[ :content , 'C' ]
]
end
class NewsArticle < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_full_text , against : [
[ :title , 'A' ] ,
{ subtitle : 'B' } ,
:content
]
end
Pencarian teks lengkap PostgreSQL cocok dengan seluruh kata secara default. Namun, jika Anda ingin mencari sebagian kata, Anda dapat menyetel :prefix ke true. Karena ini adalah opsi khusus :tsearch, Anda harus meneruskannya ke :tsearch secara langsung, seperti yang ditunjukkan dalam contoh berikut.
class Superhero < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :whose_name_starts_with ,
against : :name ,
using : {
tsearch : { prefix : true }
}
end
batman = Superhero . create name : 'Batman'
batgirl = Superhero . create name : 'Batgirl'
robin = Superhero . create name : 'Robin'
Superhero . whose_name_starts_with ( "Bat" ) # => [batman, batgirl]
Pencarian teks lengkap PostgreSQL cocok dengan semua istilah pencarian secara default. Jika Anda ingin mengecualikan kata-kata tertentu, Anda dapat menyetel :negation ke true. Lalu istilah apa saja yang diawali dengan tanda seru !
akan dikeluarkan dari hasil. Karena ini adalah opsi khusus :tsearch, Anda harus meneruskannya ke :tsearch secara langsung, seperti yang ditunjukkan dalam contoh berikut.
Perhatikan bahwa menggabungkan ini dengan fitur pencarian lainnya dapat memberikan hasil yang tidak diharapkan. Misalnya, penelusuran :trigram tidak memiliki konsep istilah yang dikecualikan, sehingga jika Anda menggunakan :tsearch dan :trigram secara bersamaan, Anda mungkin masih menemukan hasil yang berisi istilah yang ingin Anda kecualikan.
class Animal < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :with_name_matching ,
against : :name ,
using : {
tsearch : { negation : true }
}
end
one_fish = Animal . create ( name : "one fish" )
two_fish = Animal . create ( name : "two fish" )
red_fish = Animal . create ( name : "red fish" )
blue_fish = Animal . create ( name : "blue fish" )
Animal . with_name_matching ( "fish !red !blue" ) # => [one_fish, two_fish]
Pencarian teks lengkap PostgreSQL juga mendukung banyak kamus untuk stemming. Anda dapat mempelajari lebih lanjut tentang cara kerja kamus dengan membaca dokumentasi PostgreSQL. Jika Anda menggunakan salah satu kamus bahasa, misalnya “english”, maka varian kata (misalnya “jumping” dan “jumped”) akan cocok satu sama lain. Jika Anda tidak ingin melakukan stemming, sebaiknya pilih kamus "sederhana" yang tidak melakukan stemming apa pun. Jika Anda tidak menentukan kamus, kamus "sederhana" akan digunakan.
class BoringTweet < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :kinda_matching ,
against : :text ,
using : {
tsearch : { dictionary : "english" }
}
pg_search_scope :literally_matching ,
against : :text ,
using : {
tsearch : { dictionary : "simple" }
}
end
sleep = BoringTweet . create! text : "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleep"
sleeping = BoringTweet . create! text : "You know what I like? Sleeping. That's what. #enjoyment"
sleeps = BoringTweet . create! text : "In the jungle, the mighty jungle, the lion sleeps #tonight"
BoringTweet . kinda_matching ( "sleeping" ) # => [sleep, sleeping, sleeps]
BoringTweet . literally_matching ( "sleeps" ) # => [sleeps]
PostgreSQL mendukung banyak algoritme untuk memberi peringkat hasil berdasarkan kueri. Misalnya, Anda mungkin ingin mempertimbangkan ukuran dokumen secara keseluruhan atau jarak antara beberapa istilah pencarian dalam teks asli. Opsi ini mengambil bilangan bulat, yang diteruskan langsung ke PostgreSQL. Menurut dokumentasi PostgreSQL terbaru, algoritma yang didukung adalah:
0 (the default) ignores the document length
1 divides the rank by 1 + the logarithm of the document length
2 divides the rank by the document length
4 divides the rank by the mean harmonic distance between extents
8 divides the rank by the number of unique words in document
16 divides the rank by 1 + the logarithm of the number of unique words in document
32 divides the rank by itself + 1
Bilangan bulat ini adalah bitmask, jadi jika Anda ingin menggabungkan algoritme, Anda dapat menjumlahkan angka-angkanya. (misalnya untuk menggunakan algoritma 1, 8, dan 32, Anda akan melewati 1 + 8 + 32 = 41)
class BigLongDocument < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :regular_search ,
against : :text
pg_search_scope :short_search ,
against : :text ,
using : {
tsearch : { normalization : 2 }
}
long = BigLongDocument . create! ( text : "Four score and twenty years ago" )
short = BigLongDocument . create! ( text : "Four score" )
BigLongDocument . regular_search ( "four score" ) #=> [long, short]
BigLongDocument . short_search ( "four score" ) #=> [short, long]
Menyetel atribut ini ke true akan menjalankan penelusuran yang akan mengembalikan semua model yang mengandung kata apa pun dalam istilah penelusuran.
class Number < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_any_word ,
against : :text ,
using : {
tsearch : { any_word : true }
}
pg_search_scope :search_all_words ,
against : :text
end
one = Number . create! text : 'one'
two = Number . create! text : 'two'
three = Number . create! text : 'three'
Number . search_any_word ( 'one two three' ) # => [one, two, three]
Number . search_all_words ( 'one two three' ) # => []
Menyetel atribut ini ke true akan membuat fitur ini tersedia untuk pengurutan, namun tidak akan menyertakannya dalam kondisi WHERE kueri.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search ,
against : :name ,
using : {
tsearch : { any_word : true } ,
dmetaphone : { any_word : true , sort_only : true }
}
end
exact = Person . create! ( name : 'ash hines' )
one_exact_one_close = Person . create! ( name : 'ash heinz' )
one_exact = Person . create! ( name : 'ash smith' )
one_close = Person . create! ( name : 'leigh heinz' )
Person . search ( 'ash hines' ) # => [exact, one_exact_one_close, one_exact]
Menambahkan .with_pg_search_highlight setelah pg_search_scope Anda dapat mengakses atribut pg_highlight
untuk setiap objek.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search ,
against : :bio ,
using : {
tsearch : {
highlight : {
StartSel : '<b>' ,
StopSel : '</b>' ,
MaxWords : 123 ,
MinWords : 456 ,
ShortWord : 4 ,
HighlightAll : true ,
MaxFragments : 3 ,
FragmentDelimiter : '…'
}
}
}
end
Person . create! ( :bio => "Born in rural Alberta, where the buffalo roam." )
first_match = Person . search ( "Alberta" ) . with_pg_search_highlight . first
first_match . pg_search_highlight # => "Born in rural <b>Alberta</b>, where the buffalo roam."
Opsi sorotan menerima semua opsi yang didukung oleh ts_headline, dan menggunakan default PostgreSQL.
Lihat dokumentasi untuk detail tentang arti setiap opsi.
Metafon Ganda adalah algoritme untuk mencocokkan kata-kata yang bunyinya sama meskipun ejaannya sangat berbeda. Misalnya, "Geoff" dan "Jeff" terdengar identik sehingga cocok. Saat ini, ini bukanlah metafon ganda yang sebenarnya, karena hanya metafon pertama yang digunakan untuk pencarian.
Dukungan Metaphone Ganda saat ini tersedia sebagai bagian dari ekstensi fuzzystrmatch yang harus diinstal sebelum fitur ini dapat digunakan. Selain ekstensi, Anda harus menginstal fungsi utilitas ke dalam database Anda. Untuk membuat dan menjalankan migrasi untuk ini, jalankan:
$ rails g pg_search:migration:dmetaphone
$ rake db:migrate
Contoh berikut menunjukkan cara menggunakan :dmetaphone.
class Word < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :that_sounds_like ,
against : :spelling ,
using : :dmetaphone
end
four = Word . create! spelling : 'four'
far = Word . create! spelling : 'far'
fur = Word . create! spelling : 'fur'
five = Word . create! spelling : 'five'
Word . that_sounds_like ( "fir" ) # => [four, far, fur]
Pencarian trigram bekerja dengan menghitung berapa banyak substring tiga huruf (atau "trigram") yang cocok antara kueri dan teks. Misalnya, string "Lorem ipsum" dapat dibagi menjadi trigram berikut:
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
Pencarian Trigram memiliki beberapa kemampuan untuk bekerja bahkan dengan kesalahan ketik dan salah eja dalam kueri atau teks.
Dukungan Trigram saat ini tersedia sebagai bagian dari ekstensi pg_trgm yang harus diinstal sebelum fitur ini dapat digunakan.
class Website < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :kinda_spelled_like ,
against : :name ,
using : :trigram
end
yahooo = Website . create! name : "Yahooo!"
yohoo = Website . create! name : "Yohoo!"
gogle = Website . create! name : "Gogle"
facebook = Website . create! name : "Facebook"
Website . kinda_spelled_like ( "Yahoo!" ) # => [yahooo, yohoo]
Secara default, pencarian trigram menemukan catatan yang memiliki kesamaan minimal 0,3 menggunakan perhitungan pg_trgm. Anda dapat menentukan ambang batas khusus jika Anda mau. Angka yang lebih tinggi akan dicocokkan dengan lebih ketat, sehingga memberikan hasil yang lebih sedikit. Angka yang lebih rendah cocok dengan lebih permisif, sehingga menghasilkan lebih banyak hasil. Harap perhatikan bahwa menyetel ambang trigram akan memaksa pemindaian tabel karena kueri turunan menggunakan fungsi similarity()
alih-alih operator %
.
class Vegetable < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :strictly_spelled_like ,
against : :name ,
using : {
trigram : {
threshold : 0.5
}
}
pg_search_scope :roughly_spelled_like ,
against : :name ,
using : {
trigram : {
threshold : 0.1
}
}
end
cauliflower = Vegetable . create! name : "cauliflower"
Vegetable . roughly_spelled_like ( "couliflower" ) # => [cauliflower]
Vegetable . strictly_spelled_like ( "couliflower" ) # => [cauliflower]
Vegetable . roughly_spelled_like ( "collyflower" ) # => [cauliflower]
Vegetable . strictly_spelled_like ( "collyflower" ) # => []
Memungkinkan Anda mencocokkan kata dalam string yang lebih panjang. Secara default, pencarian trigram menggunakan %
atau similarity()
sebagai nilai kesamaan. Setel word_similarity
ke true
untuk memilih <%
dan word_similarity
sebagai gantinya. Hal ini menyebabkan pencarian trigram menggunakan kesamaan istilah kueri dan kata dengan kesamaan terbesar.
class Sentence < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :similarity_like ,
against : :name ,
using : {
trigram : {
word_similarity : true
}
}
pg_search_scope :word_similarity_like ,
against : :name ,
using : [ :trigram ]
end
sentence = Sentence . create! name : "Those are two words."
Sentence . similarity_like ( "word" ) # => []
Sentence . word_similarity_like ( "word" ) # => [sentence]
Terkadang saat melakukan kueri yang menggabungkan fitur berbeda, Anda mungkin ingin menelusuri hanya beberapa bidang dengan fitur tertentu. Misalnya mungkin Anda hanya ingin melakukan pencarian trigram pada kolom yang lebih pendek sehingga Anda tidak perlu mengurangi ambang batas secara berlebihan. Anda dapat menentukan bidang mana yang menggunakan opsi 'hanya':
class Image < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :combined_search ,
against : [ :file_name , :short_description , :long_description ]
using : {
tsearch : { dictionary : 'english' } ,
trigram : {
only : [ :file_name , :short_description ]
}
}
end
Sekarang Anda berhasil mengambil Gambar dengan nama_file: 'image_foo.jpg' dan long_description: 'Deskripsi ini sangat panjang sehingga akan membuat pencarian trigram gagal mencapai batas ambang batas yang wajar' dengan:
Image . combined_search ( 'reasonable' ) # found with tsearch
Image . combined_search ( 'foo' ) # found with trigram
Sering kali Anda ingin mengabaikan tanda aksen saat menelusuri. Hal ini memungkinkan untuk menemukan kata-kata seperti "piñata" saat menelusuri dengan kueri "pinata". Jika Anda menyetel pg_search_scope untuk mengabaikan aksen, aksen dalam teks yang dapat dicari dan istilah kueri akan diabaikan.
Mengabaikan aksen menggunakan ekstensi tanpa aksen yang harus diinstal sebelum fitur ini dapat digunakan.
class SpanishQuestion < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :gringo_search ,
against : :word ,
ignoring : :accents
end
what = SpanishQuestion . create ( word : "Qué" )
how_many = SpanishQuestion . create ( word : "Cuánto" )
how = SpanishQuestion . create ( word : "Cómo" )
SpanishQuestion . gringo_search ( "Que" ) # => [what]
SpanishQuestion . gringo_search ( "Cüåñtô" ) # => [how_many]
Pengguna tingkat lanjut mungkin ingin menambahkan indeks untuk ekspresi yang dihasilkan pg_search. Sayangnya, fungsi tanpa aksen yang disediakan oleh ekstensi ini tidak dapat diindeks (mulai PostgreSQL 9.1). Oleh karena itu, Anda mungkin ingin menulis fungsi wrapper Anda sendiri dan menggunakannya. Ini dapat dikonfigurasi dengan memanggil kode berikut, mungkin di penginisialisasi.
PgSearch . unaccent_function = "my_unaccent"
PostgreSQL memungkinkan Anda melakukan pencarian berdasarkan kolom dengan tipe tsvector alih-alih menggunakan ekspresi; ini mempercepat pencarian secara dramatis karena membongkar pembuatan tsvector yang menjadi dasar evaluasi tsquery.
Untuk menggunakan fungsi ini Anda perlu melakukan beberapa hal:
Buat kolom dengan tipe tsvector yang ingin Anda cari. Jika Anda ingin mencari menggunakan beberapa metode pencarian, misalnya tsearch dan dmetaphone, Anda memerlukan kolom untuk masing-masing metode.
Buat fungsi pemicu yang akan memperbarui kolom menggunakan ekspresi yang sesuai untuk jenis pencarian tersebut. Lihat: dokumentasi PostgreSQL untuk pemicu pencarian teks
Jika Anda memiliki data yang sudah ada sebelumnya di tabel, perbarui kolom tsvector yang baru dibuat dengan ekspresi yang digunakan fungsi pemicu Anda.
Tambahkan opsi ke pg_search_scope, misalnya:
pg_search_scope :fast_content_search ,
against : :content ,
using : {
dmetaphone : {
tsvector_column : 'tsvector_content_dmetaphone'
} ,
tsearch : {
dictionary : 'english' ,
tsvector_column : 'tsvector_content_tsearch'
} ,
trigram : { } # trigram does not use tsvectors
}
Harap dicatat bahwa kolom :against hanya digunakan ketika tsvector_column tidak ada untuk jenis pencarian.
Dimungkinkan untuk mencari lebih dari satu tsvector sekaligus. Ini bisa berguna jika Anda ingin mempertahankan beberapa cakupan pencarian tetapi tidak ingin mempertahankan vektor terpisah untuk setiap cakupan. Misalnya:
pg_search_scope :search_title ,
against : :title ,
using : {
tsearch : {
tsvector_column : "title_tsvector"
}
}
pg_search_scope :search_body ,
against : :body ,
using : {
tsearch : {
tsvector_column : "body_tsvector"
}
}
pg_search_scope :search_title_and_body ,
against : [ :title , :body ] ,
using : {
tsearch : {
tsvector_column : [ "title_tsvector" , "body_tsvector" ]
}
}
Secara default, pg_search memberi peringkat pada hasil berdasarkan kesamaan :tsearch antara teks yang dapat dicari dan kueri. Untuk menggunakan algoritme pemeringkatan yang berbeda, Anda dapat meneruskan opsi :ranked_by ke pg_search_scope.
pg_search_scope :search_by_tsearch_but_rank_by_trigram ,
against : :title ,
using : [ :tsearch ] ,
ranked_by : ":trigram"
Perhatikan bahwa :ranked_by menggunakan String untuk mewakili ekspresi peringkat. Hal ini memungkinkan kemungkinan yang lebih kompleks. String seperti ":tsearch", ":trigram", dan ":dmetaphone" secara otomatis diperluas ke ekspresi SQL yang sesuai.
# Weighted ranking to balance multiple approaches
ranked_by : ":dmetaphone + (0.25 * :trigram)"
# A more complex example, where books.num_pages is an integer column in the table itself
ranked_by : "(books.num_pages * :trigram) + (:tsearch / 2.0)"
PostgreSQL tidak menjamin urutan yang konsisten ketika beberapa catatan memiliki nilai yang sama dalam klausa ORDER BY. Hal ini dapat menyebabkan masalah pada penomoran halaman. Bayangkan sebuah kasus di mana 12 catatan semuanya memiliki nilai peringkat yang sama. Jika Anda menggunakan pustaka penomoran halaman seperti kaminari atau will_paginate untuk mengembalikan hasil di halaman 10, maka Anda akan melihat 10 catatan di halaman 1, dan 2 catatan sisanya di bagian atas halaman berikutnya, di depan yang lebih rendah. hasil peringkat.
Namun karena tidak ada pengurutan yang konsisten, PostgreSQL mungkin memilih untuk mengatur ulang urutan 12 catatan tersebut di antara pernyataan SQL yang berbeda. Anda mungkin juga mendapatkan beberapa catatan yang sama dari halaman 1 di halaman 2, dan mungkin juga ada catatan yang tidak muncul sama sekali.
pg_search memperbaiki masalah ini dengan menambahkan ekspresi kedua ke klausa ORDER BY, setelah ekspresi :ranked_by dijelaskan di atas. Secara default, urutan tiebreaker naik berdasarkan id.
ORDER BY [complicated :ranked_by expression...], id ASC
Ini mungkin tidak diinginkan untuk aplikasi Anda, terutama jika Anda tidak ingin data lama mengungguli data baru. Dengan meneruskan :order_within_rank, Anda dapat menentukan ekspresi tiebreaker alternatif. Contoh umum adalah menurun berdasarkanupdate_at, untuk memberi peringkat pada catatan yang paling baru diperbarui terlebih dahulu.
pg_search_scope :search_and_break_ties_by_latest_update ,
against : [ :title , :content ] ,
order_within_rank : "blog_posts.updated_at DESC"
Mungkin berguna atau menarik untuk melihat peringkat rekor tertentu. Ini dapat berguna untuk men-debug mengapa satu data mengungguli yang lain. Anda juga dapat menggunakannya untuk menunjukkan nilai relevansi kepada pengguna akhir suatu aplikasi.
Untuk mengambil peringkat, panggil .with_pg_search_rank
pada cakupan, lalu panggil .pg_search_rank
pada rekaman yang dikembalikan.
shirt_brands = ShirtBrand . search_by_name ( "Penguin" ) . with_pg_search_rank
shirt_brands [ 0 ] . pg_search_rank #=> 0.0759909
shirt_brands [ 1 ] . pg_search_rank #=> 0.0607927
Setiap cakupan PgSearch menghasilkan subkueri bernama untuk peringkat pencarian. Jika Anda merangkai beberapa cakupan maka PgSearch akan menghasilkan kueri peringkat untuk setiap cakupan, sehingga kueri peringkat harus memiliki nama yang unik. Jika Anda perlu mereferensikan kueri peringkat (misalnya dalam klausa GROUP BY), Anda dapat membuat ulang nama subkueri dengan metode PgScope::Configuration.alias
dengan meneruskan nama tabel kueri.
shirt_brands = ShirtBrand . search_by_name ( "Penguin" )
. joins ( :shirt_sizes )
. group ( "shirt_brands.id, #{ PgSearch :: Configuration . alias ( 'shirt_brands' ) } .rank" )
PgSearch tidak akan mungkin terjadi tanpa inspirasi dari textacular (sekarang berganti nama menjadi textacular). Terima kasih kepada Aaron Patterson untuk versi aslinya dan kepada Casebook PBC (https://www.casebook.net) yang telah menghadiahkannya kepada komunitas!
Silakan baca panduan KONTRIBUSI kami.
Kami juga memiliki Grup Google untuk mendiskusikan pg_search dan proyek sumber terbuka Casebook PBC lainnya.
Hak Cipta © 2010–2022 Buku Kasus PBC. Berlisensi di bawah lisensi MIT, lihat file LISENSI.