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