pub 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|