orchestrate 0.8.1 → 0.9.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.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/lib/orchestrate.rb +2 -0
- data/lib/orchestrate/event.rb +232 -0
- data/lib/orchestrate/event_source.rb +330 -0
- data/lib/orchestrate/graph.rb +4 -0
- data/lib/orchestrate/key_value.rb +16 -5
- data/lib/orchestrate/version.rb +1 -1
- data/test/orchestrate/api/event_test.rb +1 -1
- data/test/orchestrate/event_enumeration_test.rb +165 -0
- data/test/orchestrate/event_test.rb +266 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 260fdf369f126768067a8bae1f0dd3671a5a9779
|
4
|
+
data.tar.gz: ee9677aa33a74fd362a538084ff05e98188afb89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 621a858872aaf62e4ed91ed6cf32313a014cf52f1300886220b5976acffec67d65afa99c76ba18b03b084e25d419f83b91bcc2dd07553b4084a67d1119f431e6
|
7
|
+
data.tar.gz: 2c086b71f2be610e25c99d8893bcfbe068ecf0e5afd82429aa0b9d4716a5a8963a7e806ada86d581fc03087d7f48b933d9fabef778dd48b88724cd550105a818
|
data/README.md
CHANGED
@@ -159,6 +159,10 @@ end
|
|
159
159
|
|
160
160
|
## Release Notes
|
161
161
|
|
162
|
+
### September 1, 2014: release 0.9.0
|
163
|
+
- Implement `KeyValue#events`, `EventList` and `Events` to access events associated with a KeyValue.
|
164
|
+
- Removed `KeyValue#loaded` attr reader, it pointed to an instance variable no longer in use. Use `#loaded?` instead.
|
165
|
+
|
162
166
|
### August 6, 2014: release 0.8.1
|
163
167
|
- Implement `KeyValue#refs`, `RefList` and `Ref` to access a KeyValue's Refs.
|
164
168
|
- Refactor `Client` api accessors on Object client to internal `#perform` methods.
|
data/lib/orchestrate.rb
CHANGED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'uri'
|
2
|
+
module Orchestrate
|
3
|
+
|
4
|
+
# Events are a way to associate Time-ordered data with a key.
|
5
|
+
class Event
|
6
|
+
|
7
|
+
# Instantiates an Event from a value and response without a body.
|
8
|
+
# @param stem [Orchestrate::EventType] The associated EventType.
|
9
|
+
# @param value [Hash] The value for the event.
|
10
|
+
# @param response [API::Response] The associated response for creating the event.
|
11
|
+
# @return [Event]
|
12
|
+
def self.from_bodyless_response(stem, value, response)
|
13
|
+
event = new(stem, response)
|
14
|
+
event.value = value
|
15
|
+
event
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instantiates an Event from a value in a listing and an associated response.
|
19
|
+
# @param stem [Orchestrate::EventType] The associated EventType
|
20
|
+
# @param listing [Hash] The entry in the LIST result
|
21
|
+
# @param response [API::Response] The associated response for the list query.
|
22
|
+
# @return [Event]
|
23
|
+
def self.from_listing(stem, listing, response=nil)
|
24
|
+
event = new(stem)
|
25
|
+
event.value = listing['value']
|
26
|
+
event.instance_variable_set(:@timestamp, listing['timestamp'])
|
27
|
+
event.instance_variable_set(:@ordinal, listing['ordinal'])
|
28
|
+
event.instance_variable_set(:@ref, listing['path']['ref'])
|
29
|
+
event.instance_variable_set(:@last_request_time, response.request_time) if response
|
30
|
+
event
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Orchestrate::EventType] The associated EventType.
|
34
|
+
attr_reader :type
|
35
|
+
|
36
|
+
# @return [String] The associated name of the event type.
|
37
|
+
attr_reader :type_name
|
38
|
+
|
39
|
+
# @return [Orchestrate::KeyValue] The assoicated KeyValue item.
|
40
|
+
attr_reader :key
|
41
|
+
|
42
|
+
# @return [String] The key of the associated item.
|
43
|
+
attr_reader :key_name
|
44
|
+
|
45
|
+
# @return [String] The immutable ref for the current value of this event.
|
46
|
+
attr_reader :ref
|
47
|
+
|
48
|
+
# @return [Integer] The timestamp of the event, in number of milliseconds since epoch.
|
49
|
+
# @see #time
|
50
|
+
attr_reader :timestamp
|
51
|
+
|
52
|
+
# @return [Integer] The ordinal of the event.
|
53
|
+
attr_reader :ordinal
|
54
|
+
|
55
|
+
# @return [Time] The time which the event was loaded.
|
56
|
+
attr_reader :last_request_time
|
57
|
+
|
58
|
+
# @return [Hash] The value for the event.
|
59
|
+
attr_accessor :value
|
60
|
+
|
61
|
+
# Instantiates a new Event item. You generally don't want to call this
|
62
|
+
# yourself, but use accessor methods from the KeyValue and EventType.
|
63
|
+
# @param stem [Orchestrate::EventType] The associated EventType.
|
64
|
+
# @param response [nil, Orchestrate::API::Response]
|
65
|
+
# If an API::Response, used to load attributes and value.
|
66
|
+
# @return Orchestrate::Event
|
67
|
+
def initialize(stem, response=nil)
|
68
|
+
@type = stem
|
69
|
+
@type_name = type.type
|
70
|
+
@key = stem.kv_item
|
71
|
+
@key_name = key.key
|
72
|
+
@value = nil
|
73
|
+
load_from_response(response) if response
|
74
|
+
end
|
75
|
+
|
76
|
+
# Equivalent to `String#==`. Compares by KeyValue, Type, Timestamp and Ordinal.
|
77
|
+
# @param other [Orchestrate::Event] the Event to compare against.
|
78
|
+
# @return [true, false]
|
79
|
+
def ==(other)
|
80
|
+
other.kind_of?(Orchestrate::Event) && \
|
81
|
+
other.type == type && \
|
82
|
+
other.timestamp == timestamp && \
|
83
|
+
other.ordinal == ordinal
|
84
|
+
end
|
85
|
+
alias :eql? :==
|
86
|
+
|
87
|
+
# Equivalent to `String#<=>`. Compares by KeyValue, Type, Timestamp and
|
88
|
+
# Ordinal. Sorts newer timestamps before older timestamps. If timestamps
|
89
|
+
# are equivalent, sorts by ordinal. This behavior emulates the order which
|
90
|
+
# events are returned in a list events query from the Orchestrate API.
|
91
|
+
# @param other [Orchestrate::Event] the Event to compare against.
|
92
|
+
# @return [nil, -1, 0, 1]
|
93
|
+
def <=>(other)
|
94
|
+
return nil unless other.kind_of?(Orchestrate::Event)
|
95
|
+
return nil unless other.type == type
|
96
|
+
if other.timestamp == timestamp
|
97
|
+
other.ordinal <=> ordinal
|
98
|
+
else
|
99
|
+
other.timestamp > timestamp ? -1 : 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [String] A pretty-printed representation of the event.
|
104
|
+
def to_s
|
105
|
+
"#<Orchestrate::Event collection=#{key.collection_name} key=#{key_name} type=#{type_name} time=#{time} ordinal=#{ordinal} ref=#{ref} last_request_time=#{last_request_time}>"
|
106
|
+
end
|
107
|
+
|
108
|
+
# @group Attributes
|
109
|
+
|
110
|
+
# @return [String] The full path of the event.
|
111
|
+
def path
|
112
|
+
@path ||= "/#{key.path}/events/#{URI.escape(type_name)}/#{timestamp}/#{ordinal}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Time] The timestamp of the event as a Time object.
|
116
|
+
def time
|
117
|
+
@time ||= Time.at(@timestamp / 1000.0)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Accessor for a property on the event's value.
|
121
|
+
# @param attr_name [#to_s] the attribute name to retrieve.
|
122
|
+
# @return [nil, true, false, Numeric, String, Array, Hash] The value for the attribute.
|
123
|
+
def [](attr_name)
|
124
|
+
value[attr_name.to_s]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set an attribute on the event's value to a specified value.
|
128
|
+
# @param attr_name [#to_s] The name of the attribute.
|
129
|
+
# @param attr_val [#to_json] The new value for the attribute
|
130
|
+
# @return [attr_val]
|
131
|
+
def []=(attr_name, attr_val)
|
132
|
+
value[attr_name.to_s] = attr_val
|
133
|
+
end
|
134
|
+
|
135
|
+
# @endgroup
|
136
|
+
# @group Persistence
|
137
|
+
|
138
|
+
# Saves the Event item to Orchestrate using 'If-Match' with the current ref.
|
139
|
+
# Sets the new ref for this value to the ref attribute.
|
140
|
+
# Returns false on failure to save, and rescues from all Orchestrate::API errors.
|
141
|
+
# @return [true, false]
|
142
|
+
def save
|
143
|
+
begin
|
144
|
+
save!
|
145
|
+
rescue API::RequestError, API::ServiceError
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Saves the Event item to Orchestrate using 'If-Match' with the current ref.
|
151
|
+
# Sets the new ref for this value to the ref attribute.
|
152
|
+
# Raises an exception on failure to save.
|
153
|
+
# @return [true]
|
154
|
+
# @raise [Orchestrate::API::VersionMismatch] If the Event has been updated with a new value since this Event was loaded.
|
155
|
+
# @raise [Orchestrate::API::RequestError, Orchestrate::API::ServiceError] If there are any other problems with saving.
|
156
|
+
def save!
|
157
|
+
response = perform(:put_event, value, ref)
|
158
|
+
load_from_response(response)
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
# Merges a set of values into the event's existing value and saves.
|
163
|
+
# @param merge [#each_pair] The Hash-like to merge into #value. Keys will be stringified.
|
164
|
+
# @return [true, false]
|
165
|
+
def update(merge)
|
166
|
+
begin
|
167
|
+
update!(merge)
|
168
|
+
rescue API::RequestError, API::ServiceError
|
169
|
+
false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Merges a set of values into the event's existing value and saves.
|
174
|
+
# @param merge [#each_pair] The Hash-like to merge into #value. Keys will be stringified.
|
175
|
+
# @return [true]
|
176
|
+
# @raise [Orchestrate::API::VersionMismatch] If the Event has been updated with a new value since this Event was loaded.
|
177
|
+
# @raise [Orchestrate::API::RequestError, Orchestrate::API::ServiceError] If there are any other problems with saving.
|
178
|
+
def update!(merge)
|
179
|
+
merge.each_pair {|key, value| @value[key.to_s] = value }
|
180
|
+
save!
|
181
|
+
end
|
182
|
+
|
183
|
+
# Deletes the event from Orchesrate using 'If-Match' with the current ref.
|
184
|
+
# Returns false if the event failed to delete because a new ref has been created since this Event was loaded.
|
185
|
+
# @return [true, false]
|
186
|
+
def purge
|
187
|
+
begin
|
188
|
+
purge!
|
189
|
+
rescue API::RequestError
|
190
|
+
false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Deletes the event from Orchesrate using 'If-Match' with the current ref.
|
195
|
+
# @return [true]
|
196
|
+
# @raise [Orchestrate::API::VersionMismatch] If the Event has been updated with a new ref since this Event was loaded.
|
197
|
+
def purge!
|
198
|
+
response = perform(:purge_event, ref)
|
199
|
+
@ref = nil
|
200
|
+
@last_request_time = response.request_time
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
# @endgroup persistence
|
205
|
+
|
206
|
+
# Performs a request using the associated API client, with collection_name,
|
207
|
+
# key, event_type, timestamp, and ordinal pre-filled.
|
208
|
+
# @param api_method [Symbol] the method on API::Client to call.
|
209
|
+
# @param args [#to_s, #to_json, Hash] The remaining arguments for the method being called.
|
210
|
+
# @return [API::Request]
|
211
|
+
def perform(api_method, *args)
|
212
|
+
type.perform(api_method, timestamp, ordinal, *args)
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
def load_from_response(response)
|
217
|
+
response.on_complete do
|
218
|
+
@ref = response.ref if response.respond_to?(:ref)
|
219
|
+
if response.headers['Location']
|
220
|
+
loc = response.headers['Location'].split('/')
|
221
|
+
@timestamp = loc[6].to_i
|
222
|
+
@ordinal = loc[7].to_i
|
223
|
+
elsif response.body.kind_of?(Hash)
|
224
|
+
@value = response.body['value']
|
225
|
+
@timestamp = response.body['timestamp']
|
226
|
+
@ordinal = response.body['ordinal']
|
227
|
+
end
|
228
|
+
@last_request_time = response.request_time
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
module Orchestrate
|
2
|
+
|
3
|
+
# Manages Event Types for a KeyValue item.
|
4
|
+
class EventSource
|
5
|
+
|
6
|
+
# @return [Orchestrate::KeyValue] The KeyValue to which the sourced events belong.
|
7
|
+
attr_reader :kv_item
|
8
|
+
|
9
|
+
# Instantiages a new EventSource manager.
|
10
|
+
# @param kv_item [Orchestrate::KeyValue] The KeyValue item to which events belong.
|
11
|
+
def initialize(kv_item)
|
12
|
+
@kv_item = kv_item
|
13
|
+
@types = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Accessor for event types.
|
17
|
+
# @param event_type [#to_s] The type key for the events.
|
18
|
+
# @return [EventType]
|
19
|
+
def [](event_type)
|
20
|
+
@types[event_type.to_s] ||= EventType.new(@kv_item, event_type)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Equivalent to `String#==`. Compares by kv_item.
|
24
|
+
# @param other [Orchestrate::EventSource] the EventSource to compare against.
|
25
|
+
# @return [true, false]
|
26
|
+
def ==(other)
|
27
|
+
other.kind_of?(Orchestrate::EventSource) && other.kv_item == kv_item
|
28
|
+
end
|
29
|
+
alias :eql? :==
|
30
|
+
|
31
|
+
# @return Pretty-Printed string representation
|
32
|
+
def to_s
|
33
|
+
"#<Orchestrate::EventSource key_value=#{kv_item}>"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Manages events of a specific type that belong to a specific KeyValue item.
|
38
|
+
class EventType
|
39
|
+
|
40
|
+
# @return [Orchestrate::KeyValue] The KeyValue this EventType is managing events for.
|
41
|
+
attr_reader :kv_item
|
42
|
+
|
43
|
+
# @return [String] The name for the type of events this EventType manages.
|
44
|
+
attr_reader :type
|
45
|
+
|
46
|
+
# Instantiates a new EventType.
|
47
|
+
# @param kv_item [Orchestrate::KeyValue] The KeyValue this EventType manages events for.
|
48
|
+
# @param event_type [#to_s] The type of events this EventType manages.
|
49
|
+
def initialize(kv_item, event_type)
|
50
|
+
@kv_item = kv_item
|
51
|
+
@type = event_type.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# Equivalent to `String#==`. Compares by KeyValue and Type.
|
55
|
+
# @param other [Orchestrate::EventType] The EventType to compare against.
|
56
|
+
# @return [true, false]
|
57
|
+
def ==(other)
|
58
|
+
other.kind_of?(Orchestrate::EventType) && \
|
59
|
+
other.kv_item == kv_item && \
|
60
|
+
other.type == type
|
61
|
+
end
|
62
|
+
alias :eql? :==
|
63
|
+
|
64
|
+
# Equivalent to `String#<=>`. Comapres by key_value and type.
|
65
|
+
# @param other [Orchestrate::EventType] The EventType to compare against.
|
66
|
+
# @return [nil, -1, 0, 1]
|
67
|
+
def <=>(other)
|
68
|
+
return nil unless other.kind_of?(Orchestrate::EventType)
|
69
|
+
return nil unless other.kv_item == kv_item
|
70
|
+
other.type <=> type
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return A pretty-printed string representation of the EventType
|
74
|
+
def to_s
|
75
|
+
"#<Orchestrate::EventType key_value=#{kv_item} type=#{type}>"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Calls a method on the KeyValue's Collection's API Client, providing the event type.
|
79
|
+
# @param api_method [Symbol] The method on the client to call.
|
80
|
+
# @param args [#to_s, #to_json, Hash] The remaining arguments for the specified method.
|
81
|
+
# @return [API::Response]
|
82
|
+
def perform(api_method, *args)
|
83
|
+
kv_item.perform(api_method, type, *args)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a new Event of the given type for the associated KeyValue.
|
87
|
+
# @param body [#to_json] The value for the event.
|
88
|
+
# @return [Orchestrate::Event] The event that was created.
|
89
|
+
def <<(body)
|
90
|
+
TimeSlice.new(self).push(body)
|
91
|
+
end
|
92
|
+
alias :push :<<
|
93
|
+
|
94
|
+
# Instantiates a new EventType::List with the given bounds. Can be used to
|
95
|
+
# access single events or create events with a specific timestamp.
|
96
|
+
# @param bounds [#to_s, Time, Date, Integer, Hash] The bounds value for the List.
|
97
|
+
# @see EventType::List#initialize
|
98
|
+
# @example Accessing an existing event by timestamp/ordinal
|
99
|
+
# kv.events[:checkins][timestamp][1]
|
100
|
+
# @example Creating an Event with a specified timestamp
|
101
|
+
# kv.events[:checkins][Time.now - 3600] << {"place" => "home"}
|
102
|
+
# @example Listing events within a time range
|
103
|
+
# kv.events[:checkins][{before: Time.now - 3600, after: Time.now - 24 * 3600}].to_a
|
104
|
+
# # equivalent to:
|
105
|
+
# # kv.events[:checkins].before(Time.now - 3600).after(Time.now - 24 * 3600).to_a
|
106
|
+
def [](bounds)
|
107
|
+
TimeSlice.new(self, bounds)
|
108
|
+
end
|
109
|
+
|
110
|
+
include Enumerable
|
111
|
+
|
112
|
+
# Iterates over events belonging to the KeyValue of the specified Type.
|
113
|
+
# Used as the basis for enumerable methods. Events are provided in reverse
|
114
|
+
# chronological order by timestamp and ordinal value.
|
115
|
+
# @overlaod each
|
116
|
+
# @return Enumerator
|
117
|
+
# @overload each(&block)
|
118
|
+
# @yieldparam [Orchestrate::Event] event The Event item
|
119
|
+
def each(&block)
|
120
|
+
TimeSlice.new(self).each(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Creates a Lazy Enumerator for the EventList. If called inside the app's
|
124
|
+
# `#in_parallel` block, will prefetch results.
|
125
|
+
# @return Enumerator::Lazy
|
126
|
+
def lazy
|
127
|
+
TimeSlice.new(self).lazy
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the first n items. Equivalent to Enumerable#take. Sets the `limit`
|
131
|
+
# parameter on the query to Orchestrate, so we don't ask for more than is needed.
|
132
|
+
# @param count [Integer] The number of events to limit to.
|
133
|
+
# @return [Array]
|
134
|
+
def take(count)
|
135
|
+
TimeSlice.new(self).take(count)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Sets the inclusive start boundary for enumeration over events.
|
139
|
+
# Overwrites any value given to #before.
|
140
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The inclusive start of the event range.
|
141
|
+
# @return [EventType::TimeSlice]
|
142
|
+
def start(bound)
|
143
|
+
TimeSlice.new(self).start(bound)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Sets the exclusive start boundary for enumeration over events.
|
147
|
+
# Overwrites any value given to #start
|
148
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The exclusive start of the event range.
|
149
|
+
# @return [EventType::TimeSlice]
|
150
|
+
def after(bound)
|
151
|
+
TimeSlice.new(self).after(bound)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Sets the exclusive end boundary for enumeration over events.
|
155
|
+
# Overwrites any value given to #end
|
156
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The exclusive end of the event range.
|
157
|
+
# @return [EventType::TimeSlice]
|
158
|
+
def before(bound)
|
159
|
+
TimeSlice.new(self).before(bound)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets the inclusive end boundary for enumeration over events.
|
163
|
+
# Overwrites any value given to #before
|
164
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The inclusive end of the event range.
|
165
|
+
# @return [EventType::TimeSlice]
|
166
|
+
def end(bound)
|
167
|
+
TimeSlice.new(self).start(bound)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Manages events in a specified duration of time.
|
171
|
+
class TimeSlice
|
172
|
+
|
173
|
+
# @return [EventType] The associated EventType
|
174
|
+
attr_reader :type
|
175
|
+
|
176
|
+
# @return [String] The associated event type name
|
177
|
+
attr_reader :type_name
|
178
|
+
|
179
|
+
# @return [KeyValue] The associated KeyValue
|
180
|
+
attr_reader :kv_item
|
181
|
+
|
182
|
+
# Instantiates a new TimeSlice
|
183
|
+
# @param type [EventType] The associated EventType
|
184
|
+
# @param bounds [nil, String, Integer, Time, Date, Hash] Boundaries for the Time
|
185
|
+
# If `nil`, no boundaries are set. Used to enumerate over all events.
|
186
|
+
# If `String`, `Integer`, `Time`, `Date`, used as `:start` option, below.
|
187
|
+
# If `Hash`, see options.
|
188
|
+
# @option bounds [nil, String, Integer, Time, Date] :start Used as the 'startEvent' key.
|
189
|
+
# @option bounds [nil, String, Integer, Time, Date] :after Used as the 'afterEvent' key.
|
190
|
+
# @option bounds [nil, String, Integer, Time, Date] :before Used as the 'beforeEvent' key.
|
191
|
+
# @option bounds [nil, String, Integer, Time, Date] :end Used as the 'endEvent' key.
|
192
|
+
# @option bounds [Integer] :limit (100) The number of results to return.
|
193
|
+
def initialize(type, bounds=nil)
|
194
|
+
@type = type
|
195
|
+
@type_name = type.type
|
196
|
+
@kv_item = type.kv_item
|
197
|
+
if bounds.kind_of?(Hash)
|
198
|
+
@bounds = bounds
|
199
|
+
else
|
200
|
+
@bounds = {}
|
201
|
+
@bounds[:start] = bounds if bounds
|
202
|
+
end
|
203
|
+
@bounds[:limit] ||= 100
|
204
|
+
end
|
205
|
+
|
206
|
+
# Calls a method on the associated API Client, with collection, key and
|
207
|
+
# event_type being provided by EventType.
|
208
|
+
# @param api_method [Symbol] The method to call
|
209
|
+
# @param args [#to_s, #to_json, Hash] The arguments for the method being called.
|
210
|
+
# @return [API::Response]
|
211
|
+
def perform(api_method, *args)
|
212
|
+
type.perform(api_method, *args)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Creates a new Event of the given type for the associated KeyValue.
|
216
|
+
# @param body [#to_json] The value for the event.
|
217
|
+
# @return [Orchestrate::Event] The event that was created.
|
218
|
+
def <<(body)
|
219
|
+
response = perform(:post_event, body, @bounds[:start])
|
220
|
+
Event.from_bodyless_response(type, body, response)
|
221
|
+
end
|
222
|
+
alias :push :<<
|
223
|
+
|
224
|
+
# Retrieves a single ID by timestamp and ordinal. Uses the timestamp
|
225
|
+
# value from the associated bounds, by `start`, `before`, `after`, or
|
226
|
+
# `end` in that order.
|
227
|
+
# @param ordinal [Integer, #to_s] The ordinal value for the event to retrieve.
|
228
|
+
# @return [Orchestrate::Event]
|
229
|
+
def [](ordinal)
|
230
|
+
begin
|
231
|
+
timestamp = @bounds[:start] || @bounds[:before] || @bounds[:after] || @bounds[:end]
|
232
|
+
response = perform(:get_event, timestamp, ordinal)
|
233
|
+
Event.new(type, response)
|
234
|
+
rescue API::NotFound
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
include Enumerable
|
240
|
+
|
241
|
+
# Iterates over events belonging to the KeyValue of the specified Type
|
242
|
+
# within the given bounds. Used as the basis for enumerable methods.
|
243
|
+
# Events are provided in reverse chronological order by timestamp and
|
244
|
+
# ordinal value.
|
245
|
+
# @overlaod each
|
246
|
+
# @return Enumerator
|
247
|
+
# @overload each(&block)
|
248
|
+
# @yieldparam [Orchestrate::Event] event The Event item
|
249
|
+
def each(&block)
|
250
|
+
@response = perform(:list_events, @bounds)
|
251
|
+
return enum_for(:each) unless block_given?
|
252
|
+
raise ResultsNotReady.new if type.kv_item.collection.app.inside_parallel?
|
253
|
+
loop do
|
254
|
+
@response.results.each do |listing|
|
255
|
+
yield Event.from_listing(type, listing, @response)
|
256
|
+
end
|
257
|
+
break unless @response.next_link
|
258
|
+
@response = @response.next_results
|
259
|
+
end
|
260
|
+
@response = nil
|
261
|
+
end
|
262
|
+
|
263
|
+
# Creates a Lazy Enumerator for the EventList. If called inside the app's
|
264
|
+
# `#in_parallel` block, will prefetch results.
|
265
|
+
# @return Enumerator::Lazy
|
266
|
+
def lazy
|
267
|
+
return each.lazy if type.kv_item.collection.app.inside_parallel?
|
268
|
+
super
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns the first n items. Equivalent to Enumerable#take. Sets the `limit`
|
272
|
+
# parameter on the query to Orchestrate, so we don't ask for more than is needed.
|
273
|
+
# @param count [Integer] The number of events to limit to.
|
274
|
+
# @return [Array]
|
275
|
+
def take(count)
|
276
|
+
count = 1 if count < 1
|
277
|
+
@bounds[:limit] = count > 100 ? 100 : count
|
278
|
+
super(count)
|
279
|
+
end
|
280
|
+
|
281
|
+
# Sets the inclusive start boundary for enumeration over events.
|
282
|
+
# Overwrites any value given to #before.
|
283
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The inclusive start of the event range.
|
284
|
+
# @return [EventType::TimeSlice]
|
285
|
+
def start(bound)
|
286
|
+
@bounds[:start] = extract_bound_from(bound)
|
287
|
+
@bounds.delete(:after)
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
# Sets the exclusive start boundary for enumeration over events.
|
292
|
+
# Overwrites any value given to #start
|
293
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The exclusive start of the event range.
|
294
|
+
# @return [EventType::TimeSlice]
|
295
|
+
def after(bound)
|
296
|
+
@bounds[:after] = extract_bound_from(bound)
|
297
|
+
@bounds.delete(:start)
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# Sets the exclusive end boundary for enumeration over events.
|
302
|
+
# Overwrites any value given to #end
|
303
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The exclusive end of the event range.
|
304
|
+
# @return [EventType::TimeSlice]
|
305
|
+
def before(bound)
|
306
|
+
@bounds[:before] = extract_bound_from(bound)
|
307
|
+
@bounds.delete(:end)
|
308
|
+
self
|
309
|
+
end
|
310
|
+
|
311
|
+
# Sets the inclusive end boundary for enumeration over events.
|
312
|
+
# Overwrites any value given to #before
|
313
|
+
# @param bound [Orchestrate::Event, Time, Date, Integer, #to_s] The inclusive end of the event range.
|
314
|
+
# @return [EventType::TimeSlice]
|
315
|
+
def end(bound)
|
316
|
+
@bounds[:end] = extract_bound_from(bound)
|
317
|
+
@bounds.delete(:before)
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
def extract_bound_from(bound)
|
323
|
+
if bound.kind_of?(Event)
|
324
|
+
"#{bound.timestamp}/#{bound.ordinal}"
|
325
|
+
else bound
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
data/lib/orchestrate/graph.rb
CHANGED
@@ -35,6 +35,10 @@ module Orchestrate
|
|
35
35
|
@type = type_name.to_s
|
36
36
|
end
|
37
37
|
|
38
|
+
# Calls a method on the KeyValue's Collection's API Client, providing the relation type.
|
39
|
+
# @param api_method [Symbol] The method on the client to call.
|
40
|
+
# @param args [#to_s, #to_json, Hash] The remaining arguments for the specified method.
|
41
|
+
# @return [API::Response]
|
38
42
|
def perform(api_method, *args)
|
39
43
|
kv_item.perform(api_method, type, *args)
|
40
44
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'uri'
|
1
2
|
module Orchestrate
|
2
3
|
# Key/Value pairs are pieces of data identified by a unique key for
|
3
4
|
# a collection and have corresponding value.
|
@@ -58,6 +59,10 @@ module Orchestrate
|
|
58
59
|
# @return [String]
|
59
60
|
attr_reader :key
|
60
61
|
|
62
|
+
# The 'address' of this KeyValue item, representated as #[collection_name]/#[key]
|
63
|
+
# @return [String]
|
64
|
+
attr_reader :path
|
65
|
+
|
61
66
|
# For comparison purposes only, the 'address' of this KeyValue item.
|
62
67
|
# Represented as "[collection_name]/[key]"
|
63
68
|
# @return [String]
|
@@ -75,10 +80,6 @@ module Orchestrate
|
|
75
80
|
# @return [#to_json]
|
76
81
|
attr_accessor :value
|
77
82
|
|
78
|
-
# Whether the KeyValue has been loaded from Orchestrate or not.
|
79
|
-
# @return boolean
|
80
|
-
attr_reader :loaded
|
81
|
-
|
82
83
|
# When the KeyValue was last loaded from Orchestrate.
|
83
84
|
# @return [Time]
|
84
85
|
attr_reader :last_request_time
|
@@ -88,7 +89,7 @@ module Orchestrate
|
|
88
89
|
# @param coll [Orchestrate::Collection] The collection to which this KeyValue belongs.
|
89
90
|
# @param key_name [#to_s] The name of the key
|
90
91
|
# @param associated_response [nil, Orchestrate::API::Response]
|
91
|
-
# If an API::
|
92
|
+
# If an API::Response, used to load attributes and value.
|
92
93
|
# @return Orchestrate::KeyValue
|
93
94
|
def initialize(coll, key_name, associated_response=nil)
|
94
95
|
@collection = coll
|
@@ -96,6 +97,7 @@ module Orchestrate
|
|
96
97
|
@app = coll.app
|
97
98
|
@key = key_name.to_s
|
98
99
|
@id = "#{collection_name}/#{key}"
|
100
|
+
@path = "#{URI.escape(collection_name)}/#{URI.escape(key)}"
|
99
101
|
@value = {}
|
100
102
|
@ref = false
|
101
103
|
load_from_response(associated_response) if associated_response
|
@@ -285,6 +287,15 @@ module Orchestrate
|
|
285
287
|
end
|
286
288
|
|
287
289
|
# @!endgroup relations
|
290
|
+
# @!group events
|
291
|
+
|
292
|
+
# Entry point for managing events associated with this KeyValue item.
|
293
|
+
# @return [Orchestrate::EventSource]
|
294
|
+
def events
|
295
|
+
@events ||= EventSource.new(self)
|
296
|
+
end
|
297
|
+
|
298
|
+
# @!endgroup events
|
288
299
|
|
289
300
|
# Calls a method on the Collection's Application's client, providing the
|
290
301
|
# Collection's name and KeyValue's key.
|
data/lib/orchestrate/version.rb
CHANGED
@@ -0,0 +1,165 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class EventEnumerationTest < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@app, @stubs = make_application({parallel: true})
|
6
|
+
@kv = make_kv_item(@app[:items], @stubs)
|
7
|
+
@type = :events
|
8
|
+
@limit = "100"
|
9
|
+
@called = false
|
10
|
+
@stubs.get("/v0/items/#{@kv.key}/events/#{@type}") do |env|
|
11
|
+
@called = true
|
12
|
+
assert_equal @limit, env.params['limit']
|
13
|
+
body = case env.params['beforeEvent']
|
14
|
+
when nil
|
15
|
+
events = 100.times.map{|i| make_event({ts: @start_time - i}) }
|
16
|
+
next_event = events.last.values_at("timestamp", "ordinal").join('/')
|
17
|
+
{ "results" => events, "count" => 100,
|
18
|
+
"next" => "/v0/items/#{@kv.key}/events/#{@type}?beforeEvent=#{next_event}&limit=100" }
|
19
|
+
when "#{@start_time - 99}/1"
|
20
|
+
events = 10.times.map{|i| make_event({ts: @start_time - 100 - i})}
|
21
|
+
{ "results" => events, "count" => 10 }
|
22
|
+
else
|
23
|
+
raise ArgumentError.new("unexpected beforeEvent: #{env.params['beforeEvent']}")
|
24
|
+
end
|
25
|
+
[200, response_headers, body.to_json]
|
26
|
+
end
|
27
|
+
@start_time = (Time.now.to_f * 1000).to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_event(opts={})
|
31
|
+
timestamp = opts[:ts] || @start_time
|
32
|
+
ordinal = opts[:ord] || 1
|
33
|
+
ref = opts[:ref] || make_ref
|
34
|
+
value = opts[:val] || { "msg" => "hello world" }
|
35
|
+
{ "path" => { "collection" => @kv.collection_name, "key" => @kv.key, "type" => @type.to_s,
|
36
|
+
"timestamp" => timestamp, "ordinal" => ordinal, "ref" => ref },
|
37
|
+
"value" => value,
|
38
|
+
"timestamp" => timestamp, "ordinal" => ordinal }
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_enumerates_all_from_event_type
|
42
|
+
events = @kv.events[@type].to_a
|
43
|
+
assert_equal 110, events.length
|
44
|
+
events.each_with_index do |event, index|
|
45
|
+
assert_equal @kv, event.key
|
46
|
+
assert_equal @type.to_s, event.type_name
|
47
|
+
assert_equal @start_time - index, event.timestamp
|
48
|
+
assert_equal 1, event.ordinal
|
49
|
+
assert event.ref
|
50
|
+
assert event.value
|
51
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_enumerates_over_range_from_start_end
|
56
|
+
app, stubs = make_application
|
57
|
+
kv = make_kv_item(app[:items], stubs)
|
58
|
+
start_event = Orchestrate::Event.new(kv.events[@type])
|
59
|
+
start_event.instance_variable_set(:@timestamp, @start_time - 1000)
|
60
|
+
start_event.instance_variable_set(:@ordinal, 5)
|
61
|
+
end_event = @start_time
|
62
|
+
stubs.get("/v0/items/#{@kv.key}/events/#{@type}") do |env|
|
63
|
+
assert_equal "#{start_event.timestamp}/#{start_event.ordinal}", env.params['startEvent']
|
64
|
+
assert_equal "#{end_event}", env.params['endEvent']
|
65
|
+
[200, response_headers, {"count" => 0, "results" => []}.to_json]
|
66
|
+
end
|
67
|
+
response = kv.events[@type].start(start_event).end(end_event).to_a
|
68
|
+
assert_equal [], response
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_enumerates_over_range_from_after_before
|
72
|
+
app, stubs = make_application
|
73
|
+
kv = make_kv_item(app[:items], stubs)
|
74
|
+
after_event = Orchestrate::Event.new(kv.events[@type])
|
75
|
+
after_event.instance_variable_set(:@timestamp, @start_time - 1000)
|
76
|
+
after_event.instance_variable_set(:@ordinal, 5)
|
77
|
+
before_event = Time.at(@start_time)
|
78
|
+
stubs.get("/v0/items/#{@kv.key}/events/#{@type}") do |env|
|
79
|
+
assert_equal "#{after_event.timestamp}/#{after_event.ordinal}", env.params['afterEvent']
|
80
|
+
assert_equal "#{(before_event.to_f * 1000).to_i}", env.params['beforeEvent']
|
81
|
+
[200, response_headers, {"count" => 0, "results" => []}.to_json]
|
82
|
+
end
|
83
|
+
response = kv.events[@type].before(before_event).after(after_event).to_a
|
84
|
+
assert_equal [], response
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_enumerator_sets_limit_from_limit
|
88
|
+
@limit = "5"
|
89
|
+
events = @kv.events[@type].take(5)
|
90
|
+
assert_equal 5, events.length
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_enumerator_in_parallel_raises_not_ready_if_forced
|
94
|
+
assert_raises Orchestrate::ResultsNotReady do
|
95
|
+
@app.in_parallel { @kv.events[@type].to_a }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_enumerator_in_parallel_prefetches_lazy_enums
|
100
|
+
return unless [].respond_to?(:lazy)
|
101
|
+
events = nil
|
102
|
+
@app.in_parallel { events = @kv.events[@type].lazy.map{|e| e } }
|
103
|
+
assert @called, "lazy enumerator wasn't prefetched inside of parallel"
|
104
|
+
events = events.force
|
105
|
+
assert_equal 110, events.to_a.size
|
106
|
+
events.each_with_index do |event, index|
|
107
|
+
assert_equal @kv, event.key
|
108
|
+
assert_equal @type.to_s, event.type_name
|
109
|
+
assert_equal @start_time - index, event.timestamp
|
110
|
+
assert_equal 1, event.ordinal
|
111
|
+
assert event.ref
|
112
|
+
assert event.value
|
113
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_enumerator_in_parallel_prefetches_enums
|
118
|
+
events = nil
|
119
|
+
@app.in_parallel { events = @kv.events[@type].each }
|
120
|
+
assert @called, "enumerator wasn't prefetched inside of parallel"
|
121
|
+
assert_equal 110, events.to_a.size
|
122
|
+
events.each_with_index do |event, index|
|
123
|
+
assert_equal @kv, event.key
|
124
|
+
assert_equal @type.to_s, event.type_name
|
125
|
+
assert_equal @start_time - index, event.timestamp
|
126
|
+
assert_equal 1, event.ordinal
|
127
|
+
assert event.ref
|
128
|
+
assert event.value
|
129
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_enumerator_doesnt_prefetch_lazy_enums
|
134
|
+
return unless [].respond_to?(:lazy)
|
135
|
+
events = @kv.events[@type].lazy.map{|e| e }
|
136
|
+
refute @called, "lazy enumerator was prefetched"
|
137
|
+
events = events.force
|
138
|
+
assert_equal 110, events.to_a.size
|
139
|
+
events.each_with_index do |event, index|
|
140
|
+
assert_equal @kv, event.key
|
141
|
+
assert_equal @type.to_s, event.type_name
|
142
|
+
assert_equal @start_time - index, event.timestamp
|
143
|
+
assert_equal 1, event.ordinal
|
144
|
+
assert event.ref
|
145
|
+
assert event.value
|
146
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_enumerator_prefetches_enums
|
151
|
+
events = @kv.events[@type].each
|
152
|
+
assert @called, "enumerator wasn't prefetched"
|
153
|
+
assert_equal 110, events.to_a.size
|
154
|
+
events.each_with_index do |event, index|
|
155
|
+
assert_equal @kv, event.key
|
156
|
+
assert_equal @type.to_s, event.type_name
|
157
|
+
assert_equal @start_time - index, event.timestamp
|
158
|
+
assert_equal 1, event.ordinal
|
159
|
+
assert event.ref
|
160
|
+
assert event.value
|
161
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class EventTest < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@app, @stubs = make_application
|
7
|
+
@kv = make_kv_item(@app[:items], @stubs)
|
8
|
+
@body = {"place" => 'Home'}
|
9
|
+
@time = Time.now
|
10
|
+
@timestamp = (@time.to_f * 1000).to_i
|
11
|
+
@ord = 6
|
12
|
+
@type = 'checkins'
|
13
|
+
@ref = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_event
|
17
|
+
@stubs.get("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do
|
18
|
+
@ref = make_ref
|
19
|
+
body = {value: @body, timestamp: @timestamp, ordinal: @ord}
|
20
|
+
[200, response_headers({"Etag" => %|"#{@ref}"|}), body.to_json]
|
21
|
+
end
|
22
|
+
@kv.events[@type][@timestamp][@ord]
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_event_data(event)
|
26
|
+
assert_kind_of Orchestrate::Event, event
|
27
|
+
assert_equal @kv.key, event.key_name
|
28
|
+
assert_equal @kv, event.key
|
29
|
+
assert_equal @type, event.type_name
|
30
|
+
assert_equal @kv.events[:checkins], event.type
|
31
|
+
assert_equal @ref, event.ref
|
32
|
+
assert_kind_of Time, event.time
|
33
|
+
assert_in_delta @time.to_f, event.time.to_f, 1.1
|
34
|
+
assert_equal @timestamp, event.timestamp
|
35
|
+
assert_equal @ord, event.ordinal
|
36
|
+
assert_equal "/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}", event.path
|
37
|
+
assert_equal @body, event.value
|
38
|
+
@body.each do |key, value|
|
39
|
+
assert_equal value, event[key]
|
40
|
+
end
|
41
|
+
assert_in_delta Time.now.to_f, event.last_request_time.to_f, 1.1
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_creates_event_without_timestamp
|
45
|
+
@stubs.post("/v0/items/#{@kv.key}/events/#{@type}") do |env|
|
46
|
+
assert_equal @body, JSON.parse(env.body)
|
47
|
+
loc = "/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}"
|
48
|
+
@ref = make_ref
|
49
|
+
[201, response_headers({"Location" => loc, "Etag" => %|"#{@ref}"|}), '']
|
50
|
+
end
|
51
|
+
event = @kv.events[:checkins] << @body
|
52
|
+
@stubs.verify_stubbed_calls
|
53
|
+
assert_event_data(event)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_creates_event_with_time
|
57
|
+
@stubs.post("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}") do |env|
|
58
|
+
assert_equal @body, JSON.parse(env.body)
|
59
|
+
loc = "/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}"
|
60
|
+
@ref = make_ref
|
61
|
+
[201, response_headers({"Location" => loc, "Etag" => %|"#{@ref}"|}), '']
|
62
|
+
end
|
63
|
+
event = @kv.events[:checkins][@time].push(@body)
|
64
|
+
@stubs.verify_stubbed_calls
|
65
|
+
assert_event_data(event)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_retrieves_single_event
|
69
|
+
@stubs.get("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
70
|
+
@ref = make_ref
|
71
|
+
body = {value: @body, timestamp: @timestamp, ordinal: @ord}
|
72
|
+
[200, response_headers({"Etag" => %|"#{@ref}-gzip"|}), body.to_json]
|
73
|
+
end
|
74
|
+
event = @kv.events[:checkins][@time][@ord]
|
75
|
+
assert_event_data(event)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_retrieves_single_event_returns_nil_on_not_found
|
79
|
+
@stubs.get("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
80
|
+
[404, response_headers, response_not_found('')]
|
81
|
+
end
|
82
|
+
assert_nil @kv.events[@type][@time][@ord]
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_save_on_success_returns_true_and_updates
|
86
|
+
event = make_event
|
87
|
+
new_body = @body.merge({'foo' => 'bar'})
|
88
|
+
event[:foo] = "bar"
|
89
|
+
assert_equal new_body, event.value
|
90
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
91
|
+
assert_equal new_body, JSON.parse(env.body)
|
92
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
93
|
+
@ref = make_ref
|
94
|
+
[ 204, response_headers({"Etag" => %|"#{@ref}"|}), '' ]
|
95
|
+
end
|
96
|
+
assert_equal true, event.save
|
97
|
+
assert_equal @ref, event.ref
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_save_on_error_returns_false
|
101
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do
|
102
|
+
error_response(:version_mismatch)
|
103
|
+
end
|
104
|
+
event = make_event
|
105
|
+
assert_equal false, event.save
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_save_bang_on_success_returns_true_and_updates
|
109
|
+
event = make_event
|
110
|
+
new_body = @body.merge({'foo' => 'bar'})
|
111
|
+
event[:foo] = "bar"
|
112
|
+
assert_equal new_body, event.value
|
113
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
114
|
+
assert_equal new_body, JSON.parse(env.body)
|
115
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
116
|
+
@ref = make_ref
|
117
|
+
[ 204, response_headers({"Etag" => %|"#{@ref}"|}), '' ]
|
118
|
+
end
|
119
|
+
assert_equal true, event.save!
|
120
|
+
assert_equal @ref, event.ref
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_save_bang_on_error_raises
|
124
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do
|
125
|
+
error_response(:version_mismatch)
|
126
|
+
end
|
127
|
+
event = make_event
|
128
|
+
assert_raises Orchestrate::API::VersionMismatch do
|
129
|
+
event.save!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_update_modifies_values_and_saves
|
134
|
+
event = make_event
|
135
|
+
update = {'foo' => 'bar'}
|
136
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
137
|
+
assert_equal @body.update(update), JSON.parse(env.body)
|
138
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
139
|
+
@ref = make_ref
|
140
|
+
[ 204, response_headers({"Etag" => %|"#{@ref}"|}), '' ]
|
141
|
+
end
|
142
|
+
assert_equal true, event.update(update)
|
143
|
+
assert_equal @body.update(update), event.value
|
144
|
+
assert_equal @ref, event.ref
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_update_modifies_values_and_returns_false_on_error
|
148
|
+
event = make_event
|
149
|
+
update = {'foo' => 'bar'}
|
150
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
151
|
+
assert_equal @body.update(update), JSON.parse(env.body)
|
152
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
153
|
+
error_response(:version_mismatch)
|
154
|
+
end
|
155
|
+
assert_equal false, event.update(update)
|
156
|
+
assert_equal @body.update(update), event.value
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_update_bang_modifies_values_and_saves
|
160
|
+
event = make_event
|
161
|
+
update = {'foo' => 'bar'}
|
162
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
163
|
+
assert_equal @body.update(update), JSON.parse(env.body)
|
164
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
165
|
+
@ref = make_ref
|
166
|
+
[ 204, response_headers({"Etag" => %|"#{@ref}"|}), '' ]
|
167
|
+
end
|
168
|
+
assert_equal true, event.update!(update)
|
169
|
+
assert_equal @body.update(update), event.value
|
170
|
+
assert_equal @ref, event.ref
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_update_bang_modifies_value_and_raises_on_error
|
174
|
+
event = make_event
|
175
|
+
update = {'foo' => 'bar'}
|
176
|
+
@stubs.put("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
177
|
+
assert_equal @body.update(update), JSON.parse(env.body)
|
178
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
179
|
+
error_response(:version_mismatch)
|
180
|
+
end
|
181
|
+
assert_raises Orchestrate::API::VersionMismatch do
|
182
|
+
event.update!(update)
|
183
|
+
end
|
184
|
+
assert_equal @body.update(update), event.value
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_purge_performs_delete_if_match_returns_true_on_success
|
188
|
+
event = make_event
|
189
|
+
@stubs.delete("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
190
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
191
|
+
[204, response_headers, '']
|
192
|
+
end
|
193
|
+
assert_equal true, event.purge
|
194
|
+
assert_nil event.ref
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_purge_performs_delete_if_match_returns_false_on_error
|
198
|
+
event = make_event
|
199
|
+
@stubs.delete("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
200
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
201
|
+
error_response(:version_mismatch)
|
202
|
+
end
|
203
|
+
assert_equal false, event.purge
|
204
|
+
assert_equal @ref, event.ref
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_purge_bang_performs_delete_if_match_raises_on_error
|
208
|
+
event = make_event
|
209
|
+
@stubs.delete("/v0/items/#{@kv.key}/events/#{@type}/#{@timestamp}/#{@ord}") do |env|
|
210
|
+
assert_header 'If-Match', %|"#{event.ref}"|, env
|
211
|
+
error_response(:version_mismatch)
|
212
|
+
end
|
213
|
+
assert_raises Orchestrate::API::VersionMismatch do
|
214
|
+
event.purge!
|
215
|
+
end
|
216
|
+
assert_equal @ref, event.ref
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_equality_and_comparison
|
220
|
+
app, stubs = make_application
|
221
|
+
items = app[:items]
|
222
|
+
|
223
|
+
foo = Orchestrate::KeyValue.new(items, :foo)
|
224
|
+
bar = Orchestrate::KeyValue.new(items, :bar)
|
225
|
+
assert_equal foo.events, Orchestrate::KeyValue.new(items, :foo).events
|
226
|
+
assert foo.events.eql?(Orchestrate::KeyValue.new(items, :foo).events)
|
227
|
+
refute_equal foo.events, bar.events
|
228
|
+
refute foo.events.eql?(bar.events)
|
229
|
+
|
230
|
+
tweets = foo.events[:tweets]
|
231
|
+
assert_equal tweets, Orchestrate::KeyValue.new(items, :foo).events['tweets']
|
232
|
+
assert tweets.eql?(Orchestrate::KeyValue.new(items, :foo).events['tweets'])
|
233
|
+
refute_equal tweets, foo.events[:checkins]
|
234
|
+
refute tweets.eql?(foo.events[:checkins])
|
235
|
+
assert_equal(1, foo.events[:checkins] <=> tweets)
|
236
|
+
assert_equal(0, tweets <=> foo.events[:tweets])
|
237
|
+
assert_equal(-1, tweets <=> foo.events[:checkins])
|
238
|
+
refute_equal tweets, bar.events['tweets']
|
239
|
+
refute tweets.eql?(bar.events['tweets'])
|
240
|
+
assert_nil tweets <=> bar.events['tweets']
|
241
|
+
|
242
|
+
ts = (Time.now.to_f * 1000).floor
|
243
|
+
make_tweet_1 = lambda { Orchestrate::Event.from_listing(tweets, {"path" => {"ref" => make_ref}, "timestamp" => ts, "ordinal" => 10}) }
|
244
|
+
tweet1 = make_tweet_1.call
|
245
|
+
assert_equal tweet1, make_tweet_1.call
|
246
|
+
assert tweet1.eql?(make_tweet_1.call)
|
247
|
+
assert_equal 0, tweet1 <=> make_tweet_1.call
|
248
|
+
|
249
|
+
tweet2 = Orchestrate::Event.from_listing(tweets, {"path" => {"ref" => make_ref}, "timestamp" => ts, "ordinal" => 11})
|
250
|
+
refute_equal tweet1, tweet2
|
251
|
+
refute tweet1.eql?(tweet2)
|
252
|
+
assert_equal 1, tweet1 <=> tweet2
|
253
|
+
assert_equal(-1, tweet2 <=> tweet1)
|
254
|
+
|
255
|
+
tweet3 = Orchestrate::Event.from_listing(tweets, {"path" => {"ref" => make_ref}, "timestamp" => ts - 1, "ordinal" => 2})
|
256
|
+
assert_equal(1, tweet1 <=> tweet3)
|
257
|
+
assert_equal(-1, tweet3 <=> tweet1)
|
258
|
+
assert_equal(1, tweet2 <=> tweet3)
|
259
|
+
assert_equal(-1, tweet3 <=> tweet2)
|
260
|
+
|
261
|
+
checkin = Orchestrate::Event.from_listing(foo.events[:checkins], {"path" => {"ref" => make_ref}, "timestamp" => ts, "ordinal" => 40})
|
262
|
+
refute_equal tweet1, checkin
|
263
|
+
refute tweet1.eql?(checkin)
|
264
|
+
assert_nil tweet1 <=> checkin
|
265
|
+
end
|
266
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: orchestrate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Lyon
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-09-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: faraday
|
@@ -134,6 +134,8 @@ files:
|
|
134
134
|
- lib/orchestrate/application.rb
|
135
135
|
- lib/orchestrate/client.rb
|
136
136
|
- lib/orchestrate/collection.rb
|
137
|
+
- lib/orchestrate/event.rb
|
138
|
+
- lib/orchestrate/event_source.rb
|
137
139
|
- lib/orchestrate/graph.rb
|
138
140
|
- lib/orchestrate/key_value.rb
|
139
141
|
- lib/orchestrate/refs.rb
|
@@ -151,6 +153,8 @@ files:
|
|
151
153
|
- test/orchestrate/collection_kv_accessors_test.rb
|
152
154
|
- test/orchestrate/collection_searching_test.rb
|
153
155
|
- test/orchestrate/collection_test.rb
|
156
|
+
- test/orchestrate/event_enumeration_test.rb
|
157
|
+
- test/orchestrate/event_test.rb
|
154
158
|
- test/orchestrate/key_value_persistence_test.rb
|
155
159
|
- test/orchestrate/key_value_test.rb
|
156
160
|
- test/orchestrate/ref_test.rb
|
@@ -193,9 +197,10 @@ test_files:
|
|
193
197
|
- test/orchestrate/collection_kv_accessors_test.rb
|
194
198
|
- test/orchestrate/collection_searching_test.rb
|
195
199
|
- test/orchestrate/collection_test.rb
|
200
|
+
- test/orchestrate/event_enumeration_test.rb
|
201
|
+
- test/orchestrate/event_test.rb
|
196
202
|
- test/orchestrate/key_value_persistence_test.rb
|
197
203
|
- test/orchestrate/key_value_test.rb
|
198
204
|
- test/orchestrate/ref_test.rb
|
199
205
|
- test/orchestrate/relations_test.rb
|
200
206
|
- test/test_helper.rb
|
201
|
-
has_rdoc:
|