micro_q 0.6.3 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
1
  --color
2
- --format progress
2
+ --format documentation
@@ -10,13 +10,15 @@ module MicroQ
10
10
  #
11
11
  def initialize
12
12
  @data = {
13
- 'workers' => 3,
13
+ 'workers' => 5,
14
14
  'timeout' => 120,
15
15
  'interval' => 5,
16
16
  'middleware' => Middleware::Chain.new,
17
17
  'manager' => Manager::Default,
18
18
  'worker' => Worker::Standard,
19
- 'queue' => Queue::Default
19
+ 'queue' => Queue::Default,
20
+ 'redis_pool' => { :size => 15, :timeout => 1 },
21
+ 'redis' => { :host => 'localhost', :port => 6379 }
20
22
  }
21
23
  end
22
24
 
@@ -25,13 +25,15 @@ module MicroQ
25
25
  end
26
26
 
27
27
  def start
28
- if (messages = queue.dequeue).any?
28
+ count = workers.idle_size
29
+
30
+ if (messages = queue.dequeue(count)).any?
29
31
  messages.each do |message|
30
32
  workers.perform!(message)
31
33
  end
32
34
  end
33
35
 
34
- after(5) { start }
36
+ after(2) { start }
35
37
  end
36
38
  end
37
39
  end
@@ -1,5 +1,11 @@
1
1
  module MicroQ
2
2
  module Methods
3
+ ##
4
+ # Methods that are added to ActionMailer instances
5
+ #
6
+ # When mailing asynchronously, the deliver method needs to be
7
+ # called which means a custom wrapper.
8
+ #
3
9
  module ActionMailer
4
10
  def async
5
11
  MicroQ::Proxy::ActionMailer.new(
@@ -1,5 +1,6 @@
1
1
  # add Class and Instance methods first then
2
2
  # override with additional extensions
3
+
3
4
  require 'micro_q/methods/class'
4
5
  require 'micro_q/methods/instance'
5
6
  require 'micro_q/methods/active_record'
@@ -5,6 +5,9 @@ module MicroQ
5
5
  # Handles messages that should be run immediately as well as messages that
6
6
  # should be run at some specified time in the future.
7
7
  #
8
+ # When shutting down, if the MicroQ.config.queue_file is defined and accessible,
9
+ # the messages in the queue will be written for persistence.
10
+ #
8
11
  # Usage:
9
12
  #
10
13
  # item = { 'class' => 'MyWorker', 'args' => [user.id] }
@@ -29,6 +32,8 @@ module MicroQ
29
32
  def initialize
30
33
  @entries = []
31
34
  @later = []
35
+
36
+ load_queues
32
37
  end
33
38
 
34
39
  ##
@@ -39,17 +44,18 @@ module MicroQ
39
44
  end
40
45
 
41
46
  ##
42
- # Synchronously push a message item to the queue.
47
+ # Asynchronously push a message item to the queue.
43
48
  # Either push it to the immediate portion of the queue or store it for after when
44
- # it should be run with the 'when' option.
49
+ # it should be run with the :when option.
45
50
  #
46
51
  # Options:
47
52
  # when: The time/timestamp after which to run the message.
48
53
  #
49
54
  def sync_push(item, options={})
50
- item, options = before_push(item, options)
55
+ item, options = MicroQ::Util.stringify(item, options)
56
+ klass = item['class'] = item['class'].to_s
51
57
 
52
- MicroQ.middleware.client.call(item['class'], item, options) do
58
+ MicroQ.middleware.client.call(klass, item, options) do
53
59
  if (time = options['when'])
54
60
  @later.push(
55
61
  'when' => time.to_f,
@@ -63,11 +69,15 @@ module MicroQ
63
69
 
64
70
  ##
65
71
  # Remove and return all available messages.
72
+ # Optionally give a limit and return only limit number of messages
66
73
  #
67
- def dequeue
74
+ def dequeue(limit = 30)
75
+ return [] if limit == 0
76
+
77
+ idx = 0
68
78
  [].tap do |items|
69
79
  entries.each do |entry|
70
- items << entry
80
+ items << entry unless (idx += 1) > limit
71
81
  end if entries.any?
72
82
 
73
83
  items.each {|i| entries.delete(i) }
@@ -76,7 +86,7 @@ module MicroQ
76
86
 
77
87
  if available.any?
78
88
  available.each do |entry|
79
- items << entry['worker']
89
+ items << entry['worker'] unless (idx += 1) > limit
80
90
  end
81
91
 
82
92
  available.each {|a| later.delete(a) }
@@ -84,15 +94,36 @@ module MicroQ
84
94
  end
85
95
  end
86
96
 
97
+ ##
98
+ # Stop the queue and store items for later
99
+ #
100
+ def stop
101
+ File.open(queue_file, 'w+') do |f|
102
+ f.write(YAML.dump(entries))
103
+ end if queue_file?
104
+
105
+ terminate
106
+ end
107
+
87
108
  private
88
109
 
89
110
  ##
90
- # Duplicate the given items and stringify the keys.
111
+ # Parse the entries back into the queue from the filesystem
91
112
  #
92
- def before_push(args, options)
93
- [MicroQ::Util.stringify_keys(args),
94
- MicroQ::Util.stringify_keys(options)
95
- ]
113
+ def load_queues
114
+ if queue_file? && File.exists?(queue_file)
115
+ @entries = YAML.load(File.new(queue_file).read)
116
+
117
+ File.unlink(queue_file)
118
+ end
119
+ end
120
+
121
+ def queue_file
122
+ @queue_file ||= MicroQ.config.queue_file
123
+ end
124
+
125
+ def queue_file?
126
+ queue_file && File.exists?(File.dirname(queue_file))
96
127
  end
97
128
  end
98
129
  end
@@ -0,0 +1,97 @@
1
+ module MicroQ
2
+ module Queue
3
+ class Redis
4
+ include Celluloid
5
+
6
+ QUEUES = {
7
+ :entries => 'micro_q:queue:entries',
8
+ :later => 'micro_q:queue:later'
9
+ }.freeze
10
+
11
+ ##
12
+ # All of the items in the entries queue
13
+ #
14
+ def entries
15
+ MicroQ.redis do |r|
16
+ r.lrange(QUEUES[:entries], 0, -1)
17
+ end.collect(&MicroQ::Util.json_parse)
18
+ end
19
+
20
+ ##
21
+ # All of the items in the later queue
22
+ #
23
+ def later
24
+ MicroQ.redis do |r|
25
+ r.zrangebyscore(QUEUES[:later], '-inf', '+inf')
26
+ end.collect(&MicroQ::Util.json_parse)
27
+ end
28
+
29
+ ##
30
+ # Asynchronously push a message item to the queue.
31
+ # Either push it to the immediate portion of the queue (a Redis list)
32
+ # or store it in the later queue (a Redis sorted-set). The message will
33
+ # be available for dequeue after the :when time passes.
34
+ #
35
+ # Options:
36
+ # when: The time/timestamp after which to run the message.
37
+ #
38
+ def push(item, options = {})
39
+ async.sync_push(item, options)
40
+ end
41
+
42
+ def sync_push(item, options = {})
43
+ item, options = MicroQ::Util.stringify(item, options)
44
+ klass = item['class'] = item['class'].to_s
45
+
46
+ MicroQ.middleware.client.call(klass, item, options) do
47
+ json = JSON.dump(item)
48
+
49
+ MicroQ.redis do |r|
50
+ if (time = options['when'])
51
+ r.zadd(QUEUES[:later], time.to_f, json)
52
+ else
53
+ r.lpush(QUEUES[:entries], json)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ ##
60
+ # Remove and return up to :limit number of items from the
61
+ # entries queue. Move any available items from the later
62
+ # queue into the entries for future dequeue.
63
+ #
64
+ def dequeue(limit = 30)
65
+ idx, items = 0, []
66
+
67
+ fetch(limit).tap do |(e, later)|
68
+ e.each {|item| items << item unless (idx += 1) > limit }
69
+
70
+ MicroQ.redis do |r|
71
+ ((e - items) + later).each {|l| r.rpush(QUEUES[:entries], l)}
72
+ end
73
+ end
74
+
75
+ items.collect(&MicroQ::Util.json_parse)
76
+ end
77
+
78
+ private
79
+
80
+ def fetch(limit)
81
+ return [] unless limit > 0
82
+
83
+ time = Time.now.to_f
84
+
85
+ MicroQ.redis do |r|
86
+ [r.multi {
87
+ limit.times.collect { r.rpop(QUEUES[:entries]) }
88
+ }.compact,
89
+ r.multi {
90
+ r.zrangebyscore(QUEUES[:later], '-inf', time)
91
+ r.zremrangebyscore(QUEUES[:later], '-inf', time)
92
+ }.first]
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
data/lib/micro_q/queue.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'micro_q/queue/default'
2
+ require 'micro_q/queue/redis'
2
3
 
3
4
  ##
4
5
  # The Queueing interface
@@ -14,8 +15,13 @@ require 'micro_q/queue/default'
14
15
  # message is the hash the represents an pushed item
15
16
  # options are for items that dont require storing in the message itself
16
17
  # but are important in queueing.
18
+ #
17
19
  # :method: dequeue
18
- # - Remove and return items from the data store
20
+ # - Remove and return items from the data store (limited to n items)
21
+ # - :args: (limit = 30) optionally return no more than.
22
+ #
23
+ # :method: stop
24
+ # - Perform any finalizing tasks (e.g. persist the queue)
19
25
  #
20
26
  # You are otherwise able to implement this class in any suitable manner.
21
27
  # If adhering to the other conventions around data structures, keys, etc,
@@ -0,0 +1,15 @@
1
+ module MicroQ
2
+ def self.redis
3
+ redis_connection.with do |r|
4
+ yield r
5
+ end
6
+ end
7
+
8
+ def self.redis_connection
9
+ @@redis_connection ||= begin
10
+ ::ConnectionPool.new(config.redis_pool) do
11
+ ::Redis.new(config.redis)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/micro_q/util.rb CHANGED
@@ -15,6 +15,16 @@ module MicroQ
15
15
  rescue
16
16
  end
17
17
 
18
+ def self.json_parse
19
+ @@json_parse ||= proc {|entry| JSON.parse(entry) }
20
+ end
21
+
22
+ def self.stringify(*args)
23
+ args.collect do |a|
24
+ stringify_keys(a)
25
+ end
26
+ end
27
+
18
28
  ##
19
29
  # Copy a hash and convert all keys to strings.
20
30
  # Stringifies to infinite hash depth
@@ -1,7 +1,7 @@
1
1
  module MicroQ
2
2
  MAJOR = 0
3
3
  MINOR = 6
4
- POINT = 3
4
+ POINT = 5
5
5
 
6
6
  VERSION = [MAJOR, MINOR, POINT].join('.')
7
7
  end
data/lib/micro_q.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'celluloid'
2
+ require 'connection_pool'
3
+ require 'redis'
4
+
2
5
  require 'micro_q/util'
3
6
  require 'micro_q/config'
4
7
  require 'micro_q/manager'
@@ -45,3 +48,5 @@ require 'micro_q/methods'
45
48
  require 'micro_q/proxies'
46
49
  require 'micro_q/worker'
47
50
  require 'micro_q/queue'
51
+
52
+ require 'micro_q/redis'
data/micro_q.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'micro_q/version'
4
+ require "micro_q/version"
5
5
 
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "micro_q"
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = %w(lib)
19
19
 
20
20
  gem.add_dependency "celluloid"
21
+ gem.add_dependency "redis"
22
+ gem.add_dependency "connection_pool"
21
23
  gem.add_development_dependency "rake"
22
24
  gem.add_development_dependency "rspec"
23
25
  gem.add_development_dependency "timecop"
@@ -25,4 +27,5 @@ Gem::Specification.new do |gem|
25
27
  gem.add_development_dependency "activerecord", "> 3.2.0"
26
28
  gem.add_development_dependency "actionmailer", "> 3.2.0"
27
29
  gem.add_development_dependency "sqlite3-ruby"
30
+ gem.add_development_dependency "mock_redis"
28
31
  end
@@ -0,0 +1,37 @@
1
+ shared_examples_for 'Queue#sync_push' do
2
+ it 'should add to the entries' do
3
+ subject.sync_push(item)
4
+
5
+ subject.entries.should include(item)
6
+ end
7
+
8
+ it 'should stringify the class' do
9
+ subject.sync_push(:class => MyWorker)
10
+
11
+ subject.entries.should include('class' => 'MyWorker')
12
+ end
13
+
14
+ it 'should duplicate the item' do
15
+ subject.sync_push(item)
16
+
17
+ before = item.dup
18
+ subject.entries.should include(before)
19
+
20
+ item[:key] = 'new-value'
21
+ subject.entries.should_not include(item)
22
+ subject.entries.should include(before)
23
+ end
24
+
25
+ describe 'client middleware' do
26
+ it 'should process the middleware chain' do
27
+ MicroQ.middleware.client.should_receive(:call) do |w, payload|
28
+ w.should == 'MyWorker'
29
+
30
+ payload['class'].should == 'MyWorker'
31
+ payload['args'].should == [4]
32
+ end
33
+
34
+ subject.sync_push(item)
35
+ end
36
+ end
37
+ end
@@ -24,8 +24,8 @@ describe MicroQ::Config do
24
24
  describe 'defaults' do
25
25
  subject { MicroQ.config }
26
26
 
27
- it 'should have 3 workers' do
28
- subject.workers.should == 3
27
+ it 'should have 5 workers' do
28
+ subject.workers.should == 5
29
29
  end
30
30
 
31
31
  it 'should have a 5 second interval' do
@@ -44,6 +44,14 @@ describe MicroQ::Config do
44
44
  subject.logfile.should == nil
45
45
  end
46
46
 
47
+ it 'should have a redis pool config' do
48
+ subject.redis_pool.should == { :size => 15, :timeout => 1 }
49
+ end
50
+
51
+ it 'should have a redis config' do
52
+ subject.redis.should == { :host => 'localhost', :port => 6379 }
53
+ end
54
+
47
55
  it 'should have the default queue' do
48
56
  subject.manager.should == MicroQ::Manager::Default
49
57
  end
@@ -53,10 +53,16 @@ describe MicroQ::Manager::Default do
53
53
  @queue = mock(MicroQ::Queue::Default, :dequeue => [@item, @other_item])
54
54
  MicroQ::Queue::Default.stub(:new).and_return(@queue)
55
55
 
56
- @pool = mock(Celluloid::PoolManager)
56
+ @pool = mock(Celluloid::PoolManager, :idle_size => 1234, :perform! => nil)
57
57
  MicroQ::Worker::Standard.stub(:pool).and_return(@pool)
58
58
  end
59
59
 
60
+ it 'should dequeue the number of free workers' do
61
+ @queue.should_receive(:dequeue).with(1234)
62
+
63
+ subject.start
64
+ end
65
+
60
66
  it 'should perform the items' do
61
67
  @pool.should_receive(:perform!).with(@item) do
62
68
  @pool.should_receive(:perform!).with(@other_item)
@@ -1,6 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MicroQ::Queue::Default do
4
+ let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
5
+
4
6
  describe '#entries' do
5
7
  it 'should be empty' do
6
8
  subject.entries.should == []
@@ -14,37 +16,7 @@ describe MicroQ::Queue::Default do
14
16
  end
15
17
 
16
18
  describe '#sync_push' do
17
- let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
18
-
19
- it 'should add to the entries' do
20
- subject.sync_push(item)
21
-
22
- subject.entries.should include(item)
23
- end
24
-
25
- it 'should duplicate the item' do
26
- subject.sync_push(item)
27
-
28
- before = item.dup
29
- subject.entries.should include(before)
30
-
31
- item[:key] = 'new-value'
32
- subject.entries.should_not include(item)
33
- subject.entries.should include(before)
34
- end
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
19
+ it_behaves_like 'Queue#sync_push'
48
20
 
49
21
  describe 'when given the "when" key' do
50
22
  let(:worker) { [item, { 'when' => (Time.now + 100).to_i }] }
@@ -98,8 +70,6 @@ describe MicroQ::Queue::Default do
98
70
  end
99
71
 
100
72
  describe '#push' do
101
- let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
102
-
103
73
  before do
104
74
  @async = mock(Celluloid::ActorProxy)
105
75
  subject.stub(:async).and_return(@async)
@@ -193,6 +163,100 @@ describe MicroQ::Queue::Default do
193
163
  subject.entries.should == []
194
164
  subject.later.should == [later_item[1].merge('worker' => later_item[0])]
195
165
  end
166
+
167
+ describe 'when limited to a certain number' do
168
+ it 'should return all the available items' do
169
+ subject.dequeue(2).sort {|x, y| x['args'][0] <=> y['args'][0] }.should == items.first(2)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ describe '#stop' do
176
+ let(:file_name) { '/some/file/queue.yml' }
177
+
178
+ describe 'when there are items in the queue' do
179
+ let(:other_item) { { 'class' => 'MyWorker', 'args' => ['hello'] } }
180
+
181
+ before do
182
+ MicroQ.configure {|c| c.queue_file = file_name }
183
+
184
+ @file = mock(File, :write => nil)
185
+ File.stub(:open).with(file_name, 'w+').and_yield(@file)
186
+ File.stub(:exists?).and_return(false)
187
+ File.stub(:exists?).with(File.dirname(file_name)).and_return(true)
188
+
189
+ subject.push(item)
190
+ subject.push(other_item)
191
+ end
192
+
193
+ it 'should create the target file' do
194
+ File.should_receive(:open).with(file_name, 'w+')
195
+
196
+ subject.stop
197
+ end
198
+
199
+ it 'should write the entries' do
200
+ @file.should_receive(:write).with(YAML.dump([item, other_item]))
201
+
202
+ subject.stop
203
+ end
204
+
205
+ describe 'when the file directory does not exist' do
206
+ before do
207
+ File.stub(:exists?).with(File.dirname(file_name)).and_return(false)
208
+ end
209
+
210
+ it 'should not write the file' do
211
+ File.should_not_receive(:open)
212
+
213
+ subject.stop
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ describe '.new' do
220
+ let!(:file_name) { '/some/other/file/queue.yml' }
221
+ let(:queue) { -> { subject } }
222
+
223
+ before do
224
+ MicroQ.configure {|c| c.queue_file = file_name }
225
+ end
226
+
227
+ describe 'when there are persisted queue items' do
228
+ before do
229
+ File.stub(:exists?).with(File.dirname(file_name)).and_return(true)
230
+ File.stub(:exists?).with(file_name).and_return(true)
231
+ File.stub_chain(:new, :read).and_return(YAML.dump([item]))
232
+
233
+ File.stub(:unlink)
234
+ end
235
+
236
+ it 'should check the file existence' do
237
+ File.should_receive(:exists?).with(File.dirname(file_name)).and_return(true)
238
+ File.should_receive(:exists?).with(file_name).and_return(false)
239
+
240
+ queue.call
241
+ end
242
+
243
+ it 'should open the file' do
244
+ File.should_receive(:new).with(file_name)
245
+
246
+ queue.call
247
+ end
248
+
249
+ it 'should remove the file' do
250
+ File.should_receive(:unlink).with(file_name)
251
+
252
+ queue.call
253
+ end
254
+
255
+ it 'should read the file to place the items in the queue' do
256
+ queue.call
257
+
258
+ subject.entries.should == [item]
259
+ end
196
260
  end
197
261
  end
198
262
  end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroQ::Queue::Redis do
4
+ let(:item) { { 'class' => 'MyWorker', 'args' => [4] } }
5
+
6
+ describe '#sync_push' do
7
+ it_behaves_like 'Queue#sync_push'
8
+
9
+ describe 'when given the "when" key' do
10
+ let(:worker) { [item, { 'when' => (Time.now + 100).to_i }] }
11
+
12
+ it 'should add to the later' do
13
+ subject.sync_push(*worker)
14
+
15
+ subject.later.should include(item)
16
+ end
17
+
18
+ it 'should not be in the entries' do
19
+ subject.sync_push(*worker)
20
+
21
+ subject.entries.should == []
22
+ end
23
+
24
+ it 'should process the middleware chain' do
25
+ MicroQ.middleware.client.should_receive(:call) do |w, payload, options|
26
+ w.should == 'MyWorker'
27
+
28
+ payload['class'].should == 'MyWorker'
29
+ payload['args'].should == [4]
30
+ options['when'].should == (Time.now + 100).to_i
31
+ end
32
+
33
+ subject.sync_push(*worker)
34
+ end
35
+ end
36
+
37
+ describe 'when given the symbol :when key' do
38
+ let(:worker) { [item, { :when => (Time.now + 100).to_i }] }
39
+
40
+ it 'should add to the later' do
41
+ subject.sync_push(*worker)
42
+
43
+ subject.later.should include(item)
44
+ end
45
+
46
+ it 'should not be in the entries' do
47
+ subject.sync_push(*worker)
48
+
49
+ subject.entries.should == []
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#push' do
55
+ before do
56
+ @async = mock(Celluloid::ActorProxy)
57
+ subject.stub(:async).and_return(@async)
58
+ end
59
+
60
+ it 'should asynchronously push the item' do
61
+ @async.should_receive(:sync_push).with(*item)
62
+
63
+ subject.push(*item)
64
+ end
65
+ end
66
+
67
+ describe '#dequeue' do
68
+ let(:item) { { 'class' => 'MyWorker', 'args' => [] } }
69
+
70
+ class MyWorker
71
+ def perform(*)
72
+ end
73
+ end
74
+
75
+ describe 'when there are entries' do
76
+ before do
77
+ subject.sync_push(item)
78
+ end
79
+
80
+ it 'should return the item' do
81
+ subject.dequeue.should == [item]
82
+ end
83
+
84
+ it 'should remove the item from the list' do
85
+ subject.dequeue
86
+
87
+ subject.entries.should_not include(item)
88
+ end
89
+ end
90
+
91
+ describe 'when there are items to be processed later' do
92
+ before do
93
+ subject.sync_push(item, 'when' => (Time.now + 5).to_i)
94
+ end
95
+
96
+ it 'should not return the item' do
97
+ subject.dequeue.should == []
98
+ end
99
+
100
+ it 'should not remove the item' do
101
+ subject.dequeue
102
+
103
+ subject.later.should == [item]
104
+ end
105
+
106
+ describe 'when the item is in the past' do
107
+ let(:queue_name) { MicroQ::Queue::Redis::QUEUES[:later] }
108
+
109
+ before do
110
+ MicroQ.redis {|r| r.zadd(queue_name, (Time.now - 2).to_i, item.to_json) } # update its score
111
+ subject.dequeue # move scheduled items to entries
112
+ end
113
+
114
+ it 'should return the item' do
115
+ subject.dequeue.should == [item]
116
+ end
117
+
118
+ it 'should remove the item from the list' do
119
+ subject.dequeue
120
+
121
+ subject.later.should == []
122
+ end
123
+ end
124
+ end
125
+
126
+ describe 'when there are many items' do
127
+ let(:later_item) { [item.dup.tap {|it| it['args'] = %w(special) }, 'when' => (Time.now + 5).to_i] }
128
+ let(:items) do
129
+ 5.times.collect {|i|
130
+ item.dup.tap {|it| it['args'] = [i]}
131
+ }
132
+ end
133
+
134
+ before do
135
+ items.first(4).each {|item| subject.sync_push(item) }
136
+ subject.sync_push(items.last, 'when' => (Time.now - 2).to_i)
137
+
138
+ subject.sync_push(*later_item)
139
+ end
140
+
141
+ it 'should return all the available items and move the schedule item' do
142
+ subject.dequeue.sort {|x, y| x['args'][0] <=> y['args'][0] }.should == items.first(4)
143
+ subject.entries.should include(items.last)
144
+
145
+ subject.dequeue.should == items.last(1)
146
+ end
147
+
148
+ it 'should remove the items' do
149
+ 2.times { subject.dequeue }
150
+
151
+ subject.entries.should == []
152
+ subject.later.should == [later_item[0]]
153
+ end
154
+
155
+ describe 'when limited to a certain number' do
156
+ it 'should return all the available items' do
157
+ subject.dequeue(2).sort {|x, y| x['args'][0] <=> y['args'][0] }.should == items.first(2)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,18 @@
1
1
  require 'micro_q'
2
2
  require 'time'
3
3
  require 'timecop'
4
- require 'celluloid'
4
+ require 'mock_redis'
5
5
 
6
- require 'helpers/methods_examples'
6
+ [:methods, :queues].
7
+ each {|path| require "helpers/#{path}_examples" }
7
8
 
8
9
  Celluloid.logger = nil
9
10
 
11
+ # Don't require an actual Redis instance for testing
12
+ silence_warnings do
13
+ Redis = MockRedis
14
+ end
15
+
10
16
  RSpec.configure do |config|
11
17
  config.treat_symbols_as_metadata_keys_with_true_values = true
12
18
  config.run_all_when_everything_filtered = true
@@ -15,6 +21,7 @@ RSpec.configure do |config|
15
21
 
16
22
  config.before :each do
17
23
  MicroQ.send :clear
24
+ MicroQ.redis {|r| r.flushdb }
18
25
  end
19
26
 
20
27
  config.before :each, :active_record => true do
@@ -31,7 +38,7 @@ RSpec.configure do |config|
31
38
  );
32
39
  SQL
33
40
 
34
- # ** Transactional fixtures. **
41
+ # ** Transactional fixtures. BEGIN **
35
42
  @_db.transaction
36
43
 
37
44
  ActiveRecord::Base.establish_connection(
@@ -41,6 +48,7 @@ RSpec.configure do |config|
41
48
  end
42
49
 
43
50
  config.after :each, :active_record => true do
51
+ # ** Transactional fixtures. END **
44
52
  @_db.rollback
45
53
  end
46
54
 
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.3
4
+ version: 0.6.5
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-11 00:00:00.000000000 Z
12
+ date: 2013-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid
@@ -27,6 +27,38 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: redis
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: connection_pool
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  - !ruby/object:Gem::Dependency
31
63
  name: rake
32
64
  requirement: !ruby/object:Gem::Requirement
@@ -139,6 +171,22 @@ dependencies:
139
171
  - - ! '>='
140
172
  - !ruby/object:Gem::Version
141
173
  version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: mock_redis
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
142
190
  description: ''
143
191
  email: brian.nort@gmail.com
144
192
  executables: []
@@ -172,6 +220,8 @@ files:
172
220
  - lib/micro_q/proxies/instance.rb
173
221
  - lib/micro_q/queue.rb
174
222
  - lib/micro_q/queue/default.rb
223
+ - lib/micro_q/queue/redis.rb
224
+ - lib/micro_q/redis.rb
175
225
  - lib/micro_q/util.rb
176
226
  - lib/micro_q/version.rb
177
227
  - lib/micro_q/worker.rb
@@ -180,6 +230,7 @@ files:
180
230
  - lib/micro_q/wrappers/action_mailer.rb
181
231
  - micro_q.gemspec
182
232
  - spec/helpers/methods_examples.rb
233
+ - spec/helpers/queues_examples.rb
183
234
  - spec/lib/config_spec.rb
184
235
  - spec/lib/manager/default_spec.rb
185
236
  - spec/lib/methods/action_mailer_spec.rb
@@ -195,10 +246,11 @@ files:
195
246
  - spec/lib/proxies/class_spec.rb
196
247
  - spec/lib/proxies/instance_spec.rb
197
248
  - spec/lib/queue/default_spec.rb
249
+ - spec/lib/queue/redis_spec.rb
198
250
  - spec/lib/util_spec.rb
199
251
  - spec/lib/worker/standard_spec.rb
252
+ - spec/lib/wrappers/action_mailer_spec.rb
200
253
  - spec/spec_helper.rb
201
- - spec/wrappers/action_mailer_spec.rb
202
254
  homepage: http://github.com/bnorton/micro-q
203
255
  licenses: []
204
256
  post_install_message:
@@ -211,20 +263,27 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
263
  - - ! '>='
212
264
  - !ruby/object:Gem::Version
213
265
  version: '0'
266
+ segments:
267
+ - 0
268
+ hash: -938614876978684754
214
269
  required_rubygems_version: !ruby/object:Gem::Requirement
215
270
  none: false
216
271
  requirements:
217
272
  - - ! '>='
218
273
  - !ruby/object:Gem::Version
219
274
  version: '0'
275
+ segments:
276
+ - 0
277
+ hash: -938614876978684754
220
278
  requirements: []
221
279
  rubyforge_project:
222
- rubygems_version: 1.8.25
280
+ rubygems_version: 1.8.24
223
281
  signing_key:
224
282
  specification_version: 3
225
283
  summary: ''
226
284
  test_files:
227
285
  - spec/helpers/methods_examples.rb
286
+ - spec/helpers/queues_examples.rb
228
287
  - spec/lib/config_spec.rb
229
288
  - spec/lib/manager/default_spec.rb
230
289
  - spec/lib/methods/action_mailer_spec.rb
@@ -240,8 +299,8 @@ test_files:
240
299
  - spec/lib/proxies/class_spec.rb
241
300
  - spec/lib/proxies/instance_spec.rb
242
301
  - spec/lib/queue/default_spec.rb
302
+ - spec/lib/queue/redis_spec.rb
243
303
  - spec/lib/util_spec.rb
244
304
  - spec/lib/worker/standard_spec.rb
305
+ - spec/lib/wrappers/action_mailer_spec.rb
245
306
  - spec/spec_helper.rb
246
- - spec/wrappers/action_mailer_spec.rb
247
- has_rdoc: