micro_q 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # MicroQ
1
+ # MicroQ [![Build Status](https://travis-ci.org/bnorton/micro_q.png)](https://travis-ci.org/bnorton/micro_q)
2
2
 
3
3
  MicroQ is a per-process asynchronous background queue.
4
4
 
@@ -18,6 +18,38 @@ Or install it:
18
18
 
19
19
  ## Usage
20
20
 
21
+ ```ruby
22
+ # A typical worker
23
+ class MyWorker
24
+ def perform
25
+ # do some performing here
26
+ end
27
+
28
+ def update(options = {})
29
+ # do some updating
30
+ end
31
+ end
32
+ ```
33
+
34
+ ###Simple (default)
35
+
36
+ ```ruby
37
+ # Using the async proxy API
38
+ MyWorker.async.perform
39
+
40
+ MyWorker.async.update(:user_id => user.id)
41
+
42
+ # Through the raw push API
43
+ MicroQ.push(:class => 'MyWorker') # Defaults to the perform method
44
+
45
+ # With a custom method
46
+ MicroQ.push(:class => 'MyWorker', :method => 'update', :args => [{:user_id => user.id}])
47
+ ```
48
+
49
+ ###Advanced
50
+
51
+ ###Custom Loaders
52
+
21
53
  ## Contributing
22
54
 
23
55
  1. Fork it
@@ -1,11 +1,22 @@
1
1
  module MicroQ
2
2
  class Config
3
+ ##
4
+ # Configuration accessible via:
5
+ # 1. hash syntax (config[:k] and config[:k] = val)
6
+ # 2. method syntax (config.k and config.k = val)
7
+ #
8
+ # To change the type of worker that is used simply assign
9
+ # a new worker class (after requiring the file).
10
+ #
3
11
  def initialize
4
12
  @data = {
5
13
  'workers' => 3,
6
14
  'timeout' => 120,
7
15
  'interval' => 5,
8
- 'middleware' => MicroQ::Middleware::Chain.new
16
+ 'middleware' => Middleware::Chain.new,
17
+ 'manager' => Manager::Default,
18
+ 'worker' => Worker::Standard,
19
+ 'queue' => Queue::Default
9
20
  }
10
21
  end
11
22
 
@@ -20,8 +20,8 @@ module MicroQ
20
20
  attr_reader :queue, :workers
21
21
 
22
22
  def initialize
23
- @queue = MicroQ::Queue::Default.new
24
- @workers = MicroQ::Worker::Standard.pool(:size => MicroQ.config.workers)
23
+ @queue = MicroQ.config.queue.new
24
+ @workers = MicroQ.config.worker.pool(:size => MicroQ.config.workers)
25
25
  end
26
26
 
27
27
  def start
@@ -1 +1,17 @@
1
1
  require 'micro_q/manager/default'
2
+
3
+ ##
4
+ # The Manager interface
5
+ #
6
+ # The concrete manager implementation encapsulates a queue and
7
+ # a pool of workers. The worker pool need only be a collection of
8
+ # workers that can accept and perform messages.
9
+ #
10
+ # :method: start
11
+ # - Begin the run loop for message processing.
12
+ #
13
+ # Note that the default manager is a celluloid actor that reschedules
14
+ # itself (via the after(seconds) { start }) call which is a recursive
15
+ # call that executes asynchronously. This behavior is critical and
16
+ # therefore the class must `include Celluloid`.
17
+ #
@@ -0,0 +1,17 @@
1
+ module MicroQ
2
+ module Methods
3
+ module ActionMailer
4
+ def async
5
+ MicroQ::Proxy::ActionMailer.new(
6
+ :class => MicroQ::Wrapper::ActionMailer,
7
+ :base => self
8
+ )
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ MicroQ::Util.safe_require 'action_mailer'
15
+ if defined?(ActionMailer::Base)
16
+ ActionMailer::Base.send(:extend, MicroQ::Methods::ActionMailer)
17
+ end
@@ -1,5 +1,18 @@
1
1
  module MicroQ
2
2
  module Methods
3
+ ##
4
+ # Methods that are added to AR instances
5
+ #
6
+ # When processing instance methods asynchronously, AR objects
7
+ # should not be stored. Instances that are backed by a database
8
+ # are herby serialized and re-queried from the DB at runtime.
9
+ # For AR that means simply storing the class and adding a custom 'loader'
10
+ #
11
+ # A Loader is an additional step before a method is invoked that
12
+ # generates a target object from a method invocation and arguments.
13
+ # In the case of AR, what better then 'find'. Here we simply
14
+ # store the id as the argument for find.
15
+ #
3
16
  module ActiveRecord
4
17
  def async
5
18
  options = {
@@ -16,6 +29,7 @@ module MicroQ
16
29
  end
17
30
  end
18
31
 
32
+ MicroQ::Util.safe_require 'active_record'
19
33
  if defined?(ActiveRecord::Base)
20
34
  ActiveRecord::Base.send(:include, MicroQ::Methods::ActiveRecord)
21
35
  end
@@ -1,5 +1,13 @@
1
1
  module MicroQ
2
2
  module Methods
3
+ ##
4
+ # Methods that are added to all Ruby Objects (as class methods).
5
+ #
6
+ # When processing class methods asynchronously, simply store
7
+ # the calling class. The custom 'loader' describes that no
8
+ # additional methods need be called to generate the callee of the
9
+ # message invocation
10
+ #
3
11
  module Class
4
12
  extend MicroQ::Methods::SharedMethods
5
13
 
@@ -1,5 +1,15 @@
1
1
  module MicroQ
2
2
  module Methods
3
+ ##
4
+ # Methods that are added to all Ruby Objects (as instance methods).
5
+ #
6
+ # When processing instance methods asynchronously, simply store
7
+ # the calling instances' class name. No custom 'loader' is needed but
8
+ # since the worker defines a default 'loader'. An example loader for
9
+ # a messages invoked as MyWorker.new.perform(123), is
10
+ # :loader => { :method => 'new', :args => []} which happens to be
11
+ # what the default worker does.
12
+ #
3
13
  module Instance
4
14
  include MicroQ::Methods::SharedMethods
5
15
 
@@ -4,7 +4,7 @@ module MicroQ
4
4
  def method_missing(method, *other)
5
5
  super unless /((.+)\_async$)/ === method
6
6
 
7
- name = $2 && $2.to_sym
7
+ name = $2
8
8
 
9
9
  # Define the method and call through.
10
10
  if name && respond_to?(name)
@@ -21,5 +21,9 @@ module MicroQ
21
21
  end
22
22
  end
23
23
 
24
+ # add Class and Instance methods first then
25
+ # override with additional extensions
24
26
  require 'micro_q/methods/class'
25
27
  require 'micro_q/methods/instance'
28
+ require 'micro_q/methods/active_record'
29
+ require 'micro_q/methods/action_mailer'
@@ -1,4 +1,5 @@
1
1
  require 'micro_q/middleware/server/retry'
2
+ require 'micro_q/middleware/server/connection'
2
3
 
3
4
  module MicroQ
4
5
  module Middleware
@@ -90,7 +91,8 @@ module MicroQ
90
91
  class Server < Base
91
92
  def initialize
92
93
  @entries = [
93
- MicroQ::Middleware::Server::Retry
94
+ MicroQ::Middleware::Server::Retry,
95
+ MicroQ::Middleware::Server::Connection
94
96
  ]
95
97
  end
96
98
  end
@@ -0,0 +1,15 @@
1
+ module MicroQ
2
+ module Middleware
3
+ module Server
4
+ class Connection
5
+ def call(*)
6
+ yield
7
+ ensure
8
+ if defined?(ActiveRecord::Base)
9
+ ActiveRecord::Base.clear_active_connections!
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1 +1,14 @@
1
1
  require 'micro_q/middleware/chain'
2
+
3
+ ##
4
+ # The Middleware interface
5
+ #
6
+ # Middleware is invoked around the processing of a message, either
7
+ # on the 'client' (when the message is pushed to a queue) or on the
8
+ # 'server' (when the message is being executed).
9
+ #
10
+ # :method: call
11
+ # - :args: (worker, message)
12
+ # worker is the target object in the method invocation specified by the message
13
+ # message the raw payload as de-queued by the queue
14
+ #
@@ -0,0 +1,19 @@
1
+ module MicroQ
2
+ module Proxy
3
+ class ActionMailer < Base
4
+ def method_missing(meth, *args)
5
+ @args = [@options.delete(:base).to_s, meth.to_s, *args]
6
+
7
+ defaults = [{
8
+ :class => MicroQ::Wrapper::ActionMailer,
9
+ :method => 'perform',
10
+ :args => @args
11
+ }.merge(@options)]
12
+
13
+ defaults << { :when => at } if at
14
+
15
+ MicroQ.push(*defaults)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -18,11 +18,11 @@ module MicroQ
18
18
  @method = meth.to_s
19
19
  @args = args
20
20
 
21
- defaults = [@options.merge(
21
+ defaults = [{
22
22
  :class => klass,
23
23
  :method => method,
24
24
  :args => args
25
- )]
25
+ }.merge(@options)]
26
26
 
27
27
  defaults << { :when => at } if at
28
28
 
@@ -1,3 +1,4 @@
1
1
  require 'micro_q/proxies/base'
2
2
  require 'micro_q/proxies/class'
3
3
  require 'micro_q/proxies/instance'
4
+ require 'micro_q/proxies/action_mailer'
@@ -10,8 +10,8 @@ module MicroQ
10
10
  # item = { 'class' => 'MyWorker', 'args' => [user.id] }
11
11
  #
12
12
  # queue = MicroQ::Queue::Default.new
13
- # queue.push(item) # synchronous push
14
- # queue.async.push(item) # asynchronous push (preferred)
13
+ # queue.push(item) # asynchronous push (preferred)
14
+ # queue.sync_push(item) # synchronous push
15
15
  #
16
16
  # queue.entries
17
17
  # #=> [{'class' => 'MyWorker', 'args' => [32]}]
@@ -32,23 +32,32 @@ module MicroQ
32
32
  end
33
33
 
34
34
  ##
35
- # Push a message item to the queue.
35
+ # Asynchronously push a message item to the queue.
36
+ #
37
+ def push(item, options={})
38
+ async.sync_push(item, options)
39
+ end
40
+
41
+ ##
42
+ # Synchronously push a message item to the queue.
36
43
  # Either push it to the immediate portion of the queue or store it for after when
37
44
  # it should be run with the 'when' option.
38
45
  #
39
46
  # Options:
40
47
  # when: The time/timestamp after which to run the message.
41
48
  #
42
- def push(item, options={})
49
+ def sync_push(item, options={})
43
50
  item, options = before_push(item, options)
44
51
 
45
- if (time = options['when'])
46
- @later.push(
47
- 'when' => time.to_f,
48
- 'worker' => item
49
- )
50
- else
51
- @entries.push(item)
52
+ MicroQ.middleware.client.call(item['class'], item, options) do
53
+ if (time = options['when'])
54
+ @later.push(
55
+ 'when' => time.to_f,
56
+ 'worker' => item
57
+ )
58
+ else
59
+ @entries.push(item)
60
+ end
52
61
  end
53
62
  end
54
63
 
data/lib/micro_q/queue.rb CHANGED
@@ -1 +1,24 @@
1
1
  require 'micro_q/queue/default'
2
+
3
+ ##
4
+ # The Queueing interface
5
+ #
6
+ # The concrete queue implementations will all have something simple in
7
+ # common. The queue stores and returns data. The logic used for
8
+ # selecting the next items to return is critical but not relevant to the
9
+ # rest of the system nor to the interface.
10
+ #
11
+ # :method: push
12
+ # - Add items to the backing data store
13
+ # - :args: (message, options)
14
+ # message is the hash the represents an pushed item
15
+ # options are for items that dont require storing in the message itself
16
+ # but are important in queueing.
17
+ # :method: dequeue
18
+ # - Remove and return items from the data store
19
+ #
20
+ # You are otherwise able to implement this class in any suitable manner.
21
+ # If adhering to the other conventions around data structures, keys, etc,
22
+ # then it will be easier to use the other types of classes required for
23
+ # micro_q to work properly.
24
+ #
data/lib/micro_q/util.rb CHANGED
@@ -13,9 +13,12 @@ module MicroQ
13
13
  end
14
14
  constant
15
15
  rescue
16
- nil
17
16
  end
18
17
 
18
+ ##
19
+ # Copy a hash and convert all keys to strings.
20
+ # Stringifies to infinite hash depth
21
+ #
19
22
  def self.stringify_keys(hash)
20
23
  {}.tap do |result|
21
24
  hash.keys.each do |key|
@@ -25,5 +28,13 @@ module MicroQ
25
28
  end
26
29
  end
27
30
  end
31
+
32
+ ##
33
+ # Attempt to load a library but return nil if it cannot be loaded
34
+ #
35
+ def self.safe_require(lib)
36
+ require lib
37
+ rescue LoadError
38
+ end
28
39
  end
29
40
  end
@@ -1,7 +1,7 @@
1
1
  module MicroQ
2
2
  MAJOR = 0
3
3
  MINOR = 6
4
- POINT = 1
4
+ POINT = 2
5
5
 
6
6
  VERSION = [MAJOR, MINOR, POINT].join('.')
7
7
  end
@@ -1 +1,17 @@
1
1
  require 'micro_q/worker/standard'
2
+
3
+ ##
4
+ # The Worker interface
5
+ #
6
+ # The concrete worker implementation is the member of the chain that
7
+ # actually invokes the middleware chain and executes/performs the given
8
+ # message.
9
+ #
10
+ # :method: perform
11
+ # - Re-hydrates and invokes the methods that the message specifies.
12
+ # - :args: (message)
13
+ # message is the hash the represents an pushed item
14
+ #
15
+ # Based on what keys are pushed to and de-queued from the queue,
16
+ # do the actions that the message specifies.
17
+ #
@@ -0,0 +1,11 @@
1
+ module MicroQ
2
+ module Wrapper
3
+ class ActionMailer
4
+ def perform(klass, method, *args)
5
+ email = MicroQ::Util.constantize(klass).send(method, *args)
6
+
7
+ email.deliver if email.respond_to?(:deliver)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ require 'micro_q/wrappers/action_mailer'
data/lib/micro_q.rb CHANGED
@@ -21,14 +21,14 @@ module MicroQ
21
21
  end
22
22
 
23
23
  def self.push(*args)
24
- manager.queue.async.push(*args)
24
+ manager.queue.push(*args)
25
25
  end
26
26
 
27
27
  private
28
28
 
29
29
  def self.manager
30
30
  @manager ||= begin
31
- Manager::Default.new.tap do |manager|
31
+ config.manager.new.tap do |manager|
32
32
  manager.start!
33
33
  end
34
34
  end
@@ -40,6 +40,7 @@ module MicroQ
40
40
  end
41
41
 
42
42
  require 'micro_q/middleware'
43
+ require 'micro_q/wrappers'
43
44
  require 'micro_q/methods'
44
45
  require 'micro_q/proxies'
45
46
  require 'micro_q/worker'
data/micro_q.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.name = "micro_q"
8
8
  gem.version = MicroQ::VERSION
9
9
  gem.authors = ["Brian Norton"]
10
- gem.email = ["brian.nort@gmail.com"]
10
+ gem.email = "brian.nort@gmail.com"
11
11
  gem.description = ""
12
12
  gem.summary = ""
13
13
  gem.homepage = "http://github.com/bnorton/micro-q"
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^spec/})
18
- gem.require_paths = ["lib"]
18
+ gem.require_paths = %w(lib)
19
19
 
20
20
  gem.add_dependency "celluloid"
21
21
  gem.add_development_dependency "rake"
@@ -23,5 +23,6 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency "timecop"
24
24
  gem.add_development_dependency "psych"
25
25
  gem.add_development_dependency "activerecord", "> 3.2.0"
26
+ gem.add_development_dependency "actionmailer", "> 3.2.0"
26
27
  gem.add_development_dependency "sqlite3-ruby"
27
28
  end
@@ -43,5 +43,17 @@ describe MicroQ::Config do
43
43
  it 'should not have a logfile' do
44
44
  subject.logfile.should == nil
45
45
  end
46
+
47
+ it 'should have the default queue' do
48
+ subject.manager.should == MicroQ::Manager::Default
49
+ end
50
+
51
+ it 'should have the default queue' do
52
+ subject.queue.should == MicroQ::Queue::Default
53
+ end
54
+
55
+ it 'should have the standard worker' do
56
+ subject.worker.should == MicroQ::Worker::Standard
57
+ end
46
58
  end
47
59
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Methods::ActionMailer do
4
+ class MyMailer < ActionMailer::Base
5
+ end
6
+
7
+ describe '.async' do
8
+ let(:async) { -> { MyMailer.async } }
9
+
10
+ it 'should create a mailer proxy' do
11
+ MicroQ::Proxy::ActionMailer.should_receive(:new)
12
+
13
+ async.call
14
+ end
15
+
16
+ it 'should have the class' do
17
+ MicroQ::Proxy::ActionMailer.should_receive(:new).with(hash_including(:class => MicroQ::Wrapper::ActionMailer))
18
+
19
+ async.call
20
+ end
21
+
22
+ it 'should have the base class' do
23
+ MicroQ::Proxy::ActionMailer.should_receive(:new).with(hash_including(:base => MyMailer))
24
+
25
+ async.call
26
+ end
27
+ end
28
+ end
@@ -18,7 +18,7 @@ describe MicroQ::Methods::ActiveRecord, :active_record => true do
18
18
  MicroQ::Proxy::Instance.stub(:new).and_return(@proxy)
19
19
  end
20
20
 
21
- it 'should create a proxy' do
21
+ it 'should create an instance proxy' do
22
22
  MicroQ::Proxy::Instance.should_receive(:new).and_return(@proxy)
23
23
 
24
24
  method.call
@@ -64,15 +64,14 @@ describe MicroQ do
64
64
  end
65
65
 
66
66
  before do
67
- @async = mock(Celluloid::AsyncProxy)
68
- @manager = mock(MicroQ::Manager::Default, :start! => nil, :queue => mock("Queue", :async => @async))
67
+ @manager = mock(MicroQ::Manager::Default, :start! => nil, :queue => mock("Queue"))
69
68
  MicroQ::Manager::Default.stub(:new).and_return(@manager)
70
69
 
71
70
  MicroQ.start
72
71
  end
73
72
 
74
73
  it 'should delegate to the manager\'s queue' do
75
- @async.should_receive(:push).with(*args)
74
+ @manager.queue.should_receive(:push).with(*args)
76
75
 
77
76
  push
78
77
  end
@@ -205,14 +205,14 @@ describe MicroQ::Middleware::Chain do
205
205
  end
206
206
 
207
207
  describe 'defaults' do
208
- [MicroQ::Middleware::Server::Retry].each do |klass|
208
+ [MicroQ::Middleware::Server::Retry, MicroQ::Middleware::Server::Connection].each do |klass|
209
209
  it "should include #{klass}" do
210
210
  subject.server.entries.should include(klass)
211
211
  end
212
212
  end
213
213
 
214
- it 'should be 1 item long' do
215
- subject.server.entries.should have(1).items
214
+ it 'should be 2 items long' do
215
+ subject.server.entries.should have(2).items
216
216
  end
217
217
  end
218
218
  end
@@ -238,29 +238,40 @@ describe MicroQ::Middleware::Chain do
238
238
  class MyWorker
239
239
  end
240
240
 
241
- before do
242
- @retry = mock(MicroQ::Middleware::Server::Retry)
243
- MicroQ::Middleware::Server::Retry.stub(:new).and_return(@retry)
244
- end
245
-
246
241
  describe 'server' do
247
242
  def call
248
- subject.server.call(worker, payload)
243
+ subject.server.call(worker, payload) { }
249
244
  end
250
245
 
251
- it 'should make a new middleware chain' do
252
- subject.server.entries.each do |entry|
253
- entry.should_receive(:new).and_return(mock('entry', :call => nil))
254
- end
246
+ it 'should make a new retry instance' do
247
+ MicroQ::Middleware::Server::Retry.should_receive(:new).and_call_original
248
+
249
+ call
250
+ end
251
+
252
+ it 'should make a new connections instance' do
253
+ MicroQ::Middleware::Server::Connection.should_receive(:new).and_call_original
255
254
 
256
255
  call
257
256
  end
258
257
 
259
- it 'should call each item' do
258
+ it 'should call the retry middleware' do
259
+ @retry = mock(MicroQ::Middleware::Server::Retry)
260
+ MicroQ::Middleware::Server::Retry.stub(:new).and_return(@retry)
261
+
260
262
  @retry.should_receive(:call).with(worker, payload)
261
263
 
262
264
  call
263
265
  end
266
+
267
+ it 'should call the connection middleware' do
268
+ @connection = mock(MicroQ::Middleware::Server::Connection)
269
+ MicroQ::Middleware::Server::Connection.stub(:new).and_return(@connection)
270
+
271
+ @connection.should_receive(:call).with(worker, payload)
272
+
273
+ call
274
+ end
264
275
  end
265
276
  end
266
277
  end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+
4
+ describe MicroQ::Middleware::Server::Connection, :middleware => true do
5
+ describe '#call' do
6
+ let(:foo) { mock("Foo", :bar => nil) }
7
+ let(:block) { -> { foo.bar } }
8
+
9
+ def call
10
+ subject.call @worker, @payload, &block
11
+ end
12
+
13
+ it 'should execute the block' do
14
+ foo.should_receive(:bar)
15
+
16
+ call
17
+ end
18
+
19
+ describe 'active_record' do
20
+ it 'should clear active connections' do
21
+ ActiveRecord::Base.should_receive(:clear_active_connections!)
22
+
23
+ call
24
+ end
25
+
26
+ describe 'when the job raises an exception' do
27
+ let(:block) { -> { raise } }
28
+
29
+ it 'should error' do
30
+ expect {
31
+ call
32
+ }.to raise_error
33
+ end
34
+
35
+ it 'should clear active connections' do
36
+ ActiveRecord::Base.should_receive(:clear_active_connections!)
37
+
38
+ safe(:call)
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe MicroQ::Middleware::Server::Retry, :middleware => true do
4
4
  describe '#call' do
5
5
  let(:foo) { mock("Foo", :bar => nil) }
6
- let(:block) { lambda { foo.bar } }
6
+ let(:block) { -> { foo.bar } }
7
7
 
8
8
  def call
9
9
  subject.call @worker, @payload, &block
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Proxy::ActionMailer do
4
+ let(:method) { lambda {|*args| subject.mail_me(*args) } }
5
+ let(:options) { { :class => MicroQ::Wrapper::ActionMailer, :base => MyMailer } }
6
+
7
+ class MyMailer < ActionMailer::Base
8
+ end
9
+
10
+ subject { MicroQ::Proxy::ActionMailer.new(options) }
11
+
12
+ describe 'method invocations' do
13
+ let(:method) { -> { subject.some_method(1, 2) } }
14
+
15
+ it 'should push the message' do
16
+ MicroQ.should_receive(:push)
17
+
18
+ method.call
19
+ end
20
+
21
+ it 'should have the class' do
22
+ MicroQ.should_receive(:push).with(hash_including(:class => MicroQ::Wrapper::ActionMailer))
23
+
24
+ method.call
25
+ end
26
+
27
+ it 'should have the method' do
28
+ MicroQ.should_receive(:push).with(hash_including(:method => 'perform'))
29
+
30
+ method.call
31
+ end
32
+
33
+ it 'should have the args' do
34
+ MicroQ.should_receive(:push).with(hash_including(:args => ['MyMailer', 'some_method', 1, 2]))
35
+
36
+ method.call
37
+ end
38
+
39
+ describe 'when performing at a specific time' do
40
+ before do
41
+ options[:at] = Time.now + 60
42
+ end
43
+
44
+ it 'should push with the right \'when\' key' do
45
+ MicroQ.should_receive(:push).with(anything, :when => (Time.now + 60).to_i)
46
+
47
+ method.call
48
+ end
49
+ end
50
+ end
51
+ end
@@ -13,17 +13,17 @@ describe MicroQ::Queue::Default do
13
13
  end
14
14
  end
15
15
 
16
- describe '#push' do
17
- let(:item) { { 'class' => 'MyWorker', 'args' => [] } }
16
+ describe '#sync_push' do
17
+ let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
18
18
 
19
19
  it 'should add to the entries' do
20
- subject.push(item)
20
+ subject.sync_push(item)
21
21
 
22
22
  subject.entries.should include(item)
23
23
  end
24
24
 
25
25
  it 'should duplicate the item' do
26
- subject.push(item)
26
+ subject.sync_push(item)
27
27
 
28
28
  before = item.dup
29
29
  subject.entries.should include(before)
@@ -33,11 +33,24 @@ describe MicroQ::Queue::Default do
33
33
  subject.entries.should include(before)
34
34
  end
35
35
 
36
+ describe 'client middleware' do
37
+ it 'should process the middleware chain' do
38
+ MicroQ.middleware.client.should_receive(:call) do |w, payload|
39
+ w.should == 'MyWorker'
40
+
41
+ payload['class'].should == 'MyWorker'
42
+ payload['args'].should == [4]
43
+ end
44
+
45
+ subject.sync_push(item)
46
+ end
47
+ end
48
+
36
49
  describe 'when given the "when" key' do
37
50
  let(:worker) { [item, { 'when' => (Time.now + 100).to_i }] }
38
51
 
39
52
  it 'should add to the later' do
40
- subject.push(*worker)
53
+ subject.sync_push(*worker)
41
54
 
42
55
  subject.later.should include(
43
56
  'when' => (Time.now + 100).to_i,
@@ -46,17 +59,29 @@ describe MicroQ::Queue::Default do
46
59
  end
47
60
 
48
61
  it 'should not be in the entries' do
49
- subject.push(*worker)
62
+ subject.sync_push(*worker)
50
63
 
51
64
  subject.entries.should == []
52
65
  end
66
+
67
+ it 'should process the middleware chain' do
68
+ MicroQ.middleware.client.should_receive(:call) do |w, payload, options|
69
+ w.should == 'MyWorker'
70
+
71
+ payload['class'].should == 'MyWorker'
72
+ payload['args'].should == [4]
73
+ options['when'].should == (Time.now + 100).to_i
74
+ end
75
+
76
+ subject.sync_push(*worker)
77
+ end
53
78
  end
54
79
 
55
80
  describe 'when given the symbol :when key' do
56
81
  let(:worker) { [item, { :when => (Time.now + 100).to_i }] }
57
82
 
58
83
  it 'should add to the later' do
59
- subject.push(*worker)
84
+ subject.sync_push(*worker)
60
85
 
61
86
  subject.later.should include(
62
87
  'when' => (Time.now + 100).to_i,
@@ -65,13 +90,28 @@ describe MicroQ::Queue::Default do
65
90
  end
66
91
 
67
92
  it 'should not be in the entries' do
68
- subject.push(*worker)
93
+ subject.sync_push(*worker)
69
94
 
70
95
  subject.entries.should == []
71
96
  end
72
97
  end
73
98
  end
74
99
 
100
+ describe '#push' do
101
+ let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
102
+
103
+ before do
104
+ @async = mock(Celluloid::ActorProxy)
105
+ subject.stub(:async).and_return(@async)
106
+ end
107
+
108
+ it 'should asynchronously push the item' do
109
+ @async.should_receive(:sync_push).with(*item)
110
+
111
+ subject.push(*item)
112
+ end
113
+ end
114
+
75
115
  describe '#dequeue' do
76
116
  let(:item) { { 'class' => 'MyWorker', 'args' => [] } }
77
117
 
@@ -82,7 +122,7 @@ describe MicroQ::Queue::Default do
82
122
 
83
123
  describe 'when there are entries' do
84
124
  before do
85
- subject.push(item)
125
+ subject.sync_push(item)
86
126
  end
87
127
 
88
128
  it 'should return the item' do
@@ -98,7 +138,7 @@ describe MicroQ::Queue::Default do
98
138
 
99
139
  describe 'when there are items to be processed later' do
100
140
  before do
101
- subject.push(item, 'when' => (Time.now + 5).to_i)
141
+ subject.sync_push(item, 'when' => (Time.now + 5).to_i)
102
142
  end
103
143
 
104
144
  it 'should not return the item' do
@@ -137,10 +177,10 @@ describe MicroQ::Queue::Default do
137
177
  end
138
178
 
139
179
  before do
140
- items.first(4).each {|item| subject.push(item) }
141
- subject.push(items.last, 'when' => (Time.now - 2).to_i)
180
+ items.first(4).each {|item| subject.sync_push(item) }
181
+ subject.sync_push(items.last, 'when' => (Time.now - 2).to_i)
142
182
 
143
- subject.push(*later_item)
183
+ subject.sync_push(*later_item)
144
184
  end
145
185
 
146
186
  it 'should return all the available items' do
@@ -48,4 +48,29 @@ describe MicroQ::Util do
48
48
  }
49
49
  end
50
50
  end
51
+
52
+ describe '.safe_require' do
53
+ def req(lib)
54
+ MicroQ::Util.safe_require(lib)
55
+ end
56
+
57
+ it 'should require the file' do
58
+ defined?(CMath).should == nil
59
+ req('cmath')
60
+
61
+ defined?(CMath).should == 'constant'
62
+ end
63
+
64
+ describe 'when the library is not available' do
65
+ it 'should not error' do
66
+ expect {
67
+ req('foo-bar-baz')
68
+ }.not_to raise_error
69
+ end
70
+
71
+ it 'should be nil' do
72
+ req('foo-bar-baz').should == nil
73
+ end
74
+ end
75
+ end
51
76
  end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Wrapper::ActionMailer do
4
+ class MyMailer
5
+ def self.mail_me
6
+ end
7
+ end
8
+
9
+ describe '#perform' do
10
+ let(:mail) { mock("email", :deliver => nil) }
11
+
12
+ def perform
13
+ subject.perform('MyMailer', 'mail_me', 1, 2)
14
+ end
15
+
16
+ before do
17
+ MyMailer.stub(:mail_me).and_return(mail)
18
+ end
19
+
20
+ it 'should call the mailer method' do
21
+ MyMailer.should_receive(:mail_me).and_return(mail)
22
+
23
+ perform
24
+ end
25
+
26
+ it 'should pass the arguments' do
27
+ MyMailer.should_receive(:mail_me).with(1, 2).and_return(mail)
28
+
29
+ perform
30
+ end
31
+
32
+ it 'should deliver the email' do
33
+ mail.should_receive(:deliver)
34
+
35
+ perform
36
+ end
37
+
38
+ describe 'when the email does not work' do
39
+ let(:mail) { mock("email without deliver") }
40
+
41
+ before do
42
+ mail.stub(:respond_to?).with(:deliver).and_return(false)
43
+ end
44
+
45
+ it 'should not error' do
46
+ expect {
47
+ perform
48
+ }.not_to raise_error
49
+ end
50
+
51
+ it 'should not deliver the message' do
52
+ mail.should_not_receive(:deliver)
53
+
54
+ perform
55
+ end
56
+ end
57
+ end
58
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: micro_q
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-04 00:00:00.000000000 Z
12
+ date: 2013-02-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>'
108
108
  - !ruby/object:Gem::Version
109
109
  version: 3.2.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: actionmailer
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>'
116
+ - !ruby/object:Gem::Version
117
+ version: 3.2.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>'
124
+ - !ruby/object:Gem::Version
125
+ version: 3.2.0
110
126
  - !ruby/object:Gem::Dependency
111
127
  name: sqlite3-ruby
112
128
  requirement: !ruby/object:Gem::Requirement
@@ -124,8 +140,7 @@ dependencies:
124
140
  - !ruby/object:Gem::Version
125
141
  version: '0'
126
142
  description: ''
127
- email:
128
- - brian.nort@gmail.com
143
+ email: brian.nort@gmail.com
129
144
  executables: []
130
145
  extensions: []
131
146
  extra_rdoc_files: []
@@ -142,13 +157,16 @@ files:
142
157
  - lib/micro_q/manager.rb
143
158
  - lib/micro_q/manager/default.rb
144
159
  - lib/micro_q/methods.rb
160
+ - lib/micro_q/methods/action_mailer.rb
145
161
  - lib/micro_q/methods/active_record.rb
146
162
  - lib/micro_q/methods/class.rb
147
163
  - lib/micro_q/methods/instance.rb
148
164
  - lib/micro_q/middleware.rb
149
165
  - lib/micro_q/middleware/chain.rb
166
+ - lib/micro_q/middleware/server/connection.rb
150
167
  - lib/micro_q/middleware/server/retry.rb
151
168
  - lib/micro_q/proxies.rb
169
+ - lib/micro_q/proxies/action_mailer.rb
152
170
  - lib/micro_q/proxies/base.rb
153
171
  - lib/micro_q/proxies/class.rb
154
172
  - lib/micro_q/proxies/instance.rb
@@ -158,16 +176,21 @@ files:
158
176
  - lib/micro_q/version.rb
159
177
  - lib/micro_q/worker.rb
160
178
  - lib/micro_q/worker/standard.rb
179
+ - lib/micro_q/wrappers.rb
180
+ - lib/micro_q/wrappers/action_mailer.rb
161
181
  - micro_q.gemspec
162
182
  - spec/helpers/methods_examples.rb
163
183
  - spec/lib/config_spec.rb
164
184
  - spec/lib/manager/default_spec.rb
185
+ - spec/lib/methods/action_mailer_spec.rb
165
186
  - spec/lib/methods/active_record_spec.rb
166
187
  - spec/lib/methods/class_spec.rb
167
188
  - spec/lib/methods/instance_spec.rb
168
189
  - spec/lib/micro_q_spec.rb
169
190
  - spec/lib/middleware/chain_spec.rb
191
+ - spec/lib/middleware/server/connection_spec.rb
170
192
  - spec/lib/middleware/server/retry_spec.rb
193
+ - spec/lib/proxies/action_mailer_spec.rb
171
194
  - spec/lib/proxies/base_spec.rb
172
195
  - spec/lib/proxies/class_spec.rb
173
196
  - spec/lib/proxies/instance_spec.rb
@@ -175,6 +198,7 @@ files:
175
198
  - spec/lib/util_spec.rb
176
199
  - spec/lib/worker/standard_spec.rb
177
200
  - spec/spec_helper.rb
201
+ - spec/wrappers/action_mailer_spec.rb
178
202
  homepage: http://github.com/bnorton/micro-q
179
203
  licenses: []
180
204
  post_install_message:
@@ -203,12 +227,15 @@ test_files:
203
227
  - spec/helpers/methods_examples.rb
204
228
  - spec/lib/config_spec.rb
205
229
  - spec/lib/manager/default_spec.rb
230
+ - spec/lib/methods/action_mailer_spec.rb
206
231
  - spec/lib/methods/active_record_spec.rb
207
232
  - spec/lib/methods/class_spec.rb
208
233
  - spec/lib/methods/instance_spec.rb
209
234
  - spec/lib/micro_q_spec.rb
210
235
  - spec/lib/middleware/chain_spec.rb
236
+ - spec/lib/middleware/server/connection_spec.rb
211
237
  - spec/lib/middleware/server/retry_spec.rb
238
+ - spec/lib/proxies/action_mailer_spec.rb
212
239
  - spec/lib/proxies/base_spec.rb
213
240
  - spec/lib/proxies/class_spec.rb
214
241
  - spec/lib/proxies/instance_spec.rb
@@ -216,4 +243,5 @@ test_files:
216
243
  - spec/lib/util_spec.rb
217
244
  - spec/lib/worker/standard_spec.rb
218
245
  - spec/spec_helper.rb
246
+ - spec/wrappers/action_mailer_spec.rb
219
247
  has_rdoc: