qusion 0.1.1

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/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rake", ">= 0.8.7"
4
+ gem "bundler", "~> 1.0.0"
5
+ gem "amqp", "0.6.7", :require => 'mq'
6
+
7
+ group :development do
8
+ gem "jeweler", "~> 1.5.1" # adds Bundler support for gemspec generation
9
+ gem "cucumber", ">=0.9.4"
10
+ gem "rspec", "~>1.3.0"
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ amqp (0.6.7)
5
+ eventmachine (>= 0.12.4)
6
+ builder (3.0.0)
7
+ cucumber (0.10.0)
8
+ builder (>= 2.1.2)
9
+ diff-lcs (~> 1.1.2)
10
+ gherkin (~> 2.3.2)
11
+ json (~> 1.4.6)
12
+ term-ansicolor (~> 1.0.5)
13
+ diff-lcs (1.1.2)
14
+ eventmachine (0.12.10)
15
+ gherkin (2.3.2)
16
+ json (~> 1.4.6)
17
+ term-ansicolor (~> 1.0.5)
18
+ git (1.2.5)
19
+ jeweler (1.5.1)
20
+ bundler (~> 1.0.0)
21
+ git (>= 1.2.5)
22
+ rake
23
+ json (1.4.6)
24
+ rake (0.8.7)
25
+ rspec (1.3.1)
26
+ term-ansicolor (1.0.5)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ amqp (= 0.6.7)
33
+ bundler (~> 1.0.0)
34
+ cucumber (>= 0.9.4)
35
+ jeweler (~> 1.5.1)
36
+ rake (>= 0.8.7)
37
+ rspec (~> 1.3.0)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2011 Daniel DeLeo, CustomInk, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.rdoc ADDED
@@ -0,0 +1,132 @@
1
+ = Qusion
2
+
3
+ Qusion makes AMQP[http://github.com/tmm1/amqp] work with your webserver with
4
+ no fuss. It offers three features:
5
+ * It sets up the required callbacks and/or worker threads so that AMQP will work with Passenger, Thin, or Mongrel. WEBrick, SCGI, and Evented Mongrel are experimentally supported, but not heavily tested.
6
+ * A Channel Pool. You can cause problems for yourself if you create new channels (with MQ.new) for every request. The pool sets up a few of these when your app starts and reuses them.
7
+ * YAML configuration files. In Rails, create config/amqp.yml then fill in the details for development, test and production. Use Qusion.start() in your environment.rb file (or an initializer) and you're good to go.
8
+
9
+ = This fork of Qusion
10
+
11
+ This is a fork of James Tucker's fork[https://github.com/raggi/qusion] of
12
+ Dan DeLeo's original version[https://github.com/danielsdeleo/qusion]. Tucker
13
+ did a fair amount of cleanup, mostly to the threading logic, so I went with his
14
+ fork. Improvements I've made include:
15
+ * Conversion from a Rails plugin to this gem.
16
+ * Support for Rails 3.
17
+ * Removed support for Merb (it complicated the code and tests; just use Rails 3).
18
+
19
+ = Before You Start
20
+
21
+ Qusion makes it easy to just install and start using AMQP in your application.
22
+ But there are many ways to use background jobs within a Rails app, so it's
23
+ worth taking some time to consider the tradeoffs of each approach.
24
+
25
+ * If your background job needs are simple and you're using a relational database, Delayed::Job[http://github.com/tobi/delayed_job/] lets you schedule background tasks through the database. You won't need to run another application (the AMQP Broker) to keep your app running.
26
+ * The 0.6.x version of the Ruby amqp library may drop messages when the AMQP broker goes down. Pivotal Labs has discussed this problem on their blog[http://pivots.pivotallabs.com/users/will/blog/articles/966-how-to-not-test-rabbitmq-part-1]. This issue will likely be addressed in the 0.7.0 release of amqp, but can be avoided entirely using a synchronous amqp library such as bunny[http://github.com/celldee/bunny]. For a ready-made background job solution using Bunny to publish jobs to the queue, see Minion[http://github.com/orionz/minion/].
27
+ * Qusion runs EventMachine in a separate thread on Phusion Passenger, Mongrel, and other non-evented servers. There are some inefficiencies in Ruby 1.8's threading model that make running EM in a thread quite slow. Joe Damato and Aman Gupta have created a patch[http://github.com/ice799/matzruby/tree/heap_stacks] for the problem which is included in an experimental branch of REE. You can learn more about the patch from Phusion's Blog[http://blog.phusion.nl/2009/12/15/google-tech-talk-on-ruby-enterprise-edition/].
28
+
29
+ = Getting Started
30
+
31
+ First you'll need the amqp library and a working RabbitMQ installation. This
32
+ entails:
33
+
34
+ * Install Erlang for your platform
35
+ * Install RabbitMQ for your platform
36
+ * On OSX, use Homebrew to install Erlang and RabbitMQ: brew install rabbitmq
37
+ * Install bundler: http://gembundler.com/
38
+ == Install Qusion
39
+ Include the qusion gem in your Gemfile:
40
+
41
+ gem "qusion"
42
+
43
+ Create an initializer (e.g. config/initializers/qusion.rb) and add:
44
+ Qusion.start
45
+
46
+ EM.next_tick do
47
+ # do some AMQP stuff
48
+ end
49
+
50
+ And that's it! This will set up AMQP for any ruby app server (tested on
51
+ mongrel, thin, and passenger). Now, you can use all of AMQP's functionality as
52
+ normal. In your controllers or models, you might have:
53
+
54
+ MQ.new.queue("my-work-queue").publish("do work, son!")
55
+
56
+ and it should just work.
57
+
58
+ = Channel Pools
59
+
60
+ It's considered bad practice to use MQ.new over and over, as it creates a new
61
+ AMQP channel, and that creates a new Erlang process in RabbitMQ. Erlang
62
+ processes are super light weight, but you'll be wasting them and causing the
63
+ Erlang VM GC headaches if you create them wantonly. So don't do that. Instead,
64
+ use the channel pool provided by Qusion. It's simple: wherever you'd normally
65
+ put MQ.new, just replace it with Qusion.channel. Examples:
66
+
67
+ # Create a queue:
68
+ Qusion.channel.queue("my-worker-queue")
69
+ # Topics:
70
+ Qusion.channel.topic("my-topic-exchange")
71
+ # etc.
72
+
73
+ This feature is a bit experimental, so the optimal pool size isn't known yet.
74
+ The default is 5. You can change it by adding something like the following to
75
+ your environment.rb:
76
+
77
+ Qusion.channel_pool_size(3)
78
+
79
+ = Configuration
80
+
81
+ You can put your AMQP server details in config/amqp.yml and Qusion will load
82
+ it when you call Qusion.start(). Example:
83
+
84
+ # Put this in config/amqp.yml
85
+ default: &default
86
+ host: localhost
87
+ port: 5672
88
+ user: guest
89
+ pass: guest
90
+ vhost: /
91
+ timeout: 3600 # seconds
92
+ logging: false
93
+ ssl: false
94
+
95
+ development:
96
+ <<: *default
97
+
98
+ test:
99
+ <<: *default
100
+
101
+ If you're too hardcore for Rails (maybe you're using Sinatra or Ramaze), you
102
+ can still use a YAML config file, but there's no support for different
103
+ environments. So do something like this:
104
+
105
+ # Tell Qusion where your config file is:
106
+ Qusion.start("/path/to/amqp.yml")
107
+
108
+ # Your configuration looks like this:
109
+ application:
110
+ host: localhost
111
+ port: 5672
112
+ ...
113
+
114
+ If you just want to get started without configuring anything, Qusion.start()
115
+ will use the default options if it can't find a config file. And, finally, you
116
+ can give options directly to Qusion.start() like this:
117
+
118
+ Qusion.start(:host => "my-amqp-broker.mydomain.com", :user => "me", :pass => "am_I_really_putting_this_in_VCS?")
119
+
120
+
121
+ = Bugs? Hacking?
122
+
123
+ If you find any bugs, or feel the need to add a feature, fork away. You can
124
+ also contact me directly via the email address in my profile if you have any
125
+ questions.
126
+
127
+ = Shouts
128
+ * Qusion's code for Phusion Passenger's starting_worker_process event was originally posted by Aman Gupta (tmm1[http://github.com/tmm1]) on the AMQP list[http://groups.google.com/group/ruby-amqp]
129
+ * Brightbox's Warren[http://github.com/brightbox/warren] library provides some similar functionality. It doesn't support webserver-specific EventMachine setup, but it does have built-in encryption and support for the synchronous (non-EventMachine) Bunny[http://github.com/celldee/bunny] AMQP client.
130
+
131
+ Original author: dan@kallistec.com
132
+ Forked by: cmurphy@customink.com
data/Rakefile ADDED
@@ -0,0 +1,71 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require "spec/rake/spectask"
6
+ require "cucumber"
7
+ require "cucumber/rake/task"
8
+
9
+ desc 'Default: run specs.'
10
+ task :default => :spec
11
+
12
+ begin
13
+ Bundler.setup(:default, :development)
14
+ rescue Bundler::BundlerError => e
15
+ $stderr.puts e.message
16
+ $stderr.puts "Run `bundle install` to install missing gems"
17
+ exit e.status_code
18
+ end
19
+
20
+ begin
21
+ require 'jeweler'
22
+ Jeweler::Tasks.new do |gem|
23
+ gem.name = "qusion"
24
+ gem.summary = "Makes AMQP work with Ruby on Rails with no fuss."
25
+ gem.description = %Q{See the README for more details.}
26
+ gem.email = "cmurphy@customink.com"
27
+ gem.homepage = "http://github.com/customink/qusion"
28
+ gem.authors = ["Dan DeLeo", "Christopher R. Murphy"]
29
+ end
30
+ rescue LoadError
31
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
32
+ end
33
+
34
+ desc "Run Cucumber Features"
35
+ Cucumber::Rake::Task.new do |t|
36
+ t.cucumber_opts = "-c -n"
37
+ end
38
+
39
+ desc "Run all of the specs"
40
+ Spec::Rake::SpecTask.new do |t|
41
+ t.ruby_opts << '-rubygems'
42
+ t.spec_opts = ['--options', "spec/spec.opts"]
43
+ t.fail_on_error = false
44
+ end
45
+
46
+ namespace :spec do
47
+ desc "Generate HTML report for failing examples"
48
+ Spec::Rake::SpecTask.new('report') do |t|
49
+ t.ruby_opts << '-rubygems'
50
+ t.spec_files = FileList['failing_examples/**/*.rb']
51
+ t.spec_opts = ["--format", "html:doc/tools/reports/failing_examples.html", "--diff", '--options', '"spec/spec.opts"']
52
+ t.fail_on_error = false
53
+ end
54
+
55
+ desc "Run all spec with RCov"
56
+ Spec::Rake::SpecTask.new(:rcov) do |t|
57
+ t.ruby_opts << '-rubygems'
58
+ t.rcov = true
59
+ t.rcov_dir = 'doc/tools/coverage/'
60
+ t.rcov_opts = ['--exclude', 'spec']
61
+ end
62
+ end
63
+
64
+ Rake::RDocTask.new do |rdoc|
65
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
66
+
67
+ rdoc.rdoc_dir = 'rdoc'
68
+ rdoc.title = "qusion #{version}"
69
+ rdoc.rdoc_files.include('README*')
70
+ rdoc.rdoc_files.include('lib/**/*.rb')
71
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/lib/qusion.rb ADDED
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+ require "eventmachine"
3
+ require "mq"
4
+
5
+ require "qusion/channel_pool"
6
+ require "qusion/amqp_config"
7
+
8
+ module Qusion
9
+ class << self
10
+ attr_reader :thread
11
+ end
12
+
13
+ def self.start(*opts)
14
+ amqp_opts = AmqpConfig.new(*opts).config_opts
15
+ start_amqp_dispatcher(amqp_opts)
16
+ end
17
+
18
+ def self.start_amqp_dispatcher(amqp_settings={})
19
+ AMQP.settings.merge!(amqp_settings)
20
+
21
+ if defined?(::PhusionPassenger) && ::PhusionPassenger.respond_to?(:on_event)
22
+ ::PhusionPassenger.on_event(:starting_worker_process) do |forked|
23
+ if forked
24
+ EM.stop if EM.reactor_running?
25
+ thread && thread.join
26
+ Thread.current[:mq] = nil
27
+ AMQP.instance_variable_set(:@conn, nil)
28
+ end
29
+ start_in_background
30
+ die_gracefully_on_signal
31
+ end
32
+ return
33
+ end
34
+
35
+ start_in_background
36
+ die_gracefully_on_signal
37
+ end
38
+
39
+ def self.die_gracefully_on_signal
40
+ Signal.trap("INT") { graceful_stop }
41
+ Signal.trap("TERM") { graceful_stop }
42
+ end
43
+
44
+ def self.channel
45
+ ChannelPool.instance.channel
46
+ end
47
+
48
+ def self.channel_pool_size(new_pool_size)
49
+ ChannelPool.pool_size = new_pool_size
50
+ end
51
+
52
+ def self.start_in_background
53
+ if EM.reactor_running?
54
+ raise ArgumentError, 'AMQP already connected' if ready_to_dispatch?
55
+ AMQP.start
56
+ else
57
+ raise ArgumentError, 'Qusion already started' if thread && thread.alive?
58
+ @thread = Thread.new do
59
+ EM.run { AMQP.start }
60
+ raise "Premature AMQP shutdown" unless @graceful_stop
61
+ end
62
+ thread.abort_on_exception = true
63
+ thread.join(0.1) until ready_to_dispatch?
64
+ end
65
+ end
66
+
67
+ def self.graceful_stop
68
+ EM.schedule do
69
+ @graceful_stop = true
70
+ AMQP.stop { EM.stop }
71
+ end
72
+ thread && thread.join
73
+ end
74
+
75
+ def self.ready_to_dispatch?
76
+ EM.reactor_running? && AMQP.conn && AMQP.conn.connected?
77
+ end
78
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+ require 'yaml'
3
+
4
+ module Qusion
5
+
6
+ class AmqpConfig
7
+ attr_reader :config_path, :framework_env
8
+
9
+ def initialize(opts=nil)
10
+ if opts && opts.respond_to?(:keys)
11
+ @config_path = nil
12
+ @config_opts = opts
13
+ elsif opts
14
+ @config_path = opts
15
+ else
16
+ load_framework_config
17
+ end
18
+ end
19
+
20
+ def load_framework_config
21
+ @config_path = Rails.root + "/config/amqp.yml"
22
+ @framework_env = Rails.env
23
+ end
24
+
25
+ def config_opts
26
+ @config_opts ||= load_config_opts
27
+ end
28
+
29
+ def load_config_opts
30
+ if config_path && config_from_yaml = load_amqp_config_file
31
+ if framework_env
32
+ framework_amqp_opts(config_from_yaml)
33
+ else
34
+ amqp_opts(config_from_yaml)
35
+ end
36
+ else
37
+ {}
38
+ end
39
+ end
40
+
41
+ def framework_amqp_opts(config_hash)
42
+ symbolize_keys(config_hash[framework_env.to_s])
43
+ end
44
+
45
+ def amqp_opts(config_hash)
46
+ symbolize_keys(config_hash.first.last)
47
+ end
48
+
49
+ def symbolize_keys(config_hash)
50
+ symbolized_hsh = {}
51
+ config_hash.each {|option, value| symbolized_hsh[option.to_sym] = value }
52
+ symbolized_hsh
53
+ end
54
+
55
+ def load_amqp_config_file
56
+ begin
57
+ YAML.load_file(config_path)
58
+ rescue Errno::ENOENT
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: UTF-8
2
+ require "singleton"
3
+
4
+ module Qusion
5
+
6
+ # ChannelPool maintains a pool of AMQP channel objects that can be reused.
7
+ # The motivation behind this is that if you were to use MQ.new to create a
8
+ # new channel for every request, your AMQP broker could be swamped trying to
9
+ # maintain a bunch of channels that you're only using once.
10
+ #
11
+ # To use the channel pool, just replace <tt>MQ.new</tt> in your code with <tt>Qusion.channel</tt>
12
+ #
13
+ # # Instead of this:
14
+ # MQ.new.queue("my-worker-queue")
15
+ # # Do this:
16
+ # Qusion.channel.queue("my-worker-queue")
17
+ #
18
+ # By default, ChannelPool maintains a pool of 5 channels. This can be adjusted with
19
+ # <tt>ChannelPool.pool_size=()</tt> or <tt>Qusion.channel_pool_size()</tt>
20
+ # The optimal pool size is not yet known, but I suspect you might need a
21
+ # larger value if using Thin in production, and a smaller value otherwise.
22
+ class ChannelPool
23
+ include Singleton
24
+
25
+ class << self
26
+
27
+ def pool_size=(new_pool_size)
28
+ reset
29
+ @pool_size = new_pool_size
30
+ end
31
+
32
+ def pool_size
33
+ @pool_size ||= 5
34
+ end
35
+
36
+ def reset
37
+ @pool_size = nil
38
+ instance.reset
39
+ end
40
+
41
+ end
42
+
43
+ attr_reader :pool
44
+
45
+ def channel
46
+ @i ||= 1
47
+ @i = (@i + 1) % pool_size
48
+ pool[@i]
49
+ end
50
+
51
+ def pool
52
+ @pool ||= Array.new(pool_size) { MQ.new }
53
+ end
54
+
55
+ def reset
56
+ @pool = nil
57
+ end
58
+
59
+ def pool_size
60
+ self.class.pool_size
61
+ end
62
+
63
+ end
64
+ end
data/qusion.gemspec ADDED
@@ -0,0 +1,79 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{qusion}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Dan DeLeo", "Christopher R. Murphy"]
12
+ s.date = %q{2010-12-09}
13
+ s.description = %q{See the README for more details.}
14
+ s.email = %q{cmurphy@customink.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/qusion.rb",
27
+ "lib/qusion/amqp_config.rb",
28
+ "lib/qusion/channel_pool.rb",
29
+ "qusion.gemspec",
30
+ "spec/fixtures/framework-amqp.yml",
31
+ "spec/fixtures/hardcoded-amqp.yml",
32
+ "spec/mock_rails.rb",
33
+ "spec/spec.opts",
34
+ "spec/spec_helper.rb",
35
+ "spec/unit/amqp_config_spec.rb",
36
+ "spec/unit/channel_pool_spec.rb",
37
+ "spec/unit/qusion_spec.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/customink/qusion}
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.7}
42
+ s.summary = %q{Makes AMQP work with Ruby on Rails with no fuss.}
43
+ s.test_files = [
44
+ "spec/mock_rails.rb",
45
+ "spec/spec_helper.rb",
46
+ "spec/unit/amqp_config_spec.rb",
47
+ "spec/unit/channel_pool_spec.rb",
48
+ "spec/unit/qusion_spec.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<rake>, [">= 0.8.7"])
57
+ s.add_runtime_dependency(%q<bundler>, ["~> 1.0.0"])
58
+ s.add_runtime_dependency(%q<amqp>, ["= 0.6.7"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
60
+ s.add_development_dependency(%q<cucumber>, [">= 0.9.4"])
61
+ s.add_development_dependency(%q<rspec>, ["~> 1.3.0"])
62
+ else
63
+ s.add_dependency(%q<rake>, [">= 0.8.7"])
64
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
67
+ s.add_dependency(%q<cucumber>, [">= 0.9.4"])
68
+ s.add_dependency(%q<rspec>, ["~> 1.3.0"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<rake>, [">= 0.8.7"])
72
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
73
+ s.add_dependency(%q<amqp>, ["= 0.6.7"])
74
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
75
+ s.add_dependency(%q<cucumber>, [">= 0.9.4"])
76
+ s.add_dependency(%q<rspec>, ["~> 1.3.0"])
77
+ end
78
+ end
79
+
@@ -0,0 +1,28 @@
1
+ development:
2
+ host: localhost
3
+ port: 5672
4
+ user: guest
5
+ pass: guest
6
+ vhost: /
7
+ timeout: 600
8
+ logging: false
9
+ ssl: false
10
+
11
+ test:
12
+ host: localhost
13
+ port: 5672
14
+ user: guest
15
+ pass: guest
16
+ vhost: /
17
+ logging: false
18
+ ssl: false
19
+
20
+ production:
21
+ host: localhost
22
+ port: 5672
23
+ user: guest
24
+ pass: guest
25
+ vhost: /
26
+ timeout: 3600
27
+ logging: false
28
+ ssl: false
@@ -0,0 +1,9 @@
1
+ application:
2
+ host: localhost
3
+ port: 5672
4
+ user: guest
5
+ pass: guest
6
+ vhost: /
7
+ timeout: 600
8
+ logging: false
9
+ ssl: false
@@ -0,0 +1,14 @@
1
+ class Rails
2
+ module VERSION
3
+ MAJOR = 3
4
+ end
5
+
6
+ def self.env
7
+ # ActiveSupport::StringInquirer.new("test")
8
+ "test"
9
+ end
10
+
11
+ def self.root
12
+ "/path/to/rails"
13
+ end
14
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ -f specdoc -c -t 2
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ require 'spec/autorun'
3
+ require 'qusion'
4
+ include Qusion
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + "/../../spec_helper"
3
+ require File.dirname(__FILE__) + "/../../mock_rails"
4
+
5
+ describe AmqpConfig do
6
+
7
+ it "should use #{Rails.root}/config/amqp.yml" do
8
+ AmqpConfig.new.config_path.should == "/path/to/rails/config/amqp.yml"
9
+ end
10
+
11
+ it "should use the provided path no matter what" do
12
+ path = AmqpConfig.new("/custom/path/to/amqp.yml").config_path
13
+ path.should == "/custom/path/to/amqp.yml"
14
+ end
15
+
16
+ it "should use a provided options hash if given" do
17
+ conf = AmqpConfig.new(:host => "my-broker.mydomain.com")
18
+ conf.config_path.should be_nil
19
+ conf.config_opts.should == {:host => "my-broker.mydomain.com"}
20
+ end
21
+
22
+ it "should use the default amqp options in rails if amqp.yml doesn't exist" do
23
+ Rails.stub!(:root).and_return(File.dirname(__FILE__) + '/../')
24
+ AmqpConfig.new.config_opts.should == {}
25
+ end
26
+
27
+ it "should load a YAML file when using a framework" do
28
+ conf = AmqpConfig.new
29
+ conf.stub!(:config_path).and_return(File.dirname(__FILE__) + "/../fixtures/framework-amqp.yml")
30
+ conf.stub!(:framework_env).and_return("production")
31
+ conf.config_opts.should == {:host => 'localhost',:port => 5672,
32
+ :user => 'guest', :pass => 'guest',
33
+ :vhost => '/', :timeout => 3600,
34
+ :logging => false, :ssl => false}
35
+ end
36
+
37
+ it "should use the first set of opts when given a explicit file path" do
38
+ conf = AmqpConfig.new
39
+ conf.stub!(:config_path).and_return(File.dirname(__FILE__) + "/../fixtures/hardcoded-amqp.yml")
40
+ conf.stub!(:framework_env).and_return(nil)
41
+ p conf.config_opts
42
+ conf.config_opts.should == {:host => 'localhost',:port => 5672,
43
+ :user => 'guest', :pass => 'guest',
44
+ :vhost => '/', :timeout => 600,
45
+ :logging => false, :ssl => false}
46
+ end
47
+
48
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + "/../../spec_helper"
3
+
4
+ describe ChannelPool do
5
+ MQ = Object.new
6
+
7
+ before(:each) do
8
+ ChannelPool.reset
9
+ @channel_pool = ChannelPool.instance
10
+ end
11
+
12
+ it "should be singleton" do
13
+ lambda { ChannelPool.new }.should raise_error
14
+ end
15
+
16
+ it "should adjust the pool size" do
17
+ ChannelPool.pool_size = 5
18
+ ChannelPool.pool_size.should == 5
19
+ end
20
+
21
+ it "should reset itself when the pool size is set" do
22
+ ChannelPool.should_receive(:reset)
23
+ ChannelPool.pool_size = 23
24
+ end
25
+
26
+ it "should create a pool of AMQP channels" do
27
+ ChannelPool.pool_size = 3
28
+ ::MQ.should_receive(:new).exactly(3).times
29
+ @channel_pool.pool
30
+ end
31
+
32
+ it "should default to a pool size of 5" do
33
+ ::MQ.should_receive(:new).exactly(5).times.and_return("swanky")
34
+ @channel_pool.pool
35
+ @channel_pool.instance_variable_get(:@pool).should == %w{ swanky swanky swanky swanky swanky}
36
+ end
37
+
38
+ it "should return a channel in a round-robin" do
39
+ @channel_pool.instance_variable_set(:@pool, [1,2,3,4,5])
40
+ @channel_pool.channel.should == 3
41
+ @channel_pool.channel.should == 4
42
+ @channel_pool.channel.should == 5
43
+ @channel_pool.channel.should == 1
44
+ @channel_pool.channel.should == 2
45
+ @channel_pool.channel.should == 3
46
+ end
47
+
48
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + "/../../spec_helper"
3
+
4
+ describe "Qusion Convenience Methods" do
5
+
6
+ it "should get a channel from the pool" do
7
+ channel_pool = mock("channel pool")
8
+ ChannelPool.should_receive(:instance).and_return(channel_pool)
9
+ channel_pool.should_receive(:channel)
10
+ Qusion.channel
11
+ end
12
+
13
+ it "should set the channel pool size" do
14
+ ChannelPool.should_receive(:pool_size=).with(7)
15
+ Qusion.channel_pool_size(7)
16
+ end
17
+
18
+ it "should load the configuration and setup AMQP for the webserver" do
19
+ config = mock("config")
20
+ AmqpConfig.should_receive(:new).and_return(config)
21
+ config.should_receive(:config_opts).and_return(:config => :opts)
22
+ Qusion.should_receive(:start_amqp_dispatcher).with(:config => :opts)
23
+ Qusion.start
24
+ end
25
+
26
+ end
27
+
28
+ describe Qusion, 'amqp startup' do
29
+
30
+ before do
31
+ AMQP.stub!(:settings).and_return({})
32
+ end
33
+
34
+ after(:each) do
35
+ Object.send(:remove_const, :PhusionPassenger) if defined? ::PhusionPassenger
36
+ Object.send(:remove_const, :Thin) if defined? ::Thin
37
+ Object.send(:remove_const, :Mongrel) if defined? ::Mongrel
38
+ end
39
+
40
+ it "should kill the reactor and start a new AMQP connection when forked in Passenger" do
41
+ Qusion.should_receive(:die_gracefully_on_signal).once
42
+ ::PhusionPassenger = Module.new
43
+ forked = mock("starting_worker_process_callback_obj")
44
+ ::PhusionPassenger.should_receive(:on_event).with(:starting_worker_process).and_yield(forked)
45
+ EM.should_receive(:reactor_running?).exactly(3).times.and_return(true)
46
+ amqp_conn = mock('amqp_conn')
47
+ amqp_conn.should_receive(:connected?).and_return(false)
48
+ AMQP.should_receive(:conn).any_number_of_times.and_return(amqp_conn)
49
+ EM.should_receive(:stop)
50
+ AMQP.should_receive(:start).once
51
+ Qusion.start_amqp_dispatcher
52
+ end
53
+
54
+ it "should set AMQP's connection settings when running under Thin" do
55
+ Qusion.should_receive(:die_gracefully_on_signal)
56
+ Qusion.should_receive(:start_in_background)
57
+ ::Thin = Module.new
58
+ Qusion.start_amqp_dispatcher(:cookie => "yummy")
59
+ AMQP.settings[:cookie].should == "yummy"
60
+ end
61
+
62
+ it "should start a worker thread when running under Mongrel" do
63
+ Qusion.should_receive(:die_gracefully_on_signal)
64
+ mock_thread = mock('thread')
65
+ mock_thread.should_receive(:abort_on_exception=).with(true)
66
+ Qusion.should_receive(:ready_to_dispatch?).twice.and_return(false, true)
67
+ mock_thread.should_receive(:join)
68
+ Thread.should_receive(:new).and_return(mock_thread)
69
+ ::Mongrel = Module.new
70
+ Qusion.start_amqp_dispatcher
71
+ end
72
+
73
+ it "should be ready to dispatch when the reactor is running and amqp is connected" do
74
+ EM.should_receive(:reactor_running?).and_return(true)
75
+ amqp_conn = mock('amqp_conn')
76
+ amqp_conn.should_receive(:connected?).and_return(true)
77
+ AMQP.should_receive(:conn).any_number_of_times.and_return(amqp_conn)
78
+ Qusion.ready_to_dispatch?.should == true
79
+ end
80
+
81
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qusion
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Dan DeLeo
13
+ - Christopher R. Murphy
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-09 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 8
31
+ - 7
32
+ version: 0.8.7
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 0
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: amqp
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - "="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 6
61
+ - 7
62
+ version: 0.6.7
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: jeweler
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 1
75
+ - 5
76
+ - 1
77
+ version: 1.5.1
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: cucumber
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ - 9
91
+ - 4
92
+ version: 0.9.4
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: rspec
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 1
105
+ - 3
106
+ - 0
107
+ version: 1.3.0
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: *id006
111
+ description: See the README for more details.
112
+ email: cmurphy@customink.com
113
+ executables: []
114
+
115
+ extensions: []
116
+
117
+ extra_rdoc_files:
118
+ - LICENSE
119
+ - README.rdoc
120
+ files:
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE
124
+ - README.rdoc
125
+ - Rakefile
126
+ - VERSION
127
+ - lib/qusion.rb
128
+ - lib/qusion/amqp_config.rb
129
+ - lib/qusion/channel_pool.rb
130
+ - qusion.gemspec
131
+ - spec/fixtures/framework-amqp.yml
132
+ - spec/fixtures/hardcoded-amqp.yml
133
+ - spec/mock_rails.rb
134
+ - spec/spec.opts
135
+ - spec/spec_helper.rb
136
+ - spec/unit/amqp_config_spec.rb
137
+ - spec/unit/channel_pool_spec.rb
138
+ - spec/unit/qusion_spec.rb
139
+ has_rdoc: true
140
+ homepage: http://github.com/customink/qusion
141
+ licenses: []
142
+
143
+ post_install_message:
144
+ rdoc_options: []
145
+
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 2610415720212610617
154
+ segments:
155
+ - 0
156
+ version: "0"
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ segments:
163
+ - 0
164
+ version: "0"
165
+ requirements: []
166
+
167
+ rubyforge_project:
168
+ rubygems_version: 1.3.7
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: Makes AMQP work with Ruby on Rails with no fuss.
172
+ test_files:
173
+ - spec/mock_rails.rb
174
+ - spec/spec_helper.rb
175
+ - spec/unit/amqp_config_spec.rb
176
+ - spec/unit/channel_pool_spec.rb
177
+ - spec/unit/qusion_spec.rb