matthewrudy-rudeq 0.1 → 2.0
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 +3 -1
- data/lib/rude_q.rb +149 -41
- data/spec/process_queue.rb +10 -0
- data/spec/rude_q_spec.rb +160 -25
- data/spec/spec.opts +1 -1
- metadata +4 -4
data/README
CHANGED
|
@@ -13,7 +13,6 @@ RudeQ
|
|
|
13
13
|
INSTALL
|
|
14
14
|
============
|
|
15
15
|
This plugin requires Rails 2.* currently, and has only been tested on MySQL.
|
|
16
|
-
It definitely does not work on SQLite (as it requires :limit and :order options for a SQL UPDATE command)
|
|
17
16
|
|
|
18
17
|
On rails 2.1 you can install straight from github:
|
|
19
18
|
ruby script/plugin install git://github.com/matthewrudy/rudeq.git
|
|
@@ -44,6 +43,9 @@ After you've installed it just run
|
|
|
44
43
|
|
|
45
44
|
RudeQueue.set(:queue_name, RandomObject)
|
|
46
45
|
RudeQueue.get(:queue_name)
|
|
46
|
+
RudeQueue.fetch(:queue_name) do |data|
|
|
47
|
+
process(data)
|
|
48
|
+
end
|
|
47
49
|
|
|
48
50
|
And, to keep the queue running fast,
|
|
49
51
|
set up a cron job to run
|
data/lib/rude_q.rb
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# RudeQ
|
|
2
2
|
require 'digest/sha1'
|
|
3
|
+
|
|
4
|
+
# simply doing;
|
|
5
|
+
# class RudeQueue < ActiveRecord::Base
|
|
6
|
+
# include RudeQ
|
|
7
|
+
# end
|
|
8
|
+
# will include RudeQ::ClassMethods
|
|
9
|
+
# :get
|
|
10
|
+
# :set
|
|
3
11
|
module RudeQ
|
|
4
12
|
|
|
5
|
-
def self.included(mod)
|
|
13
|
+
def self.included(mod) # :nodoc:
|
|
6
14
|
mod.extend(ClassMethods)
|
|
7
15
|
mod.serialize(:data)
|
|
8
16
|
end
|
|
@@ -16,17 +24,17 @@ module RudeQ
|
|
|
16
24
|
self.delete_all(["processed = ? AND updated_at < ?", true, expiry.to_i.ago])
|
|
17
25
|
end
|
|
18
26
|
|
|
19
|
-
# Add any serialize-able
|
|
27
|
+
# Add any serialize-able +data+ to the queue +queue_name+ (strings and symbols are treated the same)
|
|
20
28
|
#
|
|
21
29
|
# RudeQueue.set(:sausage_queue, Sausage.new(:sauce => "yummy"))
|
|
22
30
|
# RudeQueue.set("sausage_queue", Sausage.new(:other => true))
|
|
23
31
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
32
|
+
# >> RudeQueue.get("sausage_queue")
|
|
33
|
+
# -> *yummy sausage*
|
|
34
|
+
# >> RudeQueue.get(:sausage_queue)
|
|
35
|
+
# -> *other_sausage*
|
|
36
|
+
# >> RudeQueue.get(:sausage_queue)
|
|
37
|
+
# -> nil
|
|
30
38
|
def set(queue_name, data)
|
|
31
39
|
queue_name = sanitize_queue_name(queue_name)
|
|
32
40
|
self.create!(:queue_name => queue_name, :data => data)
|
|
@@ -34,51 +42,151 @@ module RudeQ
|
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
# Grab the first item from the queue *queue_name* (strings and symbols are treated the same)
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
45
|
+
# - it should always come out the same as it went in
|
|
46
|
+
# - they should always come out in the same order they went in
|
|
47
|
+
# - it will return a nil if there is no unprocessed entry in the queue
|
|
40
48
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
49
|
+
# >> RudeQueue.get(21)
|
|
50
|
+
# -> {:a => "hash"}
|
|
51
|
+
# >> RudeQueue.get(:a_symbol)
|
|
52
|
+
# -> 255
|
|
53
|
+
# >> RudeQueue.get("a string")
|
|
54
|
+
# -> nil
|
|
47
55
|
def get(queue_name)
|
|
48
56
|
qname = sanitize_queue_name(queue_name)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
fetch_with_lock(qname) do |record|
|
|
59
|
+
if record
|
|
60
|
+
processed!(record)
|
|
61
|
+
return record.data
|
|
62
|
+
else
|
|
63
|
+
return nil # Starling waits indefinitely for a corresponding queue item
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Grab the first item from the queue, and execute the supplied block if there is one
|
|
69
|
+
# - it will return the value of the block
|
|
70
|
+
#
|
|
71
|
+
# >> RudeQueue.fetch(:my_queue) do |data|
|
|
72
|
+
# >> Monster.devour(data)
|
|
73
|
+
# >> end
|
|
74
|
+
# -> nil
|
|
75
|
+
#
|
|
76
|
+
# >> status = RudeQueue.fetch(:my_queue) do |data|
|
|
77
|
+
# >> process(data) # returns the value :update in this case
|
|
78
|
+
# >> end
|
|
79
|
+
# -> :update
|
|
80
|
+
# >> status
|
|
81
|
+
# -> :update
|
|
82
|
+
def fetch(queue_name, &block)
|
|
83
|
+
if data = get(queue_name)
|
|
84
|
+
return block.call(data)
|
|
58
85
|
end
|
|
59
86
|
end
|
|
60
|
-
|
|
61
|
-
def get_unique_token # :nodoc:
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
# A snapshot count of unprocessed items for the given +queue_name+
|
|
89
|
+
def backlog(queue_name)
|
|
90
|
+
qname = sanitize_queue_name(queue_name)
|
|
91
|
+
self.count(:conditions => {:queue_name => qname, :processed => false})
|
|
92
|
+
end
|
|
68
93
|
|
|
69
|
-
|
|
94
|
+
def fetch_with_lock(qname, &block) # :nodoc:
|
|
95
|
+
lock = case queue_options[:lock]
|
|
96
|
+
when :pessimistic : RudeQ::PessimisticLock
|
|
97
|
+
when :token : RudeQ::TokenLock
|
|
98
|
+
else
|
|
99
|
+
raise(ArgumentError, "bad queue_option for :lock - #{queue_options[:lock].inspect}")
|
|
100
|
+
end
|
|
101
|
+
lock.fetch_with_lock(self, qname, &block)
|
|
70
102
|
end
|
|
71
103
|
|
|
72
|
-
|
|
104
|
+
# class method to make it more easily stubbed
|
|
105
|
+
def processed!(record) # :nodoc:
|
|
106
|
+
case queue_options[:processed]
|
|
107
|
+
when :set_flag
|
|
108
|
+
record.update_attribute(:processed, true)
|
|
109
|
+
when :destroy
|
|
110
|
+
record.destroy
|
|
111
|
+
else
|
|
112
|
+
raise(ArgumentError, "bad queue_option for :processed - #{queue_options[:processed].inspect}")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
protected :processed!
|
|
73
116
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
117
|
+
# configure your RudeQ
|
|
118
|
+
# ==== :processed - what do we do after retrieving a queue item?
|
|
119
|
+
# * <tt>:set_flag</tt> - set the +processed+ flag to +true+ (keep data in the db) [*default*]
|
|
120
|
+
# * <tt>:destroy</tt> - destroy the processed item (keep our queue as lean as possible
|
|
121
|
+
#
|
|
122
|
+
# ==== :lock - what locking method should we use?
|
|
123
|
+
# * <tt>:pessimistic</tt> - RudeQ::PessimisticLock [*default*]
|
|
124
|
+
# * <tt>:token</tt> - RudeQ::TokenLock
|
|
125
|
+
def queue_options
|
|
126
|
+
@queue_options ||= {:processed => :set_flag, :lock => :pessimistic}
|
|
78
127
|
end
|
|
128
|
+
|
|
129
|
+
private
|
|
79
130
|
|
|
80
|
-
def sanitize_queue_name(queue_name)
|
|
131
|
+
def sanitize_queue_name(queue_name) # :nodoc:
|
|
81
132
|
queue_name.to_s
|
|
82
133
|
end
|
|
83
134
|
end
|
|
84
|
-
|
|
135
|
+
|
|
136
|
+
# uses standard ActiveRecord :lock => true
|
|
137
|
+
# this will invoke a lock on the particular queue
|
|
138
|
+
# eg. daemon1: RudeQueue.get(:abc)
|
|
139
|
+
# daemon2: RudeQueue.get(:abc) - will have to wait for daemon1 to finish
|
|
140
|
+
# daemon3: RudeQueue.get(:def) - will avoid the lock
|
|
141
|
+
module PessimisticLock
|
|
142
|
+
class << self
|
|
143
|
+
|
|
144
|
+
def fetch_with_lock(klass, qname) # :nodoc:
|
|
145
|
+
klass.transaction do
|
|
146
|
+
record = klass.find(:first,
|
|
147
|
+
:conditions => {:queue_name => qname, :processed => false},
|
|
148
|
+
:lock => true, :order => "id ASC", :limit => 1)
|
|
149
|
+
|
|
150
|
+
return yield(record)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# a crazy hack around database locking
|
|
158
|
+
# that I thought was a good idea
|
|
159
|
+
# turns out we can't make it use transactions properly
|
|
160
|
+
# without creating a whole table lock
|
|
161
|
+
# which misses the point
|
|
162
|
+
#
|
|
163
|
+
# also, it doesn't work on SQLite as it requires "UPDATE ... LIMIT 1 ORDER id ASC"
|
|
164
|
+
module TokenLock
|
|
165
|
+
class << self
|
|
166
|
+
|
|
167
|
+
def fetch_with_lock(klass, qname) # :nodoc:
|
|
168
|
+
token = get_unique_token
|
|
169
|
+
klass.update_all(["token = ?", token], ["queue_name = ? AND processed = ? AND token IS NULL", qname, false], :limit => 1, :order => "id ASC")
|
|
170
|
+
record = klass.find_by_queue_name_and_token_and_processed(qname, token, false)
|
|
171
|
+
|
|
172
|
+
return yield(record)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def token_count! # :nodoc:
|
|
176
|
+
@token_count ||= 0
|
|
177
|
+
@token_count += 1
|
|
178
|
+
return @token_count
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def get_unique_token # :nodoc:
|
|
182
|
+
digest = Digest::SHA1.new
|
|
183
|
+
digest << Time.now.to_s
|
|
184
|
+
digest << Process.pid.to_s
|
|
185
|
+
digest << Socket.gethostname
|
|
186
|
+
digest << self.token_count!.to_s # multiple requests from the same pid in the same second get different token
|
|
187
|
+
return digest.hexdigest
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
end
|
data/spec/process_queue.rb
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
class ProcessQueue < ActiveRecord::Base
|
|
2
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
|
+
|
|
3
13
|
end
|
data/spec/rude_q_spec.rb
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
|
2
2
|
|
|
3
|
-
describe ProcessQueue
|
|
3
|
+
describe RudeQ::ClassMethods do # ProcessQueue extends ClassMethods
|
|
4
4
|
before(:each) do
|
|
5
5
|
ProcessQueue.delete_all
|
|
6
|
+
ProcessQueue.raise_on_processed = false
|
|
6
7
|
create_some_noise
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def create_some_noise
|
|
10
11
|
ProcessQueue.create!(:queue_name => "doNT use this in Specs", :data => {:not => "to be messed with"})
|
|
11
12
|
ProcessQueue.create!(:queue_name => "abcde", :data => {:same_as => "the specs but already processed"}, :processed => true)
|
|
12
|
-
ProcessQueue.create!(:queue_name => "abcde", :data => {:same_as => "the specs but with token"}, :token => " unlikely ")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
describe "get and set" do
|
|
@@ -31,6 +31,17 @@ describe ProcessQueue do
|
|
|
31
31
|
ProcessQueue.set('abcde', hash)
|
|
32
32
|
ProcessQueue.get('abcde').should == hash
|
|
33
33
|
end
|
|
34
|
+
it "should work with integers" do
|
|
35
|
+
ProcessQueue.set('abcde', 7816327370)
|
|
36
|
+
ProcessQueue.get('abcde').should == 7816327370
|
|
37
|
+
end
|
|
38
|
+
it "unfortunately doesnt resolve booleans correctly" do
|
|
39
|
+
ProcessQueue.set('abcde', true)
|
|
40
|
+
ProcessQueue.get('abcde').should == 1
|
|
41
|
+
|
|
42
|
+
ProcessQueue.set('abcde', false)
|
|
43
|
+
ProcessQueue.get('abcde').should == 0
|
|
44
|
+
end
|
|
34
45
|
|
|
35
46
|
it "should :get in the same order they are :set" do
|
|
36
47
|
ProcessQueue.set('abcde', :first)
|
|
@@ -82,38 +93,125 @@ describe ProcessQueue do
|
|
|
82
93
|
end
|
|
83
94
|
end
|
|
84
95
|
|
|
85
|
-
describe ".get" do
|
|
86
|
-
it "should
|
|
87
|
-
|
|
88
|
-
ProcessQueue.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
ProcessQueue.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
describe ".get" do
|
|
97
|
+
it "should revert a record if something goes wrong before it finishes" do
|
|
98
|
+
ProcessQueue.raise_on_processed = true
|
|
99
|
+
ProcessQueue.set('abcde', :this_will_remain_unprocessed)
|
|
100
|
+
|
|
101
|
+
# confirm the object is in the db
|
|
102
|
+
record = ProcessQueue.find(:first, :order => "id DESC")
|
|
103
|
+
record.queue_name.should == 'abcde'
|
|
104
|
+
record.data.should == :this_will_remain_unprocessed
|
|
105
|
+
record.processed?.should == false
|
|
106
|
+
record.token.should == nil
|
|
107
|
+
|
|
108
|
+
lambda {ProcessQueue.get('abcde')}.should raise_error(RuntimeError)
|
|
109
|
+
|
|
110
|
+
record.reload
|
|
111
|
+
record.queue_name.should == 'abcde'
|
|
112
|
+
record.data.should == :this_will_remain_unprocessed
|
|
113
|
+
record.processed?.should == false
|
|
114
|
+
record.token.should == nil
|
|
96
115
|
end
|
|
97
116
|
end
|
|
117
|
+
|
|
118
|
+
describe "fetch" do
|
|
119
|
+
describe "with data" do
|
|
120
|
+
|
|
121
|
+
before(:each) do
|
|
122
|
+
ProcessQueue.set(:fetch_queue, "some data")
|
|
123
|
+
end
|
|
98
124
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
it "should return the value of the block" do
|
|
126
|
+
rtn = ProcessQueue.fetch(:fetch_queue) do |data|
|
|
127
|
+
data.should == "some data"
|
|
128
|
+
:the_return
|
|
129
|
+
end
|
|
130
|
+
rtn.should == :the_return
|
|
103
131
|
end
|
|
104
|
-
|
|
132
|
+
|
|
133
|
+
it "should execute the block with the data" do
|
|
134
|
+
self.should_receive(:something)
|
|
135
|
+
ProcessQueue.fetch(:fetch_queue) do |data|
|
|
136
|
+
self.something
|
|
137
|
+
data.should == "some data"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
105
141
|
end
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
ProcessQueue.
|
|
142
|
+
|
|
143
|
+
describe "without data" do
|
|
144
|
+
|
|
145
|
+
it "should not execute the block" do
|
|
146
|
+
self.should_not_receive(:something)
|
|
147
|
+
ProcessQueue.fetch(:fetch_queue) do |data|
|
|
148
|
+
raise(Exception, "this should never get here")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "should return nil" do
|
|
153
|
+
rtn = ProcessQueue.fetch(:fetch_queue) do |data|
|
|
154
|
+
raise(Exception, "again this shouldnt happen")
|
|
155
|
+
end
|
|
156
|
+
rtn.should be_nil
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "queue_options" do
|
|
163
|
+
describe :processed do
|
|
164
|
+
describe "set to :destroy" do
|
|
165
|
+
before(:each) do
|
|
166
|
+
@old_processed = ProcessQueue.queue_options[:processed]
|
|
167
|
+
ProcessQueue.queue_options[:processed] = :destroy
|
|
168
|
+
end
|
|
169
|
+
after(:each) do
|
|
170
|
+
ProcessQueue.queue_options[:processed] = @old_processed
|
|
171
|
+
end
|
|
172
|
+
it "should delete processed items" do
|
|
173
|
+
count = ProcessQueue.count
|
|
174
|
+
|
|
175
|
+
ProcessQueue.set(:abcde, "some value")
|
|
176
|
+
ProcessQueue.count.should == (count + 1)
|
|
177
|
+
|
|
178
|
+
ProcessQueue.get(:abcde).should == "some value"
|
|
179
|
+
ProcessQueue.count.should == count
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
describe "set to something crazy" do
|
|
183
|
+
before(:each) do
|
|
184
|
+
@old_processed = ProcessQueue.queue_options[:processed]
|
|
185
|
+
ProcessQueue.queue_options[:processed] = :something_crazy
|
|
186
|
+
end
|
|
187
|
+
after(:each) do
|
|
188
|
+
ProcessQueue.queue_options[:processed] = @old_processed
|
|
189
|
+
end
|
|
190
|
+
it "should raise an exception" do
|
|
191
|
+
ProcessQueue.set(:abcde, "some value")
|
|
192
|
+
lambda {ProcessQueue.get(:abcde)}.should raise_error(ArgumentError)
|
|
193
|
+
end
|
|
112
194
|
end
|
|
113
|
-
lots_of_tokens.uniq.should == lots_of_tokens
|
|
114
195
|
end
|
|
115
196
|
end
|
|
116
197
|
|
|
198
|
+
describe ".backlog" do
|
|
199
|
+
it "should count the unprocessed items for the provided queue_name" do
|
|
200
|
+
ProcessQueue.backlog(:abcde).should == 0
|
|
201
|
+
|
|
202
|
+
ProcessQueue.set(:abcde, "a value")
|
|
203
|
+
ProcessQueue.backlog(:abcde).should == 1
|
|
204
|
+
|
|
205
|
+
ProcessQueue.set(:something_else, "another value")
|
|
206
|
+
3.times { ProcessQueue.set(:abcde, :add_three_more)}
|
|
207
|
+
|
|
208
|
+
ProcessQueue.backlog(:abcde).should == 4
|
|
209
|
+
|
|
210
|
+
ProcessQueue.get(:abcde).should == "a value"
|
|
211
|
+
ProcessQueue.backlog(:abcde).should == 3
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
117
215
|
describe ".cleanup!" do
|
|
118
216
|
it "should use :delete_all" do
|
|
119
217
|
ProcessQueue.should_receive(:delete_all) # not :destroy_all
|
|
@@ -223,3 +321,40 @@ describe ProcessQueue do
|
|
|
223
321
|
end
|
|
224
322
|
end
|
|
225
323
|
end
|
|
324
|
+
|
|
325
|
+
describe RudeQ::TokenLock do
|
|
326
|
+
|
|
327
|
+
describe ".get_unique_token" do
|
|
328
|
+
it "should create a unique token" do
|
|
329
|
+
lots_of_tokens = Array.new(50) do
|
|
330
|
+
RudeQ::TokenLock.get_unique_token
|
|
331
|
+
end
|
|
332
|
+
lots_of_tokens.uniq.should == lots_of_tokens
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it "should create a unique token even if time stands still" do
|
|
336
|
+
time_now = Time.now
|
|
337
|
+
Time.should_receive(:now).at_least(50).times.and_return(time_now)
|
|
338
|
+
lots_of_tokens = Array.new(50) do
|
|
339
|
+
RudeQ::TokenLock.get_unique_token
|
|
340
|
+
end
|
|
341
|
+
lots_of_tokens.uniq.should == lots_of_tokens
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# it "should not return a processed item with the same token" do
|
|
346
|
+
# @token = "tokEEEannn"
|
|
347
|
+
#
|
|
348
|
+
# RudeQ::TokenLock.should respond_to(:get_unique_token) # ensure our stub is safe
|
|
349
|
+
# RudeQ::TokenLock.should_receive(:get_unique_token).exactly(3).times.and_return(@token)
|
|
350
|
+
#
|
|
351
|
+
# @existing = ProcessQueue.create!(:queue_name => 'abcde', :data => :old_data, :token => @token, :processed => true)
|
|
352
|
+
#
|
|
353
|
+
# ProcessQueue.get('abcde').should be(nil)
|
|
354
|
+
#
|
|
355
|
+
# ProcessQueue.set('abcde', :new_data)
|
|
356
|
+
# ProcessQueue.get('abcde').should == :new_data
|
|
357
|
+
# ProcessQueue.get('abcde').should be(nil)
|
|
358
|
+
# end
|
|
359
|
+
|
|
360
|
+
end
|
data/spec/spec.opts
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: matthewrudy-rudeq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: "0
|
|
4
|
+
version: "2.0"
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Rudy Jacobs
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date:
|
|
12
|
+
date: 2009-02-12 00:00:00 -08:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -21,7 +21,7 @@ dependencies:
|
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
22
|
version: "0"
|
|
23
23
|
version:
|
|
24
|
-
description: A simple DB queueing library built on top of ActiveRecord
|
|
24
|
+
description: A simple DB queueing library built on top of ActiveRecord.
|
|
25
25
|
email: MatthewRudyJacobs@gmail.com
|
|
26
26
|
executables: []
|
|
27
27
|
|
|
@@ -62,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
62
62
|
requirements: []
|
|
63
63
|
|
|
64
64
|
rubyforge_project:
|
|
65
|
-
rubygems_version: 1.0
|
|
65
|
+
rubygems_version: 1.2.0
|
|
66
66
|
signing_key:
|
|
67
67
|
specification_version: 2
|
|
68
68
|
summary: ActiveRecord-based DB-queue
|