orchestrate-rails 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 35f1213941eab49abfccaaad4a7eda59de6d0807
4
+ data.tar.gz: c1f62d130844202acf89e8f6a8092adfea4a121b
5
+ SHA512:
6
+ metadata.gz: 193afdfd1767315d72e84a74d1cecfec6b1af58473cbd0dbfc64c2e7f7b3540a87fdcd9cea805f7aa0b065fbf1062f7e72ef270f1b23107c9683e4268215776f
7
+ data.tar.gz: ca1bcc6b0a34b22c0d5e639a8237e3126167e7100d4994b6730724d4457193ac8ac81f6ff3d5175e7489f845fadd78d598afe5972e3f61dac1aa0f55df879d65
@@ -0,0 +1,35 @@
1
+ module Orchestrate
2
+
3
+ =begin rdoc
4
+
5
+ ==== orchestrate-application
6
+
7
+ This <em>hidden gem <b>orchestrate-application</b></em> is embedded within
8
+ the <em>orchestrate-rails</em> gem to provide a basic interface and
9
+ accompanying set of services for accessing orchestrate.io collections from
10
+ <b><em>any type of application written in ruby</em></b>.
11
+ The application layer features services for connecting
12
+ to the api, sending requests and handling responses, as well as additional
13
+ services for schema definition and cache management.
14
+
15
+ The application-layer interface to orchestrate.io collections is defined
16
+ in the <b>Record[Application/Record.html]</b> class.
17
+
18
+ ==== {Usage example}[Application/Example.html]
19
+ =end
20
+
21
+ module Application
22
+
23
+ require "orchestrate_application/schema_collection"
24
+ require "orchestrate_application/schema"
25
+ require "orchestrate_application/connect"
26
+ require "orchestrate_application/record"
27
+ require "orchestrate_application/document"
28
+ require "orchestrate_application/result"
29
+ require "orchestrate_application/response"
30
+ require "orchestrate_application/simple_cache_store"
31
+ require "orchestrate_application/simple_cache_request"
32
+ require "orchestrate_application/simple_cache_response"
33
+
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module Orchestrate
2
+
3
+ =begin rdoc
4
+
5
+ ==== orchestrate-rails
6
+
7
+ Ruby gem <tt>orchestrate-rails</tt> provides an ActiveRecord-style interface
8
+ to map rails models to Orchestrate.io Databases-as-a-Service.
9
+
10
+ The rails model interface to orchestrate.io collections
11
+ is defined in the <b> Model</b> class.
12
+
13
+ ==== {Usage example}[Rails/Example.html]
14
+
15
+ ==== {Try out the Tutorial!}[Rails/Tutorial.html]
16
+ =end
17
+
18
+ module Rails
19
+
20
+ extend ActiveSupport::Concern
21
+
22
+ # include the 'hidden gem'
23
+ require "orchestrate-application"
24
+
25
+ require "orchestrate_rails/document"
26
+ require "orchestrate_rails/schema"
27
+ require "orchestrate_rails/model"
28
+ require "orchestrate_rails/event"
29
+ require "orchestrate_rails/search_result"
30
+ require "orchestrate_rails/extensions"
31
+
32
+ end
33
+ end
34
+
@@ -0,0 +1,25 @@
1
+ module Orchestrate::Application
2
+
3
+ # Class for creating the connection between the application and the
4
+ # orchestrate.io api.
5
+ #
6
+ class Connect
7
+
8
+ # default config file: "<app-root-dir>/orch_config.json"
9
+ @@config_file = "orch_config.json"
10
+
11
+ def self.config(config_file)
12
+ @@config_file = config_file
13
+ end
14
+
15
+ def self.client
16
+ @@client ||= connect
17
+ end
18
+
19
+ private
20
+ def self.connect
21
+ Orchestrate::API::Wrapper.new @@config_file
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,66 @@
1
+ module Orchestrate::Application
2
+
3
+ # Object to encapsulate the key/value data along with the metadata
4
+ # associated with an Orchestrate.io record. Used to return results
5
+ # from the application layer.
6
+ #
7
+ class Document < Object
8
+
9
+ # Metadata.
10
+ attr_reader :metadata
11
+
12
+ # The key/value data for this record.
13
+ attr_reader :key_value_pairs
14
+
15
+ # The <tt>ref</tt> value associated with the document.
16
+ attr_reader :id
17
+
18
+ # Saves the key/value data and Metadata. Saves the doc to cache
19
+ # when cache is enabled.
20
+ def initialize(key_value_pairs, metadata)
21
+ @key_value_pairs = key_value_pairs
22
+ @metadata = metadata
23
+ @id = @metadata.ref
24
+
25
+ save if cache_enabled?
26
+ end
27
+
28
+ # Saves the document to cache.
29
+ def save
30
+ # puts "DOC: saving -> '#{cache}'"
31
+ # puts " respond_to? :save -> '#{cache.respond_to? :save}'"
32
+ cache.save self
33
+ # puts "DOC: saved"
34
+ end
35
+
36
+ # Returns handle to the SimpleCacheStore singleton instance.
37
+ def cache
38
+ @@cache ||= SimpleCacheStore.instance
39
+ end
40
+
41
+ def cache_enabled?
42
+ cache.is_enabled?
43
+ end
44
+ end
45
+
46
+ # Holds the metadata associated with each document in the results
47
+ # from a GET request.
48
+ class Metadata < Object
49
+
50
+ # For all documents, the metadata includes:
51
+ # - collection
52
+ # - key
53
+ # - ref
54
+ # and the following for specific result types:
55
+ # - score (search)
56
+ # - etype (events)
57
+ # - kind, from_collection and from_key (graph)
58
+ def initialize(metadata)
59
+ metadata.each do |k,v|
60
+ self.class.send :attr_reader, k.to_s
61
+ instance_variable_set "@#{k.to_s}", v
62
+ end
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,467 @@
1
+ module Orchestrate::Application
2
+
3
+ require "active_support/core_ext"
4
+
5
+ # Class for accessing collections belonging to an Orchestrate.io application.
6
+ # - public class methods for collection requests
7
+ # - public instance methods for key/value requests
8
+ #
9
+ # PUT and DELETE requests return
10
+ # <b>{Orchestrate::API::Response}[../API/Response.html]</b>.
11
+ #
12
+ # GET requests return <b>Result[Result.html]</b> or
13
+ # <b>SearchResult[SearchResult.html]</b>.
14
+ # The result data is returned as <b>Document[Document.html]</b> objects.
15
+ #
16
+ class Record < Object
17
+
18
+ # The unique primary_key that identifies a set of key/value data
19
+ # stored in an orchestrate.io collection.
20
+ attr_reader :id
21
+
22
+ # attr_reader :event_types, :graphs
23
+
24
+ # :stopdoc:
25
+ # The <tt>ref</tt> value for the current instance.
26
+ # Convenience method #orchestrate_ref_value is provided as the
27
+ # recommended way for an application to read this attribute.
28
+ attr_reader :__ref_value__
29
+ # :startdoc:
30
+
31
+ @@_collection = {}
32
+ @@_cache = {}
33
+
34
+ # Performs application-level initialization functions.
35
+ def initialize(params={})
36
+ name = init_collection_name params[:define_collection_name]
37
+ @@_collection[self.class.name] ||= Schema.instance.get_collection(name)
38
+ @id = params[:id] if params[:id]
39
+ @__ref_value__ = '"*"'
40
+
41
+ # @event_types = params[:event_types] ? params[:event_types] : []
42
+ # @relation_kinds = params[:graphs] ? params[:graphs] : []
43
+
44
+ @@_cache[self.class.name] ||= SimpleCacheRequest.new(ocollection)
45
+ @@cache_store ||= SimpleCacheStore.instance
46
+ end
47
+
48
+ # -------------------------------------------------------------------------
49
+ # Class methods - collection
50
+
51
+ # Searches the collection; encodes any whitespace contained in the query
52
+ # string. Returns SearchResult object where the results attribute contains
53
+ # an array of Document objects.
54
+ def self.orchio_search(collection, query_str)
55
+ response = client.send_request( :get, {
56
+ collection: collection,
57
+ path: "?query=#{query_str.gsub(/\s/, '%20')}"
58
+ })
59
+
60
+ SearchResult.new(
61
+ status: orchio_status(response, 200),
62
+ response: sanitize(response),
63
+ count: response.body.count,
64
+ total_count: response.body.total_count,
65
+ results: response.body.results.map { |result|
66
+ Document.new(
67
+ result['value'].merge(id: result['path']['key']),
68
+ Metadata.new(
69
+ collection: result['path']['collection'],
70
+ key: result['path']['key'],
71
+ ref: result['path']['ref'],
72
+ score: result['score']
73
+ ))})
74
+ end
75
+
76
+ # Lists the collection contents. The results are based the options.
77
+ # Returns Result object.
78
+ def self.orchio_list(collection, path=nil)
79
+ response = client.send_request :get, { collection: collection, path: path }
80
+ Result.new(
81
+ status: orchio_status(response, 200),
82
+ response: sanitize(response),
83
+ count: response.body.count,
84
+ :next => response.body.next,
85
+ results: response.body.results.map { |result|
86
+ Document.new(
87
+ result['value'].merge(id: result['path']['key']),
88
+ Metadata.new(
89
+ collection: result['path']['collection'],
90
+ key: result['path']['key'],
91
+ ref: result['path']['ref'],
92
+ )
93
+ )})
94
+ end
95
+
96
+ # Delete the specified collection.
97
+ def self.orchio_delete(collection)
98
+ response = client.send_request(
99
+ :delete, { collection: collection, path: "?force=true" }
100
+ )
101
+ # SchemaCollection.delete(collection) if response.header.code == 204
102
+ orchio_status response, 204
103
+ end
104
+
105
+ # Delete key from collection.
106
+ def self.orchio_delete_key(collection, key)
107
+ new(collection: collection, id: key).orchio_delete
108
+ end
109
+
110
+ # -------------------------------------------------------------------------
111
+ # Instance methods - collection/key
112
+
113
+ # Fetches the data associated with this id from the collection.
114
+ def orchio_get
115
+ if cache.enabled? and response = cache_request.get(id)
116
+ if response.header.status == :cache
117
+ doc = response.body.document
118
+ end
119
+ else
120
+ response = client.send_request :get, inst_args if response.nil?
121
+ doc = Document.new(
122
+ response.body.to_hash,
123
+ Metadata.new(
124
+ :collection => ocollection,
125
+ :key => @id,
126
+ :ref => response.header.etag
127
+ ))
128
+ end
129
+ Result.new(
130
+ status: orchio_status(response, 200),
131
+ response: response,
132
+ results: [ doc ]
133
+ )
134
+ end
135
+
136
+ # Updates the collection with the data associated with this instance.
137
+ def orchio_put(jdoc)
138
+ response = client.send_request :put, inst_args(json: jdoc)
139
+ if cache.enabled?
140
+ simple_cache.save(
141
+ Document.new(
142
+ response.body.to_hash,
143
+ Metadata.new(
144
+ :collection => ocollection,
145
+ :key => @id,
146
+ :ref => response.header.etag
147
+ )))
148
+ end
149
+ set_ref_value response
150
+ orchio_status response, 201
151
+ end
152
+
153
+ # Deletes the primary_key and data associated with this instance from
154
+ # the collection.
155
+ def orchio_delete
156
+ response = client.send_request :delete, inst_args
157
+ orchio_status response, 204
158
+ end
159
+
160
+ # Deletes the primary_key and <b>purges all of its immutable data</b>
161
+ # from the collection.
162
+ def orchio_purge
163
+ response = client.send_request :delete, inst_args(path: "?purge=true")
164
+ orchio_status response, 204
165
+ end
166
+
167
+ # -------------------------------------------------------------------------
168
+ # Instance methods - collection/key/ref
169
+
170
+ # Gets the key/value pair by its 'ref' value.
171
+ def orchio_get_by_ref(ref)
172
+ response = client.send_request :get, inst_args(ref: ref)
173
+ Result.new(
174
+ status: orchio_status(response, 200),
175
+ response: response,
176
+ results: [ Document.new(
177
+ response.body.to_hash,
178
+ Metadata.new(
179
+ :collection => ocollection,
180
+ :key => @id,
181
+ :ref => response.header.etag
182
+ ))]
183
+ )
184
+ end
185
+
186
+ # Updates the key/value if the send'ref' value matches the 'ref' value
187
+ # for the latest version in the collection.
188
+ def orchio_put_if_match(document, ref)
189
+ response = client.send_request :put, inst_args(json: document, ref: ref)
190
+ set_ref_value response
191
+ orchio_status response, 201
192
+ end
193
+
194
+ # Updates the key/value if the key does not already exist in the collection.
195
+ def orchio_put_if_none_match(document)
196
+ orchio_put_if_match(document, '"*"')
197
+ end
198
+
199
+ # -------------------------------------------------------------------------
200
+ # Instance methods - collection/key/events
201
+
202
+ # Gets all events of specified type, within the timestamp parameters, where
203
+ # timestamp = { :start => start, :end => end }.
204
+ def orchio_get_events(event_type, timestamp={})
205
+ # add_event_type event_type
206
+
207
+ if cache.enabled? and response = cache_request.get_events(id, event_type)
208
+ if response.header.status == :cache
209
+ docs = response.body.documents
210
+ count = docs.length
211
+ end
212
+ else
213
+ response = client.send_request(
214
+ :get, inst_args(event_type: event_type, timestamp: timestamp)
215
+ )
216
+ docs = response.body.results.map { |result|
217
+ Document.new result['value'].merge(
218
+ key: @id,
219
+ etype: event_type,
220
+ timestamp: result['timestamp']
221
+ ),
222
+ Metadata.new(
223
+ collection: ocollection,
224
+ key: @id,
225
+ ref: funkify("#{event_type}#{result['timestamp']}"),
226
+ etype: event_type,
227
+ )
228
+ }
229
+ cache.save_event(id, event_type) if cache.enabled? and count == 0
230
+ end
231
+ Result.new(
232
+ status: orchio_status(response, 200),
233
+ response: response,
234
+ count: response.body.count,
235
+ results: docs
236
+ )
237
+ end
238
+
239
+ def orchio_put_event(event_type, timestamp={}, document)
240
+ # add_event_type event_type
241
+ response = client.send_request(:put, inst_args(
242
+ event_type: event_type, timestamp: timestamp, json: document
243
+ ))
244
+ orchio_status response, 204
245
+ end
246
+
247
+ # -------------------------------------------------------------------------
248
+ # Instance methods - collection/key/graph
249
+
250
+ # Gets the graph for the specified kind of relation.
251
+ def orchio_get_graph(kind)
252
+ # add_relation_kind kind
253
+ if cache.enabled? and response = cache_request.get_graph(id, kind)
254
+ if response.header.status == :cache
255
+ docs = response.body.documents
256
+ count = docs.length
257
+ end
258
+ else
259
+ response = client.send_request :get, inst_args(kind: kind)
260
+ docs = response.body.results.map { |result|
261
+ Document.new(
262
+ result['value'].merge(id: result['path']['key']),
263
+ Metadata.new(
264
+ :collection => result['path']['collection'],
265
+ :key => result['path']['key'],
266
+ :ref => result['path']['ref'],
267
+ :kind => kind,
268
+ :from_collection => ocollection,
269
+ :from_key => @id,
270
+ ))}
271
+ cache.save_graph(id, kind) if cache.enabled? and count == 0
272
+ end
273
+ Result.new(
274
+ status: orchio_status(response, 200),
275
+ response: response,
276
+ count: response.body.count,
277
+ results: docs
278
+ )
279
+ end
280
+
281
+ # Add a graph/relation to the collection.
282
+ # Store the to_key's 'ref' value if caching is enabled.
283
+ def orchio_put_graph(kind, to_collection, to_key)
284
+ # add_relation_kind kind
285
+
286
+ if cache.enabled?
287
+ ref = simple_cache.get_ref to_collection, to_key
288
+ simple_cache.get_cc(ocollection).save_graph Metadata.new(
289
+ { from_key: @id, kind: kind, ref: ref }
290
+ )
291
+ end
292
+
293
+ response = client.send_request(
294
+ :put,
295
+ inst_args(kind: kind, to_collection: to_collection, to_key: to_key)
296
+ )
297
+ orchio_status response, 204
298
+ end
299
+
300
+ # Delete a graph/relation from the collection.
301
+ def orchio_delete_graph(kind, to_collection, to_key)
302
+ response = client.send_request(
303
+ :delete,
304
+ inst_args(
305
+ kind: kind,
306
+ to_collection: to_collection,
307
+ to_key: to_key,
308
+ path: "?purge=true"
309
+ ))
310
+ orchio_status response, 204
311
+ end
312
+
313
+ # -------------------------------------------------------------------------
314
+ # Public instance methods JMC
315
+
316
+ # Returns the collection name for the current instance.
317
+ def orchestrate_collection_name
318
+ ocollection
319
+ end
320
+
321
+ # Returns the <tt>ref</tt> value for the current instance.
322
+ # The <tt>ref</tt> value is an immutable value assigned to each
323
+ # version of primary_key data in an orchestrate.io collection.
324
+ def orchestrate_ref_value
325
+ __ref_value__
326
+ end
327
+
328
+ # Returns the primary_key for the current instance.
329
+ def orchestrate_primary_key
330
+ id
331
+ end
332
+
333
+ # Returns the client handle.
334
+ def orchestrate_client
335
+ client
336
+ end
337
+
338
+ # -------------------------------------------------------------------------
339
+ private
340
+
341
+ # Returns the collection name for this instance.
342
+ #
343
+ # This method is called during initializtion with the value
344
+ # of <tt>:define_collection_name</tt> from the params hash.
345
+ # If this value is nil or blank, it is expected that the collection
346
+ # name can be derived from the class name as shown:
347
+ #
348
+ # - class: Film => 'films'
349
+ # - class: FilmClassic => 'film_classics'
350
+ #
351
+ # Any collection names that do not follow this convention must be
352
+ # specified by adding the <tt>:define_collection_name</tt> key to
353
+ # the params hash in the model class definition.
354
+ #
355
+ # class Film < Orchestrate::Application::Record
356
+ # def initialize(params={})
357
+ # params[:define_collection_name] = "Classic_Film_Collection"
358
+ # super(params)
359
+ # end
360
+ # end
361
+ #
362
+ def init_collection_name(collection_name)
363
+ (collection_name.blank?) ? File.basename(self.class.name.tableize)
364
+ : collection_name
365
+ end
366
+
367
+ # Updates the current instance's <tt>ref</tt> value, aka <tt>etag</tt>,
368
+ # after a successful PUT request.
369
+ def set_ref_value(response)
370
+ unless response.header.code != 201 || response.header.etag.blank?
371
+ @__ref_value__ = response.header.etag
372
+ end
373
+ end
374
+
375
+ # :stopdoc:
376
+ # JMC
377
+ def orchio_status(response, expected_code)
378
+ self.class.orchio_status response, expected_code
379
+ end
380
+ # :startdoc:
381
+
382
+ # Returns the client handle for requests to the orchestrate.io api.
383
+ def client
384
+ @@client ||= Orchestrate::Application::Connect.client
385
+ end
386
+
387
+ # Returns the collection name associated with the current instance's class.
388
+ def ocollection
389
+ @@_collection[self.class.name].name
390
+ end
391
+
392
+ # Returns hash that merges additional arguments with the ever-present
393
+ # collection and key args.
394
+ def inst_args(args={})
395
+ args.merge(collection: ocollection, key: id)
396
+ end
397
+
398
+ # def add_event_type(type)
399
+ # @event_types << type unless @event_types.include? type
400
+ # end
401
+
402
+ # def add_relation_kind(kind)
403
+ # @relation_kinds << kind unless @relation_kinds.include? kind
404
+ # end
405
+
406
+ # Returns a unique identifer for the specified event.
407
+ def funkify(etype_and_timestamp)
408
+ funky = etype_and_timestamp.reverse.concat("#{ocollection}#{id}")
409
+ funkier = funky.split('').shuffle(random: funky.length).join.delete('_')
410
+ funkiest = funkier.bytes.join.to_i % 0xffffffffff
411
+ end
412
+
413
+ # Returns handle to the SimpleCacheCollection instance for this class.
414
+ def cache
415
+ @@_cache[self.class.name]
416
+ end
417
+
418
+ # Calls #cache. Explain! Used for clarity? JMC
419
+ def cache_request
420
+ cache
421
+ end
422
+
423
+ # Returns handle to the SimpleCacheStore singleton instance.
424
+ def simple_cache
425
+ @@cache_store
426
+ end
427
+
428
+ # -------------------------------------------------------------------------
429
+ # Private class methods
430
+
431
+ # Calls #client instance method.
432
+ def self.client
433
+ new.orchestrate_client
434
+ end
435
+
436
+ # Removes result data from the response body for a successful GET request
437
+ # This is done to avoid redundancy, since the result data is already
438
+ # included in the Result object.
439
+ def self.sanitize(response)
440
+ if response.header.code.to_i == 200
441
+ Response.new do |r|
442
+ r.success = response.success?
443
+ r.header = response.header
444
+ r.body = ResponseBody.new nil
445
+ end
446
+ else
447
+ response
448
+ end
449
+ end
450
+
451
+ # :stopdoc:
452
+ # JMC
453
+ def self.orchio_status(response, expected_code)
454
+ # puts " orchio_status: '#{response.header.code}'"
455
+ status = true
456
+ if response.header.code != expected_code
457
+ puts " Error: #{response.body.code}: \"#{response.body.message}\""
458
+ status = false
459
+ end
460
+ status
461
+ end
462
+ # :startdoc:
463
+
464
+ end
465
+
466
+ end
467
+
@@ -0,0 +1,39 @@
1
+ module Orchestrate::Application
2
+
3
+ # ---------------------------------------------------------------------------
4
+ # Inherits from Orchestrate::API::Response but
5
+ # Classes to handle orchestrate.io API responses - HTTParty
6
+ #
7
+ class Response < Orchestrate::API::Response
8
+ attr_writer :success, :header, :body
9
+
10
+ def initialize
11
+ yield self if block_given?
12
+ end
13
+ end
14
+
15
+ class ResponseBody < Orchestrate::API::ResponseBody
16
+ end
17
+
18
+ # ---------------------------------------------------------------------------
19
+
20
+ # class Response
21
+ # attr_accessor :success, :header, :body
22
+
23
+ # def initialize
24
+ # yield self if block_given?
25
+ # end
26
+
27
+ # def success?
28
+ # @success == true
29
+ # end
30
+ # end
31
+
32
+ # class ResponseBody
33
+ # attr_reader :content, :to_hash, :results, :count, :total_count, :next
34
+
35
+ # def result_keys
36
+ # end
37
+ # end
38
+
39
+ end