matthewrudy-rudeq 0.1

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 ADDED
@@ -0,0 +1,62 @@
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
+ It definitely does not work on SQLite (as it requires :limit and :order options for a SQL UPDATE command)
17
+
18
+ On rails 2.1 you can install straight from github:
19
+ ruby script/plugin install git://github.com/matthewrudy/rudeq.git
20
+
21
+ Else just check it out into your plugins directory:
22
+ git clone git://github.com/matthewrudy/rudeq.git vendor/plugins/rudeq
23
+
24
+ USAGE
25
+ ============
26
+ After you've installed it just run
27
+ rake queue:setup
28
+
29
+ matthew@iRudy:~/code/jbequeueing $ rake queue:setup
30
+ (in /Users/matthew/code/jbequeueing)
31
+ exists app/models/
32
+ exists spec/fixtures/
33
+ exists spec/models/
34
+ create app/models/rude_queue.rb
35
+ create spec/fixtures/rude_queues.yml
36
+ create spec/models/rude_queue_spec.rb
37
+ exists db/migrate
38
+ create db/migrate/029_create_rude_queues.rb
39
+
40
+ and you're done.
41
+ Fully tested, fully index... BOOM!
42
+
43
+ Now run migrations, start up a console, and;
44
+
45
+ RudeQueue.set(:queue_name, RandomObject)
46
+ RudeQueue.get(:queue_name)
47
+
48
+ And, to keep the queue running fast,
49
+ set up a cron job to run
50
+
51
+ rake queue:cleanup
52
+
53
+ the cleanup will remove any queued items which have been processed longer than an hour ago.
54
+
55
+ rake queue:cleanup CLEANUP_TIME=86,400
56
+
57
+ will clear processed queue items processed longer than 86,400 seconds ago (1 day)
58
+
59
+ Try Yourself!
60
+
61
+ Copyright (c) 2008 [Matthew Rudy Jacobs Email: MatthewRudyJacobs@gmail.com],
62
+ 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
data/lib/rude_q.rb ADDED
@@ -0,0 +1,84 @@
1
+ # RudeQ
2
+ require 'digest/sha1'
3
+ module RudeQ
4
+
5
+ def self.included(mod)
6
+ mod.extend(ClassMethods)
7
+ mod.serialize(:data)
8
+ end
9
+
10
+ module ClassMethods
11
+ # Cleanup old processed items
12
+ #
13
+ # RudeQueue.cleanup!
14
+ # RudeQueue.cleanup!(1.week)
15
+ def cleanup!(expiry=1.hour)
16
+ self.delete_all(["processed = ? AND updated_at < ?", true, expiry.to_i.ago])
17
+ end
18
+
19
+ # Add any serialize-able *data* to the queue *queue_name* (strings and symbols are treated the same)
20
+ #
21
+ # RudeQueue.set(:sausage_queue, Sausage.new(:sauce => "yummy"))
22
+ # RudeQueue.set("sausage_queue", Sausage.new(:other => true))
23
+ #
24
+ # RudeQueue.get("sausage_queue")
25
+ # -> *yummy sausage*
26
+ # RudeQueue.get(:sausage_queue)
27
+ # -> *other_sausage*
28
+ # RudeQueue.get(:sausage_queue)
29
+ # -> nil
30
+ def set(queue_name, data)
31
+ queue_name = sanitize_queue_name(queue_name)
32
+ self.create!(:queue_name => queue_name, :data => data)
33
+ return nil # in line with Starling
34
+ end
35
+
36
+ # Grab the first item from the queue *queue_name* (strings and symbols are treated the same)
37
+ # - it should always come out the same as it went in
38
+ # - they should always come out in the same order they went in
39
+ # - it will return a nil if there is no unprocessed entry in the queue
40
+ #
41
+ # RudeQueue.get(21)
42
+ # -> {:a => "hash"}
43
+ # RudeQueue.get(:a_symbol)
44
+ # -> 255
45
+ # RudeQueue.get("a string")
46
+ # -> nil
47
+ def get(queue_name)
48
+ qname = sanitize_queue_name(queue_name)
49
+ token = get_unique_token
50
+
51
+ self.update_all(["token = ?", token], ["queue_name = ? AND processed = ? AND token IS NULL", qname, false], :limit => 1, :order => "id ASC")
52
+ queued = self.find_by_queue_name_and_token_and_processed(qname, token, false)
53
+ if queued
54
+ queued.update_attribute(:processed, true)
55
+ return queued.data
56
+ else
57
+ return nil # in line with Starling
58
+ end
59
+ end
60
+
61
+ def get_unique_token # :nodoc:
62
+
63
+ digest = Digest::SHA1.new
64
+ digest << Time.now.to_s
65
+ digest << Process.pid.to_s
66
+ digest << Socket.gethostname
67
+ digest << self.token_count!.to_s # multiple requests from the same pid in the same second get different token
68
+
69
+ return digest.hexdigest
70
+ end
71
+
72
+ protected
73
+
74
+ def token_count!
75
+ @token_count ||= 0
76
+ @token_count += 1
77
+ return @token_count
78
+ end
79
+
80
+ def sanitize_queue_name(queue_name)
81
+ queue_name.to_s
82
+ end
83
+ end
84
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,4 @@
1
+ rude_q_test:
2
+ adapter: mysql
3
+ username: rude_q
4
+ database: rude_q_test
@@ -0,0 +1,3 @@
1
+ class ProcessQueue < ActiveRecord::Base
2
+ include RudeQ
3
+ end
@@ -0,0 +1,225 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ProcessQueue do
4
+ before(:each) do
5
+ ProcessQueue.delete_all
6
+ create_some_noise
7
+ end
8
+
9
+ def create_some_noise
10
+ ProcessQueue.create!(:queue_name => "doNT use this in Specs", :data => {:not => "to be messed with"})
11
+ 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
+ end
14
+
15
+ describe "get and set" do
16
+ it "should work with strings" do
17
+ ProcessQueue.set('abcde', "Something to set")
18
+ ProcessQueue.get('abcde').should == "Something to set"
19
+ end
20
+ it "should work with symbols" do
21
+ ProcessQueue.set('abcde', :a_symbol)
22
+ ProcessQueue.get('abcde').should == :a_symbol
23
+ end
24
+ it "should work with arrays" do
25
+ array = [1, :b, "C"]
26
+ ProcessQueue.set('abcde', array)
27
+ ProcessQueue.get('abcde').should == array
28
+ end
29
+ it "should work with hashes" do
30
+ hash = {:symbol => "A string", "stringy" => 23, 74 => :cheese}
31
+ ProcessQueue.set('abcde', hash)
32
+ ProcessQueue.get('abcde').should == hash
33
+ end
34
+
35
+ it "should :get in the same order they are :set" do
36
+ ProcessQueue.set('abcde', :first)
37
+ ProcessQueue.set('abcde', "second")
38
+
39
+ ProcessQueue.get('abcde').should == :first
40
+
41
+ ProcessQueue.set('abcde', 33.3333)
42
+
43
+ ProcessQueue.get('abcde').should == "second"
44
+ ProcessQueue.get('abcde').should == 33.3333
45
+ ProcessQueue.get('abcde').should be(nil)
46
+ end
47
+
48
+ it "should keep queues seperated" do
49
+ ProcessQueue.set('queue_1', :data_1)
50
+ ProcessQueue.set('queue_2', "DATA2")
51
+
52
+ ProcessQueue.get('queue_2').should == "DATA2"
53
+ ProcessQueue.get('queue_2').should be(nil)
54
+ ProcessQueue.get('queue_1').should == :data_1
55
+ ProcessQueue.get('queue_1').should be(nil)
56
+ end
57
+
58
+ it "should call to_s on inputs" do
59
+ qname = stub("fake input")
60
+ qname.should_receive(:to_s).exactly(:twice).and_return("fake queue name")
61
+
62
+ ProcessQueue.set(qname, ["Data"])
63
+ ProcessQueue.get(qname).should == ["Data"]
64
+ end
65
+
66
+ it "should work with queue name as strings or symbols" do
67
+ ProcessQueue.set(:bah, "something about bah")
68
+ ProcessQueue.get("bah").should == "something about bah"
69
+
70
+ ProcessQueue.set("girah", {:craziness => "embodied"})
71
+ ProcessQueue.get(:girah).should == {:craziness => "embodied"}
72
+ end
73
+ end
74
+
75
+ describe ".set" do
76
+ it "should delegate to :create!" do
77
+ ProcessQueue.should_receive(:create!).with(:queue_name => 'abcde', :data => :magical_planet)
78
+ ProcessQueue.set('abcde', :magical_planet)
79
+ end
80
+ it "should return nil" do
81
+ ProcessQueue.set('abcde', "something").should be(nil)
82
+ end
83
+ end
84
+
85
+ describe ".get" do
86
+ it "should not return a processed item with the same token" do
87
+ @token = "tokEEEannn"
88
+ ProcessQueue.should_receive(:get_unique_token).exactly(3).times.and_return(@token)
89
+ @existing = ProcessQueue.create!(:queue_name => 'abcde', :data => :old_data, :token => @token, :processed => true)
90
+
91
+ ProcessQueue.get('abcde').should be(nil)
92
+
93
+ ProcessQueue.set('abcde', :new_data)
94
+ ProcessQueue.get('abcde').should == :new_data
95
+ ProcessQueue.get('abcde').should be(nil)
96
+ end
97
+ end
98
+
99
+ describe ".get_unique_token" do
100
+ it "should create a unique token" do
101
+ lots_of_tokens = Array.new(50) do
102
+ ProcessQueue.get_unique_token
103
+ end
104
+ lots_of_tokens.uniq.should == lots_of_tokens
105
+ end
106
+
107
+ it "should create a unique token even if time stands still" do
108
+ time_now = Time.now
109
+ Time.should_receive(:now).at_least(50).times.and_return(time_now)
110
+ lots_of_tokens = Array.new(50) do
111
+ ProcessQueue.get_unique_token
112
+ end
113
+ lots_of_tokens.uniq.should == lots_of_tokens
114
+ end
115
+ end
116
+
117
+ describe ".cleanup!" do
118
+ it "should use :delete_all" do
119
+ ProcessQueue.should_receive(:delete_all) # not :destroy_all
120
+ ProcessQueue.cleanup!
121
+ end
122
+
123
+ it "should allow string inputs" do
124
+ ProcessQueue.cleanup!("3600")
125
+ end
126
+
127
+ it "should allow integer inputs" do
128
+ ProcessQueue.cleanup!(3600)
129
+ end
130
+
131
+ it "should not clear unprocessed items" do
132
+ ProcessQueue.set('abcde', :giraffe)
133
+ ProcessQueue.set('abcde', :monkey)
134
+ ProcessQueue.count.should >= 2
135
+
136
+ ProcessQueue.cleanup!
137
+
138
+ ProcessQueue.count.should >=2
139
+ ProcessQueue.get('abcde').should == :giraffe
140
+ end
141
+
142
+ it "should not clear old unprocessed items" do
143
+ ProcessQueue.set('abcde', :giraffe)
144
+ giraffe = ProcessQueue.find(:first, :conditions => {:data => :giraffe})
145
+
146
+ time_now = Time.now
147
+ Time.stub!(:now).and_return(time_now + 1.year)
148
+
149
+ giraffe.updated_at.should < 2.weeks.ago
150
+
151
+ ProcessQueue.cleanup!
152
+
153
+ giraffe.reload
154
+ ProcessQueue.get('abcde').should == :giraffe
155
+ end
156
+
157
+ it "should not clear processed items newer than the argument" do
158
+ ProcessQueue.set('abcde', :giraffe)
159
+ ProcessQueue.get('abcde').should == :giraffe
160
+
161
+ giraffe = ProcessQueue.find(:first, :conditions => {:data => :giraffe})
162
+
163
+ time_now = Time.now
164
+ Time.stub!(:now).and_return(time_now + 1.week - 5.minutes)
165
+
166
+ giraffe.updated_at.should > 1.week.ago
167
+ giraffe.processed.should be(true)
168
+
169
+ ProcessQueue.cleanup!(1.week)
170
+
171
+ giraffe.reload
172
+ end
173
+
174
+ it "should not clear processed items newer than one hour, by default" do
175
+ ProcessQueue.set('abcde', :giraffe)
176
+ ProcessQueue.get('abcde').should == :giraffe
177
+
178
+ giraffe = ProcessQueue.find(:first, :conditions => {:data => :giraffe})
179
+
180
+ time_now = Time.now
181
+ Time.stub!(:now).and_return(time_now + 59.minutes)
182
+
183
+ giraffe.updated_at.should > 1.hour.ago
184
+ giraffe.processed.should be(true)
185
+
186
+ ProcessQueue.cleanup!()
187
+
188
+ giraffe.reload
189
+ end
190
+
191
+ it "should clear processed items older than the argument" do
192
+ ProcessQueue.set('abcde', :giraffe)
193
+ ProcessQueue.get('abcde').should == :giraffe
194
+
195
+ giraffe = ProcessQueue.find(:first, :conditions => {:data => :giraffe})
196
+
197
+ time_now = Time.now
198
+ Time.stub!(:now).and_return(time_now + 1.week + 5.minutes)
199
+
200
+ giraffe.updated_at.should < 1.week.ago
201
+ giraffe.processed.should be(true)
202
+
203
+ ProcessQueue.cleanup!(1.week)
204
+
205
+ lambda { giraffe.reload }.should raise_error(ActiveRecord::RecordNotFound)
206
+ end
207
+
208
+ it "should clear processed items older than one hour, by default" do
209
+ ProcessQueue.set('abcde', :giraffe)
210
+ ProcessQueue.get('abcde').should == :giraffe
211
+
212
+ giraffe = ProcessQueue.find(:first, :conditions => {:data => :giraffe})
213
+
214
+ time_now = Time.now()
215
+ Time.stub!(:now).and_return(time_now + 61.minutes)
216
+
217
+ giraffe.updated_at.should < 1.hour.ago
218
+ giraffe.processed.should be(true)
219
+
220
+ ProcessQueue.cleanup!
221
+
222
+ lambda { giraffe.reload }.should raise_error(ActiveRecord::RecordNotFound)
223
+ end
224
+ end
225
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,12 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+ create_table :process_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 :process_queues, :processed
11
+ add_index :process_queues, [:queue_name, :processed]
12
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,12 @@
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}/process_queue"
8
+
9
+ config = YAML::load(IO.read(current_dir + '/database.yml'))
10
+ ActiveRecord::Base.logger = Logger.new(current_dir + "/debug.log")
11
+ ActiveRecord::Base.establish_connection(config['rude_q_test'])
12
+ load(current_dir + "/schema.rb")
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: matthewrudy-rudeq
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Rudy Jacobs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: A simple DB queueing library built on top of ActiveRecord, and designed for use with MySQL.
25
+ email: MatthewRudyJacobs@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ files:
33
+ - README
34
+ - Rakefile
35
+ - lib/rude_q.rb
36
+ - spec/database.yml
37
+ - spec/process_queue.rb
38
+ - spec/rude_q_spec.rb
39
+ - spec/schema.rb
40
+ - spec/spec.opts
41
+ - spec/spec_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/matthewrudy/rudeq
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --main
47
+ - README
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.0.1
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: ActiveRecord-based DB-queue
69
+ test_files:
70
+ - spec/rude_q_spec.rb