orchestrate 0.6.3 → 0.7.0

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.
@@ -0,0 +1,310 @@
1
+ module Orchestrate
2
+ # Collections are groupings of KeyValue items. They are analagous to tables in SQL databases.
3
+ class Collection
4
+
5
+ # @return [Orchestrate::Application] The application this collection belongs to.
6
+ attr_reader :app
7
+
8
+ # @return [String] The name of this collection.
9
+ attr_reader :name
10
+
11
+ # Instantiate the Collection
12
+ # @param app [Orchestrate::Client, Orchestrate::Application] The application the Collection belongs to.
13
+ # @param collection_name [#to_s] The name of the collection.
14
+ # @return Orchestrate::Collection
15
+ def initialize(app, collection_name)
16
+ if app.kind_of? Orchestrate::Client
17
+ @app = Application.new(app)
18
+ else
19
+ @app = app
20
+ end
21
+ @name = collection_name.to_s
22
+ end
23
+
24
+ # @return a pretty-printed representation of the collection.
25
+ def to_s
26
+ "#<Orchestrate::Collection name=#{name} api_key=#{app.api_key[0..7]}...>"
27
+ end
28
+ alias :inspect :to_s
29
+
30
+ # Equivalent to `String#==`. Compares by name and app's api_key.
31
+ # @param other [Orchestrate::Collection] the collection to compare against.
32
+ # @return [true, false]
33
+ def ==(other)
34
+ other.kind_of?(Orchestrate::Collection) && \
35
+ other.app.api_key == app.api_key && \
36
+ other.name == name
37
+ end
38
+ alias :eql? :==
39
+
40
+ # Equivalent to `String#<=>`. Compares by name and app's api_key.
41
+ # @param other [Orchestrate::Collection] the collection to compare against.
42
+ # @return [nil, -1, 0, 1]
43
+ def <=>(other)
44
+ return nil unless other.kind_of?(Orchestrate::Collection)
45
+ return nil unless other.app.api_key == app.api_key
46
+ other.name <=> name
47
+ end
48
+
49
+ # @!group Collection api
50
+
51
+ # Deletes the collection.
52
+ # @return Orchestrate::API::Response
53
+ def destroy!
54
+ app.client.delete_collection(name)
55
+ end
56
+
57
+ # @!endgroup
58
+
59
+ # @!group KeyValue getters, setters
60
+
61
+ # [Retrieves a KeyValue item by key](http://orchestrate.io/docs/api/#key/value/get).
62
+ # @param key_name [#to_s] The key of the item
63
+ # @return [Orchestrate::KeyValue, nil] The KeyValue item if found, nil if not.
64
+ def [](key_name)
65
+ begin
66
+ KeyValue.load(self, key_name)
67
+ rescue API::NotFound
68
+ nil
69
+ end
70
+ end
71
+
72
+ # [Sets a KeyValue item by key](http://orchestrate.io/docs/api/#key/value/put-\(create/update\)).
73
+ # @param key_name [#to_s] The key of the item.
74
+ # @param value [#to_json] The value to store at the key.
75
+ # @return [value] The item provided for 'value'.
76
+ # @note Ruby will return the value provided to `something=` methods. If you want the KeyValue returned, use #set.
77
+ # @raise Orchestrate::API::BadRequest the body is not valid JSON.
78
+ # @see #set
79
+ def []=(key_name, value)
80
+ set(key_name, value)
81
+ end
82
+
83
+ # [Sets a KeyValue item by key](http://orchestrate.io/docs/api/#key/value/put-\(create/update\)).
84
+ # @param key_name [#to_s] The key of the item.
85
+ # @param value [#to_json] The value to store at the key.
86
+ # @param condition [nil, false, #to_s] (see Orchestraate::Client#put)
87
+ # @return [Orchestrate::KeyValue] The new KeyValue item.
88
+ # @raise Orchestrate::API::BadRequest the body is not valid JSON.
89
+ # @raise Orchestrate::API::VersionMismatch a String condition was provided, but does not match the ref for the current value.
90
+ # @raise Orchestrate::API::AlreadyPresent a false condition was provided, but a value already exists for this key
91
+ # @see #[]=
92
+ def set(key_name, value, condition=nil)
93
+ response = app.client.put(name, key_name, value, condition)
94
+ KeyValue.from_bodyless_response(self, key_name, value, response)
95
+ end
96
+
97
+ # [Creates a KeyValue item with an auto-generated
98
+ # key](http://orchestrate.io/docs/api/#key/value/post-\(create-&-generate-key\))
99
+ # @param value [#to_json] The value to store
100
+ # @return [Orchestrate::KeyValue] The KeyValue created.
101
+ # @note If the create is considered a success but the client is unable to parse the key
102
+ # from the location header, an API::ServiceError is raised.
103
+ def <<(value)
104
+ response = app.client.post(name, value)
105
+ match_data = response.location.match(%r{#{name}/([^/]+)})
106
+ raise API::ServiceError.new(response) unless match_data
107
+ KeyValue.from_bodyless_response(self, match_data[1], value, response)
108
+ end
109
+
110
+ # Creates a KeyValue item in the collection.
111
+ # @overload create(value)
112
+ # (see #<<)
113
+ # [Creates a KeyValue item with an auto-generated
114
+ # key](http://orchestrate.io/docs/api/#key/value/post-\(create-&-generate-key\))
115
+ # @param value [#to_json] The value to store
116
+ # @return [Orchestrate::KeyValue] The KeyValue created.
117
+ # @note If the create is considered a success but the client is unable to parse the key
118
+ # from the location header, an API::ServiceError is raised.
119
+ # @overload create(key_name, value)
120
+ # [Creates a KeyValue item by key, if the key doesn't have
121
+ # a value](http://orchestrate.io/docs/api/#key/value/put-\(create/update\)).
122
+ # @param key_name [#to_s] The name of the key
123
+ # @param value [#to_json] The value to store at the key
124
+ # @return [Orchestrate::KeyValue, nil] The KeyValue if created, false if not
125
+ def create(key_name_or_value, value=nil)
126
+ if value.nil? and key_name_or_value.respond_to?(:to_json)
127
+ self << key_name_or_value
128
+ else
129
+ begin
130
+ set(key_name_or_value, value, false)
131
+ rescue Orchestrate::API::AlreadyPresent
132
+ false
133
+ end
134
+ end
135
+ end
136
+
137
+ # [Deletes the value for a KeyValue
138
+ # item](http://orchestrate.io/docs/api/#key/value/delete12).
139
+ # @param key_name [#to_s] The name of the key
140
+ # @return [true] If the request suceeds.
141
+ def delete(key_name)
142
+ app.client.delete(name, key_name)
143
+ true
144
+ end
145
+
146
+ # [Purges a KeyValue item and its Ref
147
+ # history](http://orchestrate.io/docs/api/#key/value/delete12).
148
+ # @param key_name [#to_s] The name of the key
149
+ # @return [true] If the request suceeds.
150
+ def purge(key_name)
151
+ app.client.purge(name, key_name)
152
+ true
153
+ end
154
+
155
+ # @!endgroup
156
+
157
+
158
+ include Enumerable
159
+ # @!group KeyValue enumerators
160
+
161
+ # Iterates over each KeyValue item in the collection. Used as the basis for Enumerable methods.
162
+ # Items are provided in lexicographically sorted order by key name.
163
+ # @yieldparam [Orchestrate::KeyValue] key_value The KeyValue item
164
+ # @see KeyValueList#each
165
+ # @example
166
+ # keys = collection.take(20).map(&:key)
167
+ # # returns the first 20 keys in the collection.
168
+ def each(&block)
169
+ return enum_for(:each) unless block
170
+ KeyValueList.new(self).each(&block)
171
+ end
172
+
173
+ # Sets the inclusive start key for enumeration over the KeyValue items in the collection.
174
+ # @see KeyValueList#start
175
+ # @return [KeyValueList]
176
+ def start(start_key)
177
+ KeyValueList.new(self).start(start_key)
178
+ end
179
+
180
+ # Sets the exclusive start key for enumeration over the KeyValue items in the collection.
181
+ # @see KeyValueList#after
182
+ # @return [KeyValueList]
183
+ def after(start_key)
184
+ KeyValueList.new(self).after(start_key)
185
+ end
186
+
187
+ # Sets the exclusive end key for enumeration over the KeyValue items in the collection.
188
+ # @see KeyValueList#before
189
+ # @return [KeyValueList]
190
+ def before(end_key)
191
+ KeyValueList.new(self).before(end_key)
192
+ end
193
+
194
+ # Sets the inclusive end key for enumeration over the KeyValue items in the collection.
195
+ # @see KeyValueList#end
196
+ # @return [KeyValueList]
197
+ def end(end_key)
198
+ KeyValueList.new(self).end(end_key)
199
+ end
200
+
201
+ # Returns the first n items. Equivalent to Enumerable#take. Sets the
202
+ # `limit` parameter on the query to Orchestrate, so we don't ask for more than is needed.
203
+ # @param count [Integer] The number of items to limit to.
204
+ # @return [Array]
205
+ def take(count)
206
+ KeyValueList.new(self).take(count)
207
+ end
208
+
209
+ # @!endgroup
210
+
211
+ # An enumerator with boundaries for performing a [KeyValue List
212
+ # query](http://orchestrate.io/docs/api/#key/value/list) against a collection.
213
+ class KeyValueList
214
+ # @return [Collection] The collection which this KeyValueList enumerates over.
215
+ attr_reader :collection
216
+
217
+ # @return [Hash] parameters for setting the boundaries of the enumeration.
218
+ attr_reader :range
219
+
220
+ # Instantiate a new KeyValueList to enumerate over a collection
221
+ # @param collection [Collection] the collection to enumerate over.
222
+ # @param range [Hash] parameters for setting the boundaries of the enumeration.
223
+ # @option range [#to_s] :begin The beginning of the range.
224
+ # @option range [true, false] :begin_inclusive Whether the begin key is inclusive or not.
225
+ # @option range [#to_s] :end The end of the range.
226
+ # @option range [true, false] :end_inclusive Whether the end key is inclusive or not.
227
+ # @return KeyValueList
228
+ def initialize(collection, range={})
229
+ @collection = collection
230
+ @range = range
231
+ range[:limit] ||= 100
232
+ end
233
+
234
+ # Sets the inclusive start key for enumeration over the KeyValue items in the collection.
235
+ # Overwrites any value given to #after.
236
+ # @param start_key [#to_s] The inclusive start of the key range.
237
+ # @return [KeyValueList] A new KeyValueList with the new range.
238
+ def start(start_key)
239
+ self.class.new(collection, range.merge({begin: start_key, begin_inclusive: true}))
240
+ end
241
+
242
+ # Sets the exclusive start key for enumeration over the KeyValue items in the collection.
243
+ # Overwrites any value given to #start.
244
+ # @param start_key [#to_s] The exclusive start of the key range.
245
+ # @return [KeyValueList] A new KeyValueList with the new range.
246
+ def after(start_key)
247
+ self.class.new(collection, range.merge({begin: start_key, begin_inclusive: false}))
248
+ end
249
+
250
+ # Sets the exclusive end key for enumeration over the KeyValue items in the collection.
251
+ # Overwrites any value given to #end.
252
+ # @param end_key [#to_s] The exclusive end of the key range.
253
+ # @return [KeyValueList] A new KeyValueList with the new range.
254
+ def before(end_key)
255
+ self.class.new(collection, range.merge({end: end_key, end_inclusive: false}))
256
+ end
257
+
258
+ # Sets the inclusive end key for enumeration over the KeyValue items in the collection.
259
+ # Overwrites any value given to #before.
260
+ # @param end_key [#to_s] The inclusive end of the key range.
261
+ # @return [KeyValueList] A new KeyValueList with the new range.
262
+ def end(end_key)
263
+ self.class.new(collection, range.merge({end: end_key, end_inclusive: true}))
264
+ end
265
+
266
+ include Enumerable
267
+
268
+ # Lazily iterates over each KeyValue item in the collection. Used as the basis for Enumerable methods.
269
+ # Items are provided in lexicographically sorted order by key name.
270
+ # @yieldparam [Orchestrate::KeyValue] key_value The KeyValue item
271
+ # @example
272
+ # keys = collection.after(:foo).take(20).map(&:key)
273
+ # # returns the first 20 keys in the collection that occur after "foo"
274
+ def each
275
+ return enum_for(:each) unless block_given?
276
+ params = {}
277
+ if range[:begin]
278
+ begin_key = range[:begin_inclusive] ? :start : :after
279
+ params[begin_key] = range[:begin]
280
+ end
281
+ if range[:end]
282
+ end_key = range[:end_inclusive] ? :end : :before
283
+ params[end_key] = range[:end]
284
+ end
285
+ params[:limit] = range[:limit]
286
+ response = collection.app.client.list(collection.name, params)
287
+ raise ResultsNotReady.new if collection.app.client.http.parallel_manager
288
+ loop do
289
+ response.results.each do |doc|
290
+ yield KeyValue.new(collection, doc, response.request_time)
291
+ end
292
+ break unless response.next_link
293
+ response = response.next_results
294
+ end
295
+ end
296
+
297
+ # Returns the first n items. Equivalent to Enumerable#take. Sets the
298
+ # `limit` parameter on the query to Orchestrate, so we don't ask for more than is needed.
299
+ # @param count [Integer] The number of items to limit to.
300
+ # @return [Array]
301
+ def take(count)
302
+ count = 1 if count < 1
303
+ range[:limit] = count > 100 ? 100 : count
304
+ super(count)
305
+ end
306
+
307
+ end
308
+
309
+ end
310
+ end
@@ -0,0 +1,232 @@
1
+ module Orchestrate
2
+ # Key/Value pairs are pieces of data identified by a unique key for
3
+ # a collection and have corresponding value.
4
+ class KeyValue
5
+
6
+ # Instantiates and loads a KeyValue item.
7
+ # @param collection [Orchestrate::Collection] The collection to which the KeyValue belongs.
8
+ # @param key [#to_s] The key name.
9
+ # @return Orchestrate::KeyValue The KeyValue item.
10
+ # @raise Orchestrate::API::NotFound if there is no value for the provided key.
11
+ def self.load(collection, key)
12
+ kv = new(collection, key)
13
+ kv.reload
14
+ kv
15
+ end
16
+
17
+ # Instantiate a KeyValue from a PUT or POST request and known value
18
+ # @param collection [Orchestrate::Collection] The collection to which the KeyValue belongs.
19
+ # @param key [#to_s] The key name.
20
+ # @param value [#to_json] The key's value.
21
+ # @param response [Orchestrate::API::Response] The response that is associated with the PUT/POST.
22
+ # @return Orchestrate::KeyValue The KeyValue item.
23
+ def self.from_bodyless_response(collection, key, value, response)
24
+ kv = new(collection, key, response)
25
+ kv.value = value
26
+ kv
27
+ end
28
+
29
+ # The collection this KeyValue belongs to.
30
+ # @return [Orchestrate::Collection]
31
+ attr_reader :collection
32
+
33
+ # The name of the collection this KeyValue belongs to.
34
+ # @return [String]
35
+ attr_reader :collection_name
36
+
37
+ # The key for this KeyValue.
38
+ # @return [String]
39
+ attr_reader :key
40
+
41
+ # For comparison purposes only, the 'address' of this KeyValue item.
42
+ # Represented as "[collection_name]/[key]"
43
+ # @return [String]
44
+ attr_reader :id
45
+
46
+ # The ref for the current value for this KeyValue.
47
+ # @return [String]
48
+ attr_reader :ref
49
+
50
+ # If known, the time at which this ref was created.
51
+ # @return [Time, nil]
52
+ attr_reader :reftime
53
+
54
+ # The value for the KeyValue at this ref.
55
+ # @return [#to_json]
56
+ attr_accessor :value
57
+
58
+ # Whether the KeyValue has been loaded from Orchestrate or not.
59
+ # @return boolean
60
+ attr_reader :loaded
61
+
62
+ # When the KeyValue was last loaded from Orchestrate.
63
+ # @return [Time]
64
+ attr_reader :last_request_time
65
+
66
+ # Instantiates a new KeyValue item. You generally don't want to call this yourself, but rather use
67
+ # the methods on Orchestrate::Collection to load a KeyValue.
68
+ # @param coll [Orchestrate::Collection] The collection to which this KeyValue belongs.
69
+ # @param key_name_or_listing [Hash] A listing result from Client#list
70
+ # @param key_name_or_listing [#to_s] The name of the key
71
+ # @param response_or_request_time [nil, Time, Orchestrate::API::Response]
72
+ # If key_name_or_listing is a listing, and this value is a Time, used to set last_request_time.
73
+ # Otherwise if an API::Request, used to load attributes and value.
74
+ # @return Orchestrate::KeyValue
75
+ def initialize(coll, key_name_or_listing, response_or_request_time=nil)
76
+ @collection = coll
77
+ @collection_name = coll.name
78
+ @app = coll.app
79
+ if key_name_or_listing.kind_of?(Hash)
80
+ path = key_name_or_listing.fetch('path')
81
+ @key = path.fetch('key')
82
+ @ref = path.fetch('ref')
83
+ @reftime = Time.at(key_name_or_listing.fetch('reftime') / 1000.0)
84
+ @value = key_name_or_listing.fetch('value')
85
+ @last_request_time = response_or_request_time if response_or_request_time.kind_of?(Time)
86
+ else
87
+ @key = key_name_or_listing.to_s
88
+ end
89
+ @id = "#{collection_name}/#{key}"
90
+ load_from_response(response_or_request_time) if response_or_request_time.kind_of?(API::Response)
91
+ end
92
+
93
+ # Equivalent to `String#==`. Compares by key and collection.
94
+ # @param other [Orchestrate::KeyValue] the KeyValue to compare against.
95
+ # @return [true, false]
96
+ def ==(other)
97
+ other.kind_of?(Orchestrate::KeyValue) && \
98
+ other.collection == collection && \
99
+ other.key == key
100
+ end
101
+ alias :eql? :==
102
+
103
+ # Equivalent to `String#<=>`. Compares by key and collection.
104
+ # @param other [Orchestrate::KeyValue] the KeyValue to compare against.
105
+ # @return [nil, -1, 0, 1]
106
+ def <=>(other)
107
+ return nil unless other.kind_of?(Orchestrate::KeyValue)
108
+ return nil unless other.collection == collection
109
+ other.key <=> key
110
+ end
111
+
112
+ # @return Pretty-Printed string representation of the KeyValue
113
+ def to_s
114
+ "#<Orchestrate::KeyValue id=#{id} ref=#{ref} last_request_time=#{last_request_time}>"
115
+ end
116
+ alias :inspect :to_s
117
+
118
+ # If the KeyValue has been loaded or not.
119
+ # @return [true, false] loaded
120
+ def loaded?
121
+ !! last_request_time
122
+ end
123
+
124
+ # Reload this KeyValue item from Orchestrate.
125
+ # @raise Orchestrate::API::NotFound if the KeyValue no longer exists.
126
+ def reload
127
+ load_from_response(@app.client.get(collection_name, key))
128
+ end
129
+
130
+ # @!group Attribute accessors
131
+
132
+ # Get an attribute from the KeyValue item's value.
133
+ # @param attr_name [#to_s] The name of the attribute.
134
+ # @return [nil, true, false, Numeric, String, Array, Hash] the value for the attribute.
135
+ def [](attr_name)
136
+ value[attr_name.to_s]
137
+ end
138
+
139
+ # Set a value to an attribute on the KeyValue item's value.
140
+ # @param attr_name [#to_s] The name of the attribute.
141
+ # @param attr_value [#to_json] The new value for the attribute.
142
+ # @return [attr_value]
143
+ def []=(attr_name, attr_value)
144
+ value[attr_name.to_s] = attr_value
145
+ end
146
+
147
+ # @!endgroup
148
+ #
149
+ # @!group Persistence
150
+
151
+ # Saves the KeyValue item to Orchestrate using 'If-Match' with the current ref.
152
+ # Sets the new ref for this value to the ref attribute.
153
+ # Returns false on failure to save, and rescues from all Orchestrate::API errors.
154
+ # @return [true, false]
155
+ def save
156
+ begin
157
+ save!
158
+ rescue API::RequestError, API::ServiceError
159
+ false
160
+ end
161
+ end
162
+
163
+ # Saves the KeyValue item to Orchestrate using 'If-Match' with the current ref.
164
+ # Sets the new ref for this value to the ref attribute.
165
+ # Raises an exception on failure to save.
166
+ # @return [true]
167
+ # @raise [Orchestrate::API::VersionMismatch] If the KeyValue item has been updated with a new ref since this KeyValue was loaded.
168
+ # @raise [Orchestrate::API::RequestError, Orchestrate::API::ServiceError] If there are any other problems with saving.
169
+ def save!
170
+ begin
171
+ load_from_response(@app.client.put(collection_name, key, value, ref))
172
+ true
173
+ rescue API::IndexingConflict => e
174
+ @ref = e.response.headers['Location'].split('/').last
175
+ @last_request_time = Time.parse(e.response.headers['Date'])
176
+ true
177
+ end
178
+ end
179
+
180
+ # Deletes the KeyValue item from Orchestrate using 'If-Match' with the current ref.
181
+ # Returns false if the item failed to delete because a new ref had been created since this KeyValue was loaded.
182
+ # @return [true, false]
183
+ def destroy
184
+ begin
185
+ destroy!
186
+ rescue API::VersionMismatch
187
+ false
188
+ end
189
+ end
190
+
191
+ # Deletes a KeyValue item from Orchestrate using 'If-Match' with the current ref.
192
+ # @raise [Orchestrate::API::VersionMismatch] If the KeyValue item has been updated with a new ref since this KeyValue was loaded.
193
+ def destroy!
194
+ response = @app.client.delete(collection_name, key, ref)
195
+ @ref = nil
196
+ @last_request_time = response.request_time
197
+ true
198
+ end
199
+
200
+ # Deletes a KeyValue item and its entire Ref history from Orchestrate using 'If-Match' with the current ref.
201
+ # Returns false if the item failed to delete because a new ref had been created since this KeyValue was loaded.
202
+ # @return [true, false]
203
+ def purge
204
+ begin
205
+ purge!
206
+ rescue API::VersionMismatch
207
+ false
208
+ end
209
+ end
210
+
211
+ # Deletes a KeyValue item and its entire Ref history from Orchestrate using 'If-Match' with the current ref.
212
+ # @raise [Orchestrate::API::VersionMismatch] If the KeyValue item has been updated with a new ref since this KeyValue was loaded.
213
+ def purge!
214
+ response = @app.client.purge(collection_name, key, ref)
215
+ @ref = nil
216
+ @last_request_time = response.request_time
217
+ true
218
+ end
219
+
220
+ # @!engroup persistence
221
+
222
+ private
223
+ def load_from_response(response)
224
+ response.on_complete do
225
+ @ref = response.ref
226
+ @value = response.body unless response.body.respond_to?(:strip) && response.body.strip.empty?
227
+ @last_request_time = response.request_time
228
+ end
229
+ end
230
+
231
+ end
232
+ end