gustin-rudeq 2.1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +64 -0
- data/Rakefile +22 -0
- data/generators/rude_q/USAGE +24 -0
- data/generators/rude_q/rude_q_generator.rb +27 -0
- data/generators/rude_q/templates/rude_q_migration.rb +18 -0
- data/generators/rude_q/templates/rude_q_model.rb +3 -0
- data/generators/rude_q/templates/rude_q_model_spec.rb +71 -0
- data/lib/rude_q.rb +220 -0
- data/lib/rude_q/scope.rb +24 -0
- data/lib/rude_q/tasks.rb +25 -0
- data/lib/rude_q/worker.rb +66 -0
- data/spec/database.yml +4 -0
- data/spec/models/rude_queue.rb +14 -0
- data/spec/models/something.rb +3 -0
- data/spec/rude_q_spec.rb +372 -0
- data/spec/schema.rb +19 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/worker_spec.rb +49 -0
- data/tasks/rails.rake +1 -0
- metadata +84 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [Matthew Rudy Jacobs]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
== Author
|
2
|
+
Matthew Rudy Jacobs
|
3
|
+
|
4
|
+
== Contact
|
5
|
+
MatthewRudyJacobs@gmail.com
|
6
|
+
|
7
|
+
RudeQ
|
8
|
+
=============
|
9
|
+
A simple DB based queue,
|
10
|
+
designed for situations where a server based queue is unnecessary.
|
11
|
+
|
12
|
+
|
13
|
+
INSTALL
|
14
|
+
============
|
15
|
+
This plugin requires Rails 2.* currently, and has only been tested on MySQL.
|
16
|
+
|
17
|
+
On rails 2.1 you can install straight from github:
|
18
|
+
ruby script/plugin install git://github.com/matthewrudy/rudeq.git
|
19
|
+
|
20
|
+
Else just check it out into your plugins directory:
|
21
|
+
git clone git://github.com/matthewrudy/rudeq.git vendor/plugins/rudeq
|
22
|
+
|
23
|
+
USAGE
|
24
|
+
============
|
25
|
+
After you've installed it just run
|
26
|
+
rake queue:setup
|
27
|
+
|
28
|
+
matthew@iRudy:~/code/jbequeueing $ rake queue:setup
|
29
|
+
(in /Users/matthew/code/jbequeueing)
|
30
|
+
exists app/models/
|
31
|
+
exists spec/fixtures/
|
32
|
+
exists spec/models/
|
33
|
+
create app/models/rude_queue.rb
|
34
|
+
create spec/fixtures/rude_queues.yml
|
35
|
+
create spec/models/rude_queue_spec.rb
|
36
|
+
exists db/migrate
|
37
|
+
create db/migrate/029_create_rude_queues.rb
|
38
|
+
|
39
|
+
and you're done.
|
40
|
+
Fully tested, fully index... BOOM!
|
41
|
+
|
42
|
+
Now run migrations, start up a console, and;
|
43
|
+
|
44
|
+
RudeQueue.set(:queue_name, RandomObject)
|
45
|
+
RudeQueue.get(:queue_name)
|
46
|
+
RudeQueue.fetch(:queue_name) do |data|
|
47
|
+
process(data)
|
48
|
+
end
|
49
|
+
|
50
|
+
And, to keep the queue running fast,
|
51
|
+
set up a cron job to run
|
52
|
+
|
53
|
+
rake queue:cleanup
|
54
|
+
|
55
|
+
the cleanup will remove any queued items which have been processed longer than an hour ago.
|
56
|
+
|
57
|
+
rake queue:cleanup CLEANUP_TIME=86,400
|
58
|
+
|
59
|
+
will clear processed queue items processed longer than 86,400 seconds ago (1 day)
|
60
|
+
|
61
|
+
Try Yourself!
|
62
|
+
|
63
|
+
Copyright (c) 2008 [Matthew Rudy Jacobs Email: MatthewRudyJacobs@gmail.com],
|
64
|
+
released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
desc 'Default: run the specs.'
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc 'Run specs for rude_q plugin'
|
10
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
11
|
+
t.spec_opts = ['--options', "\"spec/spec.opts\""]
|
12
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the rude_q plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'RudeQ'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Description:
|
2
|
+
The rude_q generator creates a fully functioning RudeQ.
|
3
|
+
|
4
|
+
The generator takes a model name as its argument. The model name may be
|
5
|
+
given in CamelCase or under_score and should not be suffixed with 'Model'.
|
6
|
+
|
7
|
+
The generator creates a model class in app/models, an RSpec spec in
|
8
|
+
spec/models, database fixtures in spec/fixtures/plural_name.yml, and a migration
|
9
|
+
in db/migrate.
|
10
|
+
|
11
|
+
All the generated files are fully functioning, and no extra work should be necessary.
|
12
|
+
|
13
|
+
Example:
|
14
|
+
./script/generate rude_q ProcessQueue
|
15
|
+
|
16
|
+
This will create a ProcessQueue model:
|
17
|
+
Model: app/models/process_queue.rb
|
18
|
+
Spec: spec/models/process_queue_spec.rb
|
19
|
+
Fixtures: spec/fixtures/process_queues.yml
|
20
|
+
Migration: db/migrate/XXX_add_process_queues.rb
|
21
|
+
|
22
|
+
Run migrations, and you can use it immediately;
|
23
|
+
ProcessQueue.set(queue_name, value)
|
24
|
+
ProcessQueue.get(queue_name)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class RudeQGenerator < Rails::Generator::NamedBase
|
2
|
+
def manifest
|
3
|
+
|
4
|
+
record do |m|
|
5
|
+
# Check for class naming collisions.
|
6
|
+
m.class_collisions class_path, class_name
|
7
|
+
|
8
|
+
# Model, spec, and fixture directories.
|
9
|
+
m.directory File.join('app/models', class_path)
|
10
|
+
m.directory File.join('spec/fixtures', class_path)
|
11
|
+
m.directory File.join('spec/models', class_path)
|
12
|
+
|
13
|
+
# Model class, spec and fixtures.
|
14
|
+
m.template 'rude_q_model.rb', File.join('app/models', class_path, "#{file_name}.rb")
|
15
|
+
m.template 'model:fixtures.yml', File.join('spec/fixtures', class_path, "#{table_name}.yml")
|
16
|
+
m.template 'rude_q_model_spec.rb', File.join('spec/models', class_path, "#{file_name}_spec.rb")
|
17
|
+
|
18
|
+
unless options[:skip_migration]
|
19
|
+
m.migration_template 'rude_q_migration.rb', 'db/migrate', :assigns => {
|
20
|
+
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
|
21
|
+
}, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class <%= migration_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :<%= table_name %> do |t|
|
4
|
+
t.string :queue_name
|
5
|
+
t.text :data
|
6
|
+
t.boolean :processed, :default => false, :null => false
|
7
|
+
<% unless options[:skip_timestamps] %>
|
8
|
+
t.timestamps
|
9
|
+
<% end -%>
|
10
|
+
end
|
11
|
+
add_index :<%= table_name %>, :processed
|
12
|
+
add_index :<%= table_name %>, [:queue_name, :processed]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :<%= table_name %>
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../spec_helper'
|
2
|
+
|
3
|
+
describe <%= class_name %> do
|
4
|
+
before(:each) do
|
5
|
+
@<%= file_name %> = <%= class_name %>.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be valid" do
|
9
|
+
@<%= file_name %>.should be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "get and set" do
|
13
|
+
it "should work with strings" do
|
14
|
+
<%= class_name %>.set('abcde', "Something to set")
|
15
|
+
<%= class_name %>.get('abcde').should == "Something to set"
|
16
|
+
end
|
17
|
+
it "should work with symbols" do
|
18
|
+
<%= class_name %>.set('abcde', :a_symbol)
|
19
|
+
<%= class_name %>.get('abcde').should == :a_symbol
|
20
|
+
end
|
21
|
+
it "should work with arrays" do
|
22
|
+
array = [1, :b, "C"]
|
23
|
+
<%= class_name %>.set('abcde', array)
|
24
|
+
<%= class_name %>.get('abcde').should == array
|
25
|
+
end
|
26
|
+
it "should work with hashes" do
|
27
|
+
hash = {:symbol => "A string", "stringy" => 23, 74 => :cheese}
|
28
|
+
<%= class_name %>.set('abcde', hash)
|
29
|
+
<%= class_name %>.get('abcde').should == hash
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should :get in the same order they are :set" do
|
33
|
+
<%= class_name %>.set('abcde', :first)
|
34
|
+
<%= class_name %>.set('abcde', "second")
|
35
|
+
|
36
|
+
<%= class_name %>.get('abcde').should == :first
|
37
|
+
|
38
|
+
<%= class_name %>.set('abcde', 33.3333)
|
39
|
+
|
40
|
+
<%= class_name %>.get('abcde').should == "second"
|
41
|
+
<%= class_name %>.get('abcde').should == 33.3333
|
42
|
+
<%= class_name %>.get('abcde').should be(nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should keep queues seperated" do
|
46
|
+
<%= class_name %>.set('queue_1', :data_1)
|
47
|
+
<%= class_name %>.set('queue_2', "DATA2")
|
48
|
+
|
49
|
+
<%= class_name %>.get('queue_2').should == "DATA2"
|
50
|
+
<%= class_name %>.get('queue_2').should be(nil)
|
51
|
+
<%= class_name %>.get('queue_1').should == :data_1
|
52
|
+
<%= class_name %>.get('queue_1').should be(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should work with queue name as strings or symbols" do
|
56
|
+
<%= class_name %>.set(:bah, "something about bah")
|
57
|
+
<%= class_name %>.get("bah").should == "something about bah"
|
58
|
+
|
59
|
+
<%= class_name %>.set("girah", {:craziness => "embodied"})
|
60
|
+
<%= class_name %>.get(:girah).should == {:craziness => "embodied"}
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should work with queue name as strings or integers" do
|
64
|
+
<%= class_name %>.set(23, "something about bah")
|
65
|
+
<%= class_name %>.get("23").should == "something about bah"
|
66
|
+
|
67
|
+
<%= class_name %>.set("34", {:craziness => "embodied"})
|
68
|
+
<%= class_name %>.get(34).should == {:craziness => "embodied"}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/rude_q.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# RudeQ
|
2
|
+
|
3
|
+
# simply doing;
|
4
|
+
# class RudeQueue < ActiveRecord::Base
|
5
|
+
# include RudeQ
|
6
|
+
# end
|
7
|
+
# will include RudeQ::ClassMethods
|
8
|
+
# :get
|
9
|
+
# :set
|
10
|
+
module RudeQ
|
11
|
+
|
12
|
+
def self.included(mod) # :nodoc:
|
13
|
+
mod.extend(ClassMethods)
|
14
|
+
mod.send(:include, InstanceMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def data # :nodoc:
|
19
|
+
YAML.load(self[:data])
|
20
|
+
end
|
21
|
+
def data=(value) # :nodoc:
|
22
|
+
self[:data] = YAML.dump(value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
# Cleanup old processed items
|
28
|
+
#
|
29
|
+
# RudeQueue.cleanup!
|
30
|
+
# RudeQueue.cleanup!(1.week)
|
31
|
+
def cleanup!(expiry=1.hour)
|
32
|
+
self.delete_all(["processed = ? AND updated_at < ?", true, expiry.to_i.ago])
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add any serialize-able +data+ to the queue +queue_name+ (strings and symbols are treated the same)
|
36
|
+
# RudeQueue.set(:sausage_queue, Sausage.new(:sauce => "yummy"))
|
37
|
+
# RudeQueue.set("sausage_queue", Sausage.new(:other => true))
|
38
|
+
#
|
39
|
+
# >> RudeQueue.get("sausage_queue")
|
40
|
+
# -> *yummy sausage*
|
41
|
+
# >> RudeQueue.get(:sausage_queue)
|
42
|
+
# -> *other_sausage*
|
43
|
+
# >> RudeQueue.get(:sausage_queue)
|
44
|
+
# -> nil
|
45
|
+
def set(queue_name, data)
|
46
|
+
queue_name = sanitize_queue_name(queue_name)
|
47
|
+
|
48
|
+
self.create!(:queue_name => queue_name, :data => data)
|
49
|
+
return nil # in line with Starling
|
50
|
+
end
|
51
|
+
|
52
|
+
# Grab the first item from the queue *queue_name* (strings and symbols are treated the same)
|
53
|
+
# - it should always come out the same as it went in
|
54
|
+
# - they should always come out in the same order they went in
|
55
|
+
# - it will return a nil if there is no unprocessed entry in the queue
|
56
|
+
#
|
57
|
+
# >> RudeQueue.get(21)
|
58
|
+
# -> {:a => "hash"}
|
59
|
+
# >> RudeQueue.get(:a_symbol)
|
60
|
+
# -> 255
|
61
|
+
# >> RudeQueue.get("a string")
|
62
|
+
# -> nil
|
63
|
+
def get(queue_name)
|
64
|
+
qname = sanitize_queue_name(queue_name)
|
65
|
+
|
66
|
+
fetch_with_lock(qname) do |record|
|
67
|
+
if record
|
68
|
+
processed!(record)
|
69
|
+
return record.data
|
70
|
+
else
|
71
|
+
return nil # Starling waits indefinitely for a corresponding queue item
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Grab the first item from the queue, and execute the supplied block if there is one
|
77
|
+
# - it will return the value of the block
|
78
|
+
#
|
79
|
+
# >> RudeQueue.fetch(:my_queue) do |data|
|
80
|
+
# >> Monster.devour(data)
|
81
|
+
# >> end
|
82
|
+
# -> nil
|
83
|
+
#
|
84
|
+
# >> status = RudeQueue.fetch(:my_queue) do |data|
|
85
|
+
# >> process(data) # returns the value :update in this case
|
86
|
+
# >> end
|
87
|
+
# -> :update
|
88
|
+
# >> status
|
89
|
+
# -> :update
|
90
|
+
def fetch(queue_name, &block)
|
91
|
+
if data = get(queue_name)
|
92
|
+
return block.call(data)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# A snapshot count of unprocessed items for the given +queue_name+
|
97
|
+
#
|
98
|
+
# >> RudeQueue.backlog
|
99
|
+
# -> 265
|
100
|
+
# >> RudeQueue.backlog(:one_queue)
|
101
|
+
# -> 212
|
102
|
+
# >> RudeQueue.backlog(:another_queue)
|
103
|
+
# -> 53
|
104
|
+
#
|
105
|
+
def backlog(queue_name=nil)
|
106
|
+
conditions = {:processed => false}
|
107
|
+
if queue_name
|
108
|
+
conditions[:queue_name] = sanitize_queue_name(queue_name)
|
109
|
+
end
|
110
|
+
self.count(:conditions => conditions)
|
111
|
+
end
|
112
|
+
|
113
|
+
def fetch_with_lock(qname, &block) # :nodoc:
|
114
|
+
lock = case queue_options[:lock]
|
115
|
+
when :pessimistic then RudeQ::PessimisticLock
|
116
|
+
when :token then RudeQ::TokenLock
|
117
|
+
else
|
118
|
+
raise(ArgumentError, "bad queue_option for :lock - #{queue_options[:lock].inspect}")
|
119
|
+
end
|
120
|
+
lock.fetch_with_lock(self, qname, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
# class method to make it more easily stubbed
|
124
|
+
def processed!(record) # :nodoc:
|
125
|
+
case queue_options[:processed]
|
126
|
+
when :set_flag
|
127
|
+
record.update_attribute(:processed, true)
|
128
|
+
when :destroy
|
129
|
+
record.destroy
|
130
|
+
else
|
131
|
+
raise(ArgumentError, "bad queue_option for :processed - #{queue_options[:processed].inspect}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
protected :processed!
|
135
|
+
|
136
|
+
# configure your RudeQ
|
137
|
+
# ==== :processed - what do we do after retrieving a queue item?
|
138
|
+
# * <tt>:set_flag</tt> - set the +processed+ flag to +true+ (keep data in the db) [*default*]
|
139
|
+
# * <tt>:destroy</tt> - destroy the processed item (keep our queue as lean as possible
|
140
|
+
#
|
141
|
+
# ==== :lock - what locking method should we use?
|
142
|
+
# * <tt>:pessimistic</tt> - RudeQ::PessimisticLock [*default*]
|
143
|
+
# * <tt>:token</tt> - RudeQ::TokenLock
|
144
|
+
def queue_options
|
145
|
+
@queue_options ||= {:processed => :set_flag, :lock => :pessimistic}
|
146
|
+
end
|
147
|
+
|
148
|
+
def data # :nodoc:
|
149
|
+
YAML.load(self[:data])
|
150
|
+
end
|
151
|
+
def data=(value) # :nodoc:
|
152
|
+
self[:data] = YAML.dump(value)
|
153
|
+
end
|
154
|
+
private
|
155
|
+
|
156
|
+
def sanitize_queue_name(queue_name) # :nodoc:
|
157
|
+
queue_name.to_s
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# uses standard ActiveRecord :lock => true
|
162
|
+
# this will invoke a lock on the particular queue
|
163
|
+
# eg. daemon1: RudeQueue.get(:abc)
|
164
|
+
# daemon2: RudeQueue.get(:abc) - will have to wait for daemon1 to finish
|
165
|
+
# daemon3: RudeQueue.get(:def) - will avoid the lock
|
166
|
+
module PessimisticLock
|
167
|
+
class << self
|
168
|
+
|
169
|
+
def fetch_with_lock(klass, qname) # :nodoc:
|
170
|
+
klass.transaction do
|
171
|
+
record = klass.find(:first,
|
172
|
+
:conditions => {:queue_name => qname, :processed => false},
|
173
|
+
:lock => true, :order => "id ASC", :limit => 1)
|
174
|
+
|
175
|
+
return yield(record)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# a crazy hack around database locking
|
183
|
+
# that I thought was a good idea
|
184
|
+
# turns out we can't make it use transactions properly
|
185
|
+
# without creating a whole table lock
|
186
|
+
# which misses the point
|
187
|
+
#
|
188
|
+
# also, it doesn't work on SQLite as it requires "UPDATE ... LIMIT 1 ORDER id ASC"
|
189
|
+
# and as of RudeQueue2, you'll need to manually add the "token" column
|
190
|
+
module TokenLock
|
191
|
+
class << self
|
192
|
+
|
193
|
+
require 'digest/sha1'
|
194
|
+
|
195
|
+
def fetch_with_lock(klass, qname) # :nodoc:
|
196
|
+
token = get_unique_token
|
197
|
+
klass.update_all(["token = ?", token], ["queue_name = ? AND processed = ? AND token IS NULL", qname, false], :limit => 1, :order => "id ASC")
|
198
|
+
record = klass.find_by_queue_name_and_token_and_processed(qname, token, false)
|
199
|
+
|
200
|
+
return yield(record)
|
201
|
+
end
|
202
|
+
|
203
|
+
def token_count! # :nodoc:
|
204
|
+
@token_count ||= 0
|
205
|
+
@token_count += 1
|
206
|
+
return @token_count
|
207
|
+
end
|
208
|
+
|
209
|
+
def get_unique_token # :nodoc:
|
210
|
+
digest = Digest::SHA1.new
|
211
|
+
digest << Time.now.to_s
|
212
|
+
digest << Process.pid.to_s
|
213
|
+
digest << Socket.gethostname
|
214
|
+
digest << self.token_count!.to_s # multiple requests from the same pid in the same second get different token
|
215
|
+
return digest.hexdigest
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
data/lib/rude_q/scope.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../rude_q"
|
2
|
+
|
3
|
+
module RudeQ
|
4
|
+
class Scope
|
5
|
+
|
6
|
+
def initialize(queue_name)
|
7
|
+
@queue_name = queue_name
|
8
|
+
end
|
9
|
+
attr_reader :queue_name
|
10
|
+
|
11
|
+
def set(data)
|
12
|
+
RudeQueue.set(self.queue_name, data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get()
|
16
|
+
RudeQueue.get(self.queue_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def backlog()
|
20
|
+
RudeQueue.backlog(self.queue_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/lib/rude_q/tasks.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
namespace :queue do
|
2
|
+
desc "Generates your RudeQueue model"
|
3
|
+
task :setup => :environment do
|
4
|
+
require 'rails_generator'
|
5
|
+
require 'rails_generator/scripts/generate'
|
6
|
+
Rails::Generator::Scripts::Generate.new.run(["rude_q", ENV["QUEUE"] || "RudeQueue"])
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Removes all the old queue items"
|
10
|
+
task :cleanup => :environment do
|
11
|
+
queue_model = (ENV["QUEUE"] || "RudeQueue").constantize
|
12
|
+
args = [ENV["CLEANUP_TIME"]].compact
|
13
|
+
queue_model.cleanup!(*args) # no arg if no CLEANUP_TIME specified
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#namespace :spec do
|
18
|
+
# namespace :plugins do
|
19
|
+
# desc "Runs the examples for RudeQ"
|
20
|
+
# Spec::Rake::SpecTask.new(:rude_q) do |t|
|
21
|
+
# t.spec_opts = ['--options', "\"#{RAILS_ROOT}/spec/spec.opts\""]
|
22
|
+
# t.spec_files = FileList['vendor/plugins/rude_q/spec/**/*_spec.rb']
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# example worker class: lib/my_worker.rb
|
2
|
+
# class MyWorker < RudeQ::Worker
|
3
|
+
# def queue_name
|
4
|
+
# :my_queue
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
# def do_work(data)
|
8
|
+
# MyMailer.send(data)
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# example rake file: lib/tasks/worker.rake
|
13
|
+
# namespace :worker do
|
14
|
+
# desc "fire off a worker"
|
15
|
+
# task :do => :environment do
|
16
|
+
# worker = MyWorker.new
|
17
|
+
# worker.do!
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# then add a cron job to run "cd /path/to/wherever && rake worker:do RAILS_ENV=production"
|
22
|
+
module RudeQ
|
23
|
+
class Worker
|
24
|
+
|
25
|
+
def queue_name
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_work(data)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def do!
|
34
|
+
logger.info("starting up")
|
35
|
+
if work = self.queue.get
|
36
|
+
logger.info("found some work")
|
37
|
+
do_work(work)
|
38
|
+
else
|
39
|
+
logger.info("couldn't find any work")
|
40
|
+
end
|
41
|
+
logger.info("finished for now")
|
42
|
+
end
|
43
|
+
|
44
|
+
def logger
|
45
|
+
unless @logger
|
46
|
+
@logger = Logger.new(RAILS_ROOT + "/log/#{self.class.to_s.underscore}_#{RAILS_ENV}.log")
|
47
|
+
class << @logger
|
48
|
+
def format_message(severity, timestamp, progname, msg)
|
49
|
+
"#{timestamp.strftime('%Y%m%d-%H:%M:%S')} (#{$$}) #{msg}\n"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return @logger
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def queue
|
58
|
+
RudeQ::Scope.new(self.new.queue_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def queue
|
63
|
+
@queue ||= self.class.queue
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class RudeQueue < ActiveRecord::Base
|
2
|
+
include RudeQ
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def processed_with_raise_hack!(*args)
|
6
|
+
processed_without_raise_hack!(*args)
|
7
|
+
raise RuntimeError if raise_on_processed # want to be able to raise afterwards to check transactions
|
8
|
+
end
|
9
|
+
alias_method_chain :processed!, :raise_hack
|
10
|
+
attr_accessor :raise_on_processed
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
data/spec/rude_q_spec.rb
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe RudeQ::ClassMethods do # RudeQueue extends ClassMethods
|
4
|
+
before(:each) do
|
5
|
+
RudeQueue.delete_all
|
6
|
+
RudeQueue.raise_on_processed = false
|
7
|
+
create_some_noise
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_some_noise
|
11
|
+
RudeQueue.create!(:queue_name => "doNT use this in Specs", :data => {:not => "to be messed with"})
|
12
|
+
RudeQueue.create!(:queue_name => "abcde", :data => {:same_as => "the specs but already processed"}, :processed => true)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "get and set" do
|
16
|
+
it "should work with strings" do
|
17
|
+
RudeQueue.set('abcde', "Something to set")
|
18
|
+
RudeQueue.get('abcde').should == "Something to set"
|
19
|
+
end
|
20
|
+
it "should work with symbols" do
|
21
|
+
RudeQueue.set('abcde', :a_symbol)
|
22
|
+
RudeQueue.get('abcde').should == :a_symbol
|
23
|
+
end
|
24
|
+
it "should work with arrays" do
|
25
|
+
array = [1, :b, "C"]
|
26
|
+
RudeQueue.set('abcde', array)
|
27
|
+
RudeQueue.get('abcde').should == array
|
28
|
+
end
|
29
|
+
it "should work with hashes" do
|
30
|
+
hash = {:symbol => "A string", "stringy" => 23, 74 => :cheese}
|
31
|
+
RudeQueue.set('abcde', hash)
|
32
|
+
RudeQueue.get('abcde').should == hash
|
33
|
+
end
|
34
|
+
it "should work with integers" do
|
35
|
+
RudeQueue.set('abcde', 7816327370)
|
36
|
+
RudeQueue.get('abcde').should == 7816327370
|
37
|
+
end
|
38
|
+
it "should work with ActiveRecords" do
|
39
|
+
record = Something.create!(:name => "MatthewRudy")
|
40
|
+
|
41
|
+
RudeQueue.set('abcde', record)
|
42
|
+
RudeQueue.get('abcde').should == record
|
43
|
+
end
|
44
|
+
it "should resolve booleans correctly" do
|
45
|
+
RudeQueue.set('abcde', true)
|
46
|
+
RudeQueue.get('abcde').should == true
|
47
|
+
|
48
|
+
RudeQueue.set('abcde', false)
|
49
|
+
RudeQueue.get('abcde').should == false
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should :get in the same order they are :set" do
|
53
|
+
RudeQueue.set('abcde', :first)
|
54
|
+
RudeQueue.set('abcde', "second")
|
55
|
+
|
56
|
+
RudeQueue.get('abcde').should == :first
|
57
|
+
|
58
|
+
RudeQueue.set('abcde', 33.3333)
|
59
|
+
|
60
|
+
RudeQueue.get('abcde').should == "second"
|
61
|
+
RudeQueue.get('abcde').should == 33.3333
|
62
|
+
RudeQueue.get('abcde').should be(nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should keep queues seperated" do
|
66
|
+
RudeQueue.set('queue_1', :data_1)
|
67
|
+
RudeQueue.set('queue_2', "DATA2")
|
68
|
+
|
69
|
+
RudeQueue.get('queue_2').should == "DATA2"
|
70
|
+
RudeQueue.get('queue_2').should be(nil)
|
71
|
+
RudeQueue.get('queue_1').should == :data_1
|
72
|
+
RudeQueue.get('queue_1').should be(nil)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should call to_s on inputs" do
|
76
|
+
qname = stub("fake input")
|
77
|
+
qname.should_receive(:to_s).exactly(:twice).and_return("fake queue name")
|
78
|
+
|
79
|
+
RudeQueue.set(qname, ["Data"])
|
80
|
+
RudeQueue.get(qname).should == ["Data"]
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should work with queue name as strings or symbols" do
|
84
|
+
RudeQueue.set(:bah, "something about bah")
|
85
|
+
RudeQueue.get("bah").should == "something about bah"
|
86
|
+
|
87
|
+
RudeQueue.set("girah", {:craziness => "embodied"})
|
88
|
+
RudeQueue.get(:girah).should == {:craziness => "embodied"}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe ".set" do
|
93
|
+
it "should delegate to :create!" do
|
94
|
+
RudeQueue.should_receive(:create!).with(:queue_name => 'abcde', :data => :magical_planet)
|
95
|
+
RudeQueue.set('abcde', :magical_planet)
|
96
|
+
end
|
97
|
+
it "should return nil" do
|
98
|
+
RudeQueue.set('abcde', "something").should be(nil)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe ".get" do
|
103
|
+
it "should revert a record if something goes wrong before it finishes" do
|
104
|
+
RudeQueue.raise_on_processed = true
|
105
|
+
RudeQueue.set('abcde', :this_will_remain_unprocessed)
|
106
|
+
|
107
|
+
# confirm the object is in the db
|
108
|
+
record = RudeQueue.find(:first, :order => "id DESC")
|
109
|
+
record.queue_name.should == 'abcde'
|
110
|
+
record.data.should == :this_will_remain_unprocessed
|
111
|
+
record.processed?.should == false
|
112
|
+
record.token.should == nil
|
113
|
+
|
114
|
+
lambda {RudeQueue.get('abcde')}.should raise_error(RuntimeError)
|
115
|
+
|
116
|
+
record.reload
|
117
|
+
record.queue_name.should == 'abcde'
|
118
|
+
record.data.should == :this_will_remain_unprocessed
|
119
|
+
record.processed?.should == false
|
120
|
+
record.token.should == nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "fetch" do
|
125
|
+
describe "with data" do
|
126
|
+
|
127
|
+
before(:each) do
|
128
|
+
RudeQueue.set(:fetch_queue, "some data")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should return the value of the block" do
|
132
|
+
rtn = RudeQueue.fetch(:fetch_queue) do |data|
|
133
|
+
data.should == "some data"
|
134
|
+
:the_return
|
135
|
+
end
|
136
|
+
rtn.should == :the_return
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should execute the block with the data" do
|
140
|
+
self.should_receive(:something)
|
141
|
+
RudeQueue.fetch(:fetch_queue) do |data|
|
142
|
+
self.something
|
143
|
+
data.should == "some data"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "without data" do
|
150
|
+
|
151
|
+
it "should not execute the block" do
|
152
|
+
self.should_not_receive(:something)
|
153
|
+
RudeQueue.fetch(:fetch_queue) do |data|
|
154
|
+
raise(Exception, "this should never get here")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should return nil" do
|
159
|
+
rtn = RudeQueue.fetch(:fetch_queue) do |data|
|
160
|
+
raise(Exception, "again this shouldnt happen")
|
161
|
+
end
|
162
|
+
rtn.should be_nil
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "queue_options" do
|
169
|
+
describe :processed do
|
170
|
+
describe "set to :destroy" do
|
171
|
+
before(:each) do
|
172
|
+
@old_processed = RudeQueue.queue_options[:processed]
|
173
|
+
RudeQueue.queue_options[:processed] = :destroy
|
174
|
+
end
|
175
|
+
after(:each) do
|
176
|
+
RudeQueue.queue_options[:processed] = @old_processed
|
177
|
+
end
|
178
|
+
it "should delete processed items" do
|
179
|
+
count = RudeQueue.count
|
180
|
+
|
181
|
+
RudeQueue.set(:abcde, "some value")
|
182
|
+
RudeQueue.count.should == (count + 1)
|
183
|
+
|
184
|
+
RudeQueue.get(:abcde).should == "some value"
|
185
|
+
RudeQueue.count.should == count
|
186
|
+
end
|
187
|
+
end
|
188
|
+
describe "set to something crazy" do
|
189
|
+
before(:each) do
|
190
|
+
@old_processed = RudeQueue.queue_options[:processed]
|
191
|
+
RudeQueue.queue_options[:processed] = :something_crazy
|
192
|
+
end
|
193
|
+
after(:each) do
|
194
|
+
RudeQueue.queue_options[:processed] = @old_processed
|
195
|
+
end
|
196
|
+
it "should raise an exception" do
|
197
|
+
RudeQueue.set(:abcde, "some value")
|
198
|
+
lambda {RudeQueue.get(:abcde)}.should raise_error(ArgumentError)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe ".backlog" do
|
205
|
+
it "should count the unprocessed items for the provided queue_name" do
|
206
|
+
RudeQueue.delete_all
|
207
|
+
|
208
|
+
RudeQueue.backlog(:abcde).should == 0
|
209
|
+
RudeQueue.backlog().should == 0
|
210
|
+
|
211
|
+
RudeQueue.set(:abcde, "a value")
|
212
|
+
RudeQueue.backlog(:abcde).should == 1
|
213
|
+
RudeQueue.backlog().should == 1
|
214
|
+
|
215
|
+
RudeQueue.set(:something_else, "another value")
|
216
|
+
3.times { RudeQueue.set(:abcde, :add_three_more)}
|
217
|
+
|
218
|
+
RudeQueue.backlog(:abcde).should == 4
|
219
|
+
RudeQueue.backlog().should == 5
|
220
|
+
|
221
|
+
RudeQueue.get(:abcde).should == "a value"
|
222
|
+
RudeQueue.backlog(:abcde).should == 3
|
223
|
+
RudeQueue.backlog().should == 4
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe ".cleanup!" do
|
228
|
+
it "should use :delete_all" do
|
229
|
+
RudeQueue.should_receive(:delete_all) # not :destroy_all
|
230
|
+
RudeQueue.cleanup!
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should allow string inputs" do
|
234
|
+
RudeQueue.cleanup!("3600")
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should allow integer inputs" do
|
238
|
+
RudeQueue.cleanup!(3600)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should not clear unprocessed items" do
|
242
|
+
RudeQueue.set('abcde', :giraffe)
|
243
|
+
RudeQueue.set('abcde', :monkey)
|
244
|
+
RudeQueue.count.should >= 2
|
245
|
+
|
246
|
+
RudeQueue.cleanup!
|
247
|
+
|
248
|
+
RudeQueue.count.should >=2
|
249
|
+
RudeQueue.get('abcde').should == :giraffe
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should not clear old unprocessed items" do
|
253
|
+
RudeQueue.set('abcde', :giraffe)
|
254
|
+
giraffe = RudeQueue.find(:first, :conditions => {:data => :giraffe})
|
255
|
+
|
256
|
+
time_now = Time.now
|
257
|
+
Time.stub!(:now).and_return(time_now + 1.year)
|
258
|
+
|
259
|
+
giraffe.updated_at.should < 2.weeks.ago
|
260
|
+
|
261
|
+
RudeQueue.cleanup!
|
262
|
+
|
263
|
+
giraffe.reload
|
264
|
+
RudeQueue.get('abcde').should == :giraffe
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should not clear processed items newer than the argument" do
|
268
|
+
RudeQueue.set('abcde', :giraffe)
|
269
|
+
RudeQueue.get('abcde').should == :giraffe
|
270
|
+
|
271
|
+
giraffe = RudeQueue.find(:first, :conditions => {:data => :giraffe})
|
272
|
+
|
273
|
+
time_now = Time.now
|
274
|
+
Time.stub!(:now).and_return(time_now + 1.week - 5.minutes)
|
275
|
+
|
276
|
+
giraffe.updated_at.should > 1.week.ago
|
277
|
+
giraffe.processed.should be(true)
|
278
|
+
|
279
|
+
RudeQueue.cleanup!(1.week)
|
280
|
+
|
281
|
+
giraffe.reload
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should not clear processed items newer than one hour, by default" do
|
285
|
+
RudeQueue.set('abcde', :giraffe)
|
286
|
+
RudeQueue.get('abcde').should == :giraffe
|
287
|
+
|
288
|
+
giraffe = RudeQueue.find(:first, :conditions => {:data => :giraffe})
|
289
|
+
|
290
|
+
time_now = Time.now
|
291
|
+
Time.stub!(:now).and_return(time_now + 59.minutes)
|
292
|
+
|
293
|
+
giraffe.updated_at.should > 1.hour.ago
|
294
|
+
giraffe.processed.should be(true)
|
295
|
+
|
296
|
+
RudeQueue.cleanup!()
|
297
|
+
|
298
|
+
giraffe.reload
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should clear processed items older than the argument" do
|
302
|
+
RudeQueue.set('abcde', :giraffe)
|
303
|
+
RudeQueue.get('abcde').should == :giraffe
|
304
|
+
|
305
|
+
giraffe = RudeQueue.find(:first, :conditions => {:data => :giraffe})
|
306
|
+
|
307
|
+
time_now = Time.now
|
308
|
+
Time.stub!(:now).and_return(time_now + 1.week + 5.minutes)
|
309
|
+
|
310
|
+
giraffe.updated_at.should < 1.week.ago
|
311
|
+
giraffe.processed.should be(true)
|
312
|
+
|
313
|
+
RudeQueue.cleanup!(1.week)
|
314
|
+
|
315
|
+
lambda { giraffe.reload }.should raise_error(ActiveRecord::RecordNotFound)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should clear processed items older than one hour, by default" do
|
319
|
+
RudeQueue.set('abcde', :giraffe)
|
320
|
+
RudeQueue.get('abcde').should == :giraffe
|
321
|
+
|
322
|
+
giraffe = RudeQueue.find(:first, :conditions => {:data => :giraffe})
|
323
|
+
|
324
|
+
time_now = Time.now()
|
325
|
+
Time.stub!(:now).and_return(time_now + 61.minutes)
|
326
|
+
|
327
|
+
giraffe.updated_at.should < 1.hour.ago
|
328
|
+
giraffe.processed.should be(true)
|
329
|
+
|
330
|
+
RudeQueue.cleanup!
|
331
|
+
|
332
|
+
lambda { giraffe.reload }.should raise_error(ActiveRecord::RecordNotFound)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe RudeQ::TokenLock do
|
338
|
+
|
339
|
+
describe ".get_unique_token" do
|
340
|
+
it "should create a unique token" do
|
341
|
+
lots_of_tokens = Array.new(50) do
|
342
|
+
RudeQ::TokenLock.get_unique_token
|
343
|
+
end
|
344
|
+
lots_of_tokens.uniq.should == lots_of_tokens
|
345
|
+
end
|
346
|
+
|
347
|
+
it "should create a unique token even if time stands still" do
|
348
|
+
time_now = Time.now
|
349
|
+
Time.should_receive(:now).at_least(50).times.and_return(time_now)
|
350
|
+
lots_of_tokens = Array.new(50) do
|
351
|
+
RudeQ::TokenLock.get_unique_token
|
352
|
+
end
|
353
|
+
lots_of_tokens.uniq.should == lots_of_tokens
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# it "should not return a processed item with the same token" do
|
358
|
+
# @token = "tokEEEannn"
|
359
|
+
#
|
360
|
+
# RudeQ::TokenLock.should respond_to(:get_unique_token) # ensure our stub is safe
|
361
|
+
# RudeQ::TokenLock.should_receive(:get_unique_token).exactly(3).times.and_return(@token)
|
362
|
+
#
|
363
|
+
# @existing = RudeQueue.create!(:queue_name => 'abcde', :data => :old_data, :token => @token, :processed => true)
|
364
|
+
#
|
365
|
+
# RudeQueue.get('abcde').should be(nil)
|
366
|
+
#
|
367
|
+
# RudeQueue.set('abcde', :new_data)
|
368
|
+
# RudeQueue.get('abcde').should == :new_data
|
369
|
+
# RudeQueue.get('abcde').should be(nil)
|
370
|
+
# end
|
371
|
+
|
372
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 1) do
|
2
|
+
create_table :rude_queues, :force => true do |t|
|
3
|
+
t.string :queue_name
|
4
|
+
t.text :data
|
5
|
+
t.string :token, :default => nil
|
6
|
+
t.boolean :processed, :default => false, :null => false
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :rude_queues, :processed
|
11
|
+
add_index :rude_queues, [:queue_name, :processed]
|
12
|
+
|
13
|
+
create_table :somethings, :force => true do |t|
|
14
|
+
t.string :name
|
15
|
+
t.integer :count
|
16
|
+
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
current_dir = File.dirname(__FILE__)
|
6
|
+
require "#{current_dir}/../lib/rude_q"
|
7
|
+
require "#{current_dir}/../lib/rude_q/worker"
|
8
|
+
require "#{current_dir}/../lib/rude_q/scope"
|
9
|
+
require "#{current_dir}/models/rude_queue"
|
10
|
+
require "#{current_dir}/models/something"
|
11
|
+
config = YAML::load(IO.read(current_dir + '/database.yml'))
|
12
|
+
ActiveRecord::Base.logger = Logger.new(current_dir + "/debug.log")
|
13
|
+
ActiveRecord::Base.establish_connection(config['rude_q_test'])
|
14
|
+
load(current_dir + "/schema.rb")
|
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class ExampleWorker < RudeQ::Worker
|
4
|
+
def queue_name
|
5
|
+
:some_queue
|
6
|
+
end
|
7
|
+
|
8
|
+
# for the test, we'll just append each bit of data to a variable
|
9
|
+
attr_accessor :processed_data
|
10
|
+
|
11
|
+
def do_work(data)
|
12
|
+
self.processed_data ||= []
|
13
|
+
self.processed_data << data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe RudeQ::Worker do
|
18
|
+
before(:each) do
|
19
|
+
@it = ExampleWorker.new
|
20
|
+
RudeQueue.delete_all
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "queue" do
|
24
|
+
it "should expose RudeQueue.get scoped for the worker's queue" do
|
25
|
+
RudeQueue.set(:some_queue, ["some data for the worker"])
|
26
|
+
@it.queue.get.should == ["some data for the worker"]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should expose RudeQueue.set scoped for the worker's queue" do
|
30
|
+
@it.queue.set(:some_other_data_for_the_worker)
|
31
|
+
RudeQueue.get(:some_queue).should == :some_other_data_for_the_worker
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should expose RudeQueue.backlog scoped for the worker's queue" do
|
35
|
+
RudeQueue.set(:who_knows, 1)
|
36
|
+
RudeQueue.set(:my_mum, 23)
|
37
|
+
|
38
|
+
RudeQueue.backlog.should == 2
|
39
|
+
RudeQueue.backlog(:some_queue).should == 0
|
40
|
+
@it.queue.backlog.should == 0
|
41
|
+
|
42
|
+
RudeQueue.set(:some_queue, "purple")
|
43
|
+
|
44
|
+
RudeQueue.backlog.should == 3
|
45
|
+
RudeQueue.backlog(:some_queue).should == 1
|
46
|
+
@it.queue.backlog.should == 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/tasks/rails.rake
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '/../lib/rude_q/tasks')
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gustin-rudeq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Rudy Jacobs
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-09 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: A simple DB queueing library built on top of ActiveRecord.
|
26
|
+
email: MatthewRudyJacobs@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- Rakefile
|
36
|
+
- MIT-LICENSE
|
37
|
+
- lib/rude_q/worker.rb
|
38
|
+
- lib/rude_q/scope.rb
|
39
|
+
- lib/rude_q.rb
|
40
|
+
- generators/rude_q/templates/rude_q_model.rb
|
41
|
+
- generators/rude_q/templates/rude_q_model_spec.rb
|
42
|
+
- generators/rude_q/templates/rude_q_migration.rb
|
43
|
+
- generators/rude_q/rude_q_generator.rb
|
44
|
+
- generators/rude_q/USAGE
|
45
|
+
- spec/spec.opts
|
46
|
+
- spec/worker_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
- spec/database.yml
|
49
|
+
- spec/rude_q_spec.rb
|
50
|
+
- spec/models/rude_queue.rb
|
51
|
+
- spec/models/something.rb
|
52
|
+
- spec/schema.rb
|
53
|
+
- tasks/rails.rake
|
54
|
+
- lib/rude_q/tasks.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/matthewrudy/rudeq
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --main
|
60
|
+
- README
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.2.0
|
79
|
+
signing_key:
|
80
|
+
specification_version: 2
|
81
|
+
summary: ActiveRecord-based DB-queue
|
82
|
+
test_files:
|
83
|
+
- spec/rude_q_spec.rb
|
84
|
+
- spec/worker_spec.rb
|