lolitra 0.0.1 → 0.0.2

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.
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