google-cloud-pubsub 1.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 +7 -0
- data/.yardopts +18 -0
- data/AUTHENTICATION.md +177 -0
- data/CHANGELOG.md +538 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +188 -0
- data/EMULATOR.md +37 -0
- data/LICENSE +201 -0
- data/LOGGING.md +32 -0
- data/OVERVIEW.md +557 -0
- data/TROUBLESHOOTING.md +31 -0
- data/lib/google-cloud-pubsub.rb +139 -0
- data/lib/google/cloud/pubsub.rb +173 -0
- data/lib/google/cloud/pubsub/async_publisher.rb +399 -0
- data/lib/google/cloud/pubsub/async_publisher/batch.rb +309 -0
- data/lib/google/cloud/pubsub/batch_publisher.rb +99 -0
- data/lib/google/cloud/pubsub/convert.rb +91 -0
- data/lib/google/cloud/pubsub/credentials.rb +47 -0
- data/lib/google/cloud/pubsub/errors.rb +85 -0
- data/lib/google/cloud/pubsub/message.rb +158 -0
- data/lib/google/cloud/pubsub/policy.rb +187 -0
- data/lib/google/cloud/pubsub/project.rb +393 -0
- data/lib/google/cloud/pubsub/publish_result.rb +103 -0
- data/lib/google/cloud/pubsub/received_message.rb +297 -0
- data/lib/google/cloud/pubsub/retry_policy.rb +90 -0
- data/lib/google/cloud/pubsub/service.rb +514 -0
- data/lib/google/cloud/pubsub/snapshot.rb +202 -0
- data/lib/google/cloud/pubsub/snapshot/list.rb +178 -0
- data/lib/google/cloud/pubsub/subscriber.rb +399 -0
- data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
- data/lib/google/cloud/pubsub/subscriber/inventory.rb +166 -0
- data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
- data/lib/google/cloud/pubsub/subscriber/stream.rb +401 -0
- data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +231 -0
- data/lib/google/cloud/pubsub/subscription.rb +1279 -0
- data/lib/google/cloud/pubsub/subscription/list.rb +205 -0
- data/lib/google/cloud/pubsub/subscription/push_config.rb +244 -0
- data/lib/google/cloud/pubsub/topic.rb +934 -0
- data/lib/google/cloud/pubsub/topic/list.rb +171 -0
- data/lib/google/cloud/pubsub/v1.rb +17 -0
- data/lib/google/cloud/pubsub/v1/credentials.rb +41 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/iam_policy.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/options.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/policy.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +91 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/empty.rb +29 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +222 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +113 -0
- data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +833 -0
- data/lib/google/cloud/pubsub/v1/doc/google/type/expr.rb +19 -0
- data/lib/google/cloud/pubsub/v1/publisher_client.rb +928 -0
- data/lib/google/cloud/pubsub/v1/publisher_client_config.json +120 -0
- data/lib/google/cloud/pubsub/v1/subscriber_client.rb +1466 -0
- data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +153 -0
- data/lib/google/cloud/pubsub/version.rb +24 -0
- data/lib/google/pubsub/v1/pubsub_pb.rb +269 -0
- data/lib/google/pubsub/v1/pubsub_services_pb.rb +215 -0
- metadata +337 -0
@@ -0,0 +1,309 @@
|
|
1
|
+
# Copyright 2019 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "monitor"
|
17
|
+
require "google/cloud/pubsub/errors"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Cloud
|
21
|
+
module PubSub
|
22
|
+
class AsyncPublisher
|
23
|
+
##
|
24
|
+
# @private
|
25
|
+
class Batch
|
26
|
+
include MonitorMixin
|
27
|
+
|
28
|
+
attr_reader :items, :ordering_key
|
29
|
+
|
30
|
+
def initialize publisher, ordering_key
|
31
|
+
# init MonitorMixin
|
32
|
+
super()
|
33
|
+
|
34
|
+
@publisher = publisher
|
35
|
+
@ordering_key = ordering_key
|
36
|
+
@items = []
|
37
|
+
@queue = []
|
38
|
+
@default_message_bytes = publisher.topic_name.bytesize + 2
|
39
|
+
@total_message_bytes = @default_message_bytes
|
40
|
+
@publishing = false
|
41
|
+
@stopping = false
|
42
|
+
@canceled = false
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Adds a message and callback to the batch.
|
47
|
+
#
|
48
|
+
# The method will indicate how the message is added. It will either be
|
49
|
+
# added to the active list of items, it will be queued to be picked up
|
50
|
+
# once the active publishing job has been completed, or it will
|
51
|
+
# indicate that the batch is full and a publishing job should be
|
52
|
+
# created.
|
53
|
+
#
|
54
|
+
# @param [Google::Cloud::PubSub::V1::PubsubMessage] msg The message.
|
55
|
+
# @param [Proc, nil] callback The callback.
|
56
|
+
#
|
57
|
+
# @return [Symbol] The state of the batch.
|
58
|
+
#
|
59
|
+
# * `:added` - Added to the active list of items to be published.
|
60
|
+
# * `:queued` - Batch is publishing, and the messsage is queued.
|
61
|
+
# * `:full` - Batch is full and ready to be published, and the
|
62
|
+
# message is queued.
|
63
|
+
#
|
64
|
+
def add msg, callback
|
65
|
+
synchronize do
|
66
|
+
raise AsyncPublisherStopped if @stopping
|
67
|
+
raise OrderingKeyError, @ordering_key if @canceled
|
68
|
+
|
69
|
+
if @publishing
|
70
|
+
queue_add msg, callback
|
71
|
+
:queued
|
72
|
+
elsif try_add msg, callback
|
73
|
+
:added
|
74
|
+
else
|
75
|
+
queue_add msg, callback
|
76
|
+
:full
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Marks the batch to be published.
|
83
|
+
#
|
84
|
+
# The method will indicate whether a new publishing job should be
|
85
|
+
# started to publish the batch. See {publishing?}.
|
86
|
+
#
|
87
|
+
# @param [Boolean] stop Indicates whether the batch should also be
|
88
|
+
# marked for stopping, and any existing publish job should publish
|
89
|
+
# all items until the batch is empty.
|
90
|
+
#
|
91
|
+
# @return [Boolean] Returns whether a new publishing job should be
|
92
|
+
# started to publish the batch. If the batch is already being
|
93
|
+
# published then this will return `false`.
|
94
|
+
#
|
95
|
+
def publish! stop: nil
|
96
|
+
synchronize do
|
97
|
+
@stopping = true if stop
|
98
|
+
|
99
|
+
return false if @canceled
|
100
|
+
|
101
|
+
# If we are already publishing, do not indicate a new job needs to
|
102
|
+
# be started.
|
103
|
+
return false if @publishing
|
104
|
+
|
105
|
+
@publishing = !(@items.empty? && @queue.empty?)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Indicates whether the batch has an active publishing job.
|
111
|
+
#
|
112
|
+
# @return [Boolean]
|
113
|
+
#
|
114
|
+
def publishing?
|
115
|
+
# This probably does not need to be synchronized
|
116
|
+
@publishing
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Indicates whether the batch has been stopped and all items will be
|
121
|
+
# published until the batch is empty.
|
122
|
+
#
|
123
|
+
# @return [Boolean]
|
124
|
+
#
|
125
|
+
def stopping?
|
126
|
+
# This does not need to be synchronized because nothing un-stops
|
127
|
+
@stopping
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Rebalances the batch by moving any queued items that will fit into
|
132
|
+
# the active item list.
|
133
|
+
#
|
134
|
+
# This method is only intended to be used by the active publishing
|
135
|
+
# job.
|
136
|
+
#
|
137
|
+
def rebalance!
|
138
|
+
synchronize do
|
139
|
+
return [] if @canceled
|
140
|
+
|
141
|
+
until @queue.empty?
|
142
|
+
item = @queue.first
|
143
|
+
if try_add item.msg, item.callback
|
144
|
+
@queue.shift
|
145
|
+
next
|
146
|
+
end
|
147
|
+
break
|
148
|
+
end
|
149
|
+
|
150
|
+
@items
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# rubocop:disable Metrics/MethodLength
|
155
|
+
|
156
|
+
##
|
157
|
+
# Resets the batch after a successful publish. This clears the active
|
158
|
+
# item list and moves the queued items that will fit into the active
|
159
|
+
# item list.
|
160
|
+
#
|
161
|
+
# If the batch has enough queued items to fill the batch again, the
|
162
|
+
# publishing job should continue to publish the reset batch until the
|
163
|
+
# batch indicated it should stop.
|
164
|
+
#
|
165
|
+
# This method is only intended to be used by the active publishing
|
166
|
+
# job.
|
167
|
+
#
|
168
|
+
# @return [Boolean] Whether the active publishing job should continue
|
169
|
+
# publishing after the reset.
|
170
|
+
#
|
171
|
+
def reset!
|
172
|
+
synchronize do
|
173
|
+
@items = []
|
174
|
+
@total_message_bytes = @default_message_bytes
|
175
|
+
|
176
|
+
if @canceled
|
177
|
+
@queue = []
|
178
|
+
@publishing = false
|
179
|
+
return false
|
180
|
+
end
|
181
|
+
|
182
|
+
until @queue.empty?
|
183
|
+
item = @queue.first
|
184
|
+
added = try_add item.msg, item.callback
|
185
|
+
break unless added
|
186
|
+
@queue.shift
|
187
|
+
end
|
188
|
+
|
189
|
+
return false unless @publishing
|
190
|
+
if @items.empty?
|
191
|
+
@publishing = false
|
192
|
+
return false
|
193
|
+
else
|
194
|
+
return true if stopping?
|
195
|
+
if @queue.empty?
|
196
|
+
@publishing = false
|
197
|
+
return false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
# rubocop:enable Metrics/MethodLength
|
205
|
+
|
206
|
+
##
|
207
|
+
# Cancel the batch and hault futher batches until resumed. See
|
208
|
+
# {#resume!} and {#canceled?}.
|
209
|
+
#
|
210
|
+
# @return [Array<Item}] All items, including queued items
|
211
|
+
#
|
212
|
+
def cancel!
|
213
|
+
synchronize do
|
214
|
+
@canceled = true
|
215
|
+
@items + @queue
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Resume the batch and proceed to publish messages. See {#cancel!} and
|
221
|
+
# {#canceled?}.
|
222
|
+
#
|
223
|
+
# @return [Boolean] Whether the batch was resumed.
|
224
|
+
#
|
225
|
+
def resume!
|
226
|
+
synchronize do
|
227
|
+
# Return false if the batch is not canceled
|
228
|
+
return false unless @canceled
|
229
|
+
|
230
|
+
@items = []
|
231
|
+
@queue = []
|
232
|
+
@total_message_bytes = @default_message_bytes
|
233
|
+
@publishing = false
|
234
|
+
@canceled = false
|
235
|
+
end
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
##
|
240
|
+
# Indicates whether the batch has been canceled due to an error while
|
241
|
+
# publishing. See {#cancel!} and {#resume!}.
|
242
|
+
#
|
243
|
+
# @return [Boolean]
|
244
|
+
#
|
245
|
+
def canceled?
|
246
|
+
# This does not need to be synchronized because nothing un-stops
|
247
|
+
synchronize { @canceled }
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Determines whether the batch is empty and ready to be culled.
|
252
|
+
#
|
253
|
+
def empty?
|
254
|
+
synchronize do
|
255
|
+
return false if @publishing || @canceled || @stopping
|
256
|
+
|
257
|
+
@items.empty? && @queue.empty?
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
protected
|
262
|
+
|
263
|
+
def items_add msg, callback
|
264
|
+
item = Item.new msg, callback
|
265
|
+
@items << item
|
266
|
+
@total_message_bytes += item.bytesize + 2
|
267
|
+
end
|
268
|
+
|
269
|
+
def try_add msg, callback
|
270
|
+
if @items.empty?
|
271
|
+
# Always add when empty, even if bytesize is bigger than total
|
272
|
+
items_add msg, callback
|
273
|
+
return true
|
274
|
+
end
|
275
|
+
new_message_count = total_message_count + 1
|
276
|
+
new_message_bytes = total_message_bytes + msg.to_proto.bytesize + 2
|
277
|
+
if new_message_count > @publisher.max_messages ||
|
278
|
+
new_message_bytes >= @publisher.max_bytes
|
279
|
+
return false
|
280
|
+
end
|
281
|
+
items_add msg, callback
|
282
|
+
true
|
283
|
+
end
|
284
|
+
|
285
|
+
def queue_add msg, callback
|
286
|
+
item = Item.new msg, callback
|
287
|
+
@queue << item
|
288
|
+
end
|
289
|
+
|
290
|
+
def total_message_count
|
291
|
+
@items.count
|
292
|
+
end
|
293
|
+
|
294
|
+
def total_message_bytes
|
295
|
+
@total_message_bytes
|
296
|
+
end
|
297
|
+
|
298
|
+
Item = Struct.new :msg, :callback do
|
299
|
+
def bytesize
|
300
|
+
msg.to_proto.bytesize
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Copyright 2015 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
module Google
|
17
|
+
module Cloud
|
18
|
+
module PubSub
|
19
|
+
##
|
20
|
+
# Topic Batch Publisher object used to publish multiple messages at
|
21
|
+
# once.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# require "google/cloud/pubsub"
|
25
|
+
#
|
26
|
+
# pubsub = Google::Cloud::PubSub.new
|
27
|
+
#
|
28
|
+
# topic = pubsub.topic "my-topic"
|
29
|
+
# msgs = topic.publish do |t|
|
30
|
+
# t.publish "task 1 completed", foo: :bar
|
31
|
+
# t.publish "task 2 completed", foo: :baz
|
32
|
+
# t.publish "task 3 completed", foo: :bif
|
33
|
+
# end
|
34
|
+
class BatchPublisher
|
35
|
+
##
|
36
|
+
# @private The messages to publish
|
37
|
+
attr_reader :messages
|
38
|
+
|
39
|
+
##
|
40
|
+
# @private Create a new instance of the object.
|
41
|
+
def initialize data = nil, attributes = {}
|
42
|
+
@messages = []
|
43
|
+
@mode = :batch
|
44
|
+
return if data.nil?
|
45
|
+
@mode = :single
|
46
|
+
publish data, attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Add a message to the batch to be published to the topic.
|
51
|
+
# All messages added to the batch will be published at once.
|
52
|
+
# See {Google::Cloud::PubSub::Topic#publish}
|
53
|
+
def publish data, attributes = {}
|
54
|
+
@messages << create_pubsub_message(data, attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# @private Create Message objects with message ids.
|
59
|
+
def to_gcloud_messages message_ids
|
60
|
+
msgs = @messages.zip(Array(message_ids)).map do |msg, id|
|
61
|
+
msg.message_id = id
|
62
|
+
Message.from_grpc msg
|
63
|
+
end
|
64
|
+
# Return just one Message if a single publish,
|
65
|
+
# otherwise return the array of Messages.
|
66
|
+
if @mode == :single && msgs.count <= 1
|
67
|
+
msgs.first
|
68
|
+
else
|
69
|
+
msgs
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def create_pubsub_message data, attributes
|
76
|
+
attributes ||= {}
|
77
|
+
if data.is_a?(::Hash) && attributes.empty?
|
78
|
+
attributes = data
|
79
|
+
data = nil
|
80
|
+
end
|
81
|
+
# Convert IO-ish objects to strings
|
82
|
+
if data.respond_to?(:read) && data.respond_to?(:rewind)
|
83
|
+
data.rewind
|
84
|
+
data = data.read
|
85
|
+
end
|
86
|
+
# Convert data to encoded byte array to match the protobuf defn
|
87
|
+
data_bytes = String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
88
|
+
|
89
|
+
# Convert attributes to strings to match the protobuf definition
|
90
|
+
attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
|
91
|
+
|
92
|
+
Google::Cloud::PubSub::V1::PubsubMessage.new data: data_bytes, attributes: attributes
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright 2017 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "time"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module PubSub
|
21
|
+
##
|
22
|
+
# @private Helper module for converting Pub/Sub values.
|
23
|
+
module Convert
|
24
|
+
module ClassMethods
|
25
|
+
def time_to_timestamp time
|
26
|
+
return nil if time.nil?
|
27
|
+
|
28
|
+
# Force the object to be a Time object.
|
29
|
+
time = time.to_time
|
30
|
+
|
31
|
+
Google::Protobuf::Timestamp.new seconds: time.to_i, nanos: time.nsec
|
32
|
+
end
|
33
|
+
|
34
|
+
def timestamp_to_time timestamp
|
35
|
+
return nil if timestamp.nil?
|
36
|
+
|
37
|
+
Time.at timestamp.seconds, Rational(timestamp.nanos, 1000)
|
38
|
+
end
|
39
|
+
|
40
|
+
def number_to_duration number
|
41
|
+
return nil if number.nil?
|
42
|
+
|
43
|
+
Google::Protobuf::Duration.new seconds: number.to_i, nanos: (number.remainder(1) * 1_000_000_000).round
|
44
|
+
end
|
45
|
+
|
46
|
+
def duration_to_number duration
|
47
|
+
return nil if duration.nil?
|
48
|
+
|
49
|
+
return duration.seconds if duration.nanos.zero?
|
50
|
+
|
51
|
+
duration.seconds + (duration.nanos / 1_000_000_000.0)
|
52
|
+
end
|
53
|
+
|
54
|
+
def pubsub_message data, attributes, ordering_key, extra_attrs
|
55
|
+
# TODO: allow data to be a Message object,
|
56
|
+
# then ensure attributes and ordering_key are nil
|
57
|
+
if data.is_a?(::Hash) && (attributes.nil? || attributes.empty?)
|
58
|
+
attributes = data.merge extra_attrs
|
59
|
+
data = nil
|
60
|
+
else
|
61
|
+
attributes = Hash(attributes).merge extra_attrs
|
62
|
+
end
|
63
|
+
# Convert IO-ish objects to strings
|
64
|
+
if data.respond_to?(:read) && data.respond_to?(:rewind)
|
65
|
+
data.rewind
|
66
|
+
data = data.read
|
67
|
+
end
|
68
|
+
# Convert data to encoded byte array to match the protobuf defn
|
69
|
+
data_bytes = String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
70
|
+
|
71
|
+
# Convert attributes to strings to match the protobuf definition
|
72
|
+
attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
|
73
|
+
|
74
|
+
# Ordering Key must always be a string
|
75
|
+
ordering_key = String(ordering_key).freeze
|
76
|
+
|
77
|
+
Google::Cloud::PubSub::V1::PubsubMessage.new(
|
78
|
+
data: data_bytes,
|
79
|
+
attributes: attributes,
|
80
|
+
ordering_key: ordering_key
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
extend ClassMethods
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
90
|
+
end
|
91
|
+
end
|