orchestrate-rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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