pub 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +7 -19
- data/Rakefile +14 -1
- data/features/README.md +9 -0
- data/features/order.feature +63 -0
- data/features/step_definitions/numeric_transforms.rb +3 -0
- data/features/step_definitions/pub_steps.rb +112 -0
- data/features/support/env.rb +15 -0
- data/lib/pub.rb +60 -10
- data/lib/pub/bartender.rb +44 -0
- data/lib/pub/helpers.rb +18 -0
- data/lib/pub/patron.rb +56 -0
- data/lib/pub/version.rb +2 -2
- data/pub.gemspec +11 -13
- data/spec/pub/bartender_spec.rb +47 -0
- data/spec/pub/patron_spec.rb +66 -0
- data/spec/pub_spec.rb +28 -0
- data/spec/spec_helper.rb +13 -5
- metadata +47 -32
data/README.md
CHANGED
@@ -1,21 +1,9 @@
|
|
1
|
-
|
1
|
+
# Pub
|
2
2
|
|
3
|
-
|
3
|
+
Pub is a Redis-backed pub with a non-blocking bar counter, or, putting aside
|
4
|
+
the metaphor for a moment, a processing queue where consumers, instead of
|
5
|
+
simply queuing jobs and getting on with their lives, queue and wait for a
|
6
|
+
response without blocking the Ruby process.
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
# Subscriber: A request
|
9
|
-
|
10
|
-
Queue["foo"].subscribe("1234") do |resp|
|
11
|
-
puts resp
|
12
|
-
end
|
13
|
-
|
14
|
-
# Publisher: A background worker
|
15
|
-
|
16
|
-
queue = Queue["foo"]
|
17
|
-
# Process up to 10 requests at a time
|
18
|
-
reqs = queue.pop(10)
|
19
|
-
process(reqs) do |req, resp|
|
20
|
-
queue.publish(req, resp)
|
21
|
-
end
|
8
|
+
This is a barebone work-in-progress that I am about to give a test ride to in
|
9
|
+
an app I'm building.
|
data/Rakefile
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "cucumber/rake/task"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
6
|
+
t.cucumber_opts = "features --format pretty"
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Run all specs in spec directory"
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
11
|
+
t.pattern = "spec/**/*_spec.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => [:spec, :features]
|
data/features/README.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Pub
|
2
|
+
|
3
|
+
Pub is a Redis-backed pub with a non-blocking bar counter, or, putting aside
|
4
|
+
the metaphor for a moment, a processing queue where consumers, instead of
|
5
|
+
simply queuing jobs and getting on with their lives, queue and wait for a
|
6
|
+
response without blocking the Ruby process.
|
7
|
+
|
8
|
+
This is a barebone work-in-progress that I am about to give a test ride to in
|
9
|
+
an app I'm building.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
Feature: Order beers
|
2
|
+
As a pub patron
|
3
|
+
I want bartenders to take orders asynchronously
|
4
|
+
So I do not block the counter
|
5
|
+
|
6
|
+
Below we assume a bartender can fill an order in one second.
|
7
|
+
|
8
|
+
Scenario: Order a beer
|
9
|
+
Given 1 bartender
|
10
|
+
When "John" orders:
|
11
|
+
| beer |
|
12
|
+
| Brooklyn Lager |
|
13
|
+
Then "John" should receive his beer in 1 second
|
14
|
+
|
15
|
+
Scenario: Order multiple beers
|
16
|
+
Given 1 bartender
|
17
|
+
When "John" orders:
|
18
|
+
| beer |
|
19
|
+
| Brooklyn Lager |
|
20
|
+
| Efes Pilsen |
|
21
|
+
Then "John" should receive his beers in 2 seconds
|
22
|
+
|
23
|
+
Scenario: Two bartenders
|
24
|
+
Given 2 bartenders
|
25
|
+
When "John" orders:
|
26
|
+
| beer |
|
27
|
+
| Brooklyn Lager |
|
28
|
+
| Efes Pilsen |
|
29
|
+
Then "John" should receive his beers in 1 second
|
30
|
+
|
31
|
+
Scenario: Timeout
|
32
|
+
Given 1 bartender
|
33
|
+
And "John" has no patience to wait more than 2 seconds at the counter
|
34
|
+
When "John" orders:
|
35
|
+
| beer |
|
36
|
+
| Brooklyn Lager |
|
37
|
+
| Efes Pilsen |
|
38
|
+
| Stella |
|
39
|
+
Then "John" should receive the following beers in 2 seconds:
|
40
|
+
| beer |
|
41
|
+
| A pint of Brooklyn Lager |
|
42
|
+
| A pint of Efes Pilsen |
|
43
|
+
|
44
|
+
Scenario: Two patrons order the same beer
|
45
|
+
Given 1 bartender
|
46
|
+
And "John" orders:
|
47
|
+
| beer |
|
48
|
+
| Brooklyn Lager |
|
49
|
+
And "Jane" orders:
|
50
|
+
| beer |
|
51
|
+
| Brooklyn Lager |
|
52
|
+
Then "John" should receive his beer in 1 second
|
53
|
+
And "Jane" should receive her beer in 1 second
|
54
|
+
|
55
|
+
Scenario: Crowd
|
56
|
+
Given 10 bartenders
|
57
|
+
When 10 patrons order 1 beer each
|
58
|
+
Then they should receive their beers within 1 second
|
59
|
+
|
60
|
+
Scenario: Overcrowding
|
61
|
+
Given 2 bartenders
|
62
|
+
When 4 patrons order 2 beers each
|
63
|
+
Then they should receive their beers within 4 seconds
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module PubHelpers
|
2
|
+
def data_store(name)
|
3
|
+
patron_names << name unless patron_names.include?(name)
|
4
|
+
|
5
|
+
instance_variable_get("@hash_of_#{name}") ||
|
6
|
+
instance_variable_set("@hash_of_#{name}", Hash.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_or_create_patron(name)
|
10
|
+
instance_variable_get("@#{name}") ||
|
11
|
+
instance_variable_set("@#{name}", pub.new_patron)
|
12
|
+
end
|
13
|
+
|
14
|
+
def map_beers(table)
|
15
|
+
table.hashes.map { |hash| hash["beer"] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def now
|
19
|
+
Time.now
|
20
|
+
end
|
21
|
+
|
22
|
+
def orders
|
23
|
+
@orders ||= Hash.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def patron_names
|
27
|
+
@patron_names ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def pub
|
31
|
+
@pub ||= Pub.new("Ye Olde Rubies")
|
32
|
+
end
|
33
|
+
|
34
|
+
def sleep_until_order_complete(name)
|
35
|
+
fiber = data_store(name)[:fiber]
|
36
|
+
EM::Synchrony.sleep(0.1) while fiber.alive?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
World(PubHelpers)
|
41
|
+
|
42
|
+
Given /^(\d+) bartenders?$/ do |count|
|
43
|
+
count.times do |counter|
|
44
|
+
bartender = pub.new_bartender
|
45
|
+
EM.add_periodic_timer(1) do
|
46
|
+
Fiber.new do
|
47
|
+
orders = bartender.take_orders(1)
|
48
|
+
order = orders.first
|
49
|
+
bartender.serve(order) { "A pint of #{order}" } if order
|
50
|
+
end.resume
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Given /^"([^"]+)" has no patience to wait more than (\d+) seconds at the counter$/ do |name, seconds|
|
56
|
+
patron = find_or_create_patron(name)
|
57
|
+
patron.instance_variable_set(:@timeout, seconds)
|
58
|
+
end
|
59
|
+
|
60
|
+
When /^"([^"]+)" orders:$/ do |name, table|
|
61
|
+
beers = map_beers(table)
|
62
|
+
patron = find_or_create_patron(name)
|
63
|
+
|
64
|
+
data_store(name)[:started_at] = Time.now
|
65
|
+
|
66
|
+
fiber = Fiber.new do
|
67
|
+
tray = patron.order(*beers)
|
68
|
+
data_store(name)[:tray] = tray
|
69
|
+
end
|
70
|
+
data_store(name)[:fiber] = fiber
|
71
|
+
|
72
|
+
EM.next_tick { fiber.resume }
|
73
|
+
end
|
74
|
+
|
75
|
+
When /^(\d+) patrons order (\d+) beers? each$/ do |patrons_count, beers_count|
|
76
|
+
patrons_count.times do |patron_counter|
|
77
|
+
raw = beers_count.times.inject("| beer |\n") do |raw, beer_counter|
|
78
|
+
raw << "| beer_#{patron_counter}_#{beer_counter} |\n"
|
79
|
+
end
|
80
|
+
name = "patron_#{patron_counter}"
|
81
|
+
When %{"#{name}" orders:}, table(raw)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Then /^"([^"]+)" should receive (?:his|her) beers? in (\d+) seconds?$/ do |name, seconds|
|
86
|
+
sleep_until_order_complete(name)
|
87
|
+
|
88
|
+
started_at = data_store(name)[:started_at]
|
89
|
+
(now - started_at).should be_within(0.2).of(seconds)
|
90
|
+
end
|
91
|
+
|
92
|
+
Then /^"([^"]+)" should receive the following beers? in (\d+) seconds?:$/ do |name, seconds, table|
|
93
|
+
sleep_until_order_complete(name)
|
94
|
+
|
95
|
+
started_at = data_store(name)[:started_at]
|
96
|
+
(now - started_at).should be_within(0.2).of(seconds)
|
97
|
+
|
98
|
+
beers = map_beers(table)
|
99
|
+
tray = data_store(name)[:tray]
|
100
|
+
tray.should =~ beers
|
101
|
+
end
|
102
|
+
|
103
|
+
Then /^they should receive their beers within (\d+) seconds?$/ do |seconds|
|
104
|
+
started_at = now
|
105
|
+
patron_names.each do |name|
|
106
|
+
patron_started_at = data_store(name)[:started_at]
|
107
|
+
started_at = patron_started_at if patron_started_at < started_at
|
108
|
+
sleep_until_order_complete(name)
|
109
|
+
end
|
110
|
+
|
111
|
+
(now - started_at).should be_within(0.2).of(seconds)
|
112
|
+
end
|
data/lib/pub.rb
CHANGED
@@ -1,14 +1,64 @@
|
|
1
|
-
begin
|
2
|
-
require "yajl"
|
3
|
-
rescue LoadError
|
4
|
-
require "json"
|
5
|
-
end
|
6
|
-
|
7
|
-
require "redis/connection/synchrony"
|
8
1
|
require "redis"
|
2
|
+
require "redis/connection/synchrony"
|
3
|
+
|
4
|
+
require "pub/helpers"
|
5
|
+
require "pub/bartender"
|
6
|
+
require "pub/patron"
|
7
|
+
|
8
|
+
# A Redis-backed pub with a non-blocking bar counter.
|
9
|
+
#
|
10
|
+
# Or, putting aside the metaphor for a moment:
|
11
|
+
#
|
12
|
+
# A processing queue where consumers, instead of simply queuing jobs and
|
13
|
+
# getting on with their lives, queue and wait for a response without
|
14
|
+
# blocking the Ruby process.
|
15
|
+
#
|
16
|
+
# Each pub instance is a distinct queue.
|
17
|
+
class Pub
|
18
|
+
# The name of the pub.
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# A device that dispenses beer.
|
23
|
+
attr_accessor :beer_tap
|
24
|
+
|
25
|
+
# The bar counter.
|
26
|
+
def counter
|
27
|
+
Redis.new(url: beer_tap)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Enters a pub.
|
32
|
+
#
|
33
|
+
# Takes the name of the pub and an optional block.
|
34
|
+
#
|
35
|
+
# Pub.new('Ye Olde Rubies') do |pub|
|
36
|
+
#
|
37
|
+
# patron = pub.new_patron
|
38
|
+
#
|
39
|
+
# patron.order('Guinness') do |beer|
|
40
|
+
# JSON.parse(beer).drink
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
def initialize(name)
|
46
|
+
@name = name
|
47
|
+
EM.synchrony { yield self } if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
# Closes the pub for the night.
|
51
|
+
def close
|
52
|
+
EM.stop
|
53
|
+
end
|
9
54
|
|
10
|
-
|
55
|
+
# A new bartender.
|
56
|
+
def new_bartender
|
57
|
+
Bartender.new(name)
|
58
|
+
end
|
11
59
|
|
12
|
-
|
13
|
-
|
60
|
+
# A new patron.
|
61
|
+
def new_patron(timeout = 5)
|
62
|
+
Patron.new(name, timeout)
|
63
|
+
end
|
14
64
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Pub
|
2
|
+
# A bartender.
|
3
|
+
#
|
4
|
+
# In other words, a producer in our processing queue.
|
5
|
+
class Bartender
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
# Creates a new bartender.
|
9
|
+
#
|
10
|
+
# Takes the name of the pub.
|
11
|
+
def initialize(pub_name)
|
12
|
+
@pub_name = pub_name
|
13
|
+
end
|
14
|
+
|
15
|
+
# Serves a beer to a thirsty patron.
|
16
|
+
#
|
17
|
+
# Takes a block which should return a glass of beer.
|
18
|
+
#
|
19
|
+
# See below for example usage.
|
20
|
+
def serve(beer, &block)
|
21
|
+
counter.lrem(@pub_name, 0, beer)
|
22
|
+
counter.publish(order_for(beer), block.call)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Takes one or more orders from the queue.
|
26
|
+
#
|
27
|
+
# orders = bartender.take_orders(3)
|
28
|
+
# orders.each do |order|
|
29
|
+
# bartender.serve(order) do
|
30
|
+
# "A pint of #{order}"
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
def take_orders(count = 1)
|
34
|
+
orders = Array.new
|
35
|
+
|
36
|
+
count.times do
|
37
|
+
order = counter.lpop(@pub_name) || break
|
38
|
+
orders << order
|
39
|
+
end
|
40
|
+
|
41
|
+
orders
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/pub/helpers.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class Pub
|
2
|
+
# Methods used by both patron and bartender.
|
3
|
+
module Helpers
|
4
|
+
# A specific order.
|
5
|
+
#
|
6
|
+
# This is a pub/sub channel through with patron and bartender communicate.
|
7
|
+
def order_for(beer)
|
8
|
+
[@pub_name, beer].join(':')
|
9
|
+
end
|
10
|
+
|
11
|
+
# The bar counter.
|
12
|
+
def counter
|
13
|
+
@counter ||= Pub.counter
|
14
|
+
end
|
15
|
+
|
16
|
+
private :order_for, :counter
|
17
|
+
end
|
18
|
+
end
|
data/lib/pub/patron.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class Pub
|
2
|
+
# A patron.
|
3
|
+
#
|
4
|
+
# In other words, a consumer in our processing queue.
|
5
|
+
class Patron
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
# Creates a new pub patron.
|
9
|
+
#
|
10
|
+
# Takes the name of the pub and a timeout value in seconds.
|
11
|
+
#
|
12
|
+
# The timeout designates how long a patron is willing to wait at the
|
13
|
+
# counter to receive his or her beer.
|
14
|
+
def initialize(pub_name, timeout)
|
15
|
+
@pub_name, @timeout = pub_name, timeout
|
16
|
+
end
|
17
|
+
|
18
|
+
# Orders one or more beer at the bar counter.
|
19
|
+
#
|
20
|
+
# If given a block, yields beers as they become available. Otherwise,
|
21
|
+
# returns all on a tray.
|
22
|
+
#
|
23
|
+
# If not all beers are served within specified timeout, it will return only
|
24
|
+
# the beers that are ready.
|
25
|
+
def order(*beers)
|
26
|
+
raise ArgumentError, 'Empty order' if beers.empty?
|
27
|
+
|
28
|
+
orders, tray = [], []
|
29
|
+
|
30
|
+
beers.flatten!
|
31
|
+
|
32
|
+
beers.each do |beer|
|
33
|
+
counter.rpush(@pub_name, beer)
|
34
|
+
orders << order_for(beer)
|
35
|
+
end
|
36
|
+
|
37
|
+
timer = EM.add_timer(@timeout) do
|
38
|
+
foo = counter.unsubscribe
|
39
|
+
end
|
40
|
+
|
41
|
+
counter.subscribe(*orders) do |on|
|
42
|
+
on.message do |order, beer|
|
43
|
+
counter.unsubscribe(order)
|
44
|
+
if block_given?
|
45
|
+
yield beer
|
46
|
+
else
|
47
|
+
tray << beer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
EM.cancel_timer(timer)
|
53
|
+
block_given? ? nil : tray
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/pub/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.0
|
1
|
+
class Pub
|
2
|
+
VERSION = "0.1.0"
|
3
3
|
end
|
data/pub.gemspec
CHANGED
@@ -5,30 +5,28 @@ require "pub/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "pub"
|
7
7
|
s.version = Pub::VERSION
|
8
|
-
s.authors = ["
|
9
|
-
s.email =
|
10
|
-
s.homepage = "http://
|
11
|
-
s.summary =
|
12
|
-
s.description = %q{Pub is a Redis-backed pub/sub messaging system that process queues.}
|
13
|
-
|
14
|
-
s.rubyforge_project = "pub"
|
8
|
+
s.authors = ["Paper Cavalier"]
|
9
|
+
s.email = "code@papercavalier.com"
|
10
|
+
s.homepage = "http://github.com/papercavalier/pub"
|
11
|
+
s.summary = "A Redis-backed pub or processing queue with a non-blocking bar counter"
|
15
12
|
|
16
13
|
s.files = `git ls-files`.split("\n")
|
17
|
-
s.test_files = `git ls-files -- {
|
18
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
19
15
|
s.require_paths = ["lib"]
|
20
16
|
|
21
|
-
|
22
|
-
|
17
|
+
s.required_ruby_version = ">= 1.9"
|
18
|
+
|
19
|
+
{
|
23
20
|
"em-synchrony" => "~> 0.3.0.beta.1",
|
24
21
|
"hiredis" => "~> 0.3.2",
|
25
|
-
"redis" => "~> 2.2.
|
22
|
+
"redis" => "~> 2.2.1"
|
26
23
|
}.each do |lib, version|
|
27
24
|
s.add_runtime_dependency lib, version
|
28
25
|
end
|
29
26
|
|
30
27
|
{
|
31
|
-
"
|
28
|
+
"cucumber" => "~> 1.0",
|
29
|
+
"rspec" => "~> 2.6",
|
32
30
|
}.each do |lib, version|
|
33
31
|
s.add_development_dependency lib, version
|
34
32
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class Pub
|
4
|
+
describe Bartender do
|
5
|
+
let(:pub) { Pub.new("Ye Olde Rubies") }
|
6
|
+
let(:bartender) { pub.new_bartender }
|
7
|
+
let(:counter) { double("Counter").as_null_object }
|
8
|
+
|
9
|
+
before do
|
10
|
+
bartender.stub!(:counter).and_return(counter)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#take_order" do
|
14
|
+
before do
|
15
|
+
counter.stub!(:lpop).and_return(Time.now)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when not passed a number" do
|
19
|
+
it "pops one order from the queue" do
|
20
|
+
orders = bartender.take_orders
|
21
|
+
orders.count.should eql 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when passed a number" do
|
26
|
+
it "returns that many orders from the queue" do
|
27
|
+
orders = bartender.take_orders(3)
|
28
|
+
orders.count.should eql 3
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#serve" do
|
34
|
+
let(:beer) { "Guinness" }
|
35
|
+
after { bartender.serve(beer) { beer } }
|
36
|
+
|
37
|
+
it "removes duplicate instances of the beer from the queue" do
|
38
|
+
counter.should_receive(:lrem).with(pub.name, 0, beer)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "publishes the order" do
|
42
|
+
order = bartender.send(:order_for, beer)
|
43
|
+
counter.should_receive(:publish).with(order, beer)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class Pub
|
4
|
+
describe Patron do
|
5
|
+
let(:pub) { Pub.new("Ye Olde Rubies") }
|
6
|
+
let(:patron) { pub.new_patron }
|
7
|
+
let(:counter) { double("Counter").as_null_object }
|
8
|
+
|
9
|
+
before do
|
10
|
+
patron.stub!(:counter).and_return(counter)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#order" do
|
14
|
+
context "when ordering a beer" do
|
15
|
+
let(:beer) { "Guinness" }
|
16
|
+
after { patron.order(beer) }
|
17
|
+
|
18
|
+
it "queues the beer at the counter" do
|
19
|
+
counter.should_receive(:rpush).with(pub.name, beer)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "subscribes to the order" do
|
23
|
+
order = patron.send(:order_for, beer)
|
24
|
+
counter.should_receive(:subscribe).with(order)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when ordering two beers" do
|
29
|
+
let(:beers) { ["Guinness", "Stella"] }
|
30
|
+
after { patron.order(*beers) }
|
31
|
+
|
32
|
+
it "queues the beers at the counter" do
|
33
|
+
beers.each do |beer|
|
34
|
+
counter.should_receive(:rpush).with(pub.name, beer)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "subscribes to the orders" do
|
39
|
+
orders = beers.map do |beer|
|
40
|
+
patron.send(:order_for, beer)
|
41
|
+
end
|
42
|
+
counter.should_receive(:subscribe).with(*orders)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when ordering an array of beers" do
|
47
|
+
let(:beers) { ["Guinness", "Stella"] }
|
48
|
+
after { patron.order(beers) }
|
49
|
+
|
50
|
+
it "splats the array and queues the beers at the counter" do
|
51
|
+
beers.each do |beer|
|
52
|
+
counter.should_receive(:rpush).with(pub.name, beer)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when patron does not know what he or she wants" do
|
58
|
+
it "raises an error" do
|
59
|
+
expect do
|
60
|
+
patron.order
|
61
|
+
end.to raise_error ArgumentError
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/pub_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Pub do
|
4
|
+
let(:pub) { Pub.new("Ye Olde Rubies") }
|
5
|
+
|
6
|
+
describe ".counter" do
|
7
|
+
it "returns a Redis connection" do
|
8
|
+
Pub.counter.should be_a Redis
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#new_bartender" do
|
13
|
+
it "returns a new bartender" do
|
14
|
+
pub.new_bartender.should be_a Pub::Bartender
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#new_patron" do
|
19
|
+
it "returns a new patron" do
|
20
|
+
pub.new_patron.should be_a Pub::Patron
|
21
|
+
end
|
22
|
+
|
23
|
+
it "optionally sets the timeout of the patron" do
|
24
|
+
patron = pub.new_patron(10)
|
25
|
+
patron.instance_variable_get(:@timeout).should eql 10
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "rspec"
|
4
4
|
|
5
|
-
require File.expand_path(
|
5
|
+
require File.expand_path("../../lib/pub", __FILE__)
|
6
6
|
|
7
|
-
|
7
|
+
RSpec.configure do |c|
|
8
|
+
c.around(:each) do |example|
|
9
|
+
EM.synchrony do
|
10
|
+
Pub.counter.flushall
|
11
|
+
example.run
|
12
|
+
EM.stop
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -4,21 +4,21 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 0
|
8
7
|
- 1
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
|
-
-
|
12
|
+
- Paper Cavalier
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-06-
|
17
|
+
date: 2011-06-30 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
name:
|
21
|
+
name: em-synchrony
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
24
|
none: false
|
@@ -26,16 +26,16 @@ dependencies:
|
|
26
26
|
- - ~>
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
segments:
|
29
|
-
- 1
|
30
29
|
- 0
|
30
|
+
- 3
|
31
31
|
- 0
|
32
32
|
- beta
|
33
|
-
-
|
34
|
-
version:
|
33
|
+
- 1
|
34
|
+
version: 0.3.0.beta.1
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
38
|
+
name: hiredis
|
39
39
|
prerelease: false
|
40
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
@@ -45,14 +45,12 @@ dependencies:
|
|
45
45
|
segments:
|
46
46
|
- 0
|
47
47
|
- 3
|
48
|
-
-
|
49
|
-
|
50
|
-
- 1
|
51
|
-
version: 0.3.0.beta.1
|
48
|
+
- 2
|
49
|
+
version: 0.3.2
|
52
50
|
type: :runtime
|
53
51
|
version_requirements: *id002
|
54
52
|
- !ruby/object:Gem::Dependency
|
55
|
-
name:
|
53
|
+
name: redis
|
56
54
|
prerelease: false
|
57
55
|
requirement: &id003 !ruby/object:Gem::Requirement
|
58
56
|
none: false
|
@@ -60,14 +58,14 @@ dependencies:
|
|
60
58
|
- - ~>
|
61
59
|
- !ruby/object:Gem::Version
|
62
60
|
segments:
|
63
|
-
- 0
|
64
|
-
- 3
|
65
61
|
- 2
|
66
|
-
|
62
|
+
- 2
|
63
|
+
- 1
|
64
|
+
version: 2.2.1
|
67
65
|
type: :runtime
|
68
66
|
version_requirements: *id003
|
69
67
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
68
|
+
name: cucumber
|
71
69
|
prerelease: false
|
72
70
|
requirement: &id004 !ruby/object:Gem::Requirement
|
73
71
|
none: false
|
@@ -75,11 +73,10 @@ dependencies:
|
|
75
73
|
- - ~>
|
76
74
|
- !ruby/object:Gem::Version
|
77
75
|
segments:
|
78
|
-
-
|
79
|
-
- 2
|
76
|
+
- 1
|
80
77
|
- 0
|
81
|
-
version:
|
82
|
-
type: :
|
78
|
+
version: "1.0"
|
79
|
+
type: :development
|
83
80
|
version_requirements: *id004
|
84
81
|
- !ruby/object:Gem::Dependency
|
85
82
|
name: rspec
|
@@ -92,13 +89,11 @@ dependencies:
|
|
92
89
|
segments:
|
93
90
|
- 2
|
94
91
|
- 6
|
95
|
-
|
96
|
-
version: 2.6.0
|
92
|
+
version: "2.6"
|
97
93
|
type: :development
|
98
94
|
version_requirements: *id005
|
99
|
-
description:
|
100
|
-
email:
|
101
|
-
- hakan.ensari@papercavalier.com
|
95
|
+
description:
|
96
|
+
email: code@papercavalier.com
|
102
97
|
executables: []
|
103
98
|
|
104
99
|
extensions: []
|
@@ -111,12 +106,23 @@ files:
|
|
111
106
|
- Gemfile
|
112
107
|
- README.md
|
113
108
|
- Rakefile
|
109
|
+
- features/README.md
|
110
|
+
- features/order.feature
|
111
|
+
- features/step_definitions/numeric_transforms.rb
|
112
|
+
- features/step_definitions/pub_steps.rb
|
113
|
+
- features/support/env.rb
|
114
114
|
- lib/pub.rb
|
115
|
+
- lib/pub/bartender.rb
|
116
|
+
- lib/pub/helpers.rb
|
117
|
+
- lib/pub/patron.rb
|
115
118
|
- lib/pub/version.rb
|
116
119
|
- pub.gemspec
|
120
|
+
- spec/pub/bartender_spec.rb
|
121
|
+
- spec/pub/patron_spec.rb
|
122
|
+
- spec/pub_spec.rb
|
117
123
|
- spec/spec_helper.rb
|
118
124
|
has_rdoc: true
|
119
|
-
homepage: http://
|
125
|
+
homepage: http://github.com/papercavalier/pub
|
120
126
|
licenses: []
|
121
127
|
|
122
128
|
post_install_message:
|
@@ -130,8 +136,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
136
|
- - ">="
|
131
137
|
- !ruby/object:Gem::Version
|
132
138
|
segments:
|
133
|
-
-
|
134
|
-
|
139
|
+
- 1
|
140
|
+
- 9
|
141
|
+
version: "1.9"
|
135
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
143
|
none: false
|
137
144
|
requirements:
|
@@ -142,10 +149,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
149
|
version: "0"
|
143
150
|
requirements: []
|
144
151
|
|
145
|
-
rubyforge_project:
|
152
|
+
rubyforge_project:
|
146
153
|
rubygems_version: 1.3.7
|
147
154
|
signing_key:
|
148
155
|
specification_version: 3
|
149
|
-
summary: A Redis-backed pub
|
156
|
+
summary: A Redis-backed pub or processing queue with a non-blocking bar counter
|
150
157
|
test_files:
|
158
|
+
- features/README.md
|
159
|
+
- features/order.feature
|
160
|
+
- features/step_definitions/numeric_transforms.rb
|
161
|
+
- features/step_definitions/pub_steps.rb
|
162
|
+
- features/support/env.rb
|
163
|
+
- spec/pub/bartender_spec.rb
|
164
|
+
- spec/pub/patron_spec.rb
|
165
|
+
- spec/pub_spec.rb
|
151
166
|
- spec/spec_helper.rb
|