hotot 0.0.0

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,20 @@
1
+ module Hotot
2
+ module ClassAttributeAccessors
3
+
4
+ def cattr_reader(*syms)
5
+ syms.each do |sym|
6
+ raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
7
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
8
+ unless defined? @@#{sym}
9
+ @@#{sym} = nil
10
+ end
11
+
12
+ def self.#{sym}
13
+ @@#{sym}
14
+ end
15
+ EOS
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module Hotot
5
+ module Configurable
6
+
7
+ private
8
+
9
+ def load_config(config_file = nil)
10
+ if config_file && config_file.file?
11
+ YAML.load(ERB.new(config_file.read).result)
12
+ elsif defined?(::Rails)
13
+ config_file = ::Rails.root.join('config/hotot.yml')
14
+ if config_file.file?
15
+ YAML.load(ERB.new(config_file.read).result)
16
+ else
17
+ nil
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ require 'time'
2
+ require 'json' unless defined?(::Rails)
3
+
4
+ module Hotot
5
+ module Message
6
+ class Base
7
+
8
+ # Use rails default json if it exists
9
+ if defined?(::Rails)
10
+ include ActiveModel::Serializers::JSON
11
+ else
12
+ define_method :to_json do
13
+ self.as_json.to_json
14
+ end
15
+ end
16
+
17
+ attr_reader :metadata
18
+
19
+ def initialize(routing_key=nil)
20
+ raise StandardError, "routing_key cannot be nil" if routing_key.nil?
21
+ @metadata = {:host => Socket.gethostbyname(Socket.gethostname).first,
22
+ :app => Hotot.app_name, # configure this...
23
+ :key => routing_key,
24
+ :created => DateTime.now.new_offset(0).to_time.utc.iso8601}
25
+ end
26
+
27
+ def routing_key
28
+ @metadata[:key]
29
+ end
30
+
31
+ def as_json(options={})
32
+ hash = {:metadata => {:host => @metadata[:host],
33
+ :app => @metadata[:app],
34
+ :key => @metadata[:key],
35
+ :created => @metadata[:created]}}
36
+ hash
37
+ end
38
+
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ module Hotot
2
+ module MessageProducer
3
+ # Produces a message to the underlying exchange using an synchronous connection
4
+ def produce_synchronously(message)
5
+ SynchronousConnection.produce(message)
6
+ end
7
+ alias :produce :produce_synchronously
8
+ end
9
+ end
@@ -0,0 +1,106 @@
1
+ require 'bunny'
2
+ require_relative 'configurable'
3
+ require_relative 'class_attribute_accessors'
4
+
5
+ module Hotot
6
+ class SynchronousConnection
7
+ extend Configurable
8
+ extend ClassAttributeAccessors
9
+
10
+ cattr_reader :bunny, :exchange
11
+
12
+ @@setup = false
13
+ @@connected = false
14
+
15
+ def self.setup(config = nil, env = (defined?(::Rails) ? Rails.env : nil) )
16
+ if !self.setup?
17
+ @@config = load_config(config)
18
+ @@env_config = @@config[env]
19
+ raise StandardError, "Env #{env} not found in config" if @@env_config.nil?
20
+
21
+ # symbolize the keys, which Bunny expects
22
+ @@env_config.keys.each {|key| @@env_config[(key.to_sym rescue key) || key] = @@env_config.delete(key) }
23
+ raise StandardError, "'exchange' key not found in config" if !@@env_config.has_key?(:exchange)
24
+
25
+ @@bunny = Bunny.new(@@env_config)
26
+ @@setup = true
27
+ end
28
+ end
29
+
30
+ # Whether the underlying connection has been set up
31
+ def self.setup?
32
+ @@setup
33
+ end
34
+
35
+ def self.subscribe(routing_key, &block)
36
+ q = @@bunny.queue.bind(@@env_config[:exchange], :routing_key => routing_key)
37
+
38
+ q.subscribe do |delivery_info, metadata, payload|
39
+ block.call delivery_info, metadata, payload
40
+ end
41
+
42
+ end
43
+
44
+ # Establish a connection to the underlying exchange
45
+ def self.connect
46
+ raise StandardError, "AMQP not setup. Call setup before calling connect" if !self.setup?
47
+ @@bunny.start
48
+
49
+ # use defualt channel for exchange
50
+ @@exchange = @@bunny.exchange(@@env_config[:exchange], :type => :topic, :durable => true)
51
+ @@connected = true
52
+ end
53
+
54
+ # Disconnect from the underlying exchange
55
+ def self.disconnect
56
+ begin
57
+ @@bunny.stop
58
+ rescue
59
+ # if this is being called because the underlying connection went bad
60
+ # calling stop will raise an error. that's ok....
61
+ ensure
62
+ @@connected = false
63
+ end
64
+ end
65
+
66
+ # Re-connects to the underlying exchange
67
+ def self.reconnect
68
+ self.disconnect
69
+ @@setup = false
70
+ @@bunny = Bunny.new(@@env_config)
71
+ @@setup = true
72
+ self.connect
73
+ end
74
+
75
+ # Whether the underlying connection has been established
76
+ def self.connected?
77
+ @@connected
78
+ end
79
+
80
+ # Produces a message to the underlying exchange
81
+ def self.produce(message)
82
+ if !self.setup? || !self.connected?
83
+ Hotot.logger.error "AMQP not setup or connected. Call setup and connect before calling produce"
84
+ else
85
+ begin
86
+ @@exchange.publish(message.to_json, :routing_key => message.routing_key, :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json")
87
+ rescue Bunny::ServerDownError
88
+ # the connection went south, try to reconnect and try one more time
89
+ begin
90
+ self.reconnect
91
+ @@exchange.publish(message.to_json, :routing_key => message.routing_key, :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json")
92
+ rescue => err
93
+ Hotot.logger.error "Unexpected error producing AMQP messsage: (#{message.to_json})"
94
+ Hotot.logger.error "#{err.message}"
95
+ Hotot.logger.error err.backtrace.join("\n")
96
+ end
97
+ rescue => err
98
+ Hotot.logger.error "Unexpected error producing AMQP messsage: (#{message.to_json})"
99
+ Hotot.logger.error "#{err.message}"
100
+ Hotot.logger.error err.backtrace.join("\n")
101
+ end
102
+ end
103
+ end
104
+
105
+ end #SynchronousConnection
106
+ end #Hotot
@@ -0,0 +1,5 @@
1
+ module Hotot
2
+
3
+ VERSION = '0.0.0'
4
+
5
+ end
data/lib/hotot.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'logger'
2
+
3
+ module Hotot
4
+
5
+ require_relative 'hotot/synchronous_connection'
6
+ require_relative 'hotot/message_producer'
7
+ require_relative 'hotot/message/base'
8
+
9
+ def self.logger=(logger)
10
+ @logger = logger
11
+ end
12
+
13
+ def self.logger
14
+ @logger ||= Logger.new($stdout).tap do |log|
15
+ log.progname = 'Hotot'
16
+ end
17
+ end
18
+
19
+ def self.app_name=(app_name)
20
+ @app_name = app_name
21
+ end
22
+
23
+ def self.app_name
24
+ @app_name ||= "Hotot"
25
+ end
26
+
27
+ end
@@ -0,0 +1,34 @@
1
+ require 'minitest_helper'
2
+
3
+ module MyConfig
4
+ extend Hotot::Configurable
5
+
6
+ def self.setup(config)
7
+ load_config(config)
8
+ end
9
+
10
+ end
11
+
12
+ describe "Hotot::Configurable" do
13
+
14
+ it "Configurable should receive load_config" do
15
+ MyConfig.stub :load_config, true do
16
+ MyConfig.setup "config"
17
+ assert_send [MyConfig, :load_config, nil]
18
+ end
19
+ end
20
+
21
+ before do
22
+ @config_file = Pathname.new File.expand_path("../../fixtures/hotot.yml", __FILE__)
23
+ @config = MyConfig.setup @config_file
24
+ end
25
+
26
+ it "loads config file" do
27
+ test_config = @config['test']
28
+ test_config['host'].must_equal 'localhost'
29
+ test_config['vhost'].must_equal '/'
30
+ test_config['exchange'].must_equal 'heyook.topic'
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,40 @@
1
+ require 'minitest_helper'
2
+
3
+ describe "Hotot::Message::Base" do
4
+
5
+ describe "default base" do
6
+
7
+ before do
8
+ @base = Hotot::Message::Base.new("rkey")
9
+ @hash = @base.as_json
10
+ end
11
+
12
+ it "sets metadata" do
13
+ metadata = @hash[:metadata]
14
+ metadata[:app].must_equal "Hotot"
15
+ metadata[:key].must_equal "rkey"
16
+ metadata[:host].wont_be :empty?
17
+ metadata[:created].wont_be :empty?
18
+ end
19
+
20
+ it "respond to to_json" do
21
+ assert_respond_to @base, :to_json
22
+ end
23
+
24
+ it "converts to json" do
25
+ json = @base.to_json
26
+ metadata = (JSON.parse json)['metadata']
27
+
28
+ metadata['app'].must_equal "Hotot"
29
+ metadata['key'].must_equal "rkey"
30
+ metadata['host'].wont_be :empty?
31
+ metadata['created'].wont_be :empty?
32
+ end
33
+
34
+ it "gets routing_key" do
35
+ @base.routing_key.must_equal "rkey"
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,20 @@
1
+ require 'minitest_helper'
2
+
3
+ class MyProducer
4
+ include Hotot::MessageProducer
5
+ end
6
+
7
+ describe "Hotot::MessageProducer" do
8
+
9
+ before do
10
+ @mp = MyProducer.new
11
+ end
12
+
13
+ it "SynchronousConnection should receive produce" do
14
+ Hotot::SynchronousConnection.stub :produce, true do
15
+ @mp.produce "hello rabbit"
16
+ assert_send [Hotot::SynchronousConnection, :produce, "hello rabbit"]
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,75 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestMessage < Hotot::Message::Base
4
+ ROUTING_KEY = "heyook.message.test"
5
+ def initialize
6
+ super ROUTING_KEY
7
+ end
8
+ end
9
+
10
+ describe "Hotot::SynchronousConnection" do
11
+
12
+ before do
13
+ @config_file = Pathname.new File.expand_path("../../fixtures/hotot.yml", __FILE__)
14
+ @bunny = Minitest::Mock.new
15
+ @exchange = Minitest::Mock.new
16
+ end
17
+
18
+ it "raise error if not setup yet" do
19
+ assert_raises(StandardError) { Hotot::SynchronousConnection.connect }
20
+ end
21
+
22
+ describe "setup" do
23
+
24
+ it "setup Hotot::SynchronousConnection" do
25
+ Hotot::SynchronousConnection.setup @config_file, 'test'
26
+ Hotot::SynchronousConnection.setup?.must_equal true
27
+ end
28
+
29
+ end
30
+
31
+ describe "connect" do
32
+
33
+ before do
34
+ Hotot::SynchronousConnection.setup @config_file, 'test'
35
+ @bunny = Hotot::SynchronousConnection.bunny
36
+ end
37
+
38
+ after do
39
+ Hotot::SynchronousConnection.disconnect
40
+ end
41
+
42
+ it "calls start and exchange" do
43
+ Hotot::SynchronousConnection.connect
44
+ Hotot::SynchronousConnection.connected?.must_equal true
45
+ assert_send [@bunny, :start]
46
+ assert_send [@bunny, :exchange, "heyook.topic", :type => :topic, :durable => true]
47
+ end
48
+
49
+ describe "publish" do
50
+
51
+ it "let exchange to publish" do
52
+ Hotot::SynchronousConnection.connect
53
+ @exchange = Hotot::SynchronousConnection.exchange
54
+
55
+ message = TestMessage.new
56
+ Hotot::SynchronousConnection.produce message
57
+ assert_send [@exchange, :publish, message.to_json, :routing_key => "heyook.message.test", :mandatory => false, :immediate => false, :persistent => true, :content_type => "application/json"]
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ describe "disconnect" do
65
+
66
+ it "disconnects and set connected to false" do
67
+ Bunny.stub :new, @bunny do
68
+ Hotot::SynchronousConnection.disconnect
69
+ Hotot::SynchronousConnection.connected?.must_equal false
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,37 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Hotot do
4
+
5
+ describe "default settings" do
6
+
7
+ before do
8
+ Hotot.logger = nil
9
+ Hotot.app_name = nil
10
+ end
11
+
12
+ it "uses default logger" do
13
+ assert_kind_of Logger, Hotot.logger
14
+ end
15
+
16
+ it "uses default app name" do
17
+ Hotot.app_name.must_equal "Hotot"
18
+ end
19
+
20
+ end
21
+
22
+ describe "get/set settings" do
23
+
24
+ it "sets logger" do
25
+ logger_klass = Object.const_set("MyLogger", Class.new )
26
+ Hotot.logger = logger_klass.new
27
+ assert_kind_of logger_klass, Hotot.logger
28
+ end
29
+
30
+ it "sets app name" do
31
+ Hotot.app_name = "New Rabbit"
32
+ Hotot.app_name.must_equal "New Rabbit"
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require "minitest/autorun"
4
+ require "hotot"
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Qi He
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bunny
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ description: ! ' Hotot is a kind of rabbit that wraps bunny gem.
31
+
32
+ '
33
+ email: qihe229@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - lib/hotot/class_attribute_accessors.rb
39
+ - lib/hotot/configurable.rb
40
+ - lib/hotot/message/base.rb
41
+ - lib/hotot/message_producer.rb
42
+ - lib/hotot/synchronous_connection.rb
43
+ - lib/hotot/version.rb
44
+ - lib/hotot.rb
45
+ - spec/hotot/configurable_spec.rb
46
+ - spec/hotot/message/base_spec.rb
47
+ - spec/hotot/message_producer_spec.rb
48
+ - spec/hotot/synchronous_connection_spec.rb
49
+ - spec/hotot_spec.rb
50
+ - spec/minitest_helper.rb
51
+ homepage: http://github.com/he9qi/hotot
52
+ licenses:
53
+ - MIT
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.23
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A bunny wrapper.
76
+ test_files:
77
+ - spec/hotot/configurable_spec.rb
78
+ - spec/hotot/message/base_spec.rb
79
+ - spec/hotot/message_producer_spec.rb
80
+ - spec/hotot/synchronous_connection_spec.rb
81
+ - spec/hotot_spec.rb
82
+ - spec/minitest_helper.rb