gustin-rudeq 2.1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,3 @@
1
+ class <%= class_name %> < ActiveRecord::Base
2
+ include RudeQ
3
+ 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
@@ -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
+
@@ -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
+
@@ -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
@@ -0,0 +1,4 @@
1
+ rude_q_test:
2
+ adapter: mysql
3
+ username: rude_q
4
+ database: rude_q_test
@@ -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
+
@@ -0,0 +1,3 @@
1
+ class Something < ActiveRecord::Base
2
+ end
3
+
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -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")
@@ -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
@@ -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