pubsubstub 0.0.4
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/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +1 -0
- data/lib/pubsubstub/application.rb +48 -0
- data/lib/pubsubstub/channel.rb +53 -0
- data/lib/pubsubstub/event.rb +37 -0
- data/lib/pubsubstub/redis_pub_sub.rb +51 -0
- data/lib/pubsubstub/version.rb +3 -0
- data/lib/pubsubstub.rb +10 -0
- data/pubsubstub.gemspec +33 -0
- data/spec/channel_spec.rb +83 -0
- data/spec/event_spec.rb +29 -0
- data/spec/redis_pub_sub_spec.rb +78 -0
- data/spec/spec_helper.rb +20 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 88ea9eade9037b07b2c4692f4ecc33589b5ac76c
|
4
|
+
data.tar.gz: 60575d29603abe6749b46bacbfbb6ef81cc03334
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e9b0d5967e309750bca8675f49ac64f36b1a5f899f561ea7ceefce96acc523cfb3d84cd72328e626d1ceb9fa1ad74c2393c7b9d52493ed84f7d2cbfa6c61699
|
7
|
+
data.tar.gz: 1fa0683a8cdf8510c922c08cb8f1ab83daaf499dc4622a3d3f2551e6b3403a06ba9d17594fe94f07c2a6b77b2ad2edb57fa3cd2e7a90fb7b4dcce6c2fc8b6e7f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Guillaume Malette
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Pubsubstub
|
2
|
+
|
3
|
+
Pubsubstub is a rack middleware to add Pub/Sub
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'pubsubstub'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install pubsubstub
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Rails
|
22
|
+
|
23
|
+
match "/events", to: Pubsubstub::Application, via: :all
|
24
|
+
|
25
|
+
### Standalone
|
26
|
+
|
27
|
+
You can easily run Pubsubstub standalone by creating a `config.ru` file containing
|
28
|
+
|
29
|
+
require 'pubsubstub'
|
30
|
+
|
31
|
+
run Pubsubstub::Application
|
32
|
+
|
33
|
+
To start the application, run `bundle exec thin start --timeout 0 --max-conns 1024`
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it ( http://github.com/<my-github-username>/pubsubstub/fork )
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Pubsubstub
|
2
|
+
class Application < Sinatra::Base
|
3
|
+
configure :production, :development do
|
4
|
+
enable :logging
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@channels = Hash.new { |h, k| h[k] = Channel.new(k) }
|
9
|
+
@connections = []
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def channel(name)
|
14
|
+
@channels[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/', provides: 'text/event-stream' do
|
18
|
+
status(200)
|
19
|
+
headers({
|
20
|
+
'Cache-Control' => 'no-cache',
|
21
|
+
'X-Accel-Buffering' => 'no',
|
22
|
+
'Connection' => 'keep-alive',
|
23
|
+
})
|
24
|
+
stream(:keep_open) do |connection|
|
25
|
+
@connections << connection
|
26
|
+
channels = params[:channels] || [:default]
|
27
|
+
channels.each do |channel_name|
|
28
|
+
channel(channel_name).subscribe(connection, last_event_id: request.env['HTTP_LAST_EVENT_ID'])
|
29
|
+
end
|
30
|
+
|
31
|
+
connection.callback do
|
32
|
+
@connections.delete(connection)
|
33
|
+
channels.each do |channel_name|
|
34
|
+
channel(channel_name).unsubscribe(connection)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
post '/' do
|
41
|
+
event = Event.new(params[:data], name: params[:event])
|
42
|
+
(params[:channels] || [:default]).each do |channel_name|
|
43
|
+
channel(channel_name).publish(event)
|
44
|
+
end
|
45
|
+
""
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Pubsubstub
|
2
|
+
class Channel
|
3
|
+
attr_reader :name, :pubsub
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@pubsub = RedisPubSub.new(name)
|
8
|
+
@connections = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(connection, last_event_id: nil)
|
12
|
+
listen if @connections.empty?
|
13
|
+
@connections << connection
|
14
|
+
scrollback(connection, last_event_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def subscribed?(connection)
|
18
|
+
@connections.include?(connection)
|
19
|
+
end
|
20
|
+
|
21
|
+
def unsubscribe(connection)
|
22
|
+
@connections.delete(connection)
|
23
|
+
stop_listening if @connections.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish(event)
|
27
|
+
pubsub.publish(event)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def scrollback(connection, last_event_id)
|
32
|
+
return unless last_event_id
|
33
|
+
pubsub.scrollback(last_event_id) do |event|
|
34
|
+
connection << event.to_message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def broadcast(json)
|
39
|
+
string = Event.from_json(json).to_message
|
40
|
+
@connections.each do |connection|
|
41
|
+
connection << string
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def listen
|
46
|
+
pubsub.subscribe(method(:broadcast))
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop_listening
|
50
|
+
pubsub.unsubscribe(method(:broadcast))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Pubsubstub
|
2
|
+
class Event
|
3
|
+
attr_reader :id, :name, :data
|
4
|
+
|
5
|
+
def initialize(data, name: nil, id: nil)
|
6
|
+
@id = id || time_now
|
7
|
+
@name = name
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_json
|
12
|
+
{id: @id, name: @name, data: @data}.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_message
|
16
|
+
data = @data.split("\n").map{ |segment| "data: #{segment}" }.join("\n")
|
17
|
+
message = "id: #{id}" << "\n"
|
18
|
+
message << "event: #{name}" << "\n" if name
|
19
|
+
message << data << "\n\n"
|
20
|
+
message
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_json(json)
|
24
|
+
hash = JSON.load(json)
|
25
|
+
new(hash['data'], name: hash['name'], id: hash['id'])
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
id == other.id && name == other.name && data == other.data
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def time_now
|
34
|
+
(Time.now.to_f * 1000).to_i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Pubsubstub
|
2
|
+
class RedisPubSub
|
3
|
+
def initialize(channel_name)
|
4
|
+
@channel_name = channel_name
|
5
|
+
end
|
6
|
+
|
7
|
+
def subscribe(callback)
|
8
|
+
self.class.sub.subscribe(key('pubsub'), callback)
|
9
|
+
end
|
10
|
+
|
11
|
+
def unsubscribe(callback)
|
12
|
+
self.class.sub.unsubscribe_proc(key('pubsub'), callback)
|
13
|
+
end
|
14
|
+
|
15
|
+
def publish(event)
|
16
|
+
self.class.publish(@channel_name, event)
|
17
|
+
end
|
18
|
+
|
19
|
+
def scrollback(since_event_id)
|
20
|
+
self.class.nonblocking_redis.zrangebyscore(key('scrollback'), "(#{since_event_id.to_i}", '+inf') do |events|
|
21
|
+
events.each do |json|
|
22
|
+
yield Pubsubstub::Event.from_json(json)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def key(purpose)
|
29
|
+
[@channel_name, purpose].join(".")
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def publish(channel_name, event)
|
34
|
+
blocking_redis.publish("#{channel_name}.pubsub", event.to_json)
|
35
|
+
blocking_redis.zadd("#{channel_name}.scrollback", event.id, event.to_json)
|
36
|
+
end
|
37
|
+
|
38
|
+
def sub
|
39
|
+
@sub ||= nonblocking_redis.pubsub
|
40
|
+
end
|
41
|
+
|
42
|
+
def blocking_redis
|
43
|
+
@blocking_redis ||= Redis.new(url: (ENV['REDIS_URL'] || "redis://localhost:6379"))
|
44
|
+
end
|
45
|
+
|
46
|
+
def nonblocking_redis
|
47
|
+
@nonblocking_redis ||= EM::Hiredis.connect(ENV['REDIS_URL'] || "redis://localhost:6379")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/pubsubstub.rb
ADDED
data/pubsubstub.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pubsubstub/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pubsubstub"
|
8
|
+
spec.version = Pubsubstub::VERSION
|
9
|
+
spec.authors = ["Guillaume Malette"]
|
10
|
+
spec.email = ["gmalette@gmail.com"]
|
11
|
+
spec.summary = %q{Pubsubstub is a rack middleware to add Pub/Sub}
|
12
|
+
spec.description = %q{Pubsubstub can be added to a rack Application or deployed standalone. It uses Redis to do the Pub/Sub}
|
13
|
+
spec.homepage = "https://github.com/gmalette/pubsubstub"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'sinatra', "~> 1.4"
|
22
|
+
spec.add_dependency 'em-hiredis', "~> 0.2"
|
23
|
+
spec.add_dependency 'redis', "~> 3.0"
|
24
|
+
spec.add_dependency 'eventmachine', "~> 1.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.2"
|
28
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
29
|
+
spec.add_development_dependency "em-rspec", "~> 0.1"
|
30
|
+
spec.add_development_dependency "pry", "~> 0.9"
|
31
|
+
spec.add_development_dependency "thin", "~> 1.6"
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pubsubstub::Channel do
|
4
|
+
let(:pubsub) { double(Pubsubstub::RedisPubSub, publish: true, subscribe: true, unsubscribe: true) }
|
5
|
+
subject { Pubsubstub::Channel.new("test") }
|
6
|
+
before { allow(subject).to receive(:pubsub) { pubsub } }
|
7
|
+
|
8
|
+
context "#initialize" do
|
9
|
+
it "does not subscribe immediately" do
|
10
|
+
expect(pubsub).not_to receive(:subscribe)
|
11
|
+
Pubsubstub::Channel.new('test')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#subscribe" do
|
16
|
+
let(:connection) { double('connection') }
|
17
|
+
it "subscribes the client" do
|
18
|
+
subject.subscribe(connection)
|
19
|
+
expect(subject.subscribed?(connection)).to be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "starts subscribing to the channel if it's the first client" do
|
23
|
+
expect(pubsub).to receive(:subscribe)
|
24
|
+
subject.subscribe(connection)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "does not starts subscribing to the channel if it's not the first client" do
|
28
|
+
subject.subscribe(connection)
|
29
|
+
expect(pubsub).not_to receive(:subscribe)
|
30
|
+
subject.subscribe(double('connection'))
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sends the scrollback if a last_event_id is passed" do
|
34
|
+
event = Pubsubstub::Event.new("event")
|
35
|
+
expect(pubsub).to receive(:scrollback).with(1234).and_yield(event)
|
36
|
+
expect(connection).to receive(:<<).with(event.to_message)
|
37
|
+
subject.subscribe(connection, last_event_id: 1234)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#unsubscribe" do
|
42
|
+
let(:connection) { double('connection') }
|
43
|
+
before { subject.subscribe(connection) }
|
44
|
+
|
45
|
+
it "unsubscribes the client" do
|
46
|
+
expect(subject.subscribed?(connection)).to be_true
|
47
|
+
subject.unsubscribe(connection)
|
48
|
+
expect(subject.subscribed?(connection)).to be_false
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does not stop listening if it's not the last client" do
|
52
|
+
subject.subscribe(double('connection'))
|
53
|
+
expect(pubsub).not_to receive(:unsubscribe)
|
54
|
+
subject.unsubscribe(connection)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "stops listening if it's the last client" do
|
58
|
+
expect(pubsub).to receive(:unsubscribe)
|
59
|
+
subject.unsubscribe(connection)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "#publish" do
|
64
|
+
it "forwards to the pubsub" do
|
65
|
+
event = double('event')
|
66
|
+
expect(pubsub).to receive(:publish).with(event)
|
67
|
+
subject.publish(event)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "broadcasting events from redis" do
|
72
|
+
let(:event) { Pubsubstub::Event.new("message", name: "toto") }
|
73
|
+
let(:connection) { double('connection') }
|
74
|
+
before {
|
75
|
+
subject.subscribe(connection)
|
76
|
+
}
|
77
|
+
|
78
|
+
it "sends the events to the clients" do
|
79
|
+
expect(connection).to receive(:<<).with(event.to_message)
|
80
|
+
subject.send(:broadcast, event.to_json)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pubsubstub::Event do
|
4
|
+
subject { Pubsubstub::Event.new("refresh #1500\nnew #1400", id: 12345678, name: "toto") }
|
5
|
+
|
6
|
+
it "#to_json serialization" do
|
7
|
+
expect(subject.to_json).to be == {id: 12345678, name: "toto", data: "refresh #1500\nnew #1400"}.to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
context "#to_message" do
|
11
|
+
it "serializes to sse" do
|
12
|
+
expect(subject.to_message).to be == "id: 12345678\nevent: toto\ndata: refresh #1500\ndata: new #1400\n\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "does not have event if no name is specified" do
|
16
|
+
event = Pubsubstub::Event.new("refresh", id: 1234)
|
17
|
+
expect(event.to_message).to be == "id: 1234\ndata: refresh\n\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it ".from_json" do
|
22
|
+
json = subject.to_json
|
23
|
+
expect(Pubsubstub::Event.from_json(json).to_json).to be == json
|
24
|
+
end
|
25
|
+
|
26
|
+
it "== another event if they are the same" do
|
27
|
+
expect(subject).to be == Pubsubstub::Event.from_json(subject.to_json)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pubsubstub::RedisPubSub do
|
4
|
+
|
5
|
+
context "class singleton methods" do
|
6
|
+
subject { Pubsubstub::RedisPubSub }
|
7
|
+
|
8
|
+
describe "#publish" do
|
9
|
+
let(:redis) { double('redis') }
|
10
|
+
let(:event) { double('Event', to_json: "event_data", id: 1234) }
|
11
|
+
before {
|
12
|
+
allow(subject).to receive(:blocking_redis) { redis }
|
13
|
+
}
|
14
|
+
|
15
|
+
it "publishes the event to a redis channel and adds it to the scrollback" do
|
16
|
+
expect(redis).to receive(:publish).with("test.pubsub", event.to_json)
|
17
|
+
expect(redis).to receive(:zadd).with("test.scrollback", event.id, event.to_json)
|
18
|
+
subject.publish("test", event)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#sub" do
|
23
|
+
it "memoizes the connection" do
|
24
|
+
expect(subject.send(:sub)).to be == subject.send(:sub)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "pubsub" do
|
30
|
+
subject { Pubsubstub::RedisPubSub.new("test") }
|
31
|
+
let(:pubsub) { double('redis pubsub') }
|
32
|
+
let(:callback) { ->{} }
|
33
|
+
|
34
|
+
describe "#subscribe" do
|
35
|
+
before {
|
36
|
+
allow(subject.class).to receive(:sub) { pubsub }
|
37
|
+
}
|
38
|
+
|
39
|
+
it "creates a redis sub with the callback" do
|
40
|
+
expect(pubsub).to receive(:subscribe).with("test.pubsub", callback)
|
41
|
+
subject.subscribe(callback)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#unsubscribe" do
|
46
|
+
before {
|
47
|
+
allow(subject.class).to receive(:sub) { pubsub }
|
48
|
+
}
|
49
|
+
|
50
|
+
it "creates a redis sub with the callback" do
|
51
|
+
expect(pubsub).to receive(:unsubscribe_proc).with("test.pubsub", callback)
|
52
|
+
subject.unsubscribe(callback)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#publish" do
|
57
|
+
let(:event) { Pubsubstub::Event.new("toto") }
|
58
|
+
it "delegates to RedisPubSub.publish" do
|
59
|
+
expect(Pubsubstub::RedisPubSub).to receive(:publish).with("test", event)
|
60
|
+
subject.publish(event)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#scrollback" do
|
66
|
+
subject { Pubsubstub::RedisPubSub.new("test") }
|
67
|
+
|
68
|
+
let(:event1) { Pubsubstub::Event.new("toto", id: 1235) }
|
69
|
+
let(:event2) { Pubsubstub::Event.new("toto", id: 1236) }
|
70
|
+
|
71
|
+
it "yields the events in the scrollback" do
|
72
|
+
redis = double('redis')
|
73
|
+
expect(redis).to receive(:zrangebyscore).with('test.scrollback', '(1234', '+inf').and_yield([event1.to_json, event2.to_json])
|
74
|
+
expect(Pubsubstub::RedisPubSub).to receive(:nonblocking_redis).and_return(redis)
|
75
|
+
expect { |block| subject.scrollback(1234, &block) }.to yield_successive_args(event1, event2)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'em-rspec'
|
2
|
+
require 'pry'
|
3
|
+
require_relative '../lib/pubsubstub'
|
4
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
7
|
+
# loaded once.
|
8
|
+
#
|
9
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = 'random'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pubsubstub
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Malette
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: em-hiredis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: eventmachine
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.14'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: em-rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.1'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.1'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.9'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.9'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: thin
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.6'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.6'
|
153
|
+
description: Pubsubstub can be added to a rack Application or deployed standalone.
|
154
|
+
It uses Redis to do the Pub/Sub
|
155
|
+
email:
|
156
|
+
- gmalette@gmail.com
|
157
|
+
executables: []
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".rspec"
|
163
|
+
- Gemfile
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- lib/pubsubstub.rb
|
168
|
+
- lib/pubsubstub/application.rb
|
169
|
+
- lib/pubsubstub/channel.rb
|
170
|
+
- lib/pubsubstub/event.rb
|
171
|
+
- lib/pubsubstub/redis_pub_sub.rb
|
172
|
+
- lib/pubsubstub/version.rb
|
173
|
+
- pubsubstub.gemspec
|
174
|
+
- spec/channel_spec.rb
|
175
|
+
- spec/event_spec.rb
|
176
|
+
- spec/redis_pub_sub_spec.rb
|
177
|
+
- spec/spec_helper.rb
|
178
|
+
homepage: https://github.com/gmalette/pubsubstub
|
179
|
+
licenses:
|
180
|
+
- MIT
|
181
|
+
metadata: {}
|
182
|
+
post_install_message:
|
183
|
+
rdoc_options: []
|
184
|
+
require_paths:
|
185
|
+
- lib
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
requirements: []
|
197
|
+
rubyforge_project:
|
198
|
+
rubygems_version: 2.2.2
|
199
|
+
signing_key:
|
200
|
+
specification_version: 4
|
201
|
+
summary: Pubsubstub is a rack middleware to add Pub/Sub
|
202
|
+
test_files:
|
203
|
+
- spec/channel_spec.rb
|
204
|
+
- spec/event_spec.rb
|
205
|
+
- spec/redis_pub_sub_spec.rb
|
206
|
+
- spec/spec_helper.rb
|
207
|
+
has_rdoc:
|