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.
@@ -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
@@ -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,4 @@
1
+ module Msngr
2
+ VERSION = "0.0.1"
3
+ end
4
+
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+ require "msngr"
3
+
4
+ describe Msngr do
5
+
6
+ it "should instantiate an instance of Msngr::Messenger" do
7
+ Msngr.new(mock).class.should == Msngr::Messenger
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ require "simplecov"
2
+ require "bundler"
3
+ SimpleCov.start
4
+ Bundler.require
5
+
6
+ RSpec.configure do |config|
7
+ config.mock_with :mocha
8
+ end
9
+
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: []