orchestrate 0.6.3 → 0.7.0

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