swiftype-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in swiftype-rb.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,387 @@
1
+ Swiftype Ruby Client (beta)
2
+ ===
3
+
4
+ The official [Swiftype](http://swiftype.com) Ruby client for communicating with the Swiftype API. Learn more about Swiftype by visiting [swiftype.com](http://swiftype.com) and creating an account.
5
+
6
+
7
+ Prerequisites
8
+ ---
9
+ 1. A Swiftype account. Sign up at [swiftype.com](http://swiftype.com).
10
+ 2. A compatible Ruby environment.
11
+
12
+
13
+ Installation
14
+ ---
15
+
16
+ For now, just clone this repository and then pull in the library:
17
+
18
+ #### Bundler
19
+
20
+ gem 'swiftype-rb', :git => "https://github.com/swiftype/swiftype-rb.git", :require => 'swiftype'
21
+
22
+ #### Non-Bundler
23
+
24
+ git clone https://github.com/swiftype/swiftype-rb.git
25
+
26
+ rake build && rake install
27
+
28
+ require 'swiftype'
29
+
30
+
31
+ Overview
32
+ ---
33
+
34
+ The client has a few basic methods on `Swiftype` for dealing with `Engines`. Beyond that, you can perform standard (CRUD) operations on any of the resources.
35
+
36
+ ### Resources
37
+
38
+ #### Engine
39
+
40
+ `Engines` are the top-level objects in Swiftype. They have a free-form `name` field that is translated into a `slug` identifier.
41
+
42
+ #### Document Type
43
+
44
+ `DocumentTypes` specify the structure of a set of documents in the `Engine` and are the entry point for searches. There are three types of fields on a `DocumentType`: `:string`, `:text`, `:enum`, `:integer`, `:float`, and `:date`.
45
+
46
+ `:string` is for short strings that can be matched in both prefix and full-text searches. _Example: Chapter titles in a book._
47
+
48
+ `:text` can be long strings. They are meant for full-text searches only and will not be used for prefix queries. _Example: Entire text of an essay._
49
+
50
+ `:enum` are string traits of a document. They are not analyzed in any way, and thus can be used to filter and sort queries. _Example: Hardcover or paperback._
51
+
52
+ `:date` are ISO 8601 compatible time strings. They can also be used to filter and sort queries.
53
+
54
+
55
+
56
+ #### Document
57
+
58
+ `Documents` represent all of the pieces of content in an `Engine`. They are children of a `DocumentType` and conform to its field specification (note: you do *not* need to specify the fields ahead of time, they will be inferred by the contents of a document). When you perform a search on a `DocumentType`, you will receive `Document` results. `external_id` is the only required field for a `Document`. It can be any value, such as a numeric ID.
59
+
60
+
61
+
62
+ Basic Usage
63
+ ===
64
+
65
+ Configuration:
66
+ ---
67
+
68
+ Before issuing commands to the API, configure the client with your authentication credentials:
69
+
70
+ Swiftype.configure do |config|
71
+ config.username = "{YOUR_SWIFTYPE_LOGIN}"
72
+ config.password = "{YOUR_SWIFTYPE_PASSWORD}"
73
+ end
74
+
75
+ Indexing:
76
+ ---
77
+
78
+ #### Engines:
79
+
80
+ Search engines are the top-level container for the objects you wish to search, and most sites will have a single engine. The engines themselves contain one or more document types, each of which contain the documents themselves.
81
+
82
+ Create a search engine:
83
+
84
+ engine = Swiftype::Engine.new(:name => 'bookstore')
85
+ engine.create!
86
+
87
+ Get a search engine:
88
+
89
+ Swiftype::Engine.find('bookstore')
90
+
91
+ Delete a search engine:
92
+
93
+ engine = Swiftype::Engine.find('bookstore')
94
+ engine.destroy!
95
+
96
+
97
+ #### Document Types
98
+
99
+ Create a `document_type`:
100
+
101
+ engine = Swiftype::Engine.find('bookstore')
102
+ type = engine.create_document_type(:name => 'books')
103
+
104
+ Get a `document_type`:
105
+
106
+ engine = Swiftype::Engine.find('bookstore')
107
+ type = engine.document_type('books')
108
+
109
+ Delete a `document_type`. Deleting a `document_type` will also delete every `document` contained within it:
110
+
111
+ engine = Swiftype::Engine.find('bookstore')
112
+ engine.destroy_document_type('books')
113
+
114
+ or, alternatively, call destroy on the `document_type` itself:
115
+
116
+ engine = Swiftype::Engine.find('bookstore')
117
+ type = engine.document_type('books')
118
+ type.destroy!
119
+
120
+
121
+ #### Documents
122
+
123
+ Create a `document`:
124
+
125
+ engine = Swiftype::Engine.find('bookstore')
126
+ type = engine.document_type('books')
127
+ type.create_document({
128
+ :external_id => '1',
129
+ :fields => [
130
+ {:name => 'title', :value => 'Information Retrieval', :type => 'string'},
131
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
132
+ {:name => 'author', :value => 'Stefan Buttcher', :type => 'string'},
133
+ {:name => 'in_stock', :value => true, :type => 'enum'},
134
+ {:name => 'on_sale', :value => false, :type => 'enum'}
135
+ ]});
136
+
137
+ Get a `document`:
138
+
139
+ engine = Swiftype::Engine.find('bookstore')
140
+ type = engine.document_type('books')
141
+ doc = type.document('1')
142
+
143
+ Get every `document` within a `document_type`:
144
+
145
+ engine = Swiftype::Engine.find('bookstore')
146
+ type = engine.document_type('books')
147
+ type.documents
148
+
149
+ Update field(s) of a `document`:
150
+
151
+ engine = Swiftype::Engine.find('bookstore')
152
+ type = engine.document_type('books')
153
+ doc = type.document('1')
154
+ doc.update_fields!({:in_stock => false })
155
+
156
+ or, alternatively, update a `document` without retrieving it first:
157
+
158
+ engine = Swiftype::Engine.find('bookstore')
159
+ type = engine.document_type('books')
160
+ type.update_document(:external_id => '1', :fields => { :in_stock => false })
161
+
162
+ you can also update multiple fields in the same call:
163
+
164
+ doc.update_fields({:in_stock => false, :on_sale => true })
165
+
166
+ Delete a `document`:
167
+
168
+ engine = Swiftype::Engine.find('bookstore')
169
+ type = engine.document_type('books')
170
+ doc = type.document('1')
171
+ doc.destroy!
172
+
173
+ or, alternatively, delete a `document` without retrieving it first:
174
+
175
+ engine = Swiftype::Engine.find('bookstore')
176
+ type = engine.document_type('books')
177
+ type.destroy_document('1')
178
+
179
+
180
+ #### Bulk Operations
181
+
182
+ Bulk operations will allow you to perform rapid indexing updates and avoid the latency overhead of making repeated requests.
183
+
184
+ Create `document`s in bulk:
185
+
186
+ engine = Swiftype::Engine.find('bookstore')
187
+ type = engine.document_type('books')
188
+ type.create_documents([{
189
+ :external_id => '2',
190
+ :fields => [
191
+ {:name => 'title', :value => 'Lucene in Action', :type => 'string'},
192
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
193
+ {:name => 'author', :value => 'Michael McCandless', :type => 'string'},
194
+ {:name => 'in_stock', :value => true, :type => 'enum'},
195
+ {:name => 'on_sale', :value => false, :type => 'enum'}
196
+ ]},{
197
+ :external_id => '3',
198
+ :fields => [
199
+ {:name => 'title', :value => 'MongoDB in Action', :type => 'string'},
200
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
201
+ {:name => 'author', :value => 'Kyle Banker', :type => 'string'},
202
+ {:name => 'in_stock', :value => true, :type => 'enum'},
203
+ {:name => 'on_sale', :value => false, :type => 'enum'}
204
+ ]},{
205
+ :external_id => '4',
206
+ :fields => [
207
+ {:name => 'title', :value => 'The Great Gatsby', :type => 'string'},
208
+ {:name => 'genre', :value => 'fiction', :type => 'enum'},
209
+ {:name => 'author', :value => 'F. Scott Fitzgerald', :type => 'string'},
210
+ {:name => 'in_stock', :value => true, :type => 'enum'},
211
+ {:name => 'on_sale', :value => false, :type => 'enum'}
212
+ ]}
213
+ ])
214
+
215
+ Update `document`s in bulk:
216
+
217
+ engine = Swiftype::Engine.find('bookstore')
218
+ type = engine.document_type('books')
219
+ type.update_documents([
220
+ {:external_id => '2', :fields => {:in_stock => false, :on_sale => 'false'}},
221
+ {:external_id => '3', :fields => {:in_stock => false, :on_sale => 'false'}}
222
+ ])
223
+
224
+ Delete `document`s in bulk:
225
+
226
+ engine = Swiftype::Engine.find('bookstore')
227
+ type = engine.document_type('books')
228
+ type.destroy_documents(['1','2','3','4'])
229
+
230
+
231
+ Searching:
232
+ ---
233
+
234
+ #### Full text search
235
+
236
+ Search a `document_type` for the query "lucene":
237
+
238
+ engine = Swiftype::Engine.find('bookstore')
239
+ type = engine.document_type('books')
240
+ results = type.search("lucene")
241
+
242
+ You can pass the following options to the search method: `page`, `per_page`, `fetch_fields`, `search_fields`, and `filters`.
243
+
244
+ * `page` should be an integer of the page of results you want
245
+ * `per_page` should be an integer of the number of results you want from each page
246
+ * `fetch_fields` is a hash containing arrays of the fields you want to have returned for each object of each document_type
247
+ * `search_fields` is a hash containing arrays of the fields you want to match your query against for each object of each document_type
248
+ * `functional_boosts` is a hash containing boosts that are to be applied to numerically valued fields
249
+ * `filters` is a hash specifying additional conditions that should be applied to your query for each document_type
250
+
251
+ An example of using search options is as follows:
252
+
253
+ results = type.search('lucene', :filters => { :books => { :in_stock => false, :genre => 'fiction' }}, :per_page => 10, :page => 2, :fetch_fields => {:books => ['title','genre']}, :search_fields => {:books => ['title']})
254
+
255
+ Filters also support datetime range queries. For example, to return only those books with an `updated_at` field between `2012-02-16` and now, use the following filter:
256
+
257
+ results = type.search('lucene', :filters => { :books => { :updated_at => '[2012-02-16 TO *]' }})
258
+
259
+
260
+ ##### Functional Boosts
261
+
262
+ Functional boosts allow you to boost result scores based on some numerically valued field. For example, you might want your search engine to return the most popular books first, so you would boost results on the `total_purchases` field, which contains an `integer` of the total number of purchases of that book:
263
+
264
+ results = type.search('lucene', :functional_boosts => { :books => { :total_purchases => 'logarithmic' }})
265
+
266
+ There are 3 types of functional boosts:
267
+
268
+ * `logarithmic` - multiplies the original score by log(numeric_value)
269
+ * `exponential` - multiplies the original score by exp(numeric_value)
270
+ * `linear` - multiplies the original score numeric_value
271
+
272
+ Functional boosts may be applied to `integer` and `float` valued fields.
273
+
274
+ #### Autocomplete
275
+
276
+ Get autocomplete suggestions from a `document_type` for the prefix "act"
277
+
278
+ engine = Swiftype::Engine.find('bookstore')
279
+ type = engine.document_type('books')
280
+ results = type.suggest("act")
281
+
282
+ The suggest method also accepts the same options specified for the search method above.
283
+
284
+
285
+ Simple Client:
286
+ ===
287
+ The Simple Client is a convenience class that gives you basic, direct access to the Swiftype REST API, without mapping each call to the intermediate objects seen in the examples above. These methods will be more performant, because they avoid unnecessary round-trips to the server, but you will also have to provide more information to each call. Choose whatever suites your use-case.
288
+
289
+ #### Create a Simple Client
290
+
291
+ client = Swiftype::Easy.new
292
+
293
+ #### Search
294
+
295
+ results = client.search('bookstore',{SEARCH QUERY} [, OPTIONAL SEARCH OPTIONS])
296
+
297
+ #### Autocomplete
298
+
299
+ results = client.suggest('bookstore',{AUTOCOMPLETE PREFIX QUERY} [, OPTIONAL SEARCH OPTIONS])
300
+
301
+ #### Engines
302
+
303
+ client.engines # retrieves every engine
304
+ client.create_engine(:name => 'bookstore')
305
+ client.destroy_engine('bookstore')
306
+
307
+ #### Document Types
308
+
309
+ client.document_types('bookstore')
310
+ client.create_document_type('bookstore', :name => 'books')
311
+ client.destroy_document_type('bookstore', 'books')
312
+
313
+ #### Documents
314
+
315
+ # retrieve all documents
316
+ client.documents('bookstore', 'books')
317
+
318
+ # create a document
319
+ client.create_document('bookstore', 'books', {
320
+ :external_id => '1',
321
+ :fields => [
322
+ {:name => 'title', :value => 'Information Retrieval', :type => 'string'},
323
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
324
+ {:name => 'author', :value => 'Stefan Buttcher', :type => 'string'},
325
+ {:name => 'in_stock', :value => true, :type => 'enum'},
326
+ {:name => 'on_sale', :value => false, :type => 'enum'}
327
+ ]})
328
+
329
+ # create documents in bulk
330
+ client.create_documents('bookstore', 'books', [{
331
+ :external_id => '2',
332
+ :fields => [
333
+ {:name => 'title', :value => 'Lucene in Action', :type => 'string'},
334
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
335
+ {:name => 'author', :value => 'Michael McCandless', :type => 'string'},
336
+ {:name => 'in_stock', :value => true, :type => 'enum'},
337
+ {:name => 'on_sale', :value => false, :type => 'enum'}
338
+ ]},{
339
+ :external_id => '3',
340
+ :fields => [
341
+ {:name => 'title', :value => 'MongoDB in Action', :type => 'string'},
342
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
343
+ {:name => 'author', :value => 'Kyle Banker', :type => 'string'},
344
+ {:name => 'in_stock', :value => true, :type => 'enum'},
345
+ {:name => 'on_sale', :value => false, :type => 'enum'}
346
+ ]}])
347
+
348
+ # update a document
349
+ client.update_document('bookstore','books','1', { :in_stock => false })
350
+
351
+ # update documents in bulk
352
+ client.update_documents('bookstore','books', [
353
+ {:external_id => '2', :fields => {:in_stock => false}},
354
+ {:external_id => '3', :fields => {:in_stock => true}}
355
+ ])
356
+
357
+ # create or update a document
358
+ client.create_or_update_document('bookstore', 'books', {
359
+ :external_id => '1',
360
+ :fields => [
361
+ {:name => 'title', :value => 'Information Retrieval', :type => 'string'},
362
+ {:name => 'genre', :value => 'non-fiction', :type => 'enum'},
363
+ {:name => 'author', :value => 'Stefan Buttcher', :type => 'string'},
364
+ {:name => 'in_stock', :value => false, :type => 'enum'},
365
+ {:name => 'on_sale', :value => true, :type => 'enum'}
366
+ ]})
367
+
368
+ # destroy a document
369
+ client.destroy_document('bookstore','books','1')
370
+
371
+ # destroy documents in bulk
372
+ client.destroy_documents('bookstore','books',['1','2','3'])
373
+
374
+
375
+ Todo
376
+ ===
377
+
378
+ + Proper response code handling for non-successful requests.
379
+ + Publish gem to rubygems.org
380
+ + Tests!
381
+
382
+
383
+ Questions?
384
+ ===
385
+ Get in touch! We would be happy to help you get up and running.
386
+
387
+ [Quin](mailto:quin@swiftype.com) and [Matt](mailto:matt@swiftype.com) from [Swiftype](http://swiftype.com)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,93 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Swiftype
4
+ class BaseModel < OpenStruct
5
+ include Swiftype::Connection
6
+ include Swiftype::Request
7
+
8
+ class << self
9
+ attr_reader :parent_classes
10
+
11
+ def model_name
12
+ name.split('::').last.underscore
13
+ end
14
+
15
+ def collection_name
16
+ model_name.pluralize
17
+ end
18
+
19
+ def parents(*parent_classes)
20
+ @parent_classes = parent_classes
21
+ end
22
+ end
23
+
24
+ def id
25
+ self._id
26
+ end
27
+
28
+ def to_hash
29
+ table
30
+ end
31
+
32
+ def to_json
33
+ to_hash.to_json
34
+ end
35
+
36
+ def create!
37
+ update_with! post(path_to_collection, {self.class.model_name => to_hash})
38
+ end
39
+
40
+ def update!
41
+ update_with! put(path_to_model, {self.class.model_name => to_hash})
42
+ end
43
+
44
+ def destroy!
45
+ !!delete(path_to_model)
46
+ end
47
+
48
+ def path_to_model
49
+ "#{raw_path_to_model}.json"
50
+ end
51
+
52
+ def raw_path_to_model
53
+ path = (self.class.parent_classes || []).inject("") do |_, parent|
54
+ parent_id = send("#{parent.model_name}_id")
55
+ _ += "#{parent.collection_name}/#{parent_id}/"
56
+ _
57
+ end
58
+ "#{path}#{self.class.collection_name}/#{identifier}"
59
+ end
60
+
61
+ def path_to_collection
62
+ "#{raw_path_to_collection}.json"
63
+ end
64
+
65
+ def raw_path_to_collection
66
+ path = (self.class.parent_classes || []).inject("") do |_, parent|
67
+ parent_id = send("#{parent.model_name}_id")
68
+ _ += "#{parent.collection_name}/#{parent_id}/"
69
+ _
70
+ end
71
+ "#{path}#{self.class.collection_name}"
72
+ end
73
+
74
+ def update_with!(hash)
75
+ hash.each do |k, v|
76
+ send "#{k}=", v
77
+ end
78
+ self
79
+ end
80
+
81
+ def reload
82
+ update_with! get(path_to_model)
83
+ end
84
+
85
+ def identifier
86
+ slugged? ? slug : id
87
+ end
88
+
89
+ def slugged?
90
+ respond_to?(:slug)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,30 @@
1
+ module Swiftype
2
+ class Client
3
+ include Swiftype::Connection
4
+
5
+ def initialize(options={})
6
+ end
7
+
8
+ module Engine
9
+ def engine(id)
10
+ Swiftype::Engine.find(id)
11
+ end
12
+
13
+ def create_engine(attributes)
14
+ Swiftype::Engine.new(attributes).create!
15
+ end
16
+
17
+ def update_engine(id, attributes)
18
+ engine = Swiftype::Engine.find(id)
19
+ engine.merge!(attributes)
20
+ engine.update!(attributes)
21
+ end
22
+
23
+ def destroy_engine(id)
24
+ Swiftype::Engine.find(id).destroy!
25
+ end
26
+ end
27
+
28
+ include Swiftype::Client::Engine
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ require 'swiftype/version'
2
+
3
+ module Swiftype
4
+ module Configuration
5
+ DEFAULT_ENDPOINT = "http://api.swiftype.com/api/v1/"
6
+ DEFAULT_USER_AGENT = "Swiftype-Ruby/#{Swiftype::VERSION}"
7
+
8
+ VALID_OPTIONS_KEYS = [
9
+ :username,
10
+ :password,
11
+ :api_key,
12
+ :user_agent,
13
+ :endpoint
14
+ ]
15
+
16
+ attr_accessor *VALID_OPTIONS_KEYS
17
+
18
+ def self.extended(base)
19
+ base.reset
20
+ end
21
+
22
+ def reset
23
+ self.username = nil
24
+ self.password = nil
25
+ self.api_key = nil
26
+ self.endpoint = DEFAULT_ENDPOINT
27
+ self.user_agent = DEFAULT_USER_AGENT
28
+ self
29
+ end
30
+
31
+ def configure
32
+ yield self
33
+ self
34
+ end
35
+
36
+ def options
37
+ options = {}
38
+ VALID_OPTIONS_KEYS.each{|k| options[k] = send(k)}
39
+ options
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module Swiftype
2
+ module Connection
3
+ include Swiftype::Request
4
+
5
+ def connection
6
+ raise(InvalidCredentials, "You must supply credentials to Swiftype.configure") unless (Swiftype.username && Swiftype.password ) || Swiftype.api_key
7
+
8
+ @connection ||= begin
9
+ conn = Faraday.new(Swiftype.endpoint) do |b|
10
+ b.response :raise_error
11
+ b.use Faraday::Request::UrlEncoded
12
+ b.use FaradayMiddleware::ParseJson
13
+ b.use FaradayMiddleware::Mashify
14
+ b.use ApiResponseMiddleware
15
+ b.adapter Faraday.default_adapter
16
+ end
17
+
18
+ conn.basic_auth Swiftype.username, Swiftype.password if Swiftype.username && Swiftype.password
19
+ conn.headers['User-Agent'] = Swiftype.user_agent
20
+
21
+ conn
22
+ end
23
+ end
24
+
25
+ class ApiResponseMiddleware < Faraday::Response::Middleware
26
+ def on_complete(env)
27
+ case env[:status]
28
+ when 200, 201, 204
29
+ nil
30
+ when 401
31
+ raise InvalidCredentials
32
+ when 404
33
+ raise NonExistentRecord
34
+ when 409
35
+ raise RecordAlreadyExists
36
+ else
37
+ raise UnexpectedHTTPException, env[:body]
38
+ end
39
+ end
40
+
41
+ def initialize(app)
42
+ super
43
+ @parser = nil
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ module Swiftype
2
+ class Document < BaseModel
3
+ parents Engine, DocumentType
4
+
5
+ def engine
6
+ Engine.find engine_id
7
+ end
8
+
9
+ def document_type
10
+ DocumentType.find document_type_id
11
+ end
12
+
13
+ def update_fields!(hash)
14
+ update_with! put("#{raw_path_to_model}/update_fields.json", {:fields => hash})
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ module Swiftype
2
+ class DocumentType < BaseModel
3
+ parents Engine
4
+ include Swiftype::Search
5
+
6
+ def build_document(params={})
7
+ Document.new({
8
+ :document_type_id => id || slug,
9
+ :engine_id => engine_id
10
+ }.merge(params))
11
+ end
12
+
13
+ def create_document(params={})
14
+ doc = build_document(params)
15
+ doc.create!
16
+ doc
17
+ end
18
+
19
+ def create_documents(documents=[])
20
+ post("engines/#{engine_id}/document_types/#{slug}/documents/bulk_create.json", {:documents => documents})
21
+ end
22
+
23
+ def update_documents(documents=[])
24
+ put("engines/#{engine_id}/document_types/#{slug}/documents/bulk_update.json", {:documents => documents})
25
+ end
26
+
27
+ def update_document(document={})
28
+ document_id = document[:external_id]
29
+ put("engines/#{engine_id}/document_types/#{slug}/documents/#{document_id}/update_fields", {:fields => document[:fields]})
30
+ end
31
+
32
+ def destroy_document(document_id)
33
+ !!delete("engines/#{engine_id}/document_types/#{slug}/documents/#{document_id}")
34
+ rescue NonExistentRecord
35
+ false
36
+ end
37
+
38
+ def destroy_documents(document_ids=[])
39
+ post("engines/#{engine_id}/document_types/#{slug}/documents/bulk_destroy.json", {:documents => document_ids})
40
+ rescue NonExistentRecord
41
+ false
42
+ end
43
+
44
+ def document(id)
45
+ Document.new get("engines/#{engine_id}/document_types/#{slug}/documents/#{id}.json")
46
+ end
47
+
48
+ def engine
49
+ Engine.find(engine_id)
50
+ end
51
+
52
+ def documents
53
+ get("engines/#{engine_id}/document_types/#{slug}/documents.json").map { |d| Document.new(d) }
54
+ end
55
+
56
+ def suggest(query, options={})
57
+ search_params = { :q => query }.merge(parse_suggest_options(options))
58
+ response = get("engines/#{engine_id}/document_types/#{slug}/suggest.json", search_params)
59
+ results = {}
60
+ response['records'].each { |document_type, records| results[document_type] = records.map { |d| Swiftype::Document.new(d) }}
61
+ results
62
+ end
63
+
64
+ def search(query, options={})
65
+ search_params = { :q => query }.merge(parse_search_options(options))
66
+ response = get("engines/#{engine_id}/document_types/#{slug}/search.json", search_params)
67
+ results = {}
68
+ response['records'].each { |document_type, records| results[document_type] = records.map { |d| Swiftype::Document.new(d) }}
69
+ results
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,84 @@
1
+ require 'swiftype/document'
2
+
3
+ module Swiftype
4
+ class Easy
5
+ include Swiftype::Connection
6
+ include Swiftype::Search
7
+
8
+ def initialize(options={})
9
+ end
10
+
11
+ module Engine
12
+ def engines
13
+ test_search_module
14
+ get("engines.json")
15
+ end
16
+ def create_engine(engine={})
17
+ post("engines.json", :engine => engine)
18
+ end
19
+ def destroy_engine(engine_id)
20
+ delete("engines/#{engine_id}")
21
+ end
22
+ def suggest(engine_id, query, options={})
23
+ search_params = { :q => query }.merge(parse_suggest_options(options))
24
+ response = get("engines/#{engine_id}/suggest.json", search_params)
25
+ results = {}
26
+ response['records'].each { |document_type, records| results[document_type] = records.map { |d| Swiftype::Document.new(d) }}
27
+ results
28
+ end
29
+ def search(engine_id, query, options={})
30
+ search_params = { :q => query }.merge(parse_search_options(options))
31
+ response = get("engines/#{engine_id}/search.json", search_params)
32
+ results = {}
33
+ response['records'].each { |document_type, records| results[document_type] = records.map { |d| Swiftype::Document.new(d) }}
34
+ results
35
+ end
36
+ end
37
+
38
+ module DocumentType
39
+ def document_types(engine_id)
40
+ get("engines/#{engine_id}/document_types.json")
41
+ end
42
+ def create_document_type(engine_id, document_type={})
43
+ post("engines/#{engine_id}/document_types.json", :document_type => document_type)
44
+ end
45
+ def destroy_document_type(engine_id, document_type_id)
46
+ delete("engines/#{engine_id}/document_types/#{document_type_id}")
47
+ end
48
+ end
49
+
50
+ module Document
51
+ def documents(engine_id, document_type_id)
52
+ get("engines/#{engine_id}/document_types/#{document_type_id}/documents.json")
53
+ end
54
+ def create_document(engine_id, document_type_id, document={})
55
+ post("engines/#{engine_id}/document_types/#{document_type_id}/documents.json", :document => document)
56
+ end
57
+ def create_documents(engine_id, document_type_id, documents=[])
58
+ post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_create.json", :documents => documents)
59
+ end
60
+ def destroy_document(engine_id, document_type_id, document_id)
61
+ delete("engines/#{engine_id}/document_types/#{document_type_id}/documents/#{document_id}")
62
+ end
63
+ def destroy_documents(engine_id, document_type_id, document_ids=[])
64
+ post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_destroy.json", :documents => document_ids)
65
+ end
66
+ def create_or_update_document(engine_id, document_type_id, document={})
67
+ post("engines/#{engine_id}/document_types/#{document_type_id}/documents/create_or_update.json", :document => document)
68
+ end
69
+ def create_or_update_documents(engine_id, document_type_id, documents=[])
70
+ post("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_create_or_update.json", :documents => documents)
71
+ end
72
+ def update_document(engine_id, document_type_id, document_id, fields)
73
+ put("engines/#{engine_id}/document_types/#{document_type_id}/documents/#{document_id}/update_fields.json", { :fields => fields })
74
+ end
75
+ def update_documents(engine_id, document_type_id, documents={})
76
+ put("engines/#{engine_id}/document_types/#{document_type_id}/documents/bulk_update.json", { :documents => documents })
77
+ end
78
+ end
79
+
80
+ include Swiftype::Easy::Engine
81
+ include Swiftype::Easy::DocumentType
82
+ include Swiftype::Easy::Document
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ module Swiftype
2
+ class Engine < BaseModel
3
+ def self.find(id)
4
+ new Swiftype::Client.new.get("engines/#{id}.json")
5
+ end
6
+
7
+ def build_document_type(params={})
8
+ DocumentType.new({
9
+ :engine_id => id
10
+ }.merge(params))
11
+ end
12
+
13
+ def create_document_type(params={})
14
+ doc = build_document_type(params)
15
+ doc.create!
16
+ doc
17
+ end
18
+
19
+ def destroy_document_type(document_type_name)
20
+ !!delete("engines/#{slug}/document_types/#{document_type_name}")
21
+ rescue NonExistentRecord
22
+ false
23
+ end
24
+
25
+ def document_type(id)
26
+ DocumentType.new get("engines/#{slug}/document_types/#{id}.json")
27
+ end
28
+
29
+ def document_types
30
+ get("engines/#{slug}/document_types.json").map { |dt| DocumentType.new(dt) }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module Swiftype
2
+ class ClientException < StandardError; end
3
+ class NonExistentRecord < ClientException; end
4
+ class RecordAlreadyExists < ClientException; end
5
+ class InvalidCredentials < ClientException; end
6
+ class UnexpectedHTTPException < ClientException; end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Swiftype
2
+ module Request
3
+ def get(path, params={}, options={})
4
+ request(:get, path, params, options)
5
+ end
6
+
7
+ def delete(path, params={}, options={})
8
+ request(:delete, path, params, options)
9
+ end
10
+
11
+ def post(path, params={}, options={})
12
+ request(:post, path, params, options)
13
+ end
14
+
15
+ def put(path, params={}, options={})
16
+ request(:put, path, params, options)
17
+ end
18
+
19
+ private
20
+ def request(method, path, params, options)
21
+ params.merge!({:auth_token => Swiftype.api_key}) if Swiftype.api_key
22
+ response = connection.send(method) do |request|
23
+ case method.to_sym
24
+ when :delete, :get
25
+ request.url(path, params)
26
+ when :post, :put
27
+ request.path = path
28
+ request.body = params unless params.empty?
29
+ end
30
+ end
31
+ options[:raw] ? response : response.body
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module Swiftype
2
+ module Search
3
+
4
+ VALID_SEARCH_OPTIONS = [:fetch_fields, :search_fields, :filters, :per_page, :page, :document_types, :functional_boosts]
5
+ VALID_SUGGEST_OPTIONS = [:fetch_fields, :search_fields, :filters, :document_types, :functional_boosts]
6
+
7
+ def parse_search_options(options)
8
+ parse_options(options,VALID_SEARCH_OPTIONS)
9
+ end
10
+
11
+ def parse_suggest_options(options)
12
+ parse_options(options,VALID_SUGGEST_OPTIONS)
13
+ end
14
+
15
+ def parse_options(options,valid_options)
16
+ parsed_options = {}
17
+ valid_options.each do |option_name|
18
+ next unless options[option_name]
19
+ encode_single_option(parsed_options,option_name,options[option_name])
20
+ end
21
+ parsed_options
22
+ end
23
+
24
+ # recursive method to encode arrays, hashes, and values the 'Rails' way, to make server-side parsing simpler
25
+ def encode_single_option(parsed_options,key,value,prefix='')
26
+ prefix = key if prefix.empty?
27
+ if value.instance_of?(Hash)
28
+ value.each { |k,v| encode_single_option(parsed_options,k,v,"#{prefix}[#{k}]") }
29
+ elsif value.instance_of?(Array)
30
+ parsed_options["#{prefix}[]"] = value
31
+ else
32
+ parsed_options["#{prefix}"] = value
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Swiftype
2
+ VERSION = "0.0.1"
3
+ end
data/lib/swiftype.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'ostruct'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'swiftype/exceptions'
5
+
6
+ module Swiftype
7
+ autoload :Configuration, 'swiftype/configuration'
8
+ autoload :Connection, 'swiftype/connection'
9
+ autoload :Client, 'swiftype/client'
10
+ autoload :BaseModel, 'swiftype/base_model'
11
+ autoload :Request, 'swiftype/request'
12
+ autoload :Search, 'swiftype/search'
13
+ autoload :Engine, 'swiftype/engine'
14
+ autoload :DocumentType, 'swiftype/document_type'
15
+ autoload :Document, 'swiftype/document'
16
+ autoload :Easy, 'swiftype/easy'
17
+
18
+
19
+ extend Configuration
20
+
21
+ class << self
22
+ def new(options={})
23
+ Swiftype::Client.new(options)
24
+ end
25
+
26
+ def method_missing(method, *args, &block)
27
+ return super unless new.respond_to?(method)
28
+ new.send(method, *args, &block)
29
+ end
30
+
31
+ def respond_to?(method, include_private=false)
32
+ new.respond_to?(method, include_private) || super(method, include_private)
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "swiftype/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "swiftype-rb"
7
+ s.version = Swiftype::VERSION
8
+ s.authors = ["Quin Hoxie", "Matt Riley"]
9
+ s.email = ["team@swiftype.com"]
10
+ s.homepage = "http://swiftype.com"
11
+ s.summary = %q{Swiftype API Gem}
12
+ s.description = %q{Official Gem for accessing the Swiftype Search API}
13
+
14
+ s.rubyforge_project = "swiftype-rb"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "json"
24
+ s.add_runtime_dependency "faraday", ['~> 0.7.6']
25
+ s.add_runtime_dependency "faraday_middleware", ['~> 0.8.4']
26
+ s.add_runtime_dependency "hashie", ['~> 1.2.0']
27
+ s.add_runtime_dependency 'activesupport', ['>= 2.3.9', '< 4']
28
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swiftype-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Quin Hoxie
9
+ - Matt Riley
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-07-05 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: &70265771944420 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70265771944420
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: &70265771943900 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70265771943900
37
+ - !ruby/object:Gem::Dependency
38
+ name: faraday_middleware
39
+ requirement: &70265771943380 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 0.8.4
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70265771943380
48
+ - !ruby/object:Gem::Dependency
49
+ name: hashie
50
+ requirement: &70265771942900 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.2.0
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *70265771942900
59
+ - !ruby/object:Gem::Dependency
60
+ name: activesupport
61
+ requirement: &70265771942400 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 2.3.9
67
+ - - <
68
+ - !ruby/object:Gem::Version
69
+ version: '4'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: *70265771942400
73
+ description: Official Gem for accessing the Swiftype Search API
74
+ email:
75
+ - team@swiftype.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - .gitignore
81
+ - Gemfile
82
+ - README.md
83
+ - Rakefile
84
+ - lib/swiftype.rb
85
+ - lib/swiftype/base_model.rb
86
+ - lib/swiftype/client.rb
87
+ - lib/swiftype/configuration.rb
88
+ - lib/swiftype/connection.rb
89
+ - lib/swiftype/document.rb
90
+ - lib/swiftype/document_type.rb
91
+ - lib/swiftype/easy.rb
92
+ - lib/swiftype/engine.rb
93
+ - lib/swiftype/exceptions.rb
94
+ - lib/swiftype/request.rb
95
+ - lib/swiftype/search.rb
96
+ - lib/swiftype/version.rb
97
+ - swiftype-rb.gemspec
98
+ homepage: http://swiftype.com
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project: swiftype-rb
118
+ rubygems_version: 1.8.10
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Swiftype API Gem
122
+ test_files: []