msngr 0.0.1
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/lib/msngr.rb +18 -0
- data/lib/msngr/clients/redis.rb +47 -0
- data/lib/msngr/messenger.rb +103 -0
- data/lib/msngr/receiver.rb +61 -0
- data/lib/msngr/version.rb +4 -0
- data/spec/lib/msngr/clients/redis_spec.rb +42 -0
- data/spec/lib/msngr/messenger_spec.rb +78 -0
- data/spec/lib/msngr/receiver_spec.rb +37 -0
- data/spec/lib/msngr_spec.rb +10 -0
- data/spec/spec_helper.rb +9 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 80f389f5be19c06e918bbabc6758063ebc871298
|
4
|
+
data.tar.gz: d152dc1d6e25696fa09fc917248dfbb599483c1e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 249d9724441d3a927679ed6e8c88a7e3944c7812efe0a787f656bab7878796452d6d6c46d96a69f1f793680521b2989ab972c30251eff5e38bbf54d0aedde2a9
|
7
|
+
data.tar.gz: 53dc43080979cd573aae3ca906fe15abc4f61a07bcef3cdf15f6bde205766eabdeac76cb5b64aaa188af654012499161c40932708e788818a9050fd0e681e961
|
data/lib/msngr.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "msngr/version"
|
2
|
+
require "msngr/receiver"
|
3
|
+
require "msngr/messenger"
|
4
|
+
|
5
|
+
module Msngr
|
6
|
+
module Clients; end
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Shorthand for writing Msngr::Messenger.new(*args).
|
10
|
+
#
|
11
|
+
# @param [Array] *args
|
12
|
+
# @return [Msngr::Messenger]
|
13
|
+
#
|
14
|
+
def new(*args)
|
15
|
+
Messenger.new(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
class Msngr::Clients::Redis
|
4
|
+
|
5
|
+
# (Connectivity) Arguments to initialize the Redis instance with.
|
6
|
+
#
|
7
|
+
# @return [Array]
|
8
|
+
#
|
9
|
+
attr_reader :args
|
10
|
+
|
11
|
+
# Instantiances an instance of Msngr::Clients::Redis.
|
12
|
+
#
|
13
|
+
# @param [Array] *args the arguments to pass in to the Redis client.
|
14
|
+
#
|
15
|
+
def initialize(*args)
|
16
|
+
@args = args
|
17
|
+
end
|
18
|
+
|
19
|
+
# Yields all events/messages from the Redis server.
|
20
|
+
#
|
21
|
+
# @yield [event, message]
|
22
|
+
# @yieldparam [String] event the name of the received event.
|
23
|
+
# @yieldparam [String] message the message of the received event.
|
24
|
+
#
|
25
|
+
# @note This is an interface for Msngr::Messenger.
|
26
|
+
#
|
27
|
+
def on_message
|
28
|
+
connection.psubscribe("*") do |on|
|
29
|
+
on.pmessage { |_, event, message| yield event, message }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Creates and returns a new instance of Redis using @args if present.
|
36
|
+
#
|
37
|
+
# @return [Redis]
|
38
|
+
#
|
39
|
+
def connection
|
40
|
+
if args.any?
|
41
|
+
Redis.new(*args)
|
42
|
+
else
|
43
|
+
Redis.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class Msngr::Messenger
|
4
|
+
|
5
|
+
# A client object interface to receive messages from.
|
6
|
+
#
|
7
|
+
# @return [Msngr::Clients::*]
|
8
|
+
#
|
9
|
+
attr_reader :client
|
10
|
+
|
11
|
+
# An Array of Receiver objects to dispatch callbacks/messages to.
|
12
|
+
#
|
13
|
+
# @return [Array<Receiver>]
|
14
|
+
#
|
15
|
+
attr_reader :receivers
|
16
|
+
|
17
|
+
# Initializes a new Messenger.
|
18
|
+
#
|
19
|
+
# @param [Msngr::Clients::*] client the to receive messages from.
|
20
|
+
# @return [Receiver]
|
21
|
+
#
|
22
|
+
def initialize(client)
|
23
|
+
@client = client
|
24
|
+
@receivers = []
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates and returns a new Receiver and adds it to @receivers so
|
29
|
+
# it'll receive messages matching the provided pattern.
|
30
|
+
#
|
31
|
+
# @param [Regexp] pattern
|
32
|
+
# @return [Receiver]
|
33
|
+
#
|
34
|
+
def subscribe(pattern)
|
35
|
+
Msngr::Receiver.new(pattern).tap do |receiver|
|
36
|
+
@mutex.synchronize { @receivers << receiver }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes the Receiver from @receivers and invokes the receiver's
|
41
|
+
# @on_unsubscribe_callbacks.
|
42
|
+
#
|
43
|
+
# @param [Receiver] receiver
|
44
|
+
#
|
45
|
+
def unsubscribe(receiver)
|
46
|
+
@mutex.synchronize { @receivers.delete(receiver) }
|
47
|
+
dispatch(receiver.on_unsubscribe_callbacks, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Listens on a new thread. Will auto-restart when crashed
|
51
|
+
# in an attempt to recover from exceptions.
|
52
|
+
#
|
53
|
+
def listen!
|
54
|
+
Thread.new do
|
55
|
+
loop do
|
56
|
+
begin
|
57
|
+
listen
|
58
|
+
rescue => e
|
59
|
+
puts "Messenger error occurred:"
|
60
|
+
puts "#{e.class.name}"
|
61
|
+
puts "#{e.backtrace.join("\n")}"
|
62
|
+
puts "Restarting.."
|
63
|
+
sleep 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Instructs the @client to yield events/messages when it receives them. Received
|
72
|
+
# events are matched with each Receiver's pattern and will dispatch the event's message
|
73
|
+
# to all subscribing Receiver instances that match the event's pattern.
|
74
|
+
#
|
75
|
+
def listen
|
76
|
+
client.on_message do |event, message|
|
77
|
+
subscribing_receivers(event) do |receiver|
|
78
|
+
dispatch(receiver.on_message_callbacks, message)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Yields Receiver objects who's pattern matches the event.
|
84
|
+
#
|
85
|
+
# @param [String] event
|
86
|
+
# @yield [Receiver]
|
87
|
+
#
|
88
|
+
def subscribing_receivers(event)
|
89
|
+
receivers.each do |receiver|
|
90
|
+
yield receiver if receiver.pattern.match(event)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Dispatches args to the provided callbacks.
|
95
|
+
#
|
96
|
+
# @param [Array<Proc>] callbacks
|
97
|
+
# @param [Array] *args an Array of arguments to call each Proc with.
|
98
|
+
#
|
99
|
+
def dispatch(callbacks, *args)
|
100
|
+
callbacks.each { |callback| callback.call(*args) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class Msngr::Receiver
|
4
|
+
|
5
|
+
# Event pattern to match.
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
#
|
9
|
+
attr_reader :pattern
|
10
|
+
|
11
|
+
# Array of Procs to invoke when a message is received.
|
12
|
+
#
|
13
|
+
# @return [Array<Proc>]
|
14
|
+
#
|
15
|
+
attr_reader :on_message_callbacks
|
16
|
+
|
17
|
+
# Array of Procs to invoke when unsubscribed from a Messenger.
|
18
|
+
#
|
19
|
+
# @return [Array<Proc>]
|
20
|
+
#
|
21
|
+
attr_reader :on_unsubscribe_callbacks
|
22
|
+
|
23
|
+
# Initializes a new Receiver.
|
24
|
+
#
|
25
|
+
# @param [Regexp] pattern the pattern to listen for.
|
26
|
+
# @return [Receiver]
|
27
|
+
#
|
28
|
+
def initialize(pattern = /.+/)
|
29
|
+
@pattern = pattern
|
30
|
+
@on_message_callbacks = []
|
31
|
+
@on_unsubscribe_callbacks = []
|
32
|
+
@mutex = Mutex.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define a callback that invokes on each received message.
|
36
|
+
#
|
37
|
+
# @param [Proc] block
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# receiver.on_message do |message|
|
41
|
+
# puts "Message Received: #{message}"
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
def on_message(&block)
|
45
|
+
@mutex.synchronize { @on_message_callbacks << block }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Define a callback that invokes when unsubscribed from a Messenger.
|
49
|
+
#
|
50
|
+
# @param [Proc] block
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# receiver.on_unsubscribe do |messenger|
|
54
|
+
# puts "Unsubscribed From: #{messenger}"
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def on_unsubscribe(&block)
|
58
|
+
@mutex.synchronize { @on_unsubscribe_callbacks << block }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/clients/redis"
|
3
|
+
|
4
|
+
describe Msngr::Clients::Redis do
|
5
|
+
|
6
|
+
let(:client) { Msngr::Clients::Redis.new }
|
7
|
+
|
8
|
+
it "should initialize without redis options" do
|
9
|
+
client.should have(0).args
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize with arguments" do
|
13
|
+
client = Msngr::Clients::Redis.new(host: "127.0.0.1", port: 6379)
|
14
|
+
client.args.should == [{host: "127.0.0.1", port: 6379}]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should establish a connection without any args" do
|
18
|
+
Redis.expects(:new)
|
19
|
+
client.send(:connection)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should establish a connection with args" do
|
23
|
+
options = { host: "127.0.0.1", port: 6379 }
|
24
|
+
client = Msngr::Clients::Redis.new(options)
|
25
|
+
Redis.expects(:new).with(options)
|
26
|
+
client.send(:connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should yield a message" do
|
30
|
+
connection, on = mock, mock
|
31
|
+
|
32
|
+
client.stubs(:connection).returns(connection)
|
33
|
+
connection.expects(:psubscribe).with("*").yields(on)
|
34
|
+
on.expects(:pmessage).yields("", "room.1", "John joined the room!")
|
35
|
+
|
36
|
+
client.on_message do |event, message|
|
37
|
+
event.should == "room.1"
|
38
|
+
message.should == "John joined the room!"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/receiver"
|
3
|
+
require "msngr/messenger"
|
4
|
+
|
5
|
+
describe Msngr::Messenger do
|
6
|
+
|
7
|
+
let(:client) { mock }
|
8
|
+
let(:messenger) { Msngr::Messenger.new(client) }
|
9
|
+
|
10
|
+
it "should initialize with an empty array of receivers" do
|
11
|
+
messenger.receivers.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should create a new Receiver and add it to @receivers" do
|
15
|
+
receiver = messenger.subscribe(/.+/)
|
16
|
+
messenger.should have(1).receivers
|
17
|
+
messenger.receivers.first.should == receiver
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should unsubscribe a receiver" do
|
21
|
+
receiver = messenger.subscribe(/.+/)
|
22
|
+
messenger.expects(:dispatch).
|
23
|
+
with(receiver.on_unsubscribe_callbacks, messenger)
|
24
|
+
messenger.unsubscribe(receiver)
|
25
|
+
messenger.should have(0).receivers
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should start listening on a separate thread" do
|
29
|
+
Thread.expects(:new).yields
|
30
|
+
messenger.expects(:loop).yields
|
31
|
+
messenger.expects(:listen).once
|
32
|
+
messenger.listen!
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should display a backtrace on error and restart" do
|
36
|
+
Thread.expects(:new).yields
|
37
|
+
messenger.expects(:loop).yields
|
38
|
+
messenger.expects(:listen).raises.then.returns
|
39
|
+
messenger.expects(:puts).times(4)
|
40
|
+
messenger.expects(:sleep)
|
41
|
+
messenger.listen!
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should listen to the client but not dispatch" do
|
45
|
+
messenger.subscribe(/room\.2/)
|
46
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
47
|
+
messenger.expects(:dispatch).never
|
48
|
+
messenger.send(:listen)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should listen to the client and dispatch a message" do
|
52
|
+
messenger.subscribe(/room\.1/).on_message { |m| m.should == "Hi John" }
|
53
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
54
|
+
messenger.send(:listen)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should listen to the client and dispatch 3 messages" do
|
58
|
+
3.times do
|
59
|
+
messenger.subscribe(/room\.1/).tap do |receiver|
|
60
|
+
cb = proc {}
|
61
|
+
cb.expects(:call).with("Hi John")
|
62
|
+
receiver.on_message(&cb)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
2.times do
|
67
|
+
messenger.subscribe(/room\.2/).tap do |receiver|
|
68
|
+
cb = proc {}
|
69
|
+
cb.expects(:call).never
|
70
|
+
receiver.on_message(&cb)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
75
|
+
messenger.send(:listen)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/receiver"
|
3
|
+
|
4
|
+
describe Msngr::Receiver do
|
5
|
+
|
6
|
+
let(:receiver) { Msngr::Receiver.new }
|
7
|
+
|
8
|
+
it "should initialize with a default wildcard pattern" do
|
9
|
+
receiver.pattern.should == /.+/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize with a custom pattern" do
|
13
|
+
pattern = /rooms\.1/
|
14
|
+
Msngr::Receiver.new(pattern).pattern.should == pattern
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should define an message callback" do
|
18
|
+
callback = proc { |msg| "invoked: #{msg}" }
|
19
|
+
receiver.on_message(&callback)
|
20
|
+
|
21
|
+
callbacks = receiver.on_message_callbacks
|
22
|
+
callbacks.size.should == 1
|
23
|
+
callbacks.first.call("message").should == "invoked: message"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should define an unsubscribe callback" do
|
27
|
+
callback = proc { |msngr| "unsubscribed from: #{msngr}" }
|
28
|
+
receiver.on_unsubscribe(&callback)
|
29
|
+
|
30
|
+
callbacks = receiver.on_unsubscribe_callbacks
|
31
|
+
callbacks.size.should == 1
|
32
|
+
|
33
|
+
object = Object.new
|
34
|
+
callbacks.first.call(object).should == "unsubscribed from: #{object}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: msngr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael van Rooijen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 10.1.0
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 10.1.0
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 10.1.0
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 10.1.0
|
47
|
+
description: A light-weight Ruby library for multi-threaded Ruby applications that
|
48
|
+
allows threads to share a single service connection for more efficient messaging.
|
49
|
+
email:
|
50
|
+
- meskyanichi@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- "./lib/msngr.rb"
|
56
|
+
- "./lib/msngr/clients/redis.rb"
|
57
|
+
- "./lib/msngr/messenger.rb"
|
58
|
+
- "./lib/msngr/receiver.rb"
|
59
|
+
- "./lib/msngr/version.rb"
|
60
|
+
- "./spec/lib/msngr/clients/redis_spec.rb"
|
61
|
+
- "./spec/lib/msngr/messenger_spec.rb"
|
62
|
+
- "./spec/lib/msngr/receiver_spec.rb"
|
63
|
+
- "./spec/lib/msngr_spec.rb"
|
64
|
+
- "./spec/spec_helper.rb"
|
65
|
+
homepage: https://github.com/meskyanichi/msngr/
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.0
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: A light-weight Ruby library for multi-threaded Ruby applications that allows
|
89
|
+
threads to share a single service connection for more efficient messaging.
|
90
|
+
test_files: []
|