Простая, но мощная оболочка Ruby для RediSearch, поисковой системы на базе Redis.
Во-первых, необходимо установить Redis и RediSearch.
Вы можете скачать Redis по адресу https://redis.io/download и ознакомиться с инструкциями по установке здесь. Альтернативно, в macOS или Linux вы можете установить его через Homebrew.
Чтобы установить RediSearch, посетите https://oss.redislabs.com/redisearch/Quick_Start.html. После сборки RediSearch, если вы не используете Docker, вы можете обновить файл redis.conf, чтобы всегда загружать модуль RediSearch с помощью loadmodule /path/to/redisearch.so
. (В macOS файл redis.conf можно найти по адресу /usr/local/etc/redis.conf
).
После запуска Redis и RediSearch добавьте в Gemfile следующую строку:
gem 'redi_search'
А потом:
❯ bundle
Или установите его самостоятельно:
❯ gem install redi_search
и требуйте этого:
require 'redi_search'
Как только драгоценный камень будет установлен и потребуется, вам нужно будет настроить его в соответствии с вашей конфигурацией Redis. Если вы используете Rails, это должно быть в инициализаторе ( config/initializers/redi_search.rb
).
RediSearch . configure do | config |
config . redis_config = {
host : "127.0.0.1" ,
port : "6379"
}
end
RediSearch вращается вокруг поискового индекса, поэтому давайте начнем с определения того, что такое поисковый индекс. По данным Swiftype:
Поисковый индекс — это совокупность структурированных данных, к которым обращается поисковая система при поиске результатов, соответствующих конкретному запросу. Индексы являются важной частью любой поисковой системы, поскольку они должны быть адаптированы к конкретному методу поиска информации алгоритма поисковой системы. Таким образом, алгоритм и индекс неразрывно связаны друг с другом. Индекс также может использоваться как глагол (индексирование), обозначающий процесс сбора неструктурированных данных веб-сайта в структурированном формате, адаптированном для алгоритма поисковой системы.
Один из способов подумать об индексах — рассмотреть следующую аналогию между поисковой инфраструктурой и офисной файловой системой. Представьте, что вы вручаете стажеру стопку тысяч листов бумаги (документов) и просите его упорядочить эти листы бумаги в картотеке (указателе), чтобы помочь компании более эффективно находить информацию. Стажеру сначала придется разобраться в документах и получить представление обо всей содержащейся в них информации, затем ему придется определиться с системой их размещения в картотеке, а затем, наконец, ему нужно будет решить, что такое наиболее эффективный способ поиска и выбора файлов, находящихся в шкафу. В этом примере процесс организации и хранения документов соответствует процессу индексации содержимого веб-сайта, а метод поиска по этим организованным файлам и поиска наиболее релевантных соответствует алгоритму поиска.
Это определяет поля и свойства этих полей в индексе. Схема — это простой DSL. Каждое поле может быть одного из четырех типов: географическое, числовое, теговое или текстовое, и может иметь множество параметров. Простой пример схемы:
RediSearch :: Schema . new do
text_field :first_name
text_field :last_name
end
Поддерживаемые параметры для каждого типа следующие:
Без опций: text_field :name
text_field :name, weight: 2
text_field :name, phonetic: 'dm:en'
text_field :name, sortable: true
sortable
для создания полей, обновление которых с помощью PARTIAL не приведет к полной переиндексации документа. Если поле имеет no_index
и не имеет sortable
, оно будет просто проигнорировано индексом.text_field :name, no_index: true
text_feidl :name, no_stem: true
Без опций: numeric_field :price
numeric_field :id, sortable: true
sortable
для создания полей, обновление которых с помощью PARTIAL не приведет к полной переиндексации документа. Если поле имеет no_index
и не имеет sortable
, оно будет просто проигнорировано индексом.numeric_field :id, no_index: true
Без опций: tag_field :tag
tag_field :tag, sortable: true
sortable
для создания полей, обновление которых с помощью PARTIAL не приведет к полной переиндексации документа. Если поле имеет no_index
и не имеет sortable
, оно будет просто проигнорировано индексом.tag_field :tag, no_index: true
tag_field :tag, separator: ','
Без опций: geo_field :place
geo_field :place, sortable: true
sortable
для создания полей, обновление которых с помощью PARTIAL не приведет к полной переиндексации документа. Если поле имеет no_index
и не имеет sortable
, оно будет просто проигнорировано индексом.geo_field :place, no_index: true
Document
— это Ruby-представление хеша Redis.
Вы можете получить Document
, используя методы класса .get
.
get(index, document_id)
извлекает один Document
из Index
для заданного document_id
. Вы также можете создать экземпляр Document
, используя метод класса .for_object(index, record, only: [])
. Он принимает экземпляр Index
и объект Ruby. Этот объект должен отвечать на все поля, указанные в Schema
Index
. принимает only
массив полей из схемы и ограничивает количество полей, передаваемых в Document
.
Если у вас есть экземпляр Document
, он реагирует на все поля, указанные в Schema
Index
, как методы и document_id
. document_id
автоматически добавляется к имени Index
, если это еще не сделано для обеспечения уникальности. Мы добавляем имя Index
, потому что если у вас есть два Document
с одинаковым идентификатором в разных Index
, мы не хотим, чтобы Document
переопределяли друг друга. Существует также метод #document_id_without_index
, который удаляет добавленное имя индекса.
Наконец, есть метод #del
, который удалит Document
из Index
.
Чтобы инициализировать Index
, передайте имя Index
в виде строки или символа и блок Schema
.
RediSearch :: Index . new ( name_of_index ) do
text_field :foobar
end
create
false
если индекс уже существует. Принимает несколько вариантов:max_text_fields: #{true || false}
add_field
.no_offsets: #{true || false}
no_highlight
.temporary: #{seconds}
seconds
секунд бездействия. Внутренний таймер простоя сбрасывается при каждом поиске или добавлении индекса. Поскольку такие индексы легкие, вы можете создавать тысячи таких индексов без негативных последствий для производительности.no_highlight: #{true || false}
no_highlight
также подразумевается no_offsets
.no_fields: #{true || false}
no_frequencies: #{true || false}
drop(keep_docs: false)
Index
из экземпляра Redis, возвращает логическое значение. Имеет сопутствующий метод Bang, который вызывает исключение в случае сбоя. Вернет false
если Index
уже удален. Принимает ключевое слово arg, keep_docs
, которое по умолчанию удаляет все хэши документов в Redis.exist?
Index
.info
Index
.fields
Index
.add(document)
Document
. Имеет сопутствующий метод Bang, который вызывает исключение в случае сбоя.add_multiple(documents)
Document
. Это обеспечивает более эффективный способ добавления нескольких документов в Index
. Принимает те же параметры, что и add
.del(document)
Document
из Index
.document_count
Document
в Index
add_field(name, type, **options, &block)
Index
.index.add_field(:first_name, :text, phonetic: "dm:en")
reindex(documents, recreate: false)
recreate
имеет true
Index
будет удален и воссоздан. Поиск инициируется экземпляром RediSearch::Index
с предложениями, которые можно объединить в цепочку. При поиске возвращается массив Document
s, который имеет общедоступные методы чтения для всех полей схемы.
main ❯ index = RediSearch :: Index . new ( "user_idx" ) { text_field :name , phonetic : "dm:en" }
main ❯ index . add RediSearch :: Document . for_object ( index , User . new ( "10039" , "Gene" , "Volkman" ) )
main ❯ index . add RediSearch :: Document . for_object ( index , User . new ( "9998" , "Jeannie" , "Ledner" ) )
main ❯ index . search ( "john" )
RediSearch ( 1.1 ms ) FT . SEARCH user_idx `john`
=> [ #<RediSearch::Document:0x00007f862e241b78 first: "Gene", last: "Volkman", document_id: "10039">,
#<RediSearch::Document:0x00007f862e2417b8 first: "Jeannie", last: "Ledner", document_id: "9998">]
Простой фразовый запрос - hello AND world
index . search ( "hello" ) . and ( "world" )
Запрос по точной фразе — hello FOLLOWED BY world
index . search ( "hello world" )
Запрос на объединение – hello OR world
index . search ( "hello" ) . or ( "world" )
Запрос-отрицание - hello AND NOT world
index . search ( "hello" ) . and . not ( "world" )
Сложные пересечения и объединения:
# Intersection of unions
index . search ( index . search ( "hello" ) . or ( "halo" ) ) . and ( index . search ( "world" ) . or ( "werld" ) )
# Negation of union
index . search ( "hello" ) . and . not ( index . search ( "world" ) . or ( "werld" ) )
# Union inside phrase
index . search ( "hello" ) . and ( index . search ( "world" ) . or ( "werld" ) )
Все термины поддерживают несколько вариантов, которые можно применить.
Префиксные термины : соответствие всем терминам, начинающимся с префикса. ( like term%
в SQL)
index . search ( "hel" , prefix : true )
index . search ( "hello worl" , prefix : true )
index . search ( "hel" , prefix : true ) . and ( "worl" , prefix : true )
index . search ( "hello" ) . and . not ( "worl" , prefix : true )
Необязательные условия : документы, содержащие необязательные условия, будут иметь более высокий рейтинг, чем документы без них.
index . search ( "foo" ) . and ( "bar" , optional : true ) . and ( "baz" , optional : true )
Нечеткие термины : матчи проводятся на основе расстояния Левенштейна (LD). Максимальное поддерживаемое расстояние Левенштейна — 3.
index . search ( "zuchini" , fuzziness : 1 )
Условия поиска также можно ограничить определенными полями с помощью where
:
# Simple field specific query
index . search . where ( name : "john" )
# Using where with options
index . search . where ( first : "jon" , fuzziness : 1 )
# Using where with more complex query
index . search . where ( first : index . search ( "bill" ) . or ( "bob" ) )
Поиск числовых полей занимает диапазон:
index . search . where ( number : 0 .. 100 )
# Searching to infinity
index . search . where ( number : 0 .. Float :: INFINITY )
index . search . where ( number : - Float :: INFINITY .. 0 )
slop(level)
in_order
slop
. Мы следим за тем, чтобы термины запроса отображались в Document
в том же порядке, что и в запросе, независимо от смещений между ними.no_content
Document
, а не содержимое. Это полезно, если RediSearch используется в модели Rails, где атрибуты Document
не имеют значения, и он преобразуется в объекты ActiveRecord
.language(language)
Document
s на китайском языке следует установить китайский язык, чтобы правильно маркировать условия запроса. Если отправлен неподдерживаемый язык, команда возвращает ошибку.sort_by(field, order: :asc)
:asc
или :desc
limit(num, offset = 0)
num
по offset
. Ограничение по умолчанию установлено на 10
.count
Document
, найденных в поисковом запросе.highlight(fields: [], opening_tag: "<b>", closing_tag: "</b>")
fields
— это массив полей, которые нужно выделить.verbatim
no_stop_words
with_scores
Document
. Это можно использовать для объединения результатов из нескольких экземпляров. Это добавит метод score
к возвращаемым экземплярам Document
.return(*fields)
Document
.explain
Проверка орфографии инициируется экземпляром RediSearch::Index
и выдает предложения по поисковым словам с ошибками. Он принимает необязательный аргумент distance
, который является максимальным расстоянием Левенштейна для вариантов написания. Он возвращает массив, в котором каждый элемент содержит предложения для каждого поискового термина и нормализованную оценку, основанную на его вхождениях в индекс.
main ❯ index = RediSearch :: Index . new ( "user_idx" ) { text_field :name , phonetic : "dm:en" }
main ❯ index . spellcheck ( "jimy" )
RediSearch ( 1.1 ms ) FT . SPELLCHECK user_idx jimy DISTANCE 1
=> [ #<RediSearch::Spellcheck::Result:0x00007f805591c670
term : "jimy" ,
suggestions :
[ #<struct RediSearch::Spellcheck::Suggestion score=0.0006849315068493151, suggestion="jimmy">,
#<struct RediSearch::Spellcheck::Suggestion score=0.00019569471624266145, suggestion="jim">]>]
main ❯ index . spellcheck ( "jimy" , distance : 2 ) . first . suggestions
RediSearch ( 0.5 ms ) FT . SPELLCHECK user_idx jimy DISTANCE 2
=> [ #<struct RediSearch::Spellcheck::Suggestion score=0.0006849315068493151, suggestion="jimmy">,
#<struct RediSearch::Spellcheck::Suggestion score=0.00019569471624266145, suggestion="jim">]
Интеграция с Rails очень проста! Вызовите redi_search
с аргументом ключевого слова schema
изнутри вашей модели. Бывший:
class User < ApplicationRecord
redi_search do
text_field :first , phonetic : "dm:en"
text_field :last , phonetic : "dm:en"
end
end
Это автоматически добавит методы User.search
и User.spellcheck
, которые ведут себя так же, как если бы вы вызывали их в экземпляре Index
.
User.reindex(recreate: false, only: [])
также добавляется и ведет себя аналогично RediSearch::Index#reindex
. Некоторые из различий включают в себя:
Document
s не обязательно передавать в качестве первого параметра. Область search_import
вызывается автоматически, и все записи преобразуются в Document
s.only
параметр, в котором можно указать ограниченное количество полей для обновления. Полезно, если вы меняете схему и вам нужно проиндексировать только определенное поле. При определении схемы вы можете при желании передать ей блок. Если ни один блок не передан, name
будет вызвано в модели для получения значения. Если передается блок, значение поля получается путем вызова блока.
class User < ApplicationRecord
redi_search do
text_field :name do
" #{ first_name } #{ last_name } "
end
end
end
Вы можете переопределить область search_import
в модели, чтобы обеспечить постоянные отношения загрузки при индексировании, или ее можно использовать для ограничения индексируемых записей.
class User < ApplicationRecord
scope :search_import , -> { includes ( :posts ) }
end
При поиске по умолчанию возвращается коллекция Document
s. Вызов #results
в поисковом запросе выполнит поиск, а затем найдет все найденные записи в базе данных и вернет отношение ActiveRecord.
Имя Index
по умолчанию для Index
модели — #{model_name.plural}_#{RediSearch.env}
. Метод redi_search
принимает необязательный аргумент index_prefix
, который добавляется к имени индекса:
class User < ApplicationRecord
redi_search index_prefix : 'prefix' do
text_field :first , phonetic : "dm:en"
text_field :last , phonetic : "dm:en"
end
end
User . search_index . name
# => prefix_users_development
При интеграции RediSearch в модель записи будут автоматически индексироваться после создания и обновления и удаляться из Index
при уничтожении.
Есть еще несколько удобных методов, которые общедоступны:
search_document
RediSearch::Document
remove_from_index
Index
add_to_index
Index
search_index
RediSearch::Index
После проверки репозитория запустите bin/setup
, чтобы установить зависимости. Затем запустите rake test
чтобы запустить как модульные, так и интеграционные тесты. Чтобы запустить их по отдельности, вы можете запустить rake test:unit
или rake test:integration
. Вы также можете запустить bin/console
для получения интерактивного приглашения, которое позволит вам поэкспериментировать.
Чтобы установить этот драгоценный камень на свой локальный компьютер, запустите bundle exec rake install
. Чтобы выпустить новую версию, выполните bin/publish (major|minor|patch)
, который обновит номер версии в version.rb
, создаст тег git для этой версии, отправит git коммиты и теги и отправит файл .gem
в Rubygems. .org и GitHub.
Отчеты об ошибках и запросы на включение приветствуются на GitHub. Этот проект призван стать безопасным и гостеприимным пространством для сотрудничества, и ожидается, что участники будут соблюдать Кодекс поведения участников.
Гем доступен с открытым исходным кодом в соответствии с условиями лицензии MIT.
Ожидается, что все, кто взаимодействует с кодовыми базами проекта RediSearch, системами отслеживания проблем, чатами и списками рассылки, будут следовать кодексу поведения.