pg_queue 0.0.0a → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/lib/pg_queue/job.rb CHANGED
@@ -1,18 +1,19 @@
1
+ require "multi_json"
2
+
1
3
  module PgQueue
2
4
  class Job
3
5
  attr_reader :id, :klass, :args
4
6
 
5
7
  def initialize(attributes)
6
8
  @id = attributes["id"]
7
- puts "new job #{@id}"
8
- @klass = Object.const_get(attributes["klass"])
9
- @args = JSON.load(attributes["args"])
9
+ @klass = Object.const_get(attributes["class_name"])
10
+ @args = MultiJson.decode(attributes["args"])
10
11
  end
11
12
 
12
13
  def perform
13
- puts "performing"
14
+ PgQueue.logger.debug("performing job #{@id}")
14
15
  klass.perform(*args)
15
- puts "performed"
16
+ PgQueue.logger.debug("job #{@id} performed")
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,41 @@
1
+ module PgQueue
2
+ module PgExtensions
3
+ def insert(table_name, hash, returning = nil)
4
+ fields = hash.keys.join(", ")
5
+ variables = hash.size.times.map { |n| "$#{n+1}" }.join(", ")
6
+ sql = "INSERT INTO #{table_name} (#{fields}) VALUES (#{variables})"
7
+ if returning
8
+ sql << " RETURNING #{returning}"
9
+ exec(sql, hash.values).getvalue(0, 0)
10
+ else
11
+ exec(sql, hash.values)
12
+ end
13
+ end
14
+
15
+ def delete(table_name, where)
16
+ i = 0
17
+ conditions = where.map { |k, v| i += 1; "#{k} = $#{i}" }.join(" AND ")
18
+ exec("DELETE FROM #{table_name} WHERE #{conditions}", where.values)
19
+ end
20
+
21
+ def first(sql)
22
+ result = exec(sql)
23
+ return nil unless result.count > 0
24
+ result[0]
25
+ end
26
+
27
+ def notify(key, message = nil)
28
+ sql = "NOTIFY #{key}"
29
+ sql << ", '#{message}'" if message
30
+ exec(sql)
31
+ end
32
+
33
+ def listen(key)
34
+ exec("LISTEN #{key}")
35
+ end
36
+
37
+ def unlisten(key)
38
+ exec("UNLISTEN #{key}")
39
+ end
40
+ end
41
+ end
@@ -1,3 +1,3 @@
1
1
  module PgQueue
2
- VERSION = "0.0.0a"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,47 +1,33 @@
1
1
  module PgQueue
2
2
  class Worker
3
- attr_reader :connection, :interval
4
-
5
- def initialize
6
- @connection = new_connection
7
- @queue = PgQueue::Queue.new(@connection)
8
- @interval = 5
9
- end
10
-
11
3
  def start
12
4
  @running = true
13
5
 
14
- listen
6
+ PgQueue.connection.listen(:pg_queue_jobs)
15
7
  while running?
16
- job = @queue.dequeue
8
+ job = PgQueue.dequeue
17
9
  if job
18
10
  perform(job)
19
11
  next
20
12
  end
21
13
 
22
- puts "waiting for jobs"
23
- connection.wait_for_notify do |event, pid, payload|
14
+ PgQueue.logger.debug("waiting for jobs")
15
+ PgQueue.connection.wait_for_notify do |event, pid, payload|
24
16
  if payload == "stop"
25
- puts "stop notify received"
17
+ PgQueue.logger.debug("stop notify received")
26
18
  stop
27
19
  else
28
- puts "let's perform some jobs"
20
+ PgQueue.logger.debug("let's perform some jobs")
29
21
  end
30
22
  end
31
23
  end
32
- end
33
-
34
- def listen
35
- connection.exec("LISTEN pg_queue_jobs")
36
- end
37
-
38
- def unlisten
39
- connection.exec("UNLISTEN pg_queue_jobs")
24
+ PgQueue.connection.unlisten(:pg_queue_jobs)
40
25
  end
41
26
 
42
27
  def stop
43
28
  @running = false
44
- new_connection.exec("NOTIFY pg_queue_jobs, 'stop'")
29
+ PgQueue.connection = nil
30
+ PgQueue.connection.notify(:pg_queue_jobs, "stop")
45
31
  end
46
32
 
47
33
  def running?
@@ -50,17 +36,13 @@ module PgQueue
50
36
 
51
37
  protected
52
38
 
53
- def new_connection
54
- PGconn.open(:dbname => 'pg_queue_test')
55
- end
56
-
57
39
  def perform(job)
58
40
  begin
59
- puts job.inspect
41
+ PgQueue.logger.debug(job.inspect)
60
42
  job.perform
61
43
  rescue => ex
62
- puts ex
63
- puts ex.message
44
+ PgQueue.logger.fatal(ex)
45
+ PgQueue.logger.fatal(ex.message)
64
46
  end
65
47
  end
66
48
  end
data/lib/pg_queue.rb CHANGED
@@ -1,8 +1,45 @@
1
1
  require "pg_queue/version"
2
2
  require "pg"
3
+ require "logger"
4
+ require "multi_json"
3
5
 
4
6
  module PgQueue
5
7
  autoload :Worker, "pg_queue/worker"
6
- autoload :Queue, "pg_queue/queue"
7
8
  autoload :Job, "pg_queue/job"
9
+ autoload :PgExtensions, "pg_queue/pg_extensions"
10
+
11
+ def self.logger
12
+ @logger ||= Logger.new(STDOUT)
13
+ end
14
+
15
+ def self.logger=(object)
16
+ @logger = object
17
+ end
18
+
19
+ def self.connection
20
+ @connection ||= begin
21
+ conn = PGconn.open(:dbname => 'pg_queue_test')
22
+ conn.extend(PgQueue::PgExtensions)
23
+ end
24
+ end
25
+
26
+ def self.connection=(object)
27
+ @connection = object
28
+ end
29
+
30
+ def self.enqueue(klass, *args)
31
+ id = connection.insert(:pg_queue_jobs, { :class_name => klass.name, :args => MultiJson.encode(args) }, "id")
32
+ logger.debug("enqueued #{id}")
33
+ connection.notify(:pg_queue_jobs)
34
+ end
35
+
36
+ def self.dequeue
37
+ result = connection.first("SELECT id, class_name, args FROM pg_queue_jobs LIMIT 1")
38
+ return nil unless result
39
+
40
+ PgQueue::Job.new(result).tap do |job|
41
+ PgQueue.logger.debug("dequeued #{job.id}")
42
+ connection.delete(:pg_queue_jobs, :id => job.id)
43
+ end
44
+ end
8
45
  end
data/pg_queue.gemspec CHANGED
@@ -16,4 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = PgQueue::VERSION
17
17
 
18
18
  gem.add_dependency "pg", "~> 0.12.2"
19
+ gem.add_dependency "multi_json", "~> 1.0.4"
20
+
21
+ gem.add_development_dependency "rspec", "~> 2.8.0"
19
22
  end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ class MyQueue; end
4
+
5
+ describe PgQueue::Job do
6
+ subject do
7
+ described_class.new(
8
+ "id" => 1,
9
+ "class_name" => "MyQueue",
10
+ "args" => MultiJson.encode([1, "two"])
11
+ )
12
+ end
13
+
14
+ its(:id) { should == 1 }
15
+ its(:klass) { should == MyQueue }
16
+ its(:args) { should == [1, "two"] }
17
+
18
+ it "performs the job" do
19
+ subject.klass.should_receive(:perform)
20
+ subject.perform
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe PgQueue::Worker do
4
+ subject { described_class.new }
5
+ before do
6
+ PgQueue.connection = double("connection").as_null_object
7
+ end
8
+
9
+ context "starting" do
10
+ before do
11
+ PgQueue.connection.should_receive(:listen).with(:pg_queue_jobs)
12
+ [true, false].each { |ret| subject.should_receive(:running?).and_return(ret) }
13
+ PgQueue.connection.should_receive(:unlisten).with(:pg_queue_jobs)
14
+ end
15
+
16
+ context "with jobs pending" do
17
+ let(:job) { double("job") }
18
+
19
+ it "performs the job" do
20
+ PgQueue.should_receive(:dequeue).and_return(job)
21
+ job.should_receive(:perform)
22
+
23
+ subject.start
24
+ end
25
+ end
26
+
27
+ context "with no jobs pending" do
28
+ it "waits for notifies" do
29
+ PgQueue.should_receive(:dequeue).and_return(nil)
30
+ PgQueue.connection.should_receive(:wait_for_notify).and_yield("pg_queue_jobs", "12345", nil)
31
+
32
+ subject.start
33
+ end
34
+ end
35
+ end
36
+
37
+ context "stoping" do
38
+ let(:connection) { double("connection") }
39
+
40
+ before do
41
+ subject.instance_variable_set(:@running, true)
42
+ end
43
+
44
+ it "notifies worker to stop" do
45
+ PgQueue.should_receive(:connection=).with(nil)
46
+ PgQueue.should_receive(:connection).and_return(connection)
47
+ connection.should_receive(:notify).with(:pg_queue_jobs, "stop")
48
+
49
+ subject.should be_running
50
+ subject.stop
51
+ subject.should_not be_running
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ class MyLogger; end
4
+ class MyDbConnection; end
5
+ class MyQueue; end
6
+
7
+ describe PgQueue do
8
+ context "logging" do
9
+ its(:logger) { should be_instance_of(Logger) }
10
+
11
+ it "defines a new logger" do
12
+ described_class.logger = MyLogger.new
13
+ described_class.logger.should be_instance_of(MyLogger)
14
+ end
15
+
16
+ after do
17
+ described_class.logger = nil
18
+ end
19
+ end
20
+
21
+ context "database connection" do
22
+ its(:connection) { should be_instance_of(PGconn) }
23
+
24
+ context "extensions" do
25
+ subject { described_class.connection }
26
+ it { should respond_to(:insert) }
27
+ it { should respond_to(:notify) }
28
+ it { should respond_to(:delete) }
29
+ it { should respond_to(:first) }
30
+ it { should respond_to(:listen) }
31
+ it { should respond_to(:unlisten) }
32
+ end
33
+
34
+ it "defines a new connection" do
35
+ described_class.connection = MyDbConnection.new
36
+ described_class.connection.should be_instance_of(MyDbConnection)
37
+ end
38
+
39
+ after do
40
+ described_class.connection = nil
41
+ end
42
+ end
43
+
44
+ context "enqueuing/dequeuing" do
45
+ let(:connection) { described_class.connection }
46
+ let(:result) { double("result") }
47
+
48
+ it "enqueues a job" do
49
+ connection.should_receive(:insert).with(:pg_queue_jobs, instance_of(Hash), "id").and_return("1")
50
+ connection.should_receive(:notify).with(:pg_queue_jobs)
51
+
52
+ described_class.enqueue(MyQueue, 1, "two")
53
+ end
54
+
55
+ it "dequeues a job" do
56
+ connection.should_receive(:first).with(/LIMIT 1/).and_return(result)
57
+ result.should_receive(:[]).with("id").and_return("1")
58
+ result.should_receive(:[]).with("class_name").and_return("MyQueue")
59
+ result.should_receive(:[]).with("args").and_return("[1, \"two\"]")
60
+ connection.should_receive(:delete).with(:pg_queue_jobs, :id => "1")
61
+
62
+ subject.dequeue.should be_instance_of(PgQueue::Job)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ require "pg_queue"
2
+
3
+ PgQueue.logger = Logger.new("/dev/null")
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0a
5
- prerelease: 5
4
+ version: 0.0.2
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Rafael Souza
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-26 00:00:00.000000000 Z
12
+ date: 2012-01-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg
16
- requirement: &70283076765840 !ruby/object:Gem::Requirement
16
+ requirement: &70278043238320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,29 @@ dependencies:
21
21
  version: 0.12.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70283076765840
24
+ version_requirements: *70278043238320
25
+ - !ruby/object:Gem::Dependency
26
+ name: multi_json
27
+ requirement: &70278043237820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.4
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70278043237820
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70278043237360 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70278043237360
25
47
  description: Some experimentations with LISTEN/NOTIFY for background jobs
26
48
  email:
27
49
  - me@rafaelss.com
@@ -31,6 +53,7 @@ extensions: []
31
53
  extra_rdoc_files: []
32
54
  files:
33
55
  - .gitignore
56
+ - .rspec
34
57
  - Gemfile
35
58
  - LICENSE
36
59
  - README.md
@@ -38,10 +61,14 @@ files:
38
61
  - bin/queue_worker
39
62
  - lib/pg_queue.rb
40
63
  - lib/pg_queue/job.rb
41
- - lib/pg_queue/queue.rb
64
+ - lib/pg_queue/pg_extensions.rb
42
65
  - lib/pg_queue/version.rb
43
66
  - lib/pg_queue/worker.rb
44
67
  - pg_queue.gemspec
68
+ - spec/pg_queue/job_spec.rb
69
+ - spec/pg_queue/worker_spec.rb
70
+ - spec/pg_queue_spec.rb
71
+ - spec/spec_helper.rb
45
72
  homepage: http://github.com/rafaelss/pg_queue
46
73
  licenses: []
47
74
  post_install_message:
@@ -57,13 +84,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
84
  required_rubygems_version: !ruby/object:Gem::Requirement
58
85
  none: false
59
86
  requirements:
60
- - - ! '>'
87
+ - - ! '>='
61
88
  - !ruby/object:Gem::Version
62
- version: 1.3.1
89
+ version: '0'
63
90
  requirements: []
64
91
  rubyforge_project:
65
92
  rubygems_version: 1.8.10
66
93
  signing_key:
67
94
  specification_version: 3
68
95
  summary: Background jobs using PostgreSQL's LISTEN/NOTIFY
69
- test_files: []
96
+ test_files:
97
+ - spec/pg_queue/job_spec.rb
98
+ - spec/pg_queue/worker_spec.rb
99
+ - spec/pg_queue_spec.rb
100
+ - spec/spec_helper.rb
@@ -1,26 +0,0 @@
1
- require "json"
2
-
3
- module PgQueue
4
- class Queue
5
- attr_reader :connection
6
-
7
- def initialize(connection)
8
- @connection = connection
9
- end
10
-
11
- def enqueue(klass, *args)
12
- sql = "INSERT INTO pg_queue_jobs (klass, args) VALUES ($1, $2) RETURNING id"
13
- id = connection.exec(sql, [klass.name, JSON.dump(args)]).getvalue(0, 0)
14
- puts "enqueued #{id}"
15
- connection.exec("NOTIFY pg_queue_jobs")
16
- end
17
-
18
- def dequeue
19
- result = connection.exec("SELECT id, klass, args FROM pg_queue_jobs LIMIT 1")
20
- return nil unless result.count == 1
21
- PgQueue::Job.new(result[0]).tap do |job|
22
- connection.exec("DELETE FROM pg_queue_jobs WHERE id = $1", [job.id])
23
- end
24
- end
25
- end
26
- end