typesense 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7938337742ce5b45e214069abd8f70bcabd0a247ad47dec68f5f48b43a1841c7
4
+ data.tar.gz: b71fd0346976e30ce637a42e16496b650bdfba80ba7e012f5d712c4a169decc2
5
+ SHA512:
6
+ metadata.gz: ee805aefbcea10f4275b5aac9efeace0e5bf422bd4d30911ea67223ce8e334264bb4731927a98aca7990fbe2890d7a18357e0c530d85f46d30ae8c5435766a7e
7
+ data.tar.gz: 546ba5bd5f554bad1c16e97e5cb3c37cf0945324f269b9b0273fb3e85ffc5b79c672bea80779b8347da2660b7db78f76d761fc90211c05470f581e0060a21bae
@@ -0,0 +1,67 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.4.1-node-browsers
11
+
12
+ # Specify service dependencies here if necessary
13
+ # CircleCI maintains a library of pre-built images
14
+ # documented at https://circleci.com/docs/2.0/circleci-images/
15
+ # - image: circleci/postgres:9.4
16
+
17
+ working_directory: ~/repo
18
+
19
+ steps:
20
+ - checkout
21
+
22
+ # # Download and cache dependencies
23
+ # - restore_cache:
24
+ # keys:
25
+ # - v1-dependencies-{{ checksum "Gemfile.lock" }}
26
+ # # fallback to using the latest cache if no exact match is found
27
+ # - v1-dependencies-
28
+
29
+ - run:
30
+ name: update bundler
31
+ command: |
32
+ gem install bundler
33
+
34
+ - run:
35
+ name: install dependencies
36
+ command: |
37
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
38
+
39
+ # - save_cache:
40
+ # paths:
41
+ # - ./vendor/bundle
42
+ # key: v1-dependencies-{{ checksum "Gemfile.lock" }}
43
+
44
+ - run:
45
+ name: lint
46
+ command: |
47
+ rubocop
48
+
49
+ # run tests!
50
+ - run:
51
+ name: run tests
52
+ command: |
53
+ mkdir /tmp/test-results
54
+
55
+ bundle exec rspec --format documentation \
56
+ --format RspecJunitFormatter \
57
+ --out /tmp/test-results/rspec.xml
58
+
59
+ # collect reports
60
+ - store_test_results:
61
+ path: /tmp/test-results
62
+ - store_artifacts:
63
+ path: /tmp/test-results
64
+ destination: test-results
65
+ - store_artifacts:
66
+ path: coverage
67
+ destination: coverage
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
11
+ .ruby-version
12
+ .ruby-gemset
13
+
14
+ # rspec failure tracking
15
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --format documentation
2
+ --color
3
+ --order rand
4
+ --require spec_helper
@@ -0,0 +1,12 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ require: rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: "2.4"
7
+
8
+ Metrics/BlockLength:
9
+ Exclude:
10
+ - 'spec/**/*'
11
+ - 'Gemfile'
12
+ - 'typesense.gemspec'
@@ -0,0 +1,66 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2018-03-17 15:11:06 -0700 using RuboCop version 0.53.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ Lint/ShadowedException:
11
+ Exclude:
12
+ - 'lib/typesense/api_call.rb'
13
+
14
+ # Offense count: 3
15
+ Metrics/AbcSize:
16
+ Max: 37
17
+
18
+ # Offense count: 1
19
+ Metrics/CyclomaticComplexity:
20
+ Max: 13
21
+
22
+ # Offense count: 1
23
+ # Configuration parameters: CountComments.
24
+ Metrics/MethodLength:
25
+ Max: 35
26
+
27
+ # Offense count: 1
28
+ Metrics/PerceivedComplexity:
29
+ Max: 14
30
+
31
+ # Offense count: 13
32
+ # Configuration parameters: Max.
33
+ RSpec/ExampleLength:
34
+ Exclude:
35
+ - 'spec/typesense/api_call_spec.rb'
36
+ - 'spec/typesense/collection_spec.rb'
37
+ - 'spec/typesense/collections_spec.rb'
38
+ - 'spec/typesense/debug_spec.rb'
39
+ - 'spec/typesense/document_spec.rb'
40
+ - 'spec/typesense/documents_spec.rb'
41
+
42
+ # Offense count: 2
43
+ # Configuration parameters: AggregateFailuresByDefault.
44
+ RSpec/MultipleExpectations:
45
+ Max: 2
46
+
47
+ # Offense count: 9
48
+ Style/Documentation:
49
+ Exclude:
50
+ - 'spec/**/*'
51
+ - 'test/**/*'
52
+ - 'lib/typesense.rb'
53
+ - 'lib/typesense/api_call.rb'
54
+ - 'lib/typesense/client.rb'
55
+ - 'lib/typesense/collection.rb'
56
+ - 'lib/typesense/collections.rb'
57
+ - 'lib/typesense/configuration.rb'
58
+ - 'lib/typesense/debug.rb'
59
+ - 'lib/typesense/document.rb'
60
+ - 'lib/typesense/documents.rb'
61
+
62
+ # Offense count: 92
63
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
64
+ # URISchemes: http, https
65
+ Metrics/LineLength:
66
+ Max: 245
data/Gemfile ADDED
@@ -0,0 +1,8 @@
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
+ # Specify your gem's dependencies in typesense.gemspec
8
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2018 Wreally Studios Inc. <contact@wreally.com> http://wreally.com
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this software except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,40 @@
1
+ # Typesense Ruby Library [![CircleCI](https://circleci.com/gh/typesense/typesense-ruby.svg?style=shield&circle-token=063f2179925b0b37d540126f6c96f6e1fe23f1b9)](https://circleci.com/gh/typesense/typesense-ruby)
2
+
3
+
4
+ Ruby client library for accessing the [Typesense HTTP API](https://github.com/typesense/typesense).
5
+
6
+ Follows the API spec [here](https://github.com/typesense/typesense-api-spec).
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'typesense'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install typesense
23
+
24
+ ## Usage
25
+
26
+ You'll find detailed documentation here: [https://typesense.org/api/](https://typesense.org/api/)
27
+
28
+ Here are some examples that show you how the Ruby client works: [examples](examples)
29
+
30
+ Tests are also a good place to know how the the library works internally: [spec](spec)
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at [typesense/typesense-ruby](https://github.com/typesense/typesense-ruby).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'typesense'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # These examples walk you through all the operations you can do on a collection and a document
5
+ # Search is specifically covered in another file in the examples folder
6
+
7
+ require_relative '../lib/typesense'
8
+ require 'awesome_print'
9
+
10
+ AwesomePrint.defaults = {
11
+ indent: -2
12
+ }
13
+
14
+ ##
15
+ # Setup
16
+ #
17
+ # Start the master
18
+ # $ docker run -p 8108:8108 -it -v/tmp/typesense-data-master/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=abcd --listen-port 8108
19
+ #
20
+ # Start the read replica
21
+ # $ docker run -p 8109:8109 -it -v/tmp/typesense-data-read-replica-1/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=wxyz --listen-port 8109 --master http://localhost:8108
22
+
23
+ ##
24
+ # Create a client
25
+ typesense = Typesense::Client.new(
26
+ master_node: {
27
+ host: 'localhost',
28
+ port: 8108,
29
+ protocol: 'http',
30
+ api_key: 'abcd'
31
+ },
32
+ read_replica_nodes: [
33
+ {
34
+ host: 'localhost',
35
+ port: 8109,
36
+ protocol: 'http',
37
+ api_key: 'wxyz'
38
+ }
39
+ ],
40
+ timeout_seconds: 10
41
+ )
42
+
43
+ ##
44
+ # Create a collection
45
+ schema = {
46
+ 'name' => 'companies',
47
+ 'fields' => [
48
+ {
49
+ 'name' => 'company_name',
50
+ 'type' => 'string'
51
+ },
52
+ {
53
+ 'name' => 'num_employees',
54
+ 'type' => 'int32'
55
+ },
56
+ {
57
+ 'name' => 'country',
58
+ 'type' => 'string',
59
+ 'facet' => true
60
+ }
61
+ ],
62
+ 'default_sorting_field' => 'num_employees'
63
+ }
64
+
65
+ collection = typesense.collections.create(schema)
66
+ ap collection
67
+
68
+ # {
69
+ # "name" => "companies",
70
+ # "fields" => [
71
+ # [0] {
72
+ # "name" => "company_name",
73
+ # "type" => "string"
74
+ # },
75
+ # [1] {
76
+ # "name" => "num_employees",
77
+ # "type" => "int32"
78
+ # },
79
+ # [2] {
80
+ # "name" => "country",
81
+ # "type" => "string",
82
+ # "facet" => true
83
+ # }
84
+ # ],
85
+ # "default_sorting_field" => "num_employees"
86
+ # }
87
+
88
+ ##
89
+ # Retrieve a collection
90
+ collection = typesense.collections['companies'].retrieve
91
+ ap collection
92
+
93
+ # {
94
+ # "default_sorting_field" => "num_employees",
95
+ # "fields" => [
96
+ # [0] {
97
+ # "facet" => false,
98
+ # "name" => "company_name",
99
+ # "type" => "string"
100
+ # },
101
+ # [1] {
102
+ # "facet" => false,
103
+ # "name" => "num_employees",
104
+ # "type" => "int32"
105
+ # },
106
+ # [2] {
107
+ # "facet" => true,
108
+ # "name" => "country",
109
+ # "type" => "string"
110
+ # }
111
+ # ],
112
+ # "name" => "companies",
113
+ # "num_documents" => 0
114
+ # }
115
+
116
+ ##
117
+ # Retrieve all collections
118
+ collections = typesense.collections.retrieve
119
+ ap collections
120
+
121
+ # [
122
+ # [0] {
123
+ # "default_sorting_field" => "num_employees",
124
+ # "fields" => [
125
+ # [0] {
126
+ # "facet" => false,
127
+ # "name" => "company_name",
128
+ # "type" => "string"
129
+ # },
130
+ # [1] {
131
+ # "facet" => false,
132
+ # "name" => "num_employees",
133
+ # "type" => "int32"
134
+ # },
135
+ # [2] {
136
+ # "facet" => true,
137
+ # "name" => "country",
138
+ # "type" => "string"
139
+ # }
140
+ # ],
141
+ # "name" => "companies",
142
+ # "num_documents" => 0
143
+ # }
144
+ # ]
145
+
146
+ ##
147
+ # Delete a collection
148
+ # Deletion returns the schema of the collection after deletion
149
+ collection = typesense.collections['companies'].delete
150
+ ap collection
151
+
152
+ # {
153
+ # "default_sorting_field" => "num_employees",
154
+ # "fields" => [
155
+ # [0] {
156
+ # "facet" => false,
157
+ # "name" => "company_name",
158
+ # "type" => "string"
159
+ # },
160
+ # [1] {
161
+ # "facet" => false,
162
+ # "name" => "num_employees",
163
+ # "type" => "int32"
164
+ # },
165
+ # [2] {
166
+ # "facet" => true,
167
+ # "name" => "country",
168
+ # "type" => "string"
169
+ # }
170
+ # ],
171
+ # "name" => "companies",
172
+ # "num_documents" => 0
173
+ # }
174
+
175
+ # Let's create the collection again for use in our remaining examples
176
+ typesense.collections.create(schema)
177
+
178
+ ##
179
+ # Create (index) a document
180
+ document = {
181
+ 'id' => '124',
182
+ 'company_name' => 'Stark Industries',
183
+ 'num_employees' => 5215,
184
+ 'country' => 'USA'
185
+ }
186
+
187
+ document = typesense.collections['companies'].documents.create(document)
188
+ ap document
189
+
190
+ # {
191
+ # "company_name" => "Stark Industries",
192
+ # "country" => "USA",
193
+ # "id" => "124",
194
+ # "num_employees" => 5215
195
+ # }
196
+
197
+ ##
198
+ # Retrieve a document
199
+ document = typesense.collections['companies'].documents['124'].retrieve
200
+ ap document
201
+
202
+ # {
203
+ # "company_name" => "Stark Industries",
204
+ # "country" => "USA",
205
+ # "id" => "124",
206
+ # "num_employees" => 5215
207
+ # }
208
+
209
+ ##
210
+ # Delete a document
211
+ # Deleting a document, returns the document after deletion
212
+ document = typesense.collections['companies'].documents['124'].delete
213
+ ap document
214
+
215
+ # {
216
+ # "company_name" => "Stark Industries",
217
+ # "country" => "USA",
218
+ # "id" => "124",
219
+ # "num_employees" => 5215
220
+ # }
221
+
222
+ # Let's create two documents again for use in our remaining examples
223
+ typesense.collections['companies'].documents.create(
224
+ 'id' => '124',
225
+ 'company_name' => 'Stark Industries',
226
+ 'num_employees' => 5215,
227
+ 'country' => 'USA'
228
+ )
229
+
230
+ typesense.collections['companies'].documents.create(
231
+ 'id' => '125',
232
+ 'company_name' => 'Acme Corp',
233
+ 'num_employees' => 1002,
234
+ 'country' => 'France'
235
+ )
236
+
237
+ ##
238
+ # Export all documents in a collection in JSON Lines format
239
+ # We use JSON Lines format for performance reasons. You can choose to parse selected lines (elements in the array) as needed.
240
+ array_of_json_strings = typesense.collections['companies'].documents.export
241
+ ap array_of_json_strings
242
+
243
+ # [
244
+ # [0] "{\"company_name\":\"Stark Industries\",\"country\":\"USA\",\"id\":\"124\",\"num_employees\":5215}",
245
+ # [1] "{\"company_name\":\"Acme Corp\",\"country\":\"France\",\"id\":\"125\",\"num_employees\":1002}"
246
+ # ]
247
+
248
+ ##
249
+ # Cleanup
250
+ # Drop the collection
251
+ typesense.collections['companies'].delete
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # These examples walk you through operations specifically related to search
5
+
6
+ require_relative '../lib/typesense'
7
+ require 'awesome_print'
8
+
9
+ AwesomePrint.defaults = {
10
+ indent: -2
11
+ }
12
+
13
+ ##
14
+ # Setup
15
+ #
16
+ # Start the master
17
+ # $ docker run -p 8108:8108 -it -v/tmp/typesense-data-master/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=abcd --listen-port 8108
18
+ #
19
+ # Start the read replica
20
+ # $ docker run -p 8109:8109 -it -v/tmp/typesense-data-read-replica-1/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=wxyz --listen-port 8109 --master http://localhost:8108
21
+
22
+ ##
23
+ # Create a client
24
+ typesense = Typesense::Client.new(
25
+ master_node: {
26
+ host: 'localhost',
27
+ port: 8108,
28
+ protocol: 'http',
29
+ api_key: 'abcd'
30
+ },
31
+ read_replica_nodes: [
32
+ {
33
+ host: 'localhost',
34
+ port: 8109,
35
+ protocol: 'http',
36
+ api_key: 'wxyz'
37
+ }
38
+ ],
39
+ timeout_seconds: 10
40
+ )
41
+
42
+ ##
43
+ # Create a collection
44
+ schema = {
45
+ 'name' => 'companies',
46
+ 'fields' => [
47
+ {
48
+ 'name' => 'company_name',
49
+ 'type' => 'string'
50
+ },
51
+ {
52
+ 'name' => 'num_employees',
53
+ 'type' => 'int32'
54
+ },
55
+ {
56
+ 'name' => 'country',
57
+ 'type' => 'string',
58
+ 'facet' => true
59
+ }
60
+ ],
61
+ 'default_sorting_field' => 'num_employees'
62
+ }
63
+
64
+ typesense.collections.create(schema)
65
+
66
+ # Let's create a couple documents for us to use in our search examples
67
+ typesense.collections['companies'].documents.create(
68
+ 'id' => '124',
69
+ 'company_name' => 'Stark Industries',
70
+ 'num_employees' => 5215,
71
+ 'country' => 'USA'
72
+ )
73
+
74
+ typesense.collections['companies'].documents.create(
75
+ 'id' => '127',
76
+ 'company_name' => 'Stark Corp',
77
+ 'num_employees' => 1031,
78
+ 'country' => 'USA'
79
+ )
80
+
81
+ typesense.collections['companies'].documents.create(
82
+ 'id' => '125',
83
+ 'company_name' => 'Acme Corp',
84
+ 'num_employees' => 1002,
85
+ 'country' => 'France'
86
+ )
87
+
88
+ typesense.collections['companies'].documents.create(
89
+ 'id' => '126',
90
+ 'company_name' => 'Doofenshmirtz Inc',
91
+ 'num_employees' => 2,
92
+ 'country' => 'Tri-State Area'
93
+ )
94
+
95
+ ##
96
+ # Search for documents
97
+ results = typesense.collections['companies'].documents.search(
98
+ 'q' => 'Stark',
99
+ 'query_by' => 'company_name'
100
+ )
101
+ ap results
102
+
103
+ # {
104
+ # "facet_counts" => [],
105
+ # "found" => 2,
106
+ # "hits" => [
107
+ # [0] {
108
+ # "document" => {
109
+ # "company_name" => "Stark Industries",
110
+ # "country" => "USA",
111
+ # "id" => "124",
112
+ # "num_employees" => 5215
113
+ # },
114
+ # "highlight" => {
115
+ # "company_name" => "<mark>Stark</mark> Industries"
116
+ # }
117
+ # },
118
+ # [1] {
119
+ # "document" => {
120
+ # "company_name" => "Stark Corp",
121
+ # "country" => "USA",
122
+ # "id" => "127",
123
+ # "num_employees" => 1031
124
+ # },
125
+ # "highlight" => {
126
+ # "company_name" => "<mark>Stark</mark> Corp"
127
+ # }
128
+ # }
129
+ # ],
130
+ # "page" => 1,
131
+ # "search_time_ms" => 0
132
+ # }
133
+
134
+ ##
135
+ # Search for more documents
136
+ results = typesense.collections['companies'].documents.search(
137
+ 'q' => 'Inc',
138
+ 'query_by' => 'company_name',
139
+ 'filter_by' => 'num_employees:<100',
140
+ 'sort_by' => 'num_employees:desc'
141
+ )
142
+ ap results
143
+
144
+ # {
145
+ # "facet_counts" => [],
146
+ # "found" => 1,
147
+ # "hits" => [
148
+ # [0] {
149
+ # "document" => {
150
+ # "company_name" => "Doofenshmirtz Inc",
151
+ # "country" => "Tri-State Area",
152
+ # "id" => "126",
153
+ # "num_employees" => 2
154
+ # },
155
+ # "highlight" => {
156
+ # "company_name" => "Doofenshmirtz <mark>Inc</mark>"
157
+ # }
158
+ # }
159
+ # ],
160
+ # "page" => 1,
161
+ # "search_time_ms" => 0
162
+ # }
163
+
164
+ ##
165
+ # Search for more documents
166
+ results = typesense.collections['companies'].documents.search(
167
+ 'q' => 'Non-existent',
168
+ 'query_by' => 'company_name'
169
+ )
170
+ ap results
171
+
172
+ # {
173
+ # "found" => 0,
174
+ # "hits" => [],
175
+ # "page" => 1,
176
+ # "search_time_ms" => 0
177
+ # }
178
+
179
+ ##
180
+ # Cleanup
181
+ # Drop the collection
182
+ typesense.collections['companies'].delete
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ end
5
+
6
+ require_relative 'typesense/version'
7
+ require_relative 'typesense/configuration'
8
+ require_relative 'typesense/client'
9
+ require_relative 'typesense/api_call'
10
+ require_relative 'typesense/collections'
11
+ require_relative 'typesense/collection'
12
+ require_relative 'typesense/documents'
13
+ require_relative 'typesense/document'
14
+ require_relative 'typesense/debug'
15
+ require_relative 'typesense/error'
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+
5
+ module Typesense
6
+ class ApiCall
7
+ include HTTParty
8
+
9
+ API_KEY_HEADER_NAME = 'X-TYPESENSE-API-KEY'
10
+
11
+ def initialize(configuration)
12
+ @configuration = configuration
13
+ end
14
+
15
+ def post(endpoint, parameters = {})
16
+ perform_with_error_handling(:do_not_use_read_replicas) do
17
+ self.class.post(uri_for(endpoint),
18
+ default_options.merge(
19
+ body: parameters.to_json,
20
+ headers: default_headers.merge('Content-Type' => 'application/json')
21
+ ))
22
+ end.parsed_response
23
+ end
24
+
25
+ def get(endpoint, parameters = {})
26
+ get_unparsed_response(endpoint, parameters).parsed_response
27
+ end
28
+
29
+ def get_unparsed_response(endpoint, parameters = {})
30
+ perform_with_error_handling(:use_read_replicas) do |node, node_index|
31
+ self.class.get(uri_for(endpoint, node, node_index),
32
+ default_options.merge(
33
+ query: parameters,
34
+ headers: default_headers
35
+ ))
36
+ end
37
+ end
38
+
39
+ def delete(endpoint, parameters = {})
40
+ perform_with_error_handling(:do_not_use_read_replicas) do
41
+ self.class.delete(uri_for(endpoint),
42
+ default_options.merge(
43
+ query: parameters,
44
+ headers: default_headers
45
+ ))
46
+ end.parsed_response
47
+ end
48
+
49
+ private
50
+
51
+ def uri_for(endpoint, node = :master, node_index = 0)
52
+ if node == :read_replica
53
+ "#{@configuration.read_replica_nodes[node_index][:protocol]}://#{@configuration.read_replica_nodes[node_index][:host]}:#{@configuration.read_replica_nodes[node_index][:port]}#{endpoint}"
54
+ else
55
+ "#{@configuration.master_node[:protocol]}://#{@configuration.master_node[:host]}:#{@configuration.master_node[:port]}#{endpoint}"
56
+ end
57
+ end
58
+
59
+ def perform_with_error_handling(use_read_replicas = :do_not_use_read_replicas)
60
+ @configuration.validate!
61
+
62
+ node = :master
63
+ node_index = -1
64
+
65
+ begin
66
+ response_object = yield node, node_index
67
+
68
+ return response_object if response_object.response.code_type <= Net::HTTPSuccess # 2xx
69
+
70
+ error_klass = if response_object.response.code_type <= Net::HTTPBadRequest # 400
71
+ Error::RequestMalformed
72
+ elsif response_object.response.code_type <= Net::HTTPUnauthorized # 401
73
+ Error::RequestUnauthorized
74
+ elsif response_object.response.code_type <= Net::HTTPNotFound # 404
75
+ Error::ObjectNotFound
76
+ elsif response_object.response.code_type <= Net::HTTPConflict # 409
77
+ Error::ObjectAlreadyExists
78
+ elsif response_object.response.code_type <= Net::HTTPUnprocessableEntity # 422
79
+ Error::ObjectUnprocessable
80
+ elsif response_object.response.code_type <= Net::HTTPServerError # 5xx
81
+ Error::ServerError
82
+ else
83
+ Error
84
+ end
85
+
86
+ raise error_klass, response_object.parsed_response['message']
87
+ rescue Net::ReadTimeout, Net::OpenTimeout,
88
+ EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
89
+ Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
90
+ Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
91
+ Timeout::Error, Error::ServerError, HTTParty::ResponseError
92
+ if (use_read_replicas == :use_read_replicas || use_read_replicas == true) &&
93
+ !@configuration.read_replica_nodes.nil?
94
+ node = :read_replica
95
+ node_index += 1
96
+
97
+ retry unless @configuration.read_replica_nodes[node_index].nil?
98
+ end
99
+
100
+ raise
101
+ end
102
+ end
103
+
104
+ def default_options
105
+ {
106
+ timeout: @configuration.timeout_seconds
107
+ }
108
+ end
109
+
110
+ def default_headers
111
+ {
112
+ API_KEY_HEADER_NAME.to_s => @configuration.master_node[:api_key]
113
+ }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Client
5
+ attr_reader :configuration
6
+ attr_reader :collections
7
+ attr_reader :debug
8
+
9
+ def initialize(options = {})
10
+ @configuration ||= Configuration.new(options)
11
+ @collections = Collections.new(@configuration)
12
+ @debug = Debug.new(@configuration)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Collection
5
+ attr_reader :documents
6
+
7
+ def initialize(configuration, name)
8
+ @configuration = configuration
9
+ @name = name
10
+ @documents = Documents.new(@configuration, @name)
11
+ end
12
+
13
+ def retrieve
14
+ ApiCall.new(@configuration).get(endpoint_path)
15
+ end
16
+
17
+ def delete
18
+ ApiCall.new(@configuration).delete(endpoint_path)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint_path
24
+ "#{Collections::RESOURCE_PATH}/#{@name}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Collections
5
+ RESOURCE_PATH = '/collections'
6
+
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ @collections = {}
10
+ end
11
+
12
+ def create(schema)
13
+ ApiCall.new(@configuration).post(RESOURCE_PATH, schema)
14
+ end
15
+
16
+ def retrieve
17
+ ApiCall.new(@configuration).get(RESOURCE_PATH)
18
+ end
19
+
20
+ def [](collection_name)
21
+ @collections[collection_name] ||= Collection.new(@configuration, collection_name)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Configuration
5
+ attr_accessor :master_node
6
+ attr_accessor :read_replica_nodes
7
+ attr_accessor :timeout_seconds
8
+
9
+ def initialize(options = {})
10
+ @master_node = options[:master_node] || {
11
+ host: 'localhost',
12
+ port: '8108',
13
+ protocol: 'http'
14
+ }
15
+
16
+ @read_replica_nodes = options[:read_replica_nodes] || []
17
+ @timeout_seconds = options[:timeout_seconds] || 10
18
+ end
19
+
20
+ def validate!
21
+ if @master_node.nil? ||
22
+ node_missing_parameters?(@master_node)
23
+ raise Error::MissingConfiguration, 'Missing required configuration. Ensure that master_node[:protocol], master_node[:host], master_node[:port] and master_node[:api_key] are set.'
24
+ end
25
+
26
+ if !@read_replica_nodes.nil? &&
27
+ @read_replica_nodes.any? { |node| node_missing_parameters?(node) }
28
+ raise Error::MissingConfiguration, 'Missing required configuration for read_replica_nodes. Ensure that read_replica_nodes[][:protocol], read_replica_nodes[][:host], read_replica_nodes[][:port] and read_replica_nodes[][:api_key] are set.'
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def node_missing_parameters?(node)
35
+ %i[protocol host port api_key].any? { |attr| node.send(:[], attr).nil? }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Debug
5
+ RESOURCE_PATH = '/debug'
6
+
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ end
10
+
11
+ def retrieve
12
+ ApiCall.new(@configuration).get(RESOURCE_PATH)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Document
5
+ def initialize(configuration, collection_name, document_id)
6
+ @configuration = configuration
7
+ @collection_name = collection_name
8
+ @document_id = document_id
9
+ end
10
+
11
+ def retrieve
12
+ ApiCall.new(@configuration).get(endpoint_path)
13
+ end
14
+
15
+ def delete
16
+ ApiCall.new(@configuration).delete(endpoint_path)
17
+ end
18
+
19
+ private
20
+
21
+ def endpoint_path
22
+ "#{Collections::RESOURCE_PATH}/#{@collection_name}#{Documents::RESOURCE_PATH}/#{@document_id}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Documents
5
+ RESOURCE_PATH = '/documents'
6
+
7
+ def initialize(configuration, collection_name)
8
+ @configuration = configuration
9
+ @collection_name = collection_name
10
+ @documents = {}
11
+ end
12
+
13
+ def create(document)
14
+ ApiCall.new(@configuration).post(endpoint_path, document)
15
+ end
16
+
17
+ def export
18
+ ApiCall.new(@configuration).get_unparsed_response(endpoint_path('export')).split("\n")
19
+ end
20
+
21
+ def search(search_parameters)
22
+ ApiCall.new(@configuration).get(endpoint_path('search'), search_parameters)
23
+ end
24
+
25
+ def [](document_id)
26
+ @documents[document_id] ||= Document.new(@configuration, @collection_name, document_id)
27
+ end
28
+
29
+ private
30
+
31
+ def endpoint_path(operation = nil)
32
+ "#{Collections::RESOURCE_PATH}/#{@collection_name}#{Documents::RESOURCE_PATH}#{operation.nil? ? '' : '/' + operation}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ class Error < StandardError
5
+ class MissingConfiguration < Error
6
+ end
7
+
8
+ class ObjectAlreadyExists < Error
9
+ end
10
+
11
+ class ObjectNotFound < Error
12
+ end
13
+
14
+ class ObjectUnprocessable < Error
15
+ end
16
+
17
+ class RequestMalformed < Error
18
+ end
19
+
20
+ class RequestUnauthorized < Error
21
+ end
22
+
23
+ class ServerError < Error
24
+ end
25
+
26
+ class NoMethodError < ::NoMethodError
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typesense
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'typesense/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'typesense'
9
+ spec.version = Typesense::VERSION
10
+ spec.authors = ['Wreally Studios']
11
+ spec.email = ['contact@typesense.org']
12
+
13
+ spec.summary = 'Ruby Library for Typesense'
14
+ spec.description = 'Typesense is an open source search engine for building a delightful search experience.'
15
+ spec.homepage = 'https://github.com/typesense/typesense-ruby'
16
+ spec.license = 'Apache-2.0'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'awesome_print', '~> 1.8'
26
+ spec.add_development_dependency 'bundler', '~> 1.16'
27
+ spec.add_development_dependency 'pry-byebug', '~> 3.5'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.0'
30
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
31
+ spec.add_development_dependency 'rubocop', '~> 0.53.0'
32
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.24'
33
+ spec.add_development_dependency 'simplecov', '~> 0.15'
34
+ spec.add_development_dependency 'webmock', '~> 3.2'
35
+
36
+ spec.add_dependency 'httparty', '~> 0.15'
37
+ end
metadata ADDED
@@ -0,0 +1,224 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typesense
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Wreally Studios
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: awesome_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec_junit_formatter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.53.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.53.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.24'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.24'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.15'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.15'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.2'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.2'
153
+ - !ruby/object:Gem::Dependency
154
+ name: httparty
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.15'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.15'
167
+ description: Typesense is an open source search engine for building a delightful search
168
+ experience.
169
+ email:
170
+ - contact@typesense.org
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".circleci/config.yml"
176
+ - ".gitignore"
177
+ - ".rspec"
178
+ - ".rubocop.yml"
179
+ - ".rubocop_todo.yml"
180
+ - Gemfile
181
+ - LICENSE
182
+ - README.md
183
+ - Rakefile
184
+ - bin/console
185
+ - bin/setup
186
+ - examples/collections_and_documents.rb
187
+ - examples/search.rb
188
+ - lib/typesense.rb
189
+ - lib/typesense/api_call.rb
190
+ - lib/typesense/client.rb
191
+ - lib/typesense/collection.rb
192
+ - lib/typesense/collections.rb
193
+ - lib/typesense/configuration.rb
194
+ - lib/typesense/debug.rb
195
+ - lib/typesense/document.rb
196
+ - lib/typesense/documents.rb
197
+ - lib/typesense/error.rb
198
+ - lib/typesense/version.rb
199
+ - typesense.gemspec
200
+ homepage: https://github.com/typesense/typesense-ruby
201
+ licenses:
202
+ - Apache-2.0
203
+ metadata: {}
204
+ post_install_message:
205
+ rdoc_options: []
206
+ require_paths:
207
+ - lib
208
+ required_ruby_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: '0'
213
+ required_rubygems_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '0'
218
+ requirements: []
219
+ rubyforge_project:
220
+ rubygems_version: 2.7.6
221
+ signing_key:
222
+ specification_version: 4
223
+ summary: Ruby Library for Typesense
224
+ test_files: []