lolitra 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --default_path spec
@@ -0,0 +1,231 @@
1
+ require 'singleton'
2
+
3
+ module Lolitra
4
+ module MessageHandler
5
+ module Helpers
6
+ def self.underscore(word)
7
+ word.gsub!(/::/, '/')
8
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
9
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
10
+ word.tr!("-", "_")
11
+ word.downcase!
12
+ word
13
+ end
14
+ end
15
+
16
+ class NoHandlerMessageException < StandardError
17
+ def initialize(handler, message)
18
+ @handler_class = handler.name
19
+ @message_class = message.class.name
20
+ end
21
+
22
+ def to_s
23
+ "No handler (or starter if stateful) for message #{@message_class} in class #{@handler_class}"
24
+ end
25
+ end
26
+
27
+ module MessageHandlerClass
28
+
29
+ def self.extended(base)
30
+ class << base
31
+ attr_accessor :handlers
32
+ attr_accessor :starters
33
+ attr_accessor :is_stateful
34
+ end
35
+ base.handlers = {}
36
+ base.starters = []
37
+ base.is_stateful = false
38
+ end
39
+
40
+ def handle(message)
41
+ #puts "#{self.name} try to handle new message #{message.class.name}"
42
+ begin
43
+ get_handler(message).handle(message)
44
+ rescue NoMethodError => e
45
+ raise NoHandlerMessageException.new(self, message) if e.message == "undefined method `handle' for nil:NilClass"
46
+ raise
47
+ end
48
+ end
49
+
50
+ def publish(message)
51
+ #TODO: IoC
52
+ MessageHandlerManager.publish(message)
53
+ end
54
+
55
+ def handle_messages
56
+ handlers.values.collect { |class_method_pair| class_method_pair[0] }
57
+ end
58
+
59
+ private
60
+ def message_handler(message_class, id = :id)
61
+ message_class.new.send(id) #check if id exists for this class
62
+ handlers.merge!(message_class.name => [message_class, get_method_by_class(message_class), id])
63
+ end
64
+
65
+ def started_by(message_class)
66
+ starters << message_class.name
67
+ end
68
+
69
+ def search(message)
70
+ id_method_name = handlers[message.class.name][2]
71
+ send("find_by_#{id_method_name}", message.send(id_method_name))
72
+ end
73
+
74
+ def is_starter?(message)
75
+ starters.include? message.class.name
76
+ end
77
+
78
+ def get_handler(message)
79
+ if is_stateful?
80
+ is_starter?(message) ? (search(message) || new) : search(message)
81
+ else
82
+ new
83
+ end
84
+ end
85
+
86
+ def get_method_by_class(arg_class)
87
+ MessageHandler::Helpers.underscore(arg_class.name).gsub("/","_").to_sym
88
+ end
89
+
90
+ def stateful(stateful_arg)
91
+ self.is_stateful = stateful_arg
92
+ end
93
+
94
+ def is_stateful?
95
+ is_stateful
96
+ end
97
+ end
98
+
99
+ def self.included(base)
100
+ base.send :extend, MessageHandlerClass
101
+ end
102
+
103
+ def handle(message)
104
+ handler_method = self.class.handlers[message.class.name][1]
105
+ raise "Can't handle message #{message.class}" unless handler_method
106
+ self.send(handler_method, message)
107
+ end
108
+
109
+ end
110
+
111
+ class MessageHandlerManager
112
+ include Singleton
113
+
114
+ attr_accessor :bus
115
+
116
+ def self.bus=(new_bus)
117
+ instance.bus = new_bus
118
+ end
119
+
120
+ def self.bus
121
+ instance.bus
122
+ end
123
+
124
+ def self.register(handler_class)
125
+ instance.register(handler_class)
126
+ end
127
+
128
+ def register(handler_class)
129
+ register_message_handler(handler_class)
130
+ end
131
+
132
+ def self.publish(message_instance)
133
+ instance.publish(message_instance)
134
+ end
135
+
136
+ def publish(message_instance)
137
+ bus.publish(message_instance)
138
+ end
139
+ private
140
+ def register_message_handler(handler_class)
141
+ handler_class.handle_messages.each do |message_class|
142
+ bus.subscribe(message_class, handler_class)
143
+ end
144
+ end
145
+ end
146
+
147
+ module AmqpMessage
148
+
149
+ module AmqpMessageClass
150
+ def self.extended(base)
151
+ class << base; attr_accessor :class_message_key; end
152
+ end
153
+
154
+ def message_key(key = nil)
155
+ if (key)
156
+ self.class_message_key = key
157
+ else
158
+ self.class_message_key
159
+ end
160
+ end
161
+
162
+ def unmarshall(message_json)
163
+ hash = JSON.parse(message_json)
164
+ self.new(hash)
165
+ end
166
+
167
+ end
168
+
169
+ def self.included(base)
170
+ base.send :extend, AmqpMessageClass
171
+ end
172
+
173
+ def initialize(hash={})
174
+ hash.each { |key, value| self.send("#{MessageHandler::Helper.underscore(key)}=", value) }
175
+ end
176
+
177
+ def marshall
178
+ JSON.generate(self)
179
+ end
180
+ end
181
+
182
+ module AmqpMessageHandler
183
+ module AmqpMessageHandlerClass
184
+
185
+ def self.extended(base)
186
+ class << base
187
+ attr_accessor :message_class_by_key
188
+ alias_method :message_handler_without_class_by_key, :message_handler unless method_defined?(:message_handler_without_class_by_key)
189
+ alias_method :message_handler, :message_handler_with_class_by_key
190
+ end
191
+ base.message_class_by_key = {}
192
+ end
193
+
194
+ def message_handler_with_class_by_key(message_class, id = :id)
195
+ message_class_by_key.merge!({message_class.message_key => message_class})
196
+ message_handler_without_class_by_key(message_class, id)
197
+ end
198
+
199
+ end
200
+
201
+ def self.included(base)
202
+ base.send :include, MessageHandler
203
+ base.send :extend, AmqpMessageHandlerClass
204
+ end
205
+
206
+ end
207
+
208
+ class AmqpBus
209
+ attr_accessor :queue_prefix
210
+ attr_accessor :exchange
211
+
212
+ def initialize(hash = {})
213
+ self.queue_prefix = hash[:queue_prefix]
214
+ self.exchange = hash[:exchange]
215
+ end
216
+
217
+ def subscribe(message_class, handler_class)
218
+ EM.next_tick do
219
+ AmqpNotifier.new(exchange, queue_prefix + MessageHandler::Helper.underscore(handler_class.name).subscribe(message_class.message_key)) do |info, payload|
220
+ #all message with the same handler goes here to try to maintain the order in the same queue (not ensured)
221
+ message_class_tmp = handler_class.message_class_by_key[info.routing_key]
222
+ handler_class.handle(message_class_tmp.unmarshall(payload))
223
+ end
224
+ end
225
+ end
226
+
227
+ def publish(message)
228
+ AmqpNotifier.new(exchange).publish(message.class.message_key, message.marshall)
229
+ end
230
+ end
231
+ end
@@ -1,3 +1,3 @@
1
1
  module Lolitra
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/lolitra.rb CHANGED
@@ -1,5 +1,2 @@
1
1
  require "lolitra/version"
2
-
3
- module Lolitra
4
- # Your code goes here...
5
- end
2
+ require "lolitra/handler_base"
data/lolitra.gemspec CHANGED
@@ -14,4 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "lolitra"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Lolitra::VERSION
17
+
18
+ gem.add_development_dependency("rspec")
17
19
  end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper.rb'
2
+
3
+ class TestMessage
4
+ include Lolitra::AmqpMessage
5
+
6
+ message_key "test1"
7
+
8
+ def id
9
+ 1
10
+ end
11
+ end
12
+
13
+ class TestMessage1
14
+ include Lolitra::AmqpMessage
15
+
16
+ message_key "test2"
17
+
18
+ def id
19
+ 2
20
+ end
21
+ end
22
+
23
+ class TestMessageHandler
24
+ include Lolitra::MessageHandler
25
+
26
+ started_by TestMessage
27
+ message_handler TestMessage
28
+ stateful true
29
+
30
+ def self.find_by_id(id)
31
+ nil
32
+ end
33
+
34
+ def test_message(message)
35
+ "handled"
36
+ end
37
+ end
38
+
39
+ class TestMessageHandler1
40
+ include Lolitra::MessageHandler
41
+
42
+ started_by TestMessage1
43
+ message_handler TestMessage
44
+ message_handler TestMessage1
45
+
46
+ stateful true
47
+
48
+ def self.find_by_id(id)
49
+ nil
50
+ end
51
+
52
+ def test_message(message)
53
+ "handled"
54
+ end
55
+
56
+ def test_message1(message)
57
+ "handled1"
58
+ end
59
+
60
+ end
61
+
62
+ class TestAmqpMessageHandler
63
+ include Lolitra::AmqpMessageHandler
64
+
65
+ message_handler TestMessage
66
+ message_handler TestMessage1
67
+
68
+ stateful false
69
+ end
70
+
71
+ describe Lolitra::MessageHandler,'#create_handler' do
72
+ it "returns old instance of the handler for the same message id" do
73
+ handler = TestMessageHandler.new
74
+ TestMessageHandler.should_receive(:new).exactly(1).and_return(handler)
75
+ TestMessageHandler.handle(TestMessage.new)
76
+ TestMessageHandler.should_receive(:find_by_id).at_least(:once).and_return(handler)
77
+ TestMessageHandler.handle(TestMessage.new)
78
+ TestMessageHandler.handle(TestMessage.new)
79
+ end
80
+
81
+ it "create a new handler if the message starter has different id" do
82
+ handler1 = TestMessageHandler.new
83
+ message1 = TestMessage.new
84
+ message2 = TestMessage.new
85
+ message2.stub(:id => 2)
86
+
87
+ TestMessageHandler.should_receive(:new).exactly(2).and_return(handler1)
88
+ TestMessageHandler.handle(message1)
89
+ TestMessageHandler.handle(message2)
90
+ end
91
+ end
92
+
93
+ describe Lolitra::MessageHandler, '#handle_message' do
94
+ it "handle message when message arrives" do
95
+ message_handler = TestMessageHandler1.new
96
+ TestMessageHandler1.stub(:find_by_id).and_return(nil, message_handler)
97
+ TestMessageHandler1.handle(TestMessage1.new).should eq "handled1"
98
+ TestMessageHandler1.handle(TestMessage.new).should eq "handled"
99
+ end
100
+ end
101
+
102
+ describe Lolitra::MessageHandler, "#handle_message" do
103
+ it "handle non starter message thow execption" do
104
+ expect { TestMessageHandler1.handle(TestMessage.new) }.to raise_error(Lolitra::MessageHandler::NoHandlerMessageException)
105
+ end
106
+ end
107
+
108
+ describe Lolitra::MessageHandler, '#publish' do
109
+ it "can send message to the bus" do
110
+ message = TestMessage.new
111
+ bus = TestBus.new
112
+ Lolitra::MessageHandlerManager.bus = bus
113
+ Lolitra::MessageHandlerManager.register(TestMessageHandler)
114
+
115
+
116
+ TestMessageHandler.should_receive(:handle).with(message)
117
+ TestMessageHandler.publish(message)
118
+ end
119
+ end
120
+
121
+ describe Lolitra::MessageHandlerManager, '#publish' do
122
+ it "can send message to the bus" do
123
+ bus = TestBus.new
124
+ Lolitra::MessageHandlerManager.bus = bus
125
+ Lolitra::MessageHandlerManager.register(TestMessageHandler)
126
+
127
+ message = TestMessage.new
128
+
129
+ TestMessageHandler.should_receive(:handle).with(message)
130
+
131
+ Lolitra::MessageHandlerManager.publish(message)
132
+ end
133
+ end
134
+
135
+ describe Lolitra::MessageHandlerManager, '#handle_message' do
136
+ it "handle message with the correct handler" do
137
+
138
+ bus = TestBus.new
139
+
140
+ Lolitra::MessageHandlerManager.bus = bus
141
+
142
+ Lolitra::MessageHandlerManager.register(TestMessageHandler)
143
+ Lolitra::MessageHandlerManager.register(TestMessageHandler1)
144
+
145
+ message = TestMessage.new
146
+ message1 = TestMessage1.new
147
+
148
+ TestMessageHandler.should_receive(:handle).with(message)
149
+ TestMessageHandler.should_not_receive(:handle).with(message1)
150
+
151
+ TestMessageHandler1.should_receive(:handle).with(message)
152
+ TestMessageHandler1.should_receive(:handle).with(message1)
153
+
154
+ Lolitra::MessageHandlerManager.publish(message)
155
+ Lolitra::MessageHandlerManager.publish(message1)
156
+ end
157
+ end
158
+
159
+ describe Lolitra::AmqpMessage, '#message_key' do
160
+ it "message_key has constant key for a class" do
161
+ TestMessage.message_key.should eq "test1"
162
+ TestMessage1.message_key.should eq "test2"
163
+ TestMessage.message_key.should eq "test1"
164
+ end
165
+ end
166
+
167
+ describe Lolitra::AmqpMessageHandler, '#message_class_by_key' do
168
+ it "should return the message_class that belongs to key" do
169
+ TestAmqpMessageHandler.message_class_by_key[TestMessage.message_key].name.should eq "TestMessage"
170
+ TestAmqpMessageHandler.message_class_by_key[TestMessage1.message_key].name.should eq "TestMessage1"
171
+ end
172
+ end
173
+
174
+
175
+ #TODO
176
+ #add test to Lolitra::AmqpMessageHandler and AmqpBus with evented-spec
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require 'bundler'
4
+ Bundler.setup(:default, :test)
5
+
6
+ require "lolitra"
7
+
8
+ class TestBus
9
+ def initialize
10
+ @handlers = {}
11
+ @unmarshallers = {}
12
+ end
13
+
14
+ def publish_directly(message_key, message_payload)
15
+ publish(@unmarshallers[message_key].unmarshall(message_payload))
16
+ end
17
+
18
+ def publish(message)
19
+ @handlers[message.class.name].each do |handler|
20
+ handler.handle(message)
21
+ end
22
+ end
23
+
24
+ def subscribe(message_class, handler_class)
25
+ @unmarshallers[message_class.message_key] = message_class
26
+ @handlers[message_class.name] ||= []
27
+ @handlers[message_class.name] << handler_class
28
+ end
29
+ end
30
+
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Hugo Freire
@@ -14,10 +14,22 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-09-06 00:00:00 +02:00
17
+ date: 2012-09-19 00:00:00 +02:00
18
18
  default_executable:
19
- dependencies: []
20
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
21
33
  description: Lolitra, build Sagas, a kind of Long Live Transaction (LLT), in less lines
22
34
  email:
23
35
  - hfreire@abajar.com
@@ -29,13 +41,17 @@ extra_rdoc_files: []
29
41
 
30
42
  files:
31
43
  - .gitignore
44
+ - .rspec
32
45
  - Gemfile
33
46
  - LICENSE
34
47
  - README.md
35
48
  - Rakefile
36
49
  - lib/lolitra.rb
50
+ - lib/lolitra/handler_base.rb
37
51
  - lib/lolitra/version.rb
38
52
  - lolitra.gemspec
53
+ - spec/lolitra_spec.rb
54
+ - spec/spec_helper.rb
39
55
  has_rdoc: true
40
56
  homepage: ""
41
57
  licenses: []
@@ -68,5 +84,6 @@ rubygems_version: 1.3.7
68
84
  signing_key:
69
85
  specification_version: 3
70
86
  summary: Lolitra, build Sagas, a kind of Long Live Transaction (LLT), in less lines
71
- test_files: []
72
-
87
+ test_files:
88
+ - spec/lolitra_spec.rb
89
+ - spec/spec_helper.rb