micro_q 0.6.1 → 0.6.2

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.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: