spire_io 1.2.1 → 1.2.2
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.
- data/lib/spire/api.rb +2 -18
- data/lib/spire/api/application.rb +27 -27
- data/lib/spire/api/channel.rb +14 -9
- data/lib/spire/api/notification.rb +2 -2
- data/lib/spire/api/resource.rb +4 -4
- data/lib/spire/api/session.rb +151 -31
- data/lib/spire/api/subscription.rb +74 -14
- data/lib/spire_io.rb +1 -311
- metadata +4 -4
data/lib/spire/api.rb
CHANGED
@@ -25,10 +25,11 @@ class Spire
|
|
25
25
|
end
|
26
26
|
|
27
27
|
attr_reader :client, :description, :schema
|
28
|
-
def initialize(url="https://api.spire.io", options={})
|
28
|
+
def initialize(url="https://api.spire.io", spire=nil, options={})
|
29
29
|
@version = options[:version] || "1.0"
|
30
30
|
@client = Excon
|
31
31
|
@url = url
|
32
|
+
@spire = spire
|
32
33
|
end
|
33
34
|
|
34
35
|
define_request(:discover) do
|
@@ -166,22 +167,5 @@ class Spire
|
|
166
167
|
end
|
167
168
|
API::Application.new(self, response.data)
|
168
169
|
end
|
169
|
-
|
170
|
-
# Returns a billing object than contains a list of all the plans available
|
171
|
-
# @param [String] info optional object description
|
172
|
-
# @return [Billing]
|
173
|
-
def billing(info=nil)
|
174
|
-
response = request(:billing)
|
175
|
-
raise "Error getting billing plans: #{response.status}" if response.status != 200
|
176
|
-
API::Billing.new(self, response.data)
|
177
|
-
end
|
178
|
-
|
179
|
-
class Billing < Resource
|
180
|
-
def resource_name
|
181
|
-
"billing"
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
170
|
end
|
186
|
-
|
187
171
|
end
|
@@ -9,7 +9,7 @@ class Spire
|
|
9
9
|
"application"
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(
|
12
|
+
def initialize(api, data)
|
13
13
|
super
|
14
14
|
@resources = data["resources"]
|
15
15
|
end
|
@@ -31,8 +31,8 @@ class Spire
|
|
31
31
|
:body => body,
|
32
32
|
:headers => {
|
33
33
|
"Authorization" => "Capability #{capability}",
|
34
|
-
"Accept" => @
|
35
|
-
"Content-Type" => @
|
34
|
+
"Accept" => @api.mediaType("channel"),
|
35
|
+
"Content-Type" => @api.mediaType("channel")
|
36
36
|
}
|
37
37
|
}
|
38
38
|
end
|
@@ -46,7 +46,7 @@ class Spire
|
|
46
46
|
:url => url,
|
47
47
|
:headers => {
|
48
48
|
"Authorization" => "Capability #{capability}",
|
49
|
-
"Accept" => @
|
49
|
+
"Accept" => @api.mediaType("channels"),
|
50
50
|
}
|
51
51
|
}
|
52
52
|
end
|
@@ -61,7 +61,7 @@ class Spire
|
|
61
61
|
:query => {:name => name},
|
62
62
|
:headers => {
|
63
63
|
"Authorization" => "Capability #{capability}",
|
64
|
-
"Accept" => @
|
64
|
+
"Accept" => @api.mediaType("channels"),
|
65
65
|
}
|
66
66
|
}
|
67
67
|
end
|
@@ -76,7 +76,7 @@ class Spire
|
|
76
76
|
:url => url,
|
77
77
|
:headers => {
|
78
78
|
"Authorization" => "Capability #{capability}",
|
79
|
-
"Accept" => @
|
79
|
+
"Accept" => @api.mediaType("subscriptions"),
|
80
80
|
}
|
81
81
|
}
|
82
82
|
end
|
@@ -94,8 +94,8 @@ class Spire
|
|
94
94
|
}.to_json,
|
95
95
|
:headers => {
|
96
96
|
"Authorization" => "Capability #{capability}",
|
97
|
-
"Accept" => @
|
98
|
-
"Content-Type" => @
|
97
|
+
"Accept" => @api.mediaType("subscription"),
|
98
|
+
"Content-Type" => @api.mediaType("subscription")
|
99
99
|
}
|
100
100
|
}
|
101
101
|
end
|
@@ -110,7 +110,7 @@ class Spire
|
|
110
110
|
:query => {:name => name},
|
111
111
|
:headers => {
|
112
112
|
"Authorization" => "Capability #{capability}",
|
113
|
-
"Accept" => @
|
113
|
+
"Accept" => @api.mediaType("subscriptions"),
|
114
114
|
}
|
115
115
|
}
|
116
116
|
end
|
@@ -126,8 +126,8 @@ class Spire
|
|
126
126
|
:body => data.to_json,
|
127
127
|
:headers => {
|
128
128
|
"Authorization" => "Capability #{capability}",
|
129
|
-
"Accept" => @
|
130
|
-
"Content-Type" => @
|
129
|
+
"Accept" => @api.mediaType("member"),
|
130
|
+
"Content-Type" => @api.mediaType("member")
|
131
131
|
}
|
132
132
|
}
|
133
133
|
end
|
@@ -141,7 +141,7 @@ class Spire
|
|
141
141
|
:url => url,
|
142
142
|
:headers => {
|
143
143
|
"Authorization" => "Capability #{capability}",
|
144
|
-
"Accept" => @
|
144
|
+
"Accept" => @api.mediaType("members"),
|
145
145
|
}
|
146
146
|
}
|
147
147
|
end
|
@@ -156,7 +156,7 @@ class Spire
|
|
156
156
|
:query => {:login => login},
|
157
157
|
:headers => {
|
158
158
|
"Authorization" => "Capability #{capability}",
|
159
|
-
"Accept" => @
|
159
|
+
"Accept" => @api.mediaType("member"),
|
160
160
|
}
|
161
161
|
}
|
162
162
|
end
|
@@ -169,7 +169,7 @@ class Spire
|
|
169
169
|
:url => url,
|
170
170
|
:body => data.to_json,
|
171
171
|
:headers => {
|
172
|
-
"Accept" => @
|
172
|
+
"Accept" => @api.mediaType("member"),
|
173
173
|
}
|
174
174
|
}
|
175
175
|
end
|
@@ -182,7 +182,7 @@ class Spire
|
|
182
182
|
:method => :get,
|
183
183
|
:url => url,
|
184
184
|
:headers => {
|
185
|
-
"Accept" => @
|
185
|
+
"Accept" => @api.mediaType("member"),
|
186
186
|
"Authorization" => "Basic #{auth}"
|
187
187
|
}
|
188
188
|
}
|
@@ -204,7 +204,7 @@ class Spire
|
|
204
204
|
unless response.status == 200
|
205
205
|
raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
|
206
206
|
end
|
207
|
-
API::Member.new(@
|
207
|
+
API::Member.new(@api, response.data)
|
208
208
|
end
|
209
209
|
|
210
210
|
#Alternative application authentication, without using basic auth
|
@@ -213,7 +213,7 @@ class Spire
|
|
213
213
|
unless response.status == 201
|
214
214
|
raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
|
215
215
|
end
|
216
|
-
API::Member.new(@
|
216
|
+
API::Member.new(@api, response.data)
|
217
217
|
end
|
218
218
|
|
219
219
|
#If you do not give a new password, you will get back an authenticated member but have to change
|
@@ -225,7 +225,7 @@ class Spire
|
|
225
225
|
unless response.status == 201
|
226
226
|
raise "Error reseting password for application #{self.name}: (#{response.status}) #{response.body}"
|
227
227
|
end
|
228
|
-
API::Member.new(@
|
228
|
+
API::Member.new(@api, response.data)
|
229
229
|
end
|
230
230
|
|
231
231
|
def create_member(member_data)
|
@@ -233,7 +233,7 @@ class Spire
|
|
233
233
|
unless response.status == 201
|
234
234
|
raise "Error creating member for application #{self.name}: (#{response.status}) #{response.body}"
|
235
235
|
end
|
236
|
-
API::Member.new(@
|
236
|
+
API::Member.new(@api, response.data)
|
237
237
|
end
|
238
238
|
|
239
239
|
#Resets a members password based on email
|
@@ -256,7 +256,7 @@ class Spire
|
|
256
256
|
end
|
257
257
|
@members = {}
|
258
258
|
response.data.each do |login, properties|
|
259
|
-
@members[login] = API::Member.new(@
|
259
|
+
@members[login] = API::Member.new(@api, properties)
|
260
260
|
end
|
261
261
|
@members
|
262
262
|
end
|
@@ -267,7 +267,7 @@ class Spire
|
|
267
267
|
raise "Error finding member with login #{member_login}: (#{response.status}) #{response.body}"
|
268
268
|
end
|
269
269
|
properties = response.data[member_login]
|
270
|
-
member = API::Member.new(@
|
270
|
+
member = API::Member.new(@api, properties)
|
271
271
|
@members[member_login] = member if @members.is_a?(Hash)
|
272
272
|
member
|
273
273
|
end
|
@@ -280,7 +280,7 @@ class Spire
|
|
280
280
|
raise "Error creating Channel: (#{response.status}) #{response.body}"
|
281
281
|
end
|
282
282
|
properties = response.data
|
283
|
-
channels[name] = API::Channel.new(@
|
283
|
+
channels[name] = API::Channel.new(@api, properties)
|
284
284
|
end
|
285
285
|
|
286
286
|
def create_subscription(subscription_name, channel_names)
|
@@ -290,7 +290,7 @@ class Spire
|
|
290
290
|
raise "Error creating Subscription: (#{response.status}) #{response.body}"
|
291
291
|
end
|
292
292
|
data = response.data
|
293
|
-
subscription = API::Subscription.new(@
|
293
|
+
subscription = API::Subscription.new(@api, data)
|
294
294
|
if subscription_name
|
295
295
|
subscriptions[data["name"]] = subscription
|
296
296
|
end
|
@@ -305,7 +305,7 @@ class Spire
|
|
305
305
|
channels_data = response.data
|
306
306
|
@channels = {}
|
307
307
|
channels_data.each do |name, properties|
|
308
|
-
@channels[name] = API::Channel.new(@
|
308
|
+
@channels[name] = API::Channel.new(@api, properties)
|
309
309
|
end
|
310
310
|
@channels
|
311
311
|
end
|
@@ -325,7 +325,7 @@ class Spire
|
|
325
325
|
end
|
326
326
|
@subscriptions = {}
|
327
327
|
response.data.each do |name, properties|
|
328
|
-
@subscriptions[name] = API::Subscription.new(@
|
328
|
+
@subscriptions[name] = API::Subscription.new(@api, properties)
|
329
329
|
end
|
330
330
|
@subscriptions
|
331
331
|
end
|
@@ -336,7 +336,7 @@ class Spire
|
|
336
336
|
raise "Error finding channel with name #{name}: (#{response.status}) #{response.body}"
|
337
337
|
end
|
338
338
|
properties = response.data[name]
|
339
|
-
channel = API::Channel.new(@
|
339
|
+
channel = API::Channel.new(@api, properties)
|
340
340
|
@channels[name] = channel if @channels.is_a?(Hash)
|
341
341
|
channel
|
342
342
|
end
|
@@ -347,7 +347,7 @@ class Spire
|
|
347
347
|
raise "Error finding subscription with name #{name}: (#{response.status}) #{response.body}"
|
348
348
|
end
|
349
349
|
properties = response.data[name]
|
350
|
-
sub = API::Subscription.new(@
|
350
|
+
sub = API::Subscription.new(@api, properties)
|
351
351
|
@subscriptions[name] = sub if @subscriptions.is_a?(Hash)
|
352
352
|
sub
|
353
353
|
end
|
data/lib/spire/api/channel.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
class Spire
|
2
2
|
class API
|
3
3
|
|
4
|
+
# Object representing a Spire channel
|
5
|
+
#
|
6
|
+
# You can get a channel object by calling [] on a Spire object
|
7
|
+
# * spire = Spire.new
|
8
|
+
# * spire.start("your api secret")
|
9
|
+
# * channel = spire["channel name"]
|
4
10
|
class Channel < Resource
|
5
11
|
|
6
12
|
attr_reader :resources
|
@@ -23,7 +29,7 @@ class Spire
|
|
23
29
|
:url => url,
|
24
30
|
:headers => {
|
25
31
|
"Authorization" => "Capability #{capability}",
|
26
|
-
"Accept" => @
|
32
|
+
"Accept" => @api.mediaType("subscriptions"),
|
27
33
|
}
|
28
34
|
}
|
29
35
|
end
|
@@ -35,8 +41,8 @@ class Spire
|
|
35
41
|
:body => string,
|
36
42
|
:headers => {
|
37
43
|
"Authorization" => "Capability #{@capabilities["publish"]}",
|
38
|
-
"Accept" => @
|
39
|
-
"Content-Type" => @
|
44
|
+
"Accept" => @api.mediaType("message"),
|
45
|
+
"Content-Type" => @api.mediaType("message")
|
40
46
|
}
|
41
47
|
}
|
42
48
|
end
|
@@ -51,8 +57,8 @@ class Spire
|
|
51
57
|
:body => {:name => name}.to_json,
|
52
58
|
:headers => {
|
53
59
|
"Authorization" => "Capability #{capability}",
|
54
|
-
"Accept" => @
|
55
|
-
"Content-Type" => @
|
60
|
+
"Accept" => @api.mediaType("subscription"),
|
61
|
+
"Content-Type" => @api.mediaType("subscription")
|
56
62
|
}
|
57
63
|
}
|
58
64
|
end
|
@@ -68,7 +74,7 @@ class Spire
|
|
68
74
|
end
|
69
75
|
@subscriptions = {}
|
70
76
|
response.data.each do |name, properties|
|
71
|
-
@subscriptions[name] = API::Subscription.new(@
|
77
|
+
@subscriptions[name] = API::Subscription.new(@api, properties)
|
72
78
|
end
|
73
79
|
@subscriptions
|
74
80
|
end
|
@@ -79,7 +85,7 @@ class Spire
|
|
79
85
|
unless response.status == 201
|
80
86
|
raise "Error publishing to #{self.class.name}: (#{response.status}) #{response.body}"
|
81
87
|
end
|
82
|
-
API::Message.new(@
|
88
|
+
API::Message.new(@api, response.data)
|
83
89
|
end
|
84
90
|
|
85
91
|
def subscribe(name = nil)
|
@@ -87,9 +93,8 @@ class Spire
|
|
87
93
|
unless response.status == 201
|
88
94
|
raise "Error creating subscription for #{self.name}: (#{response.status}) #{response.body}"
|
89
95
|
end
|
90
|
-
API::Subscription.new(@
|
96
|
+
API::Subscription.new(@api, response.data)
|
91
97
|
end
|
92
98
|
end
|
93
|
-
|
94
99
|
end
|
95
100
|
end
|
@@ -16,8 +16,8 @@ class Spire
|
|
16
16
|
}.to_json,
|
17
17
|
:headers => {
|
18
18
|
"Authorization" => "Capability #{@capabilities["push"]}",
|
19
|
-
"Accept" => @
|
20
|
-
"Content-Type" => @
|
19
|
+
"Accept" => @api.mediaType("notification"),
|
20
|
+
"Content-Type" => @api.mediaType("notification")
|
21
21
|
}
|
22
22
|
}
|
23
23
|
end
|
data/lib/spire/api/resource.rb
CHANGED
@@ -41,9 +41,9 @@ class Spire
|
|
41
41
|
|
42
42
|
attr_reader :url, :properties, :capabilities, :capability
|
43
43
|
|
44
|
-
def initialize(
|
45
|
-
@
|
46
|
-
@client =
|
44
|
+
def initialize(api, data)
|
45
|
+
@api = api
|
46
|
+
@client = api.client
|
47
47
|
@url = data["url"]
|
48
48
|
@capabilities = data["capabilities"]
|
49
49
|
@properties = data
|
@@ -92,7 +92,7 @@ class Spire
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def schema
|
95
|
-
@
|
95
|
+
@api.schema[resource_name]
|
96
96
|
end
|
97
97
|
|
98
98
|
def media_type
|
data/lib/spire/api/session.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
class Spire
|
2
2
|
class API
|
3
|
-
|
4
3
|
class Session
|
5
4
|
include Requestable
|
6
5
|
|
@@ -13,7 +12,7 @@ class Spire
|
|
13
12
|
:url => collection["url"],
|
14
13
|
:headers => {
|
15
14
|
"Authorization" => "Capability #{capability}",
|
16
|
-
"Accept" => @
|
15
|
+
"Accept" => @api.mediaType("channels"),
|
17
16
|
}
|
18
17
|
}
|
19
18
|
end
|
@@ -28,7 +27,7 @@ class Spire
|
|
28
27
|
:query => {:name => name},
|
29
28
|
:headers => {
|
30
29
|
"Authorization" => "Capability #{capability}",
|
31
|
-
"Accept" => @
|
30
|
+
"Accept" => @api.mediaType("channels"),
|
32
31
|
}
|
33
32
|
}
|
34
33
|
end
|
@@ -49,8 +48,8 @@ class Spire
|
|
49
48
|
:body => body,
|
50
49
|
:headers => {
|
51
50
|
"Authorization" => "Capability #{capability}",
|
52
|
-
"Accept" => @
|
53
|
-
"Content-Type" => @
|
51
|
+
"Accept" => @api.mediaType("channel"),
|
52
|
+
"Content-Type" => @api.mediaType("channel")
|
54
53
|
}
|
55
54
|
}
|
56
55
|
end
|
@@ -64,7 +63,7 @@ class Spire
|
|
64
63
|
:url => url,
|
65
64
|
:headers => {
|
66
65
|
"Authorization" => "Capability #{capability}",
|
67
|
-
"Accept" => @
|
66
|
+
"Accept" => @api.mediaType("subscriptions"),
|
68
67
|
}
|
69
68
|
}
|
70
69
|
end
|
@@ -91,8 +90,8 @@ class Spire
|
|
91
90
|
}.to_json,
|
92
91
|
:headers => {
|
93
92
|
"Authorization" => "Capability #{capability}",
|
94
|
-
"Accept" => @
|
95
|
-
"Content-Type" => @
|
93
|
+
"Accept" => @api.mediaType("subscription"),
|
94
|
+
"Content-Type" => @api.mediaType("subscription")
|
96
95
|
}
|
97
96
|
}
|
98
97
|
end
|
@@ -107,7 +106,7 @@ class Spire
|
|
107
106
|
:query => {:name => name},
|
108
107
|
:headers => {
|
109
108
|
"Authorization" => "Capability #{capability}",
|
110
|
-
"Accept" => @
|
109
|
+
"Accept" => @api.mediaType("subscriptions"),
|
111
110
|
}
|
112
111
|
}
|
113
112
|
end
|
@@ -125,8 +124,8 @@ class Spire
|
|
125
124
|
}.to_json,
|
126
125
|
:headers => {
|
127
126
|
"Authorization" => "Capability #{capability}",
|
128
|
-
"Accept" => @
|
129
|
-
"Content-Type" => @
|
127
|
+
"Accept" => @api.mediaType("notification"),
|
128
|
+
"Content-Type" => @api.mediaType("notification")
|
130
129
|
}
|
131
130
|
}
|
132
131
|
end
|
@@ -140,7 +139,7 @@ class Spire
|
|
140
139
|
:url => url,
|
141
140
|
:headers => {
|
142
141
|
"Authorization" => "Capability #{capability}",
|
143
|
-
"Accept" => @
|
142
|
+
"Accept" => @api.mediaType("notifications"),
|
144
143
|
}
|
145
144
|
}
|
146
145
|
end
|
@@ -153,7 +152,7 @@ class Spire
|
|
153
152
|
:url => collection["url"],
|
154
153
|
:headers => {
|
155
154
|
"Authorization" => "Capability #{capability}",
|
156
|
-
"Accept" => @
|
155
|
+
"Accept" => @api.mediaType("applications"),
|
157
156
|
}
|
158
157
|
}
|
159
158
|
end
|
@@ -168,7 +167,7 @@ class Spire
|
|
168
167
|
:query => {:name => name},
|
169
168
|
:headers => {
|
170
169
|
"Authorization" => "Capability #{capability}",
|
171
|
-
"Accept" => @
|
170
|
+
"Accept" => @api.mediaType("applications"),
|
172
171
|
}
|
173
172
|
}
|
174
173
|
end
|
@@ -181,21 +180,33 @@ class Spire
|
|
181
180
|
:body => data.to_json,
|
182
181
|
:headers => {
|
183
182
|
"Authorization" => "Capability #{collection["capabilities"]["create"]}",
|
184
|
-
"Accept" => @
|
185
|
-
"Content-Type" => @
|
183
|
+
"Accept" => @api.mediaType("application"),
|
184
|
+
"Content-Type" => @api.mediaType("application")
|
186
185
|
}
|
187
186
|
}
|
188
187
|
end
|
189
188
|
|
190
189
|
attr_reader :url, :resources, :schema, :capabilities, :capability
|
191
190
|
|
192
|
-
def initialize(
|
193
|
-
@
|
194
|
-
@client =
|
195
|
-
@schema =
|
191
|
+
def initialize(api, data)
|
192
|
+
@api = api
|
193
|
+
@client = api.client
|
194
|
+
@schema = api.schema["session"]
|
196
195
|
@url = data["url"]
|
197
196
|
@capabilities = data["capabilities"]
|
198
197
|
@resources = data["resources"]
|
198
|
+
|
199
|
+
@channel_error_counts = {}
|
200
|
+
@application_error_counts = {}
|
201
|
+
@subscription_error_counts = {}
|
202
|
+
@notification_error_counts = {}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns a channel object for the named channel
|
206
|
+
# @param [String] name Name of channel returned
|
207
|
+
# @return [Channel]
|
208
|
+
def [](name)
|
209
|
+
API::Channel.new(@api, channels[name] || find_or_create_channel(name))
|
199
210
|
end
|
200
211
|
|
201
212
|
def account
|
@@ -203,7 +214,7 @@ class Spire
|
|
203
214
|
end
|
204
215
|
|
205
216
|
def account!
|
206
|
-
@account = API::Account.new(@
|
217
|
+
@account = API::Account.new(@api, @resources["account"]).get
|
207
218
|
end
|
208
219
|
|
209
220
|
def create_channel(name, options={})
|
@@ -214,7 +225,33 @@ class Spire
|
|
214
225
|
raise "Error creating Channel: (#{response.status}) #{response.body}"
|
215
226
|
end
|
216
227
|
properties = response.data
|
217
|
-
channels[name] = API::Channel.new(@
|
228
|
+
channels[name] = API::Channel.new(@api, properties)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Creates a channel on spire. Returns a Channel object. Note that this will
|
232
|
+
# fail with a 409 if a channel with the same name exists.
|
233
|
+
def find_or_create_channel(name)
|
234
|
+
@channel_error_counts[name] ||= 0
|
235
|
+
|
236
|
+
begin
|
237
|
+
return create_channel(name)
|
238
|
+
# TODO custom error class for Conflict, which we can
|
239
|
+
# then match here, instead of testing for error message
|
240
|
+
rescue => error
|
241
|
+
if error.message =~ /409/
|
242
|
+
|
243
|
+
# Dear retry, I love you. Affectionately, Matthew.
|
244
|
+
if channel = channels![name]
|
245
|
+
return channel
|
246
|
+
else
|
247
|
+
@channel_error_counts[name] += 1
|
248
|
+
retry unless @channel_error_counts[name] >= RETRY_CREATION_LIMIT
|
249
|
+
end
|
250
|
+
|
251
|
+
else
|
252
|
+
raise error
|
253
|
+
end
|
254
|
+
end
|
218
255
|
end
|
219
256
|
|
220
257
|
def create_subscription(subscription_name, channel_names, expiration=nil, device_token=nil, notification_name=nil, second_try=false)
|
@@ -239,7 +276,7 @@ class Spire
|
|
239
276
|
raise "Error creating Subscription: (#{response.status}) #{response.body}"
|
240
277
|
end
|
241
278
|
data = response.data
|
242
|
-
subscription = API::Subscription.new(@
|
279
|
+
subscription = API::Subscription.new(@api, data)
|
243
280
|
if subscription_name
|
244
281
|
subscriptions[data["name"]] = subscription
|
245
282
|
end
|
@@ -252,7 +289,7 @@ class Spire
|
|
252
289
|
raise "Error finding application with name #{name}: (#{response.status}) #{response.body}"
|
253
290
|
end
|
254
291
|
properties = response.data[name]
|
255
|
-
app = API::Application.new(@
|
292
|
+
app = API::Application.new(@api, properties)
|
256
293
|
@applications[name] = app if @applications.is_a?(Hash)
|
257
294
|
app
|
258
295
|
end
|
@@ -264,7 +301,7 @@ class Spire
|
|
264
301
|
raise "Error creating Application (#{response.status}) #{response.body}"
|
265
302
|
end
|
266
303
|
properties = response.data
|
267
|
-
applications[name] = API::Application.new(@
|
304
|
+
applications[name] = API::Application.new(@api, properties)
|
268
305
|
end
|
269
306
|
|
270
307
|
def applications!
|
@@ -275,7 +312,7 @@ class Spire
|
|
275
312
|
applications_data = response.data
|
276
313
|
@applications = {}
|
277
314
|
applications_data.each do |name, properties|
|
278
|
-
@applications[name] = API::Application.new(@
|
315
|
+
@applications[name] = API::Application.new(@api, properties)
|
279
316
|
end
|
280
317
|
@applications
|
281
318
|
end
|
@@ -290,7 +327,7 @@ class Spire
|
|
290
327
|
raise "Error creating Notification: (#{response.status}) #{response.body}"
|
291
328
|
end
|
292
329
|
data = response.data
|
293
|
-
notification = API::Notification.new(@
|
330
|
+
notification = API::Notification.new(@api, data)
|
294
331
|
if options[:name]
|
295
332
|
notifications[data["name"]] = notification
|
296
333
|
end
|
@@ -305,7 +342,7 @@ class Spire
|
|
305
342
|
channels_data = response.data
|
306
343
|
@channels = {}
|
307
344
|
channels_data.each do |name, properties|
|
308
|
-
@channels[name] = API::Channel.new(@
|
345
|
+
@channels[name] = API::Channel.new(@api, properties)
|
309
346
|
end
|
310
347
|
@channels
|
311
348
|
end
|
@@ -325,11 +362,44 @@ class Spire
|
|
325
362
|
end
|
326
363
|
@subscriptions = {}
|
327
364
|
response.data.each do |name, properties|
|
328
|
-
@subscriptions[name] = API::Subscription.new(@
|
365
|
+
@subscriptions[name] = API::Subscription.new(@api, properties)
|
329
366
|
end
|
330
367
|
@subscriptions
|
331
368
|
end
|
332
369
|
|
370
|
+
# Returns a subscription object for the given channels
|
371
|
+
# @param [String] subscription_name Name for the subscription
|
372
|
+
# @param [String] channels One or more channel names for the subscription to listen on
|
373
|
+
# @return [Subscription]
|
374
|
+
def subscribe(name, *channels)
|
375
|
+
channels.each { |channel| self.find_or_create_channel(channel) }
|
376
|
+
API::Subscription.new(@api,
|
377
|
+
subscriptions[name] || find_or_create_subscription(name, *channels)
|
378
|
+
)
|
379
|
+
end
|
380
|
+
|
381
|
+
def find_or_create_subscription(subscription_name, *channels)
|
382
|
+
@subscription_error_counts[subscription_name] ||= 0
|
383
|
+
begin
|
384
|
+
return create_subscription(subscription_name, channels)
|
385
|
+
rescue => error
|
386
|
+
if error.message =~ /409/
|
387
|
+
|
388
|
+
if subscription = subscriptions![subscription_name]
|
389
|
+
return subscription
|
390
|
+
else
|
391
|
+
@subscription_error_counts[subscription_name] += 1
|
392
|
+
retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
|
393
|
+
end
|
394
|
+
|
395
|
+
else
|
396
|
+
raise error
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
alias :subscription :subscribe #For compatibility with other clients
|
402
|
+
|
333
403
|
def notifications
|
334
404
|
@notifications ||= notifications!
|
335
405
|
end
|
@@ -341,12 +411,62 @@ class Spire
|
|
341
411
|
end
|
342
412
|
@notifications = {}
|
343
413
|
response.data.each do |name, properties|
|
344
|
-
@notifications[name] = API::Notification.new(@
|
414
|
+
@notifications[name] = API::Notification.new(@api, properties)
|
345
415
|
end
|
346
416
|
@notifications
|
347
417
|
end
|
348
418
|
|
349
|
-
|
419
|
+
def notification(name, mode="development")
|
420
|
+
Notification.new(@api,
|
421
|
+
@notifications[name] || find_or_create_notification(name, ssl_cert)
|
422
|
+
)
|
423
|
+
end
|
424
|
+
|
425
|
+
def find_or_create_notification(notification_name, mode)
|
426
|
+
@notification_error_counts[notification_name] ||= 0
|
427
|
+
begin
|
428
|
+
return create_notification(
|
429
|
+
:name => notification_name,
|
430
|
+
:mode => mode
|
431
|
+
)
|
432
|
+
rescue => error
|
433
|
+
if error.message =~ /409/
|
434
|
+
|
435
|
+
if notification = notification![notification_name]
|
436
|
+
return notification
|
437
|
+
else
|
438
|
+
@notification_error_counts[notification_name] += 1
|
439
|
+
retry unless @notification_error_counts >= RETRY_CREATION_LIMIT
|
440
|
+
end
|
441
|
+
|
442
|
+
else
|
443
|
+
raise error
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
350
447
|
|
448
|
+
# Creates an application on spire. Returns an Application object. Will retry on a 409.
|
449
|
+
# @param [String] Name of the application to find/create
|
450
|
+
def find_or_create_application(name)
|
451
|
+
@application_error_counts[name] ||= 0
|
452
|
+
begin
|
453
|
+
return create_application(name)
|
454
|
+
# TODO custom error class for Conflict, which we can
|
455
|
+
# then match here, instead of testing for error message
|
456
|
+
rescue => error
|
457
|
+
if error.message =~ /409/
|
458
|
+
# Dear retry, I love you. Affectionately, Matthew.
|
459
|
+
if application = applications![name]
|
460
|
+
return application
|
461
|
+
else
|
462
|
+
@application_error_counts[name] += 1
|
463
|
+
retry unless @application_error_counts[name] >= RETRY_CREATION_LIMIT
|
464
|
+
end
|
465
|
+
else
|
466
|
+
raise error
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
351
471
|
end
|
352
472
|
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
class Spire
|
2
2
|
class API
|
3
3
|
|
4
|
+
# The subscription class represents a read connection to a Spire channel
|
5
|
+
#
|
6
|
+
# You can get a subscription by calling subscribe on a spire object with the name of the channel or
|
7
|
+
# by calling subscribe on a channel object
|
8
|
+
#
|
9
|
+
# * spire = Spire.new
|
10
|
+
# * spire.start("your api secret")
|
11
|
+
# *THEN*
|
12
|
+
# * subscription = spire.subscribe("subscription name", "channel name")
|
13
|
+
# *OR*
|
14
|
+
# * channel = spire["channel name"]
|
15
|
+
# * subscription = channel.subscribe("subscription name")
|
4
16
|
class Subscription < Resource
|
5
17
|
|
6
18
|
attr_accessor :last
|
@@ -23,7 +35,7 @@ class Spire
|
|
23
35
|
},
|
24
36
|
:headers => {
|
25
37
|
"Authorization" => "Capability #{@capabilities["events"]}",
|
26
|
-
"Accept" => @
|
38
|
+
"Accept" => @api.mediaType("events")
|
27
39
|
}
|
28
40
|
}
|
29
41
|
end
|
@@ -36,20 +48,75 @@ class Spire
|
|
36
48
|
else
|
37
49
|
@listeners = { }
|
38
50
|
EVENT_TYPES.each do |type|
|
39
|
-
@listeners[type] =
|
51
|
+
@listeners[type] = {}
|
40
52
|
end
|
41
53
|
@listeners
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
|
-
|
57
|
+
# provided named listeners, threading, and a
|
58
|
+
# stop_listening method.
|
59
|
+
def add_listener(type, listener_name = nil, &block)
|
60
|
+
type ||= ""
|
46
61
|
type.downcase!
|
47
62
|
if !EVENT_TYPES.include?(type)
|
48
63
|
throw "Listener type must be one of #{EVENT_TYPES}"
|
49
64
|
end
|
50
65
|
|
51
|
-
|
52
|
-
|
66
|
+
raise ArgumentError unless block_given?
|
67
|
+
listener_name ||= generate_listener_name
|
68
|
+
listener = wrap_listener(&block)
|
69
|
+
listeners[type][listener_name] = listener
|
70
|
+
listener_name
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_listener(type, arg)
|
74
|
+
type ||= ""
|
75
|
+
type.downcase!
|
76
|
+
if !EVENT_TYPES.include?(type)
|
77
|
+
throw "Listener type must be one of #{EVENT_TYPES}"
|
78
|
+
end
|
79
|
+
|
80
|
+
if arg.is_a? String
|
81
|
+
listener = listeners[type].delete(arg)
|
82
|
+
else
|
83
|
+
listener_name, _listener = listeners.detect {|k,v| v == arg }
|
84
|
+
listener = listeners[type].delete(listener_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
if listener
|
88
|
+
listeners[type].delete(listener)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def wrap_listener(&block)
|
93
|
+
lambda do |message|
|
94
|
+
Thread.new do
|
95
|
+
# Messages received after a call to stop_listening
|
96
|
+
# will not be processed.
|
97
|
+
yield message if @listening
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_listener_name
|
103
|
+
listener_name = nil
|
104
|
+
while !listener_name
|
105
|
+
new_name = "Listener-#{rand(9999999)}"
|
106
|
+
listener_name = new_name unless listeners.has_key?(new_name)
|
107
|
+
end
|
108
|
+
listener_name
|
109
|
+
end
|
110
|
+
|
111
|
+
def start_listening(options={})
|
112
|
+
@listening = true
|
113
|
+
Thread.new do
|
114
|
+
long_poll(options) while @listening
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop_listening
|
119
|
+
@listening = false
|
53
120
|
end
|
54
121
|
|
55
122
|
def retrieve_events(options={})
|
@@ -70,10 +137,10 @@ class Spire
|
|
70
137
|
response.data[type_pl].each do |event|
|
71
138
|
klass_name = type.capitalize
|
72
139
|
klass = API.const_get(klass_name)
|
73
|
-
event_obj = klass.new(@
|
140
|
+
event_obj = klass.new(@api, event)
|
74
141
|
event_hash[type_pl.to_sym].push(event_obj)
|
75
142
|
|
76
|
-
listeners[type].
|
143
|
+
listeners[type].each_value do |listener|
|
77
144
|
listener.call(event_obj)
|
78
145
|
end
|
79
146
|
end
|
@@ -93,13 +160,6 @@ class Spire
|
|
93
160
|
options[:last] = @last if @last
|
94
161
|
retrieve_events(options)
|
95
162
|
end
|
96
|
-
|
97
|
-
def listen(options={})
|
98
|
-
loop do
|
99
|
-
break unless yield(long_poll(options))
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
163
|
end
|
104
164
|
end
|
105
165
|
end
|
data/lib/spire_io.rb
CHANGED
@@ -13,22 +13,9 @@ class Spire
|
|
13
13
|
def initialize(url="https://api.spire.io")
|
14
14
|
@api = Spire::API.new(url)
|
15
15
|
@url = url
|
16
|
-
@channel_error_counts = {}
|
17
|
-
@application_error_counts = {}
|
18
|
-
@subscription_error_counts = {}
|
19
|
-
@notification_error_counts = {}
|
20
|
-
discover
|
21
|
-
end
|
22
|
-
|
23
|
-
def secret
|
24
|
-
@session.resources["account"]["secret"]
|
25
|
-
end
|
26
|
-
|
27
|
-
def discover
|
28
16
|
@api.discover
|
29
|
-
self
|
30
17
|
end
|
31
|
-
|
18
|
+
|
32
19
|
def start(secret)
|
33
20
|
@session = @api.create_session(secret)
|
34
21
|
self
|
@@ -52,301 +39,4 @@ class Spire
|
|
52
39
|
def password_reset_request(email)
|
53
40
|
@api.password_reset_request(email)
|
54
41
|
end
|
55
|
-
|
56
|
-
|
57
|
-
# Deletes the currently authenticated account
|
58
|
-
def delete_account
|
59
|
-
@session.account.delete
|
60
|
-
end
|
61
|
-
|
62
|
-
# Updates the current account with the new account information
|
63
|
-
# See Spire docs for available settings
|
64
|
-
def update(info)
|
65
|
-
@session.account.update(info)
|
66
|
-
self
|
67
|
-
end
|
68
|
-
|
69
|
-
# Returns a channel object for the named channel
|
70
|
-
# @param [String] name Name of channel returned
|
71
|
-
# @return [Channel]
|
72
|
-
def [](name)
|
73
|
-
Channel.new(self, channels[name] || find_or_create_channel(name))
|
74
|
-
end
|
75
|
-
|
76
|
-
def channels
|
77
|
-
@session.channels
|
78
|
-
end
|
79
|
-
|
80
|
-
def channels!
|
81
|
-
@session.channels!
|
82
|
-
end
|
83
|
-
|
84
|
-
# Creates a channel on spire. Returns a Channel object. Note that this will
|
85
|
-
# fail with a 409 if a channel with the same name exists.
|
86
|
-
def find_or_create_channel(name)
|
87
|
-
@channel_error_counts[name] ||= 0
|
88
|
-
|
89
|
-
begin
|
90
|
-
return @session.create_channel(name)
|
91
|
-
# TODO custom error class for Conflict, which we can
|
92
|
-
# then match here, instead of testing for error message
|
93
|
-
rescue => error
|
94
|
-
if error.message =~ /409/
|
95
|
-
|
96
|
-
# Dear retry, I love you. Affectionately, Matthew.
|
97
|
-
if channel = @session.channels![name]
|
98
|
-
return channel
|
99
|
-
else
|
100
|
-
@channel_error_counts[name] += 1
|
101
|
-
retry unless @channel_error_counts[name] >= RETRY_CREATION_LIMIT
|
102
|
-
end
|
103
|
-
|
104
|
-
else
|
105
|
-
raise error
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# Returns a subscription object for the given channels
|
111
|
-
# @param [String] subscription_name Name for the subscription
|
112
|
-
# @param [String] channels One or more channel names for the subscription to listen on
|
113
|
-
# @return [Subscription]
|
114
|
-
def subscribe(name, *channels)
|
115
|
-
channels.each { |channel| self.find_or_create_channel(channel) }
|
116
|
-
Subscription.new(
|
117
|
-
@session.subscriptions[name] || find_or_create_subscription(name, *channels)
|
118
|
-
)
|
119
|
-
end
|
120
|
-
|
121
|
-
def find_or_create_subscription(subscription_name, *channels)
|
122
|
-
@subscription_error_counts[subscription_name] ||= 0
|
123
|
-
begin
|
124
|
-
return @session.create_subscription(subscription_name, channels)
|
125
|
-
rescue => error
|
126
|
-
if error.message =~ /409/
|
127
|
-
|
128
|
-
if subscription = @session.subscriptions![subscription_name]
|
129
|
-
return subscription
|
130
|
-
else
|
131
|
-
@subscription_error_counts[subscription_name] += 1
|
132
|
-
retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
|
133
|
-
end
|
134
|
-
|
135
|
-
else
|
136
|
-
raise error
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
alias :subscription :subscribe #For compatibility with other clients
|
142
|
-
|
143
|
-
#Returns an array of subscription objects for all of this account's subscriptions
|
144
|
-
#@return [Array]
|
145
|
-
def subscriptions
|
146
|
-
@session.subscriptions.values
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
def notification(name, mode="development")
|
151
|
-
Notification.new(
|
152
|
-
@session.notifications[name] || find_or_create_notification(name, ssl_cert)
|
153
|
-
)
|
154
|
-
end
|
155
|
-
|
156
|
-
def find_or_create_notification(notification_name, mode)
|
157
|
-
@notification_error_counts[notification_name] ||= 0
|
158
|
-
begin
|
159
|
-
return @session.create_notification(
|
160
|
-
:name => notification_name,
|
161
|
-
:mode => mode
|
162
|
-
)
|
163
|
-
rescue => error
|
164
|
-
if error.message =~ /409/
|
165
|
-
|
166
|
-
if notification = @session.notification![notification_name]
|
167
|
-
return notification
|
168
|
-
else
|
169
|
-
@notification_error_counts[notification_name] += 1
|
170
|
-
retry unless @notification_error_counts >= RETRY_CREATION_LIMIT
|
171
|
-
end
|
172
|
-
|
173
|
-
else
|
174
|
-
raise error
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
|
180
|
-
def applications
|
181
|
-
@session.applications
|
182
|
-
end
|
183
|
-
|
184
|
-
def applications!
|
185
|
-
@session.applications!
|
186
|
-
end
|
187
|
-
|
188
|
-
# Creates an application on spire. Returns an Application object. Will retry on a 409.
|
189
|
-
# @param [String] Name of the application to find/create
|
190
|
-
def find_or_create_application(name)
|
191
|
-
@application_error_counts[name] ||= 0
|
192
|
-
begin
|
193
|
-
return @session.create_application(name)
|
194
|
-
# TODO custom error class for Conflict, which we can
|
195
|
-
# then match here, instead of testing for error message
|
196
|
-
rescue => error
|
197
|
-
if error.message =~ /409/
|
198
|
-
# Dear retry, I love you. Affectionately, Matthew.
|
199
|
-
if application = @session.applications![name]
|
200
|
-
return application
|
201
|
-
else
|
202
|
-
@application_error_counts[name] += 1
|
203
|
-
retry unless @application_error_counts[name] >= RETRY_CREATION_LIMIT
|
204
|
-
end
|
205
|
-
else
|
206
|
-
raise error
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
# Returns a billing object than contains a list of all the plans available
|
212
|
-
# @param [String] info optional object description
|
213
|
-
# @return [Billing]
|
214
|
-
def billing
|
215
|
-
@api.billing
|
216
|
-
end
|
217
|
-
|
218
|
-
# Updates and subscribe the account to a billing plan
|
219
|
-
# @param [Object] info data containing billing description
|
220
|
-
# @return [Account]
|
221
|
-
def billing_subscription(info)
|
222
|
-
@session.account.billing_subscription(info)
|
223
|
-
self
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
# Object representing a Spire channel
|
228
|
-
#
|
229
|
-
# You can get a channel object by calling [] on a Spire object
|
230
|
-
# * spire = Spire.new
|
231
|
-
# * spire.start("your api secret")
|
232
|
-
# * channel = spire["channel name"]
|
233
|
-
class Channel < SimpleDelegator
|
234
|
-
def initialize(spire, channel)
|
235
|
-
super(channel)
|
236
|
-
@spire = spire
|
237
|
-
end
|
238
|
-
# Obtain a subscription for the channel
|
239
|
-
# @param [String] subscription_name Name of the subscription
|
240
|
-
# @return [Subscription]
|
241
|
-
def subscribe(subscription_name = nil)
|
242
|
-
@spire.subscribe(subscription_name, properties["name"])
|
243
|
-
end
|
244
|
-
|
245
|
-
# this is required because Delegator's method_missing relies
|
246
|
-
# on the object having a method defined, but in this case
|
247
|
-
# the API::Channel is also using method_missing
|
248
|
-
def name
|
249
|
-
__getobj__.name
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
# The subscription class represents a read connection to a Spire channel
|
254
|
-
#
|
255
|
-
# You can get a subscription by calling subscribe on a spire object with the name of the channel or
|
256
|
-
# by calling subscribe on a channel object
|
257
|
-
#
|
258
|
-
# * spire = Spire.new
|
259
|
-
# * spire.start("your api secret")
|
260
|
-
# *THEN*
|
261
|
-
# * subscription = spire.subscribe("subscription name", "channel name")
|
262
|
-
# *OR*
|
263
|
-
# * channel = spire["channel name"]
|
264
|
-
# * subscription = channel.subscribe("subscription name")
|
265
|
-
class Subscription < SimpleDelegator
|
266
|
-
|
267
|
-
# this is required because Delegator's method_missing relies
|
268
|
-
# on the object having a method defined, but in this case
|
269
|
-
# the API::Subscription is also using method_missing
|
270
|
-
def name
|
271
|
-
__getobj__.name
|
272
|
-
end
|
273
|
-
|
274
|
-
# misnamed method, here for backompat. Should be something like #get_messages,
|
275
|
-
# because it only makes one request.
|
276
|
-
def listen(options={})
|
277
|
-
long_poll(options)
|
278
|
-
end
|
279
|
-
|
280
|
-
# wraps the underlying Subscription#add_listener to
|
281
|
-
# provided named listeners, threading, and a
|
282
|
-
# stop_listening method.
|
283
|
-
def add_listener(listener_name = nil, &block)
|
284
|
-
raise ArgumentError unless block_given?
|
285
|
-
listener_name ||= generate_listener_name
|
286
|
-
listener = wrap_listener(&block)
|
287
|
-
listeners[listener_name] = listener
|
288
|
-
__getobj__.add_listener("message", &listener)
|
289
|
-
end
|
290
|
-
|
291
|
-
def remove_listener(arg)
|
292
|
-
if arg.is_a? String
|
293
|
-
listener = listeners.delete(arg)
|
294
|
-
else
|
295
|
-
listener_name, _listener = listeners.detect {|k,v| v == arg }
|
296
|
-
listener = listeners.delete(listener_name)
|
297
|
-
end
|
298
|
-
|
299
|
-
if listener
|
300
|
-
__getobj__.listeners["message"].delete(listener)
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def wrap_listener(&block)
|
305
|
-
lambda do |message|
|
306
|
-
Thread.new do
|
307
|
-
# Messages received after a call to stop_listening
|
308
|
-
# will not be processed.
|
309
|
-
yield message["content"] if @listening
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
def listeners
|
315
|
-
@listeners ||= {}
|
316
|
-
end
|
317
|
-
|
318
|
-
def generate_listener_name
|
319
|
-
listener_name = nil
|
320
|
-
while !listener_name
|
321
|
-
new_name = "Listener-#{rand(9999999)}"
|
322
|
-
listener_name = new_name unless listeners.has_key?(new_name)
|
323
|
-
end
|
324
|
-
listener_name
|
325
|
-
end
|
326
|
-
|
327
|
-
def start_listening(options={})
|
328
|
-
@listening = true
|
329
|
-
Thread.new do
|
330
|
-
long_poll(options) while @listening
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def stop_listening
|
335
|
-
@listening = false
|
336
|
-
end
|
337
|
-
|
338
|
-
end
|
339
|
-
|
340
|
-
class Notification < SimpleDelegator
|
341
|
-
|
342
|
-
# this is required because Delegator's method_missing relies
|
343
|
-
# on the object having a method defined, but in this case
|
344
|
-
# the API::Subscription is also using method_missing
|
345
|
-
def name
|
346
|
-
__getobj__.name
|
347
|
-
end
|
348
|
-
|
349
|
-
|
350
|
-
end
|
351
|
-
|
352
42
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spire_io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 1.2.
|
9
|
+
- 2
|
10
|
+
version: 1.2.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Dan Yoder
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2012-05-
|
20
|
+
date: 2012-05-24 00:00:00 -07:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|