redi_search 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +1757 -0
- data/.travis.yml +23 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/Rakefile +12 -0
- data/bin/console +8 -0
- data/bin/publish +58 -0
- data/bin/setup +8 -0
- data/bin/test +7 -0
- data/lib/redi_search/client.rb +68 -0
- data/lib/redi_search/configuration.rb +17 -0
- data/lib/redi_search/document/converter.rb +26 -0
- data/lib/redi_search/document.rb +79 -0
- data/lib/redi_search/error.rb +6 -0
- data/lib/redi_search/index.rb +100 -0
- data/lib/redi_search/log_subscriber.rb +94 -0
- data/lib/redi_search/model.rb +57 -0
- data/lib/redi_search/result/collection.rb +22 -0
- data/lib/redi_search/schema/field.rb +21 -0
- data/lib/redi_search/schema/geo_field.rb +30 -0
- data/lib/redi_search/schema/numeric_field.rb +30 -0
- data/lib/redi_search/schema/tag_field.rb +32 -0
- data/lib/redi_search/schema/text_field.rb +36 -0
- data/lib/redi_search/schema.rb +34 -0
- data/lib/redi_search/search/and_clause.rb +15 -0
- data/lib/redi_search/search/boolean_clause.rb +72 -0
- data/lib/redi_search/search/clauses.rb +89 -0
- data/lib/redi_search/search/highlight_clause.rb +43 -0
- data/lib/redi_search/search/or_clause.rb +21 -0
- data/lib/redi_search/search/term.rb +72 -0
- data/lib/redi_search/search/where_clause.rb +66 -0
- data/lib/redi_search/search.rb +89 -0
- data/lib/redi_search/spellcheck.rb +53 -0
- data/lib/redi_search/version.rb +5 -0
- data/lib/redi_search.rb +40 -0
- data/redi_search.gemspec +48 -0
- metadata +141 -0
data/.travis.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
env:
|
3
|
+
global:
|
4
|
+
- CC_TEST_REPORTER_ID=fec34310f03fd2cc767a85fa23d5102f3dca67b9cc967a48d9940027731394e8
|
5
|
+
sudo: false
|
6
|
+
language: ruby
|
7
|
+
cache: bundler
|
8
|
+
rvm: 2.6.3
|
9
|
+
before_install: gem install bundler -v 1.17.3
|
10
|
+
services: docker
|
11
|
+
before_script:
|
12
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
13
|
+
- chmod +x ./cc-test-reporter
|
14
|
+
- ./cc-test-reporter before-build
|
15
|
+
before_install:
|
16
|
+
- docker run -d -p 6379:6379 redislabs/redisearch:latest --protected-mode no --loadmodule /usr/lib/redis/modules/redisearch.so
|
17
|
+
after_script:
|
18
|
+
- if [[ "$TRAVIS_TEST_RESULT" == 0 ]]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; fi
|
19
|
+
script: bundle exec $COMMAND
|
20
|
+
matrix:
|
21
|
+
include:
|
22
|
+
- env: COMMAND='rake test'
|
23
|
+
- env: COMMAND=rubocop
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at npezza93@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
6
|
+
|
7
|
+
gemspec
|
8
|
+
|
9
|
+
gem "faker"
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
gem "pry"
|
12
|
+
gem "pry-rails"
|
13
|
+
gem "rails", "~> 6.0.0.rc1"
|
14
|
+
gem "rubocop"
|
15
|
+
gem "rubocop-performance"
|
16
|
+
gem "simplecov"
|
17
|
+
gem "sqlite3"
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Nick Pezza
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# RediSearch
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/npezza93/redi_search.svg?branch=master)](https://travis-ci.com/npezza93/redi_search)
|
4
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/c6437acac5684de2549d/test_coverage)](https://codeclimate.com/github/npezza93/redi_search/test_coverage)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/c6437acac5684de2549d/maintainability)](https://codeclimate.com/github/npezza93/redi_search/maintainability)
|
6
|
+
|
7
|
+
A simple, but powerful Ruby wrapper around RediSearch,
|
8
|
+
a search engine on top of Redis.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Firstly, Redis and RediSearch need to be installed.
|
13
|
+
|
14
|
+
You can download Redis from https://redis.io/download, and check out installation instructions [here](https://github.com/antirez/redis#installing-redis). Alternatively, on macOS or Linux you can install via Homebrew.
|
15
|
+
|
16
|
+
To install RediSearch:
|
17
|
+
1. `git clone https://github.com/RedisLabsModules/RediSearch.git`
|
18
|
+
1. `cd RediSearch`
|
19
|
+
1. `mkdir build`
|
20
|
+
1. `cd build`
|
21
|
+
1. `cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo`
|
22
|
+
1. `make`
|
23
|
+
1. `redis-server --loadmodule ./redisearch.so or load the module in your redis.conf`
|
24
|
+
|
25
|
+
You can also checkout [here](https://oss.redislabs.com/redisearch/Quick_Start.html) for more detailed installation instructions. If you already have a redis-server running you can also update your redis.conf file to always load the redisearch module. (On macOS the redis.conf file can be found `/usr/local/etc/redis.conf`)
|
26
|
+
|
27
|
+
|
28
|
+
After Redis and RediSearch are up and running, add this line to your application's Gemfile:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
gem 'redi_search'
|
32
|
+
```
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
```bash
|
36
|
+
❯ bundle
|
37
|
+
````
|
38
|
+
|
39
|
+
Or install it yourself as:
|
40
|
+
```bash
|
41
|
+
❯ gem install redi_search
|
42
|
+
```
|
43
|
+
|
44
|
+
and require it:
|
45
|
+
```ruby
|
46
|
+
require 'redi_search'
|
47
|
+
```
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
### Configuration
|
52
|
+
```ruby
|
53
|
+
RediSearch.configure do |config|
|
54
|
+
config.redis_config = {
|
55
|
+
host: "127.0.0.1",
|
56
|
+
port: "6379"
|
57
|
+
}
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Index
|
62
|
+
|
63
|
+
All actions revolve around indexes. To instantiate one:
|
64
|
+
```ruby
|
65
|
+
RediSearch::Index.new(name_of_index, schema)
|
66
|
+
```
|
67
|
+
The name is a string identifying the index and the schema is the argument is a hash that defines all the fields in an index. Each field can be one of four types: geo, numeric, tag, or text.
|
68
|
+
|
69
|
+
#### Text field options
|
70
|
+
- *weight* (default: 1.0)
|
71
|
+
- Declares the importance of this field when calculating result accuracy. This is a multiplication factor.
|
72
|
+
- Ex: `{ name: { text: { weight: 2 } } }`
|
73
|
+
- *phonetic*
|
74
|
+
- Will perform phonetic matching on field in searches by default. The obligatory {matcher} argument specifies the phonetic algorithm and language used. The following matchers are supported:
|
75
|
+
- dm:en - Double Metaphone for English
|
76
|
+
- dm:fr - Double Metaphone for French
|
77
|
+
- dm:pt - Double Metaphone for Portuguese
|
78
|
+
- dm:es - Double Metaphone for Spanish
|
79
|
+
- Ex: `{ name: { text: { phonetic: 'dm:en' } } }`
|
80
|
+
- *sortable* (default: false)
|
81
|
+
- Allows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).
|
82
|
+
- Ex: `{ name: { text: { sortable: true } } }`
|
83
|
+
- *no_index* (default: false)
|
84
|
+
- Field will not be indexed. This is useful in conjunction with `sortable`, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has `no_index` and doesn't have `sortable`, it will just be ignored by the index.
|
85
|
+
- Ex: `{ name: { text: { no_index: true } } }`
|
86
|
+
- *no_stem* (default: false)
|
87
|
+
- Disable stemming when indexing its values. This may be ideal for things like proper names.
|
88
|
+
- Ex: `{ name: { text: { no_stem: true } } }`
|
89
|
+
|
90
|
+
#### Numeric field options
|
91
|
+
- *sortable* (default: false)
|
92
|
+
- Allows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).
|
93
|
+
- Ex: `{ id: { numeric: { sortable: true } } }`
|
94
|
+
- *no_index* (default: false)
|
95
|
+
- Field will not be indexed. This is useful in conjunction with `sortable`, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has `no_index` and doesn't have `sortable`, it will just be ignored by the index.
|
96
|
+
- Ex: `{ id: { numeric: { no_index: true } } }`
|
97
|
+
|
98
|
+
#### Tag field options
|
99
|
+
- *sortable* (default: false)
|
100
|
+
- Allows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).
|
101
|
+
- Ex: `{ tag: { tag: { sortable: true } } }`
|
102
|
+
- *no_index* (default: false)
|
103
|
+
- Field will not be indexed. This is useful in conjunction with `sortable`, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has `no_index` and doesn't have `sortable`, it will just be ignored by the index.
|
104
|
+
- Ex: `{ tag: { tag: { no_index: true } } }`
|
105
|
+
- *separator* (default: ",")
|
106
|
+
- Indicates how the text contained in the field is to be split into individual tags. The default is ,. The value must be a single character.
|
107
|
+
- Ex: `{ tag: { tag: { separator: ',' } } }`
|
108
|
+
|
109
|
+
#### Geo field options
|
110
|
+
- *sortable* (default: false)
|
111
|
+
- Allows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).
|
112
|
+
- Ex: `{ place: { geo: { sortable: true } } }`
|
113
|
+
- *no_index* (default: false)
|
114
|
+
- Field will not be indexed. This is useful in conjunction with `sortable`, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has `no_index` and doesn't have `sortable`, it will just be ignored by the index.
|
115
|
+
- Ex: `{ place: { geo: { no_index: true } } }`
|
116
|
+
|
117
|
+
Some of the commands that are available on an index are as follows:
|
118
|
+
- *create*
|
119
|
+
- creates the index in the Redis instance, returns a boolean. Has an accompanying bang method that will raise an exception upon failure.
|
120
|
+
- *drop*
|
121
|
+
- drops the index from the Redis instance, returns a boolean. Has an accompanying bang method that will raise an exception upon failure.
|
122
|
+
- *exist?*
|
123
|
+
- Returns a boolean signifying indexes existence.
|
124
|
+
- *info*
|
125
|
+
- Returns a hash with all the information for the index
|
126
|
+
- *fields*
|
127
|
+
- Returns an array of field names in the index
|
128
|
+
- *add*
|
129
|
+
- Takes an object as the first argument and a second argument that is a score (a value between 0.0 and 1.0). The object passed must respond to all the fields in the schema. Has an accompanying bang method that will raise an exception upon failure.
|
130
|
+
- *add_multiple!*
|
131
|
+
- Takes an array of objects that respond to all the fields in the schema. This provides a more performant way to add multiple documents to the index.
|
132
|
+
|
133
|
+
### Searching
|
134
|
+
|
135
|
+
Searching is initiated off an `RediSearch::Index` object.
|
136
|
+
```ruby
|
137
|
+
main ❯ index = RediSearch::Index.new("user_idx", name: { text: { phonetic: "dm:en" } })
|
138
|
+
main ❯ index.search("john")
|
139
|
+
RediSearch (1.1ms) FT.SEARCH user_idx `john`
|
140
|
+
=> [#<RediSearch::Document:0x00007f862e241b78 first: "Gene", last: "Volkman", document_id: "10039">,
|
141
|
+
#<RediSearch::Document:0x00007f862e2417b8 first: "Jeannie", last: "Ledner", document_id: "9998">]
|
142
|
+
```
|
143
|
+
- Simple phrase query - hello AND world
|
144
|
+
```ruby
|
145
|
+
index.search("hello").and("world")
|
146
|
+
```
|
147
|
+
- Exact phrase query - hello FOLLOWED BY world
|
148
|
+
```ruby
|
149
|
+
index.search("hello world")
|
150
|
+
```
|
151
|
+
- Union: documents containing either hello OR world
|
152
|
+
```ruby
|
153
|
+
index.search("hello").or("world")
|
154
|
+
```
|
155
|
+
- Not: documents containing hello but not world
|
156
|
+
```ruby
|
157
|
+
index.search("hello").and.not("world")
|
158
|
+
```
|
159
|
+
|
160
|
+
All terms support a few options that can be applied.
|
161
|
+
|
162
|
+
- Prefix Queries: match all terms starting with a prefix
|
163
|
+
```ruby
|
164
|
+
index.search("hel", prefix: true)
|
165
|
+
index.search("hello worl", prefix: true)
|
166
|
+
index.search("hel", prefix: true).and("worl", prefix: true)
|
167
|
+
index.search("hello").and.not("worl", prefix: true)
|
168
|
+
```
|
169
|
+
|
170
|
+
- Optional terms with higher priority to ones containing more matches
|
171
|
+
```ruby
|
172
|
+
index.search("foo").and("bar", optional: true).and("baz", optional: true)
|
173
|
+
```
|
174
|
+
|
175
|
+
- Fuzzy matches are performed based on Levenshtein distance (LD). The maximum Levenshtein distance supported is 3.
|
176
|
+
```ruby
|
177
|
+
index.search("zuchini", fuzziness: 1)
|
178
|
+
```
|
179
|
+
|
180
|
+
- Complex intersections and unions
|
181
|
+
```ruby
|
182
|
+
# Intersection of unions
|
183
|
+
index.search(index.search("hello").or("halo")).and(index.search("world").or("werld"))
|
184
|
+
# Negation of union
|
185
|
+
index.search("hello").and.not(index.search("world").or("werld"))
|
186
|
+
# Union inside phrase
|
187
|
+
index.search("hello").and(index.search("world").or("werld"))
|
188
|
+
```
|
189
|
+
|
190
|
+
### Rails Integration
|
191
|
+
|
192
|
+
Integration with Rails is on by default! All you have to do is add the following to the model you want to search:
|
193
|
+
```ruby
|
194
|
+
class User < ApplicationRecord
|
195
|
+
redi_search schema: {
|
196
|
+
first: { text: { phonetic: "dm:en" } },
|
197
|
+
last: { text: { phonetic: "dm:en" } }
|
198
|
+
}
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
This will automatically add `User.search` and `User.reindex` methods. You can also use `User.redi_search_index` to get the `RediSearch::Index` instance. `User.reindex` will first `drop` the index if it exists, create the index with the given schema, and then add all the records to the index.
|
203
|
+
|
204
|
+
## Development
|
205
|
+
|
206
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. You can also start a rails console if you `cd` into `test/dummy`.
|
207
|
+
|
208
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, execute `bin/publish (major|minor|patch)` which will update the version number in `version.rb`, create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
209
|
+
|
210
|
+
## Contributing
|
211
|
+
|
212
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/npezza93/redi_search. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
213
|
+
|
214
|
+
## License
|
215
|
+
|
216
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
217
|
+
|
218
|
+
## Code of Conduct
|
219
|
+
|
220
|
+
Everyone interacting in the RediSearch project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/npezza93/redi_search/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/publish
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "pathname"
|
5
|
+
require "fileutils"
|
6
|
+
require_relative "../lib/redi_search/version"
|
7
|
+
|
8
|
+
# path to your application root.
|
9
|
+
APP_ROOT = Pathname.new File.expand_path("..", __dir__)
|
10
|
+
MASTER_CHECK = <<~MASTER_CHECK
|
11
|
+
if [ $(git symbolic-ref --short -q HEAD) != 'master' ];
|
12
|
+
then exit 1;
|
13
|
+
fi
|
14
|
+
MASTER_CHECK
|
15
|
+
VERSION_TYPES = %w(major minor patch).freeze
|
16
|
+
|
17
|
+
def system!(*args)
|
18
|
+
system(*args) || abort("\n== Command #{args} failed ==")
|
19
|
+
end
|
20
|
+
|
21
|
+
abort("\n== Version Type incorrect ==") unless VERSION_TYPES.include?(ARGV[0])
|
22
|
+
|
23
|
+
abort("\n== Not on master") unless system(MASTER_CHECK)
|
24
|
+
|
25
|
+
current_version = RediSearch::VERSION.split(".").map(&:to_i)
|
26
|
+
|
27
|
+
case ARGV[0]
|
28
|
+
when "major"
|
29
|
+
current_version[0] += 1
|
30
|
+
current_version[1] = 0
|
31
|
+
current_version[2] = 0
|
32
|
+
when "minor"
|
33
|
+
current_version[1] += 1
|
34
|
+
current_version[2] = 0
|
35
|
+
when "patch"
|
36
|
+
current_version[2] += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
FileUtils.chdir APP_ROOT do
|
40
|
+
contents = <<~FILE
|
41
|
+
# frozen_string_literal: true
|
42
|
+
|
43
|
+
module RediSearch
|
44
|
+
VERSION = "#{current_version.join('.')}"
|
45
|
+
end
|
46
|
+
FILE
|
47
|
+
|
48
|
+
puts "== Updating version to #{current_version.join('.')} =="
|
49
|
+
File.write("lib/redi_search/version.rb", contents)
|
50
|
+
|
51
|
+
system! "git add lib/redi_search/version.rb"
|
52
|
+
|
53
|
+
puts "== Committing updated files =="
|
54
|
+
system! "git commit -m 'Version bump to #{current_version.join('.')}'"
|
55
|
+
|
56
|
+
puts "== Tagging release =="
|
57
|
+
system! "bundle exec rake release"
|
58
|
+
end
|
data/bin/setup
ADDED
data/bin/test
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Client
|
7
|
+
class Response < SimpleDelegator
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
|
11
|
+
super(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ok?
|
15
|
+
if response.is_a? String
|
16
|
+
response == "OK"
|
17
|
+
elsif response.is_a? Array
|
18
|
+
response.all? { |pipeline_response| pipeline_response == "OK" }
|
19
|
+
else
|
20
|
+
response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :response
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(redis_config)
|
30
|
+
@redis = Redis.new(redis_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
def call!(command, *params)
|
34
|
+
instrument(command.downcase, query: [command, params]) do
|
35
|
+
send_command(command, *params)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def pipelined
|
40
|
+
Response.new(redis.pipelined do
|
41
|
+
instrument("pipeline", query: ["begin pipeline"])
|
42
|
+
yield
|
43
|
+
instrument("pipeline", query: ["finish pipeline"])
|
44
|
+
end)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :redis
|
50
|
+
|
51
|
+
def send_command(command, *params)
|
52
|
+
Response.new(redis.call("FT.#{command}", *params))
|
53
|
+
end
|
54
|
+
|
55
|
+
def instrument(action, payload)
|
56
|
+
block =
|
57
|
+
if block_given?
|
58
|
+
Proc.new
|
59
|
+
else
|
60
|
+
proc {}
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveSupport::Notifications.instrument(
|
64
|
+
"#{action}.redi_search", { name: "RediSearch" }.merge(payload), &block
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redi_search/client"
|
4
|
+
|
5
|
+
module RediSearch
|
6
|
+
class Configuration
|
7
|
+
attr_writer :redis_config
|
8
|
+
|
9
|
+
def client
|
10
|
+
@client ||= Client.new(redis_config)
|
11
|
+
end
|
12
|
+
|
13
|
+
def redis_config
|
14
|
+
@redis_config ||= { host: "127.0.0.1", port: "6379" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Document
|
5
|
+
class Converter
|
6
|
+
def initialize(index, record)
|
7
|
+
@index = index
|
8
|
+
@record = record
|
9
|
+
end
|
10
|
+
|
11
|
+
def document
|
12
|
+
Document.new(
|
13
|
+
index,
|
14
|
+
record.id,
|
15
|
+
index.schema.fields.map do |field|
|
16
|
+
[field.to_s, record.public_send(field)]
|
17
|
+
end.to_h
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :index, :record
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RediSearch
|
4
|
+
class Document
|
5
|
+
class << self
|
6
|
+
def get(index, document_id)
|
7
|
+
response = RediSearch.client.call!("GET", index.name, document_id)
|
8
|
+
|
9
|
+
return if response.blank?
|
10
|
+
|
11
|
+
new(index, document_id, Hash[*response])
|
12
|
+
end
|
13
|
+
|
14
|
+
def mget(index, *document_ids)
|
15
|
+
document_ids.zip(
|
16
|
+
RediSearch.client.call!("MGET", index.name, *document_ids)
|
17
|
+
).map do |document|
|
18
|
+
next if document[1].blank?
|
19
|
+
|
20
|
+
new(index, document[0], Hash[*document[1]])
|
21
|
+
end.compact
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :document_id
|
26
|
+
|
27
|
+
def initialize(index, document_id, fields)
|
28
|
+
@index = index
|
29
|
+
@document_id = document_id
|
30
|
+
@to_a = []
|
31
|
+
|
32
|
+
schema_fields.each do |field|
|
33
|
+
@to_a.push([field, fields[field]])
|
34
|
+
instance_variable_set(:"@#{field}", fields[field])
|
35
|
+
define_singleton_method field do
|
36
|
+
fields[field]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def del
|
42
|
+
client.call!("DEL", index.name, document_id).ok?
|
43
|
+
end
|
44
|
+
|
45
|
+
#:nocov:
|
46
|
+
def pretty_print(printer) # rubocop:disable Metrics/MethodLength
|
47
|
+
printer.object_address_group(self) do
|
48
|
+
printer.seplist(
|
49
|
+
schema_fields.append("document_id"), proc { printer.text "," }
|
50
|
+
) do |field_name|
|
51
|
+
printer.breakable " "
|
52
|
+
printer.group(1) do
|
53
|
+
printer.text field_name
|
54
|
+
printer.text ":"
|
55
|
+
printer.breakable
|
56
|
+
printer.pp public_send(field_name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
#:nocov:
|
62
|
+
|
63
|
+
def schema_fields
|
64
|
+
@schema_fields ||= index.schema.fields.map(&:to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_a
|
68
|
+
@to_a.flatten
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_reader :index
|
74
|
+
|
75
|
+
def client
|
76
|
+
RediSearch.client
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|