vuderacha-orq 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.
- data/README.rdoc +43 -0
- data/Rakefile +67 -0
- data/lib/orq/adapters/amqp_adapter.rb +33 -0
- data/lib/orq/impulse.rb +115 -0
- data/spec/impulse_spec.rb +124 -0
- data/spec/simple_config.yml +1 -0
- data/spec/stub_config.yml +3 -0
- data/spec/support/orq/adapters/stub_adapter.rb +9 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= Introduction
|
2
|
+
ORQ (Object Relational Queueing) provides an abstraction for working with message queues directly
|
3
|
+
from your objects.
|
4
|
+
|
5
|
+
= Getting Started
|
6
|
+
Install ORQ with the following:
|
7
|
+
git clone git://github.com/vuderacha/orq.git
|
8
|
+
cd orq
|
9
|
+
rake install
|
10
|
+
|
11
|
+
Create a simple impulse:
|
12
|
+
# mymessage.rb
|
13
|
+
class MyMessage < ORQ::Impulse
|
14
|
+
field :text
|
15
|
+
field :interesting
|
16
|
+
end
|
17
|
+
|
18
|
+
Create your configuration file (assuming that you are running an AMQP broker on the default port):
|
19
|
+
# messaging.yml
|
20
|
+
development:
|
21
|
+
default:
|
22
|
+
driver: amqp
|
23
|
+
|
24
|
+
Create your application:
|
25
|
+
# testapp.rb
|
26
|
+
require 'rubygems'
|
27
|
+
require 'eventmachine'
|
28
|
+
require 'orq/impulse'
|
29
|
+
require 'mymessage'
|
30
|
+
|
31
|
+
ORQ::Impulse.configure File.join(File.dirname(__FILE__), 'messaging.yml'), 'development'
|
32
|
+
|
33
|
+
EM.run {
|
34
|
+
ORQ::Impulse.start
|
35
|
+
|
36
|
+
MyMessage.subscribe do |m|
|
37
|
+
puts "Got message: #{m.text} (#{m.interesting})"
|
38
|
+
end
|
39
|
+
|
40
|
+
EM.add_periodic_timer(1) do
|
41
|
+
MyMessage.new({:text => 'Text', :interesting => 'Cheese'}).fire!
|
42
|
+
end
|
43
|
+
}
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run all specs"
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.spec_opts = ["-cfs"]
|
7
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
8
|
+
t.libs = ['lib', 'spec/support']
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Print specdocs"
|
12
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
13
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
14
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Generate RCov code coverage report"
|
18
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
19
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
20
|
+
t.rcov = true
|
21
|
+
t.rcov_opts = ['--exclude', 'examples']
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :spec
|
25
|
+
|
26
|
+
######################################################
|
27
|
+
|
28
|
+
require 'rake'
|
29
|
+
require 'rake/testtask'
|
30
|
+
require 'rake/clean'
|
31
|
+
require 'rake/gempackagetask'
|
32
|
+
require 'rake/rdoctask'
|
33
|
+
require 'fileutils'
|
34
|
+
include FileUtils
|
35
|
+
# $:.push File.join(File.dirname(__FILE__), 'lib')
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'jeweler'
|
39
|
+
Jeweler::Tasks.new do |s|
|
40
|
+
s.name = "orq"
|
41
|
+
s.summary = "Ruby Object Relational Queueing."
|
42
|
+
s.description = "ORQ is a queueing abstraction layer allowing messaging to be done directly from object."
|
43
|
+
s.author = "Paul Jones"
|
44
|
+
s.email = "pauljones23@gmail.com"
|
45
|
+
s.homepage = "http://github.com/vuderacha/orq/"
|
46
|
+
|
47
|
+
s.files = %w(Rakefile README.rdoc) + Dir.glob("{bin,lib,spec}/**/*")
|
48
|
+
s.require_path = "lib"
|
49
|
+
s.add_dependency('activesupport', '>= 2.2.2')
|
50
|
+
end
|
51
|
+
rescue LoadError
|
52
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
53
|
+
end
|
54
|
+
|
55
|
+
task :test => [ :spec ]
|
56
|
+
|
57
|
+
Rake::RDocTask.new do |t|
|
58
|
+
t.rdoc_dir = 'rdoc'
|
59
|
+
t.title = "ORQ -- Object Relational Queueing"
|
60
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
61
|
+
t.options << '--charset' << 'utf-8'
|
62
|
+
t.rdoc_files.include('README.rdoc')
|
63
|
+
t.rdoc_files.include('lib/orq/*.rb')
|
64
|
+
end
|
65
|
+
|
66
|
+
CLEAN.include [ 'build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', 'pkg', 'lib/*.bundle', '*.gem', '.config' ]
|
67
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'mq'
|
2
|
+
|
3
|
+
# Adapter providing access to AMQP based message systems from ORQ
|
4
|
+
class AmqpAdapter
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
# Do connection
|
11
|
+
@connection = AMQP.connect :host => (@config['host'] || 'localhost'), :port => (@config['port'] || 5672).to_i
|
12
|
+
@channel = MQ.new @connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe(impulseType, &block)
|
16
|
+
raise "No block provided - a block must be provided to the subscribe method" unless block_given?
|
17
|
+
|
18
|
+
queue = MQ::Queue.new @channel, impulseType.uri
|
19
|
+
exchange = MQ::Exchange.new @channel, :direct, impulseType.uri
|
20
|
+
queue.bind(exchange)
|
21
|
+
|
22
|
+
queue.subscribe :ack => true do |headers, msg|
|
23
|
+
yield impulseType.load(msg)
|
24
|
+
headers.ack
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def fire(impulseUri, content)
|
29
|
+
exchange = MQ::Exchange.new @channel, :direct, impulseUri
|
30
|
+
exchange.publish content, :content_type => 'application/json'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/lib/orq/impulse.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module ORQ
|
6
|
+
# Base class inherited by domain objects describing a message that can be sent
|
7
|
+
class Impulse
|
8
|
+
def initialize(config = {})
|
9
|
+
apply_hash!(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Instructs the impulse to send itself to the message queue
|
13
|
+
def fire!
|
14
|
+
self.class.get_target(self.class.target_name).fire self.class.uri, self.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
# Instructs the impulse to apply the properties in the given hash
|
18
|
+
def apply_hash!(props)
|
19
|
+
props.each do |k, v|
|
20
|
+
send("#{k}=", v) if respond_to? "#{k}="
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Class Methods
|
25
|
+
class << self
|
26
|
+
#
|
27
|
+
# Configuration Methods
|
28
|
+
#
|
29
|
+
|
30
|
+
# Sets the uri for this Impulse if a uri is provided; returns the value for the uri
|
31
|
+
# (or the class name if not overriden)
|
32
|
+
def uri(uri = nil)
|
33
|
+
@uri = uri unless uri.nil?
|
34
|
+
|
35
|
+
return self.name if @uri.nil?
|
36
|
+
@uri
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the target name for this Impulse if a uri is provided; returns the value for the target name
|
40
|
+
# (or 'default' if not overriden)
|
41
|
+
def target_name(target_name = nil)
|
42
|
+
@target_name = target_name unless target_name.nil?
|
43
|
+
|
44
|
+
return 'default' if @target_name.nil?
|
45
|
+
@target_name
|
46
|
+
end
|
47
|
+
|
48
|
+
# Declares a field that should be available on an impulse.
|
49
|
+
def field(name)
|
50
|
+
attr_accessor name
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Control Methods
|
55
|
+
#
|
56
|
+
def subscribe(&block)
|
57
|
+
target = get_target self.target_name
|
58
|
+
target.subscribe self, &block
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Loading Methods
|
63
|
+
#
|
64
|
+
def load(text)
|
65
|
+
vals = ActiveSupport::JSON.decode(text)
|
66
|
+
result = new(vals)
|
67
|
+
result
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Static Methods
|
72
|
+
|
73
|
+
# Configures the ORQ infrastructure using the file on the given path.
|
74
|
+
def self.configure(path, environment)
|
75
|
+
# Load the given YAML file
|
76
|
+
config = YAML::load_file(path)
|
77
|
+
|
78
|
+
# Get the configuration for the profile
|
79
|
+
env_config = config[environment.to_s]
|
80
|
+
raise "No configuration for environment #{environment} in #{path}" unless env_config
|
81
|
+
|
82
|
+
# Clear the list of targets, and then process each
|
83
|
+
@@targets = {}
|
84
|
+
if env_config.is_a? Hash
|
85
|
+
env_config.each do |target_name, target_config|
|
86
|
+
|
87
|
+
@@targets[target_name] = load_target target_name, target_config
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Requests that the ORQ infrastructure start any connections
|
93
|
+
def self.start
|
94
|
+
@@targets.each do |name, target|
|
95
|
+
target.start
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def self.load_target(name, config)
|
101
|
+
driver = config.delete 'driver'
|
102
|
+
raise "No driver specified for target #{name}" unless driver
|
103
|
+
|
104
|
+
# Load the adapter for the given driver
|
105
|
+
require "orq/adapters/#{driver}_adapter"
|
106
|
+
driver_class = Kernel.const_get("#{driver}_adapter".classify).new config
|
107
|
+
end
|
108
|
+
|
109
|
+
# Retrieves a target with the given name
|
110
|
+
def self.get_target(name)
|
111
|
+
raise "No target #{name} available" unless @@targets[name]
|
112
|
+
@@targets[name]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'orq/impulse'
|
2
|
+
|
3
|
+
describe ORQ::Impulse do
|
4
|
+
it "should be inheritable with default details" do
|
5
|
+
class TrivialObj < ORQ::Impulse
|
6
|
+
end
|
7
|
+
|
8
|
+
TrivialObj.uri.should == 'TrivialObj'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should get fully qualified details by default" do
|
12
|
+
module TrivialNS
|
13
|
+
class TrivialObj < ORQ::Impulse
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
TrivialNS::TrivialObj.uri.should == 'TrivialNS::TrivialObj'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow multiple different types to be created with overidden uri" do
|
21
|
+
class FirstTrivialNamedObj < ORQ::Impulse
|
22
|
+
uri 'http://vuderacha.com/orq/trivial_named/first'
|
23
|
+
end
|
24
|
+
class SecondTrivialNamedObj < ORQ::Impulse
|
25
|
+
uri 'http://vuderacha.com/orq/trivial_named/second'
|
26
|
+
end
|
27
|
+
|
28
|
+
FirstTrivialNamedObj.uri.should == 'http://vuderacha.com/orq/trivial_named/first'
|
29
|
+
SecondTrivialNamedObj.uri.should == 'http://vuderacha.com/orq/trivial_named/second'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should support the declaration of fields" do
|
33
|
+
class FieldedImpulse < ORQ::Impulse
|
34
|
+
field :first_val
|
35
|
+
field :second_val
|
36
|
+
end
|
37
|
+
|
38
|
+
f = FieldedImpulse.new
|
39
|
+
f.first_val = 'a'
|
40
|
+
f.second_val = 'b'
|
41
|
+
f.first_val.should == 'a'
|
42
|
+
f.second_val.should == 'b'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should support restoring from a hash" do
|
46
|
+
class RestorableImpulse < ORQ::Impulse
|
47
|
+
field :first_val
|
48
|
+
field :second_val
|
49
|
+
end
|
50
|
+
|
51
|
+
f = RestorableImpulse.new({:first_val => 1, :second_val => 'a'})
|
52
|
+
f.first_val.should == 1
|
53
|
+
f.second_val.should == 'a'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should support restoring from a hash with too many values" do
|
57
|
+
class AnotherRestorableImpulse < ORQ::Impulse
|
58
|
+
field :first_val
|
59
|
+
field :second_val
|
60
|
+
end
|
61
|
+
|
62
|
+
f = AnotherRestorableImpulse.new({:first_val => 1, :second_val => 'a', :third_val => [1,2,3]})
|
63
|
+
f.first_val.should == 1
|
64
|
+
f.second_val.should == 'a'
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should support configuration from a YAML file" do
|
68
|
+
ORQ::Impulse.configure 'spec/simple_config.yml', :development
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return default as a target name when not overriden" do
|
72
|
+
class DefaultTargetImpulse < ORQ::Impulse; end
|
73
|
+
DefaultTargetImpulse.target_name.should == 'default'
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return the configured target name" do
|
77
|
+
class SpecialTargetImpulse < ORQ::Impulse
|
78
|
+
target_name 'special'
|
79
|
+
end
|
80
|
+
SpecialTargetImpulse.target_name.should == 'special'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "configured #{ORQ::Impulse}" do
|
85
|
+
before(:all) do
|
86
|
+
ORQ::Impulse.configure 'spec/stub_config.yml', :development
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should provide its uri and content to the adapter when being fired" do
|
90
|
+
class AnotherFireableImpulse < ORQ::Impulse
|
91
|
+
field :simple
|
92
|
+
end
|
93
|
+
StubAdapter.instance.should_receive(:fire).with('AnotherFireableImpulse', '{"simple": "a"}')
|
94
|
+
|
95
|
+
i = AnotherFireableImpulse.new({:simple => 'a'})
|
96
|
+
i.fire!
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should support subscribing with a block" do
|
100
|
+
# Declare our subscribable impulse
|
101
|
+
class SubscribableImpulse < ORQ::Impulse
|
102
|
+
field :a
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create our receiver
|
106
|
+
r = []
|
107
|
+
|
108
|
+
# Extend the stub adapter to respond the way that way we want it to
|
109
|
+
class StubAdapter
|
110
|
+
def subscribe(type, &block)
|
111
|
+
type.should == SubscribableImpulse
|
112
|
+
yield SubscribableImpulse.new({:a => 1})
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Perform the subscription
|
117
|
+
SubscribableImpulse.subscribe do |imp|
|
118
|
+
r << imp
|
119
|
+
end
|
120
|
+
|
121
|
+
r.length.should == 1
|
122
|
+
r[0].a.should == 1
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
development:
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vuderacha-orq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Jones
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-22 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.2.2
|
24
|
+
version:
|
25
|
+
description: ORQ is a queueing abstraction layer allowing messaging to be done directly from object.
|
26
|
+
email: pauljones23@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- Rakefile
|
35
|
+
- README.rdoc
|
36
|
+
- lib/orq
|
37
|
+
- lib/orq/adapters
|
38
|
+
- lib/orq/adapters/amqp_adapter.rb
|
39
|
+
- lib/orq/impulse.rb
|
40
|
+
- spec/impulse_spec.rb
|
41
|
+
- spec/simple_config.yml
|
42
|
+
- spec/stub_config.yml
|
43
|
+
- spec/support
|
44
|
+
- spec/support/orq
|
45
|
+
- spec/support/orq/adapters
|
46
|
+
- spec/support/orq/adapters/stub_adapter.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/vuderacha/orq/
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --inline-source
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.2.0
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: Ruby Object Relational Queueing.
|
74
|
+
test_files: []
|
75
|
+
|