typesensual 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +7 -5
- data/README.md +24 -19
- data/lib/tasks/typesensual.rake +10 -0
- data/lib/typesensual/callbacks.rb +2 -3
- data/lib/typesensual/config.rb +13 -3
- data/lib/typesensual/field.rb +1 -1
- data/lib/typesensual/index.rb +16 -11
- data/lib/typesensual/rake_helper.rb +19 -0
- data/lib/typesensual/schema.rb +14 -1
- data/lib/typesensual/search.rb +48 -0
- data/lib/typesensual/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ea8c4aeb857be069ae139f07adf94182acb7cf65e4fc3d21efdaca0522227a8
|
4
|
+
data.tar.gz: de66e132cc8b81b58201fc517847e4b5c0bf3644abb1c79eafa56bc696fbb7ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a29f224543a07478a3fc61e23edd3c9d3bb52995740c266f43400dd33dcf96b7e6d0a9ee54f59e7d6500c1ce3a33be1d0cd4ef590e5f716a6ade44bc22eb6c66
|
7
|
+
data.tar.gz: e05a23f4d02ecc1242ba7b2ffdae7aca2a2f8c537a838186532e18b87a97b22c76385fe05a7b6c917d291f02461487abb378ab2f649c99cf0cd2c7587d0f6f7c
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
typesensual (0.
|
4
|
+
typesensual (0.2.0)
|
5
5
|
activesupport (>= 6.1.5)
|
6
6
|
paint (>= 2.0.0)
|
7
7
|
typesense (>= 0.13.0)
|
@@ -9,7 +9,7 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (7.0.
|
12
|
+
activesupport (7.0.6)
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
14
|
i18n (>= 1.6, < 2)
|
15
15
|
minitest (>= 5.1)
|
@@ -22,11 +22,11 @@ GEM
|
|
22
22
|
ethon (0.16.0)
|
23
23
|
ffi (>= 1.15.0)
|
24
24
|
ffi (1.15.5)
|
25
|
-
i18n (1.
|
25
|
+
i18n (1.14.1)
|
26
26
|
concurrent-ruby (~> 1.0)
|
27
27
|
json (2.6.3)
|
28
|
-
minitest (5.
|
29
|
-
oj (3.
|
28
|
+
minitest (5.19.0)
|
29
|
+
oj (3.15.1)
|
30
30
|
paint (2.3.0)
|
31
31
|
parallel (1.23.0)
|
32
32
|
parser (3.2.2.1)
|
@@ -91,8 +91,10 @@ GEM
|
|
91
91
|
yard (0.9.34)
|
92
92
|
|
93
93
|
PLATFORMS
|
94
|
+
ruby
|
94
95
|
x86_64-darwin-19
|
95
96
|
x86_64-darwin-22
|
97
|
+
x86_64-linux
|
96
98
|
|
97
99
|
DEPENDENCIES
|
98
100
|
commonmarker
|
data/README.md
CHANGED
@@ -50,6 +50,14 @@ Typesensual.configure do |config|
|
|
50
50
|
end
|
51
51
|
```
|
52
52
|
|
53
|
+
Alternatively you can configure with env variables:
|
54
|
+
|
55
|
+
```env
|
56
|
+
TYPESENSUAL_NODES=http://node1:8108,http://node2:8108,http://node3:8108
|
57
|
+
TYPESENSUAL_API_KEY=xyz
|
58
|
+
TYPESENSUAL_ENV=test
|
59
|
+
```
|
60
|
+
|
53
61
|
### Creating your first index
|
54
62
|
|
55
63
|
Once the client is configured, you can create your first index. This is done by creating a subclass
|
@@ -57,7 +65,7 @@ of `Typesensual::Index` and defining your schema and how to load the data. For e
|
|
57
65
|
following index might be used to index movies from an ActiveRecord model:
|
58
66
|
|
59
67
|
```ruby
|
60
|
-
# app/indices/
|
68
|
+
# app/indices/movies_index.rb
|
61
69
|
class MoviesIndex < Typesensual::Index
|
62
70
|
# The schema of the collection
|
63
71
|
schema do
|
@@ -70,24 +78,7 @@ class MoviesIndex < Typesensual::Index
|
|
70
78
|
field 'genres', type: 'string[]', facet: true
|
71
79
|
end
|
72
80
|
|
73
|
-
def
|
74
|
-
movie = Movie.find(id).includes(:genres)
|
75
|
-
|
76
|
-
{
|
77
|
-
id: movie.id,
|
78
|
-
title: movie.title,
|
79
|
-
release_date: {
|
80
|
-
year: movie.release_date.year,
|
81
|
-
month: movie.release_date.month,
|
82
|
-
day: movie.release_date.day
|
83
|
-
},
|
84
|
-
average_rating: movie.average_rating,
|
85
|
-
user_count: movie.user_count,
|
86
|
-
genres: movie.genres.map(&:name)
|
87
|
-
}
|
88
|
-
end
|
89
|
-
|
90
|
-
def index_many(ids)
|
81
|
+
def index(ids)
|
91
82
|
Movies.where(id: ids).includes(:genres).find_each do |movie|
|
92
83
|
yield {
|
93
84
|
id: movie.id,
|
@@ -106,6 +97,20 @@ class MoviesIndex < Typesensual::Index
|
|
106
97
|
end
|
107
98
|
```
|
108
99
|
|
100
|
+
### Integrating with your model
|
101
|
+
|
102
|
+
If you use ActiveRecord, there's a set of premade callbacks you can use:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Movie < ApplicationRecord
|
106
|
+
after_commit MoviesIndex.ar_callbacks, on: %i[create update destroy]
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
You're free to use these callbacks as-is, or you can use them as a starting point for your own
|
111
|
+
integration. They're just calling `MoviesIndex.index_one` and `MoviesIndex.remove_one` under the
|
112
|
+
hood, so you can do the same in your own callbacks or outside of ActiveRecord.
|
113
|
+
|
109
114
|
### Loading data into your index
|
110
115
|
|
111
116
|
Once you have defined your index, you can load data into it and update the alias to point to the
|
data/lib/tasks/typesensual.rake
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'typesensual/rake_helper'
|
4
|
+
|
3
5
|
namespace :typesensual do
|
4
6
|
desc 'List typesensual indices and their collections'
|
5
7
|
task list: :environment do
|
@@ -21,4 +23,12 @@ namespace :typesensual do
|
|
21
23
|
model: args[:model]
|
22
24
|
)
|
23
25
|
end
|
26
|
+
|
27
|
+
desc 'Delete a version of an index'
|
28
|
+
task :drop_version, %i[index version] => :environment do |_, args|
|
29
|
+
Typesensual::RakeHelper.drop_version(
|
30
|
+
index: args[:index],
|
31
|
+
version: args[:version]
|
32
|
+
)
|
33
|
+
end
|
24
34
|
end
|
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
class Typesensual
|
4
4
|
class Callbacks
|
5
|
-
def initialize(index
|
5
|
+
def initialize(index)
|
6
6
|
@index = index
|
7
|
-
@should_update = should_update
|
8
7
|
end
|
9
8
|
|
10
9
|
def after_create_commit(record)
|
@@ -12,7 +11,7 @@ class Typesensual
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def after_update_commit(record)
|
15
|
-
@
|
14
|
+
@index.index_one(record.id)
|
16
15
|
end
|
17
16
|
|
18
17
|
def after_destroy_commit(record)
|
data/lib/typesensual/config.rb
CHANGED
@@ -2,21 +2,31 @@
|
|
2
2
|
|
3
3
|
class Typesensual
|
4
4
|
class Config
|
5
|
-
|
6
|
-
attr_writer :env, :client
|
5
|
+
attr_writer :env, :client, :nodes, :api_key
|
7
6
|
|
8
7
|
def initialize(&block)
|
9
8
|
yield self if block
|
10
9
|
end
|
11
10
|
|
12
11
|
def env
|
13
|
-
@env ||= (defined?(Rails) ? Rails.env : nil)
|
12
|
+
@env ||= ENV.fetch('TYPESENSUAL_ENV', (defined?(Rails) ? Rails.env : nil))
|
14
13
|
end
|
15
14
|
|
16
15
|
def client
|
17
16
|
@client ||= Typesense::Client.new(connection_options)
|
18
17
|
end
|
19
18
|
|
19
|
+
def nodes
|
20
|
+
@nodes ||= ENV['TYPESENSUAL_NODES']&.split(',')&.map do |node|
|
21
|
+
node_uri = URI.parse(node)
|
22
|
+
{ port: node_uri.port, host: node_uri.host, protocol: node_uri.scheme }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def api_key
|
27
|
+
@api_key ||= ENV.fetch('TYPESENSUAL_API_KEY', nil)
|
28
|
+
end
|
29
|
+
|
20
30
|
private
|
21
31
|
|
22
32
|
def connection_options
|
data/lib/typesensual/field.rb
CHANGED
data/lib/typesensual/index.rb
CHANGED
@@ -21,6 +21,12 @@ class Typesensual
|
|
21
21
|
class Index
|
22
22
|
include StateHelpers
|
23
23
|
|
24
|
+
def self.inherited(subclass)
|
25
|
+
super
|
26
|
+
# Copy the schema from the parent class to the subclass
|
27
|
+
subclass.instance_variable_set(:@schema, @schema&.dup)
|
28
|
+
end
|
29
|
+
|
24
30
|
# Get or set the name for this index
|
25
31
|
#
|
26
32
|
# @overload index_name(value)
|
@@ -94,7 +100,9 @@ class Typesensual
|
|
94
100
|
#
|
95
101
|
# See {Schema} for more information
|
96
102
|
def self.schema(&block)
|
97
|
-
@schema
|
103
|
+
@schema ||= Typesensual::Schema.new
|
104
|
+
@schema.instance_eval(&block) if block
|
105
|
+
@schema
|
98
106
|
end
|
99
107
|
|
100
108
|
# Updates the alias to point to the given collection name
|
@@ -122,28 +130,25 @@ class Typesensual
|
|
122
130
|
update_alias!(collection)
|
123
131
|
end
|
124
132
|
|
125
|
-
# The method to implement to index *one* record.
|
126
|
-
#
|
127
|
-
# @return [Hash] the document to upsert in Typesense
|
128
|
-
def index_one(_id); end
|
129
|
-
|
130
133
|
def self.index_one(id, collection: self.collection)
|
131
|
-
|
134
|
+
new.index([id]) do |record|
|
135
|
+
collection.insert_one!(record)
|
136
|
+
end
|
132
137
|
end
|
133
138
|
|
134
139
|
# The method to implement to index *many* records
|
135
|
-
#
|
140
|
+
# This method should yield successive records to index
|
136
141
|
#
|
137
142
|
# @yield [Hash] a document to upsert in Typesense
|
138
|
-
def
|
143
|
+
def index(ids)
|
139
144
|
ids.each do |id|
|
140
|
-
yield
|
145
|
+
yield({ id: id })
|
141
146
|
end
|
142
147
|
end
|
143
148
|
|
144
149
|
def self.index_many(ids, collection: self.collection, batch_size: 100)
|
145
150
|
collection.insert_many!(
|
146
|
-
new.enum_for(:
|
151
|
+
new.enum_for(:index, ids),
|
147
152
|
batch_size: batch_size
|
148
153
|
)
|
149
154
|
end
|
@@ -98,6 +98,25 @@ class Typesensual
|
|
98
98
|
created_at: new_coll.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
99
99
|
)
|
100
100
|
end
|
101
|
+
|
102
|
+
# Drop a version of an index
|
103
|
+
#
|
104
|
+
# @param index [String] The name of the index to remove a version from
|
105
|
+
# @param version [String] The version to remove
|
106
|
+
# @example
|
107
|
+
# rake typesensual:drop_version[FooIndex,1]
|
108
|
+
def drop_version(index:, version:, output: $stdout)
|
109
|
+
index = index.safe_constantize
|
110
|
+
collection = index.collection_for(version: version)
|
111
|
+
|
112
|
+
collection.delete!
|
113
|
+
|
114
|
+
output.printf(
|
115
|
+
"==> Dropped version %<version>s of %<index>s\n",
|
116
|
+
version: version,
|
117
|
+
index: index.name
|
118
|
+
)
|
119
|
+
end
|
101
120
|
end
|
102
121
|
end
|
103
122
|
end
|
data/lib/typesensual/schema.rb
CHANGED
@@ -4,8 +4,21 @@ require 'typesensual/field'
|
|
4
4
|
|
5
5
|
class Typesensual
|
6
6
|
class Schema
|
7
|
+
# Duplicate fields from the original
|
8
|
+
def initialize_copy(original)
|
9
|
+
%w[
|
10
|
+
@fields
|
11
|
+
@token_separators
|
12
|
+
@symbols_to_index
|
13
|
+
@default_sorting_field
|
14
|
+
@enable_nested_fields
|
15
|
+
].each do |var|
|
16
|
+
instance_variable_set(var, original.instance_variable_get(var).dup)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
7
20
|
def initialize(&block)
|
8
|
-
instance_eval(&block)
|
21
|
+
instance_eval(&block) unless block.nil?
|
9
22
|
end
|
10
23
|
|
11
24
|
def field(name, type: 'auto', locale: nil, facet: nil, index: nil, optional: nil)
|
data/lib/typesensual/search.rb
CHANGED
@@ -5,6 +5,8 @@ require 'typesensual/search/results'
|
|
5
5
|
|
6
6
|
class Typesensual
|
7
7
|
class Search
|
8
|
+
include StateHelpers
|
9
|
+
|
8
10
|
# Initialize a new search object for a collection
|
9
11
|
#
|
10
12
|
# @param collection [Typesensual::Collection] the Typesensual collection object
|
@@ -114,6 +116,7 @@ class Typesensual
|
|
114
116
|
end
|
115
117
|
|
116
118
|
# Generate the query document
|
119
|
+
# @return [Hash] the query document
|
117
120
|
def query
|
118
121
|
{
|
119
122
|
collection: @collection.name,
|
@@ -130,8 +133,53 @@ class Typesensual
|
|
130
133
|
end
|
131
134
|
|
132
135
|
# Load the results from the search query
|
136
|
+
# @return [Typesensual::Search::Results] the results of the search
|
133
137
|
def load
|
134
138
|
Results.new(@collection.typesense_collection.documents.search(query))
|
135
139
|
end
|
140
|
+
|
141
|
+
# Perform multiple searches in one request. There are two variants of this method, one which
|
142
|
+
# takes a list of anonymous queries and one which takes a hash of named queries. Named queries
|
143
|
+
# will probably be more readable for more than a couple of queries, but anonymous queries can be
|
144
|
+
# destructured directly.
|
145
|
+
#
|
146
|
+
# Both versions accept either a Search instance or a hash of search parameters.
|
147
|
+
#
|
148
|
+
# @overload multi(*searches)
|
149
|
+
# Perform an array of search queries in a single request. The return values are guaranteed to
|
150
|
+
# be in the same order as the provided searches.
|
151
|
+
#
|
152
|
+
# @param searches [<Typesensual::Search, Hash>] the searches to perform
|
153
|
+
# @return [<Typesensual::Search::Results>] the results of the searches
|
154
|
+
#
|
155
|
+
# @overload multi(searches)
|
156
|
+
# Perform multiple named search queries in a single request. The results will be keyed by the
|
157
|
+
# same names as the provided searches.
|
158
|
+
#
|
159
|
+
# @param searches [{Object => Typesensual::Search, Hash>] the searches to perform
|
160
|
+
# @return [{Object => Typesensual::Search::Results}] the results of the searches
|
161
|
+
def self.multi(*searches)
|
162
|
+
# If we have one argument and it's a hash, we're doing named searches
|
163
|
+
if searches.count == 1 && searches.first.is_a?(Hash)
|
164
|
+
keys = searches.first.keys
|
165
|
+
searches = searches.first.values
|
166
|
+
end
|
167
|
+
|
168
|
+
results = client.multi_search.perform({
|
169
|
+
searches: searches.flatten.map(&:query)
|
170
|
+
})
|
171
|
+
|
172
|
+
# Wrap our results in Result objects
|
173
|
+
wrapped_results = results['results'].map do |result|
|
174
|
+
Results.new(result)
|
175
|
+
end
|
176
|
+
|
177
|
+
# If we're doing named searches, re-key the results
|
178
|
+
if keys
|
179
|
+
keys.zip(wrapped_results).to_h
|
180
|
+
else
|
181
|
+
wrapped_results
|
182
|
+
end
|
183
|
+
end
|
136
184
|
end
|
137
185
|
end
|
data/lib/typesensual/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typesensual
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emma Lejeck
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|