Um wrapper Ruby simples, mas poderoso, em torno do RediSearch, um mecanismo de pesquisa baseado no Redis.
Em primeiro lugar, o Redis e o RediSearch precisam estar instalados.
Você pode baixar o Redis em https://redis.io/download e verificar as instruções de instalação aqui. Alternativamente, no macOS ou Linux você pode instalar via Homebrew.
Para instalar o RediSearch, confira https://oss.redislabs.com/redisearch/Quick_Start.html. Depois de construir o RediSearch, se não estiver usando o Docker, você pode atualizar seu arquivo redis.conf para sempre carregar o módulo RediSearch com loadmodule /path/to/redisearch.so
. (No macOS, o arquivo redis.conf pode ser encontrado em /usr/local/etc/redis.conf
)
Depois que o Redis e o RediSearch estiverem funcionando, adicione a seguinte linha ao seu Gemfile:
gem 'redi_search'
E então:
❯ bundle
Ou instale você mesmo:
❯ gem install redi_search
e exigi-lo:
require 'redi_search'
Depois que o gem estiver instalado e necessário, você precisará configurá-lo com a configuração do Redis. Se você estiver no Rails, isso deve estar em um inicializador ( config/initializers/redi_search.rb
).
RediSearch . configure do | config |
config . redis_config = {
host : "127.0.0.1" ,
port : "6379"
}
end
RediSearch gira em torno de um índice de pesquisa, então vamos começar definindo o que é um índice de pesquisa. De acordo com Swiftype:
Um índice de pesquisa é um conjunto de dados estruturados aos quais um mecanismo de pesquisa se refere ao procurar resultados relevantes para uma consulta específica. Os índices são uma peça crítica de qualquer sistema de busca, pois devem ser adaptados ao método específico de recuperação de informações do algoritmo do mecanismo de busca. Desta forma, o algoritmo e o índice estão inextricavelmente ligados um ao outro. Índice também pode ser usado como verbo (indexação), referindo-se ao processo de coleta de dados não estruturados de um site em um formato estruturado adaptado ao algoritmo do mecanismo de busca.
Uma maneira de pensar sobre índices é considerar a seguinte analogia entre uma infra-estrutura de pesquisa e um sistema de arquivo de escritório. Imagine que você entrega a um estagiário uma pilha de milhares de pedaços de papel (documentos) e pede para ele organizar esses pedaços de papel em um arquivo (índice) para ajudar a empresa a encontrar informações com mais eficiência. O estagiário terá primeiro que examinar os papéis e ter uma noção de todas as informações contidas neles, depois terá que decidir sobre um sistema para organizá-los no arquivo e, finalmente, precisará decidir qual é o maneira mais eficaz de pesquisar e selecionar os arquivos quando eles estiverem no gabinete. Neste exemplo, o processo de organização e arquivamento dos artigos corresponde ao processo de indexação do conteúdo do site, e o método de busca nesses arquivos organizados e localização dos mais relevantes corresponde ao algoritmo de busca.
Isso define os campos e as propriedades desses campos no índice. Um esquema é uma DSL simples. Cada campo pode ser de quatro tipos: geográfico, numérico, tag ou texto e pode ter várias opções. Um exemplo simples de esquema é:
RediSearch :: Schema . new do
text_field :first_name
text_field :last_name
end
As opções suportadas para cada tipo são as seguintes:
Sem opções: text_field :name
text_field :name, weight: 2
text_field :name, phonetic: 'dm:en'
text_field :name, sortable: true
sortable
, para criar campos cuja atualização usando PARTIAL não causará a reindexação completa do documento. Se um campo tiver no_index
e não tiver sortable
, ele será simplesmente ignorado pelo índice.text_field :name, no_index: true
text_feidl :name, no_stem: true
Sem opções: numeric_field :price
numeric_field :id, sortable: true
sortable
, para criar campos cuja atualização usando PARTIAL não causará a reindexação completa do documento. Se um campo tiver no_index
e não tiver sortable
, ele será simplesmente ignorado pelo índice.numeric_field :id, no_index: true
Sem opções: tag_field :tag
tag_field :tag, sortable: true
sortable
, para criar campos cuja atualização usando PARTIAL não causará a reindexação completa do documento. Se um campo tiver no_index
e não tiver sortable
, ele será simplesmente ignorado pelo índice.tag_field :tag, no_index: true
tag_field :tag, separator: ','
Sem opções: geo_field :place
geo_field :place, sortable: true
sortable
, para criar campos cuja atualização usando PARTIAL não causará a reindexação completa do documento. Se um campo tiver no_index
e não tiver sortable
, ele será simplesmente ignorado pelo índice.geo_field :place, no_index: true
Um Document
é a representação Ruby de um hash Redis.
Você pode buscar um Document
usando métodos de classe .get
.
get(index, document_id)
busca um único Document
em um Index
para um determinado document_id
. Você também pode criar uma instância Document
usando o método de classe .for_object(index, record, only: [])
. É necessária uma instância Index
e um objeto Ruby. Esse objeto deve responder a todos os campos especificados no Schema
do Index
. aceita only
uma matriz de campos do esquema e limita os campos que são passados para o Document
.
Depois de ter uma instância de um Document
, ele responde a todos os campos especificados no Schema
do Index
como métodos e document_id
. document_id
é automaticamente anexado aos nomes do Index
, a menos que já seja para garantir a exclusividade. Anexamos o nome Index
porque se você tiver dois Document
s com o mesmo id em Index
s diferentes, não queremos que os Document
s se substituam. Existe também um método #document_id_without_index
que remove o nome do índice prefixado.
Finalmente existe um método #del
que removerá o Document
do Index
.
Para inicializar um Index
, passe o nome do Index
como uma string ou símbolo e o bloco Schema
.
RediSearch :: Index . new ( name_of_index ) do
text_field :foobar
end
create
false
se o índice já existir. Aceita algumas opções:max_text_fields: #{true || false}
add_field
.no_offsets: #{true || false}
no_highlight
.temporary: #{seconds}
seconds
de inatividade. O temporizador de inatividade interno é redefinido sempre que o índice é pesquisado ou adicionado. Como esses índices são leves, você pode criar milhares deles sem implicações negativas no desempenho.no_highlight: #{true || false}
no_highlight
também está implícito em no_offsets
.no_fields: #{true || false}
no_frequencies: #{true || false}
drop(keep_docs: false)
Index
da instância do Redis e retorna um booleano. Possui um método bang que irá gerar uma exceção em caso de falha. Retornará false
se o Index
já tiver sido eliminado. Utiliza uma palavra-chave de opção arg, keep_docs
, que por padrão removerá todos os hashes de documentos no Redis.exist?
Index
.info
Index
.fields
Index
.add(document)
Document
. Possui um método bang que irá gerar uma exceção em caso de falha.add_multiple(documents)
Document
. Isso fornece uma maneira com melhor desempenho de adicionar vários documentos ao Index
. Aceita as mesmas opções de add
.del(document)
Document
do Index
.document_count
Document
s no Index
add_field(name, type, **options, &block)
Index
.index.add_field(:first_name, :text, phonetic: "dm:en")
reindex(documents, recreate: false)
recreate
for true
o Index
será descartado e recriado A pesquisa é iniciada em uma instância RediSearch::Index
com cláusulas que podem ser encadeadas. Ao pesquisar, é retornado um array de Document
s que possui métodos de leitura públicos para todos os campos do esquema.
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">]
Consulta de frase simples - hello AND world
index . search ( "hello" ) . and ( "world" )
Consulta de frase exata - hello FOLLOWED BY world
index . search ( "hello world" )
Consulta de união - hello OR world
index . search ( "hello" ) . or ( "world" )
Consulta de negação - hello AND NOT world
index . search ( "hello" ) . and . not ( "world" )
Interseções e uniões complexas:
# 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" ) )
Todos os termos suportam algumas opções que podem ser aplicadas.
Termos de prefixo : correspondem a todos os termos que começam com um prefixo. ( like term%
em 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 )
Termos opcionais : os documentos que contêm os termos opcionais terão uma classificação mais elevada do que aqueles sem
index . search ( "foo" ) . and ( "bar" , optional : true ) . and ( "baz" , optional : true )
Termos difusos : as correspondências são realizadas com base na distância de Levenshtein (LD). A distância máxima de Levenshtein suportada é 3.
index . search ( "zuchini" , fuzziness : 1 )
Os termos de pesquisa também podem ter como escopo campos específicos usando uma cláusula 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" ) )
A pesquisa por campos numéricos varia:
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
. Garantimos que os termos da consulta apareçam no Document
na mesma ordem que na consulta, independentemente dos deslocamentos entre eles.no_content
Document
e não o conteúdo. Isso é útil se RediSearch estiver sendo usado em um modelo Rails onde os atributos Document
não importam e estiver sendo convertido em objetos ActiveRecord
.language(language)
Document
s em chinês, deverá ser definido como chinês para tokenizar adequadamente os termos da consulta. Se um idioma não suportado for enviado, o comando retornará um erro.sort_by(field, order: :asc)
:asc
ou :desc
limit(num, offset = 0)
num
especificado no offset
. O limite padrão é definido como 10
.count
Document
s encontrados na consulta de pesquisahighlight(fields: [], opening_tag: "<b>", closing_tag: "</b>")
fields
são uma matriz de campos a serem destacados.verbatim
no_stop_words
with_scores
Document
. Isso pode ser usado para mesclar resultados de várias instâncias. Isso adicionará um método score
às instâncias Document
retornadas.return(*fields)
Document
serão retornados.explain
A verificação ortográfica é iniciada em uma instância RediSearch::Index
e fornece sugestões para termos de pesquisa com erros ortográficos. É necessário um argumento distance
opcional que é a distância máxima de Levenshtein para sugestões ortográficas. Ele retorna uma matriz onde cada elemento contém sugestões para cada termo de pesquisa e uma pontuação normalizada com base em suas ocorrências no índice.
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">]
A integração com Rails é super fácil! Chame redi_search
com o argumento da palavra-chave schema
de dentro do seu modelo. Ex:
class User < ApplicationRecord
redi_search do
text_field :first , phonetic : "dm:en"
text_field :last , phonetic : "dm:en"
end
end
Isso adicionará automaticamente os métodos User.search
e User.spellcheck
que se comportam da mesma forma como se você os chamasse em uma instância Index
.
User.reindex(recreate: false, only: [])
também é adicionado e se comporta de maneira semelhante a RediSearch::Index#reindex
. Algumas das diferenças incluem:
Document
s não precisam ser passados como primeiro parâmetro. O escopo search_import
é chamado automaticamente e todos os registros são convertidos em Document
s.only
um parâmetro opcional onde você pode especificar um número limitado de campos para atualizar. Útil se você alterar o esquema e precisar indexar apenas um campo específico. Ao definir o esquema, você pode opcionalmente passar um bloco para ele. Se nenhum bloco for passado, o name
será chamado no modelo para obter o valor. Se um bloco for passado, o valor do campo será obtido chamando o bloco.
class User < ApplicationRecord
redi_search do
text_field :name do
" #{ first_name } #{ last_name } "
end
end
end
Você pode substituir o escopo search_import
no modelo para relacionamentos de carga antecipada durante a indexação ou pode ser usado para limitar os registros a serem indexados.
class User < ApplicationRecord
scope :search_import , -> { includes ( :posts ) }
end
Ao pesquisar, por padrão, uma coleção de Document
s é retornada. Chamar #results
na consulta de pesquisa executará a pesquisa e, em seguida, procurará todos os registros encontrados no banco de dados e retornará uma relação ActiveRecord.
O nome Index
padrão para Index
de modelo é #{model_name.plural}_#{RediSearch.env}
. O método redi_search
usa um argumento index_prefix
opcional que é anexado ao nome do índice:
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
Ao integrar o RediSearch em um modelo, os registros serão indexados automaticamente após a criação e atualização e serão removidos do Index
após a destruição.
Existem mais alguns métodos de conveniência que estão disponíveis publicamente:
search_document
RediSearch::Document
remove_from_index
Index
add_to_index
Index
search_index
RediSearch::Index
Depois de verificar o repositório, execute bin/setup
para instalar as dependências. Em seguida, execute rake test
para executar os testes de unidade e de integração. Para executá-los individualmente você pode executar rake test:unit
ou rake test:integration
. Você também pode executar bin/console
para obter um prompt interativo que permitirá experimentar.
Para instalar esta jóia em sua máquina local, execute bundle exec rake install
. Para lançar uma nova versão, execute bin/publish (major|minor|patch)
que atualizará o número da versão em version.rb
, crie uma tag git para a versão, envie commits e tags git e envie o arquivo .gem
para rubygems .org e GitHub.
Relatórios de bugs e solicitações pull são bem-vindos no GitHub. Este projeto pretende ser um espaço seguro e acolhedor para colaboração, e espera-se que os contribuidores sigam o código de conduta do Contributor Covenant.
A gema está disponível como código aberto sob os termos da licença MIT.
Espera-se que todos que interagem nas bases de código, rastreadores de problemas, salas de bate-papo e listas de discussão do projeto RediSearch sigam o código de conduta.