gustin-rudeq 2.1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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