simple_publisher 0.1.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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Dirk Breuer/Galaxy Cats
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,91 @@
1
+ # simple_publisher
2
+
3
+ Defines a simple pub/sub layer for interaction with the Starling message queue.
4
+
5
+ ## The Idea
6
+
7
+ More often than not you don't need a full fledged message queue like ActiveMQ
8
+ or RabbitMQ with a full size message format. For simple asynchronous
9
+ distribution tasks a very light weight persistent message queue like starling
10
+ will be fine. Nevertheless you want a more sophisticated interface to interact
11
+ with than a memcache interface. Simple Publisher tries to achieve exactly
12
+ this: While using Starling as the queuing system in the background we provide
13
+ a messaging terminology well know and understood. The terminology is strongly
14
+ inspired by JMS which is kind a defacto standard for messaging on the
15
+ API-level.
16
+
17
+ ## Example
18
+
19
+ The Simple Publisher gem only helps you on the publisher site of message based
20
+ connection (as the name already suggests). For the other side, the subscriber
21
+ you need to roll your own solution. But fortunately there are aleady a lot of
22
+ options you could use. I would strongly recommend to have a look at the
23
+ [Workling](http://github.com/purzelrakete/workling "purzelrakete's workling at
24
+ master - GitHub") plugin. Originally designed to realize easy background
25
+ process integration into any Rails app, we could simply facilitate it to
26
+ receive messages published by a different app. The following examples shows
27
+ what you have to do on both the publisher and subscriber site.
28
+
29
+ First the publisher:
30
+
31
+ # Let's assume we build a distributed mailing service which will read abstract
32
+ # email-instances from a queue and deliver them asynchronously
33
+
34
+ # First: Define your Connection and Topic instances ...
35
+ topic = SimplePublisher::Topic.new(:name => "mail_service_subscribers__deliver_mail") # As you will see, this maps to Worklings naming scheme
36
+ connection = SimplePublisher::StarlingConnection.new(:host => "127.0.0.1", :port => "22122") # this Starlings default port
37
+
38
+ # ... and now get a Publisher instance
39
+ publisher = SimplePublisher::Publisher.new(
40
+ :topic => topic,
41
+ :connection => connection
42
+ )
43
+
44
+ # Now lets create our email object we want to deliver asynchronously
45
+ email = Email.new # this could be any kind of object, usually a simple type.
46
+ # If you want to send complex objects over a message queue,
47
+ # make sure the other side nows about those objects.
48
+ publisher.publish(email) # ... and publish it to starling
49
+
50
+ And here comes the subscriber, which is just another Rails-App:
51
+
52
+ # Inside our receiving Rails-App with the Workling plugin enabled we create a
53
+ # Workling class
54
+ class MailServiceSubscriber < Workling::Base
55
+
56
+ def deliver_mail(email)
57
+ begin
58
+ Notifier.send("deliver_#{email[:name_of_mail]}", email)
59
+ # Here we just call anything that will actually send the email out. The
60
+ # email object is the same we published on the other side.
61
+ rescue Exception => e
62
+ # it is usually a good idea to log any errors ...
63
+ Rails.logger.error "An Error occured: #{e.message} --- BACKTRACE:\n\t#{e.backtrace.join("\n\t")}"
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ Due to the fact, that Workling will listen on a topic that is dynamically
70
+ created out of the Worklings class name and the single method inside it, we end
71
+ up with a topic name like `mail_service_subscribers__deliver_mail` which refers
72
+ to the Workling class `MailServiceSubscriber` and the method `deliver_mail`.
73
+ You cannot configure the name of the topic using Workling but it is not a major
74
+ drawback. You just have to now the convention.
75
+
76
+ Of course you have to start both the Starling system process and the Workling-Client
77
+ instances. They are located in the `scripts` folder of your Rails-App.
78
+
79
+ ## Note on Patches/Pull Requests
80
+
81
+ * Fork the project.
82
+ * Make your feature addition or bug fix.
83
+ * Add tests for it. This is important so I don't break it in a
84
+ future version unintentionally.
85
+ * Commit, do not mess with rakefile, version, or history.
86
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
87
+ * Send me a pull request. Bonus points for topic branches.
88
+
89
+ ## Copyright
90
+
91
+ Copyright (c) 2010 Dirk Breuer. See LICENSE for details.
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "simple_publisher"
8
+ gem.summary = %Q{Defines a simple pub/sub layer for interaction with the Starling message queue.}
9
+ gem.description = <<-DESC
10
+ More often than not you don't need a full fledged message queue like ActiveMQ
11
+ or RabbitMQ with a full size message format. For simple asynchronous distribution
12
+ tasks a very light weight persistent message queue like starling will be fine.
13
+ Nevertheless you want a more sophisticated interface to interact with than a
14
+ memcache interface. Simple Publisher tries to achieve exactly this: While using
15
+ Starling as the queuing system in the background we provide a messaging terminology
16
+ well know and understood. The terminology is strongly inspired by JMS which is
17
+ kind a defacto standard for messaging on the API-level.
18
+ DESC
19
+ gem.email = "dirk@galaxycats.com"
20
+ gem.homepage = "http://github.com/galaxycats/simple_publisher"
21
+ gem.authors = ["Dirk Breuer"]
22
+
23
+ gem.add_dependency "starling"
24
+ gem.add_dependency "system_timer"
25
+
26
+ gem.add_development_dependency "rspec", ">= 1.2.9"
27
+ end
28
+ Jeweler::GemcutterTasks.new
29
+ rescue LoadError
30
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
31
+ end
32
+
33
+ require 'spec/rake/spectask'
34
+ Spec::Rake::SpecTask.new(:spec) do |spec|
35
+ spec.libs << 'lib' << 'spec'
36
+ spec.spec_files = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
40
+ spec.libs << 'lib' << 'spec'
41
+ spec.pattern = 'spec/**/*_spec.rb'
42
+ spec.rcov = true
43
+ end
44
+
45
+ task :spec => :check_dependencies
46
+
47
+ task :default => :spec
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "simple_publisher #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,4 @@
1
+ require 'simple_publisher/starling_connection'
2
+ require 'simple_publisher/publisher'
3
+ require 'simple_publisher/message'
4
+ require 'simple_publisher/topic'
@@ -0,0 +1,20 @@
1
+ require 'digest/sha1'
2
+
3
+ module SimplePublisher
4
+ class Message
5
+ attr_reader :body, :uid
6
+
7
+ def initialize(message_body)
8
+ @body = message_body
9
+ generate_uid!
10
+ end
11
+
12
+ private
13
+
14
+ def generate_uid!
15
+ # This is taken from the workling gem
16
+ @uid = ::Digest::SHA1.hexdigest("#{self}:#{@body}:#{ rand(1 << 64) }:#{ Time.now }")
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module SimplePublisher
2
+ class Publisher
3
+ attr_reader :topic, :connection
4
+
5
+ def initialize(attributes = {})
6
+ unless attributes.keys.include?(:topic) and attributes.keys.include?(:connection)
7
+ raise ArgumentError, "You must specify a connection and a topic"
8
+ end
9
+
10
+ attributes.each do |attr, value|
11
+ send("#{attr}=", value)
12
+ end
13
+ end
14
+
15
+ def publish(raw_message)
16
+ message = Message.new(raw_message)
17
+ connection.write(:message => message, :to => topic)
18
+ end
19
+
20
+ private
21
+
22
+ attr_writer :topic, :connection
23
+
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'starling'
2
+
3
+ module SimplePublisher
4
+ class StarlingConnection
5
+
6
+ attr_reader :starling_url
7
+
8
+ def initialize(attributes = {})
9
+ @starling_url = "#{attributes[:host]}:#{attributes[:port]}"
10
+ end
11
+
12
+ def write(options)
13
+ starling.set(options[:to].name, options[:message].body)
14
+ end
15
+
16
+ private
17
+
18
+ def starling
19
+ @starling ||= Starling.new(starling_url)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module SimplePublisher
2
+ class Topic
3
+ attr_accessor :name
4
+
5
+ def initialize(attributes = {})
6
+ attributes.each do |attr, value|
7
+ send("#{attr}=", value)
8
+ end
9
+ end
10
+
11
+ def ==(other)
12
+ case other
13
+ when Topic
14
+ other.name == self.name
15
+ else
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module SimplePublisher
4
+ describe "Message" do
5
+
6
+ it "should encapsulate any raw message in the class and generate a UID for itself" do
7
+ raw_message_content = "some raw message content"
8
+ message = Message.new(raw_message_content)
9
+ message.uid.should_not be(nil)
10
+ message.body.should == raw_message_content
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module SimplePublisher
4
+
5
+ describe "Publisher" do
6
+
7
+ before :each do
8
+ @topic = Topic.new(:name => "some_topic")
9
+ @connection = mock("ConnectionMock")
10
+ end
11
+
12
+ it "should initialize with topic and a connection" do
13
+ publisher = Publisher.new(:topic => @topic, :connection => @connection)
14
+ publisher.topic.should == @topic
15
+ publisher.connection.should == @connection
16
+ end
17
+
18
+ it "should require a topic and a connection on creation" do
19
+ lambda { Publisher.new(:topic => @topic) }.should raise_error(ArgumentError)
20
+ lambda { Publisher.new(:connection => @connection) }.should raise_error(ArgumentError)
21
+ end
22
+
23
+ it "should not allow to change the connection later on" do
24
+ publisher = Publisher.new(:topic => @topic, :connection => @connection)
25
+ lambda { publisher.connection = mock("AnotherConnection") }.should raise_error(NoMethodError)
26
+ end
27
+
28
+ it "should not allow to change the topic later on" do
29
+ publisher = Publisher.new(:topic => @topic, :connection => @connection)
30
+ lambda { publisher.topic = mock("another_topic") }.should raise_error(NoMethodError)
31
+ end
32
+
33
+ it "should publish a message via the connection" do
34
+ publisher = Publisher.new(:topic => @topic, :connection => @connection)
35
+
36
+ Message.should_receive(:new).with("message").and_return(message = mock("Message"))
37
+ @connection.should_receive(:write).with(:message => message, :to => @topic)
38
+
39
+ publisher.publish("message")
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'simple_publisher'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module SimplePublisher
4
+ describe "StarlingAdapter" do
5
+
6
+ it "should take a message and topic information and write them to a starling queue" do
7
+ starling_connection = StarlingConnection.new(:host => "127.0.0.1", :port => "22122")
8
+
9
+ message = Message.new("some message content")
10
+ topic = Topic.new(:name => "my_topic")
11
+
12
+ Starling.should_receive(:new).with("127.0.0.1:22122").and_return(starling_instance = mock("Starling"))
13
+ starling_instance.should_receive(:set).with("my_topic", "some message content")
14
+
15
+ starling_connection.write(:message => message, :to => topic)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module SimplePublisher
4
+ describe "Topic" do
5
+
6
+ it "should have a name" do
7
+ topic = Topic.new(:name => "my_topic")
8
+ topic.name.should == "my_topic"
9
+ end
10
+
11
+ it "should fail if wrong attributes are a assigned through constructor" do
12
+ lambda { Topic.new(:bad_attribute => true) }.should raise_error(NoMethodError)
13
+ end
14
+
15
+ it "should check object equality based on the topic name" do
16
+ topic_a = Topic.new(:name => "my_topic")
17
+ topic_b = Topic.new(:name => "my_topic")
18
+ topic_a.should == topic_b
19
+
20
+ topic_a = Topic.new(:name => "my_topic")
21
+ topic_b = Topic.new(:name => "your_topic")
22
+ topic_a.should_not == topic_b
23
+ end
24
+
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_publisher
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Dirk Breuer
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-26 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: starling
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: system_timer
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: rspec
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 1
53
+ - 2
54
+ - 9
55
+ version: 1.2.9
56
+ type: :development
57
+ version_requirements: *id003
58
+ description: |
59
+ More often than not you don't need a full fledged message queue like ActiveMQ
60
+ or RabbitMQ with a full size message format. For simple asynchronous distribution
61
+ tasks a very light weight persistent message queue like starling will be fine.
62
+ Nevertheless you want a more sophisticated interface to interact with than a
63
+ memcache interface. Simple Publisher tries to achieve exactly this: While using
64
+ Starling as the queuing system in the background we provide a messaging terminology
65
+ well know and understood. The terminology is strongly inspired by JMS which is
66
+ kind a defacto standard for messaging on the API-level.
67
+
68
+ email: dirk@galaxycats.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - LICENSE
75
+ - README.mdown
76
+ files:
77
+ - .document
78
+ - .gitignore
79
+ - LICENSE
80
+ - README.mdown
81
+ - Rakefile
82
+ - VERSION
83
+ - lib/simple_publisher.rb
84
+ - lib/simple_publisher/message.rb
85
+ - lib/simple_publisher/publisher.rb
86
+ - lib/simple_publisher/starling_connection.rb
87
+ - lib/simple_publisher/topic.rb
88
+ - spec/message_spec.rb
89
+ - spec/publisher_spec.rb
90
+ - spec/spec.opts
91
+ - spec/spec_helper.rb
92
+ - spec/starling_connection_spec.rb
93
+ - spec/topic_spec.rb
94
+ has_rdoc: true
95
+ homepage: http://github.com/galaxycats/simple_publisher
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options:
100
+ - --charset=UTF-8
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.3.6
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Defines a simple pub/sub layer for interaction with the Starling message queue.
124
+ test_files:
125
+ - spec/message_spec.rb
126
+ - spec/publisher_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/starling_connection_spec.rb
129
+ - spec/topic_spec.rb