tavern 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +17 -0
- data/lib/tavern/hub.rb +117 -0
- data/lib/tavern/mock_hub.rb +54 -0
- data/lib/tavern/subscription.rb +34 -0
- data/lib/tavern/subscriptions.rb +79 -0
- data/lib/tavern/version.rb +3 -0
- data/lib/tavern.rb +6 -0
- data/spec/hub_spec.rb +115 -0
- data/spec/spec_helper.rb +8 -0
- data/tavern.gemspec +22 -0
- metadata +127 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Darcy Laycock
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Tavern
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'tavern'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install tavern
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rake'
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'bundler/gem_tasks'
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'ci/reporter/rake/rspec'
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
17
|
+
RSpec::Core::RakeTask.new :spec
|
data/lib/tavern/hub.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'tavern/subscription'
|
2
|
+
require 'tavern/subscriptions'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
|
6
|
+
module Tavern
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Gets the current application wide hub, initializing
|
11
|
+
# a new one if it hasn't been set yet.
|
12
|
+
# @return [Hub] the subscription hub
|
13
|
+
def hub
|
14
|
+
@hub ||= Hub.new.tap { |h| h.primary = true }
|
15
|
+
end
|
16
|
+
|
17
|
+
def hub=(value)
|
18
|
+
old_hub = @hub
|
19
|
+
@hub = value.presence
|
20
|
+
if old_hub != @hub
|
21
|
+
old_hub.primary = false if old_hub
|
22
|
+
@hub.primary = true if @hub
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# We delegate the subscription management methods to the default hub.
|
27
|
+
# Note: This does not replace having an application-wide hub which is still
|
28
|
+
# a good idea.
|
29
|
+
delegate :subscribe, :unsubscribe, :publish, :to => :hub
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Implements a simplified Pub / Sub hub for in-application notifications.
|
34
|
+
# Used inside smeghead as a general replacement for observers and a way
|
35
|
+
# for items to hook into events.
|
36
|
+
class Hub
|
37
|
+
|
38
|
+
attr_reader :subscriptions
|
39
|
+
|
40
|
+
# Initializes the given hub with an empty set of subscriptions.
|
41
|
+
def initialize
|
42
|
+
@subscriptions = Subscriptions.new
|
43
|
+
@primary = false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Subscribes to a given path string and either a proc callback or
|
47
|
+
# any object responding to #call.
|
48
|
+
# @param [String] path the subscription path
|
49
|
+
# @param [#call] object if present, the callback to invoke
|
50
|
+
# @param [Proc] blk the block to use for the callback (if the object is nil)
|
51
|
+
# @example Subscribing with a block
|
52
|
+
# hub.subscribe 'hello:world' do |ctx|
|
53
|
+
# puts "Context is #{ctx.inspect}"
|
54
|
+
# end
|
55
|
+
# @example Subscribing with an object
|
56
|
+
# hub.subscribe 'hello:world', MyCallableClass
|
57
|
+
def subscribe(path, object = nil, &blk)
|
58
|
+
if object and not object.respond_to?(:call)
|
59
|
+
raise ArgumentError, "you provided an object as an argument but it doesn't respond to #call"
|
60
|
+
end
|
61
|
+
subscription = Subscription.new(path, (object || blk))
|
62
|
+
level = subscriptions.sublevel_at subscription.to_subscribe_keys
|
63
|
+
level.add subscription
|
64
|
+
subscription
|
65
|
+
end
|
66
|
+
|
67
|
+
# Deletes the given subscription from this pub sub hub.
|
68
|
+
# @param [Subscription] subscription the subscription to delete
|
69
|
+
# @return [Subscription] the deleted subscription
|
70
|
+
def unsubscribe(subscription)
|
71
|
+
return if subscription.blank?
|
72
|
+
level = subscriptions.sublevel_at subscription.to_subscribe_keys
|
73
|
+
level.delete subscription
|
74
|
+
subscription
|
75
|
+
end
|
76
|
+
|
77
|
+
# Publishes a message to the given path and with a given hash context.
|
78
|
+
# @param [String] path the pubsub path
|
79
|
+
# @param [Hash{Symbol => Object}] context the message context
|
80
|
+
# @return [true,false] whether or not all callbacks executed successfully.
|
81
|
+
# @example Publishing a message
|
82
|
+
# hub.publish 'hello:world', :hello => 'world'
|
83
|
+
def publish(path, context = {})
|
84
|
+
path_parts = path.split(":")
|
85
|
+
context = merge_path_context path_parts, context
|
86
|
+
# Actually handle publishing the subscription
|
87
|
+
subscriptions.call(context.merge(:path_parts => path_parts, :full_path => path)) != false
|
88
|
+
end
|
89
|
+
|
90
|
+
def primary?
|
91
|
+
!!@primary
|
92
|
+
end
|
93
|
+
|
94
|
+
def primary=(value)
|
95
|
+
value = !!value
|
96
|
+
if value != @value
|
97
|
+
@value = value
|
98
|
+
ActiveSupport.run_load_hooks :smeg_head_hub, self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def merge_path_context(path_parts, context)
|
105
|
+
if context.has_key?(:path_keys)
|
106
|
+
context = context.dup
|
107
|
+
path_keys = Array(context.delete(:path_keys))
|
108
|
+
path_keys.each_with_index do |part, idx|
|
109
|
+
next if part.blank?
|
110
|
+
context[part.to_sym] = path_parts[idx]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'tavern/hub'
|
2
|
+
|
3
|
+
module Tavern
|
4
|
+
# A simple hub you can use to completely disable the pub / sub process
|
5
|
+
# but still record what happens.
|
6
|
+
class MockHub < Hub
|
7
|
+
|
8
|
+
class Publication < Struct.new(:path, :context)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :subscriptions, :unsubscriptions, :publications
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
@subscriptions, @unsubscriptions, @publications = [], [], []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Subscribes to a given path string and either a proc callback or
|
19
|
+
# any object responding to #call.
|
20
|
+
# @param [String] path the subscription path
|
21
|
+
# @param [#call] object if present, the callback to invoke
|
22
|
+
# @param [Proc] blk the block to use for the callback (if the object is nil)
|
23
|
+
# @example Subscribing with a block
|
24
|
+
# hub.subscribe 'hello:world' do |ctx|
|
25
|
+
# puts "Context is #{ctx.inspect}"
|
26
|
+
# end
|
27
|
+
# @example Subscribing with an object
|
28
|
+
# hub.subscribe 'hello:world', MyCallableClass
|
29
|
+
def subscribe(path, object = nil, &blk)
|
30
|
+
subscription = Subscription.new(path, (object || blk))
|
31
|
+
subscriptions << subscription
|
32
|
+
subscription
|
33
|
+
end
|
34
|
+
|
35
|
+
# Deletes the given subscription from this pub sub hub.
|
36
|
+
# @param [Subscription] subscription the subscription to delete
|
37
|
+
# @return [Subscription] the deleted subscription
|
38
|
+
def unsubscribe(subscription)
|
39
|
+
return if subscription.blank?
|
40
|
+
subscriptions.delete subscription
|
41
|
+
subscription
|
42
|
+
end
|
43
|
+
|
44
|
+
# Publishes a message to the given path and with a given hash context.
|
45
|
+
# @param [String] path the pubsub path
|
46
|
+
# @param [Hash{Symbol => Object}] context the message context
|
47
|
+
# @example Publishing a message
|
48
|
+
# hub.publish 'hello:world', :hello => 'world'
|
49
|
+
def publish(path, context = {})
|
50
|
+
publications << Publication.new(path, context)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Tavern
|
2
|
+
# A general subscription in a given hub, including a name and a given callback
|
3
|
+
# block. As part of this, it provides tools to make it easy to handle the subscriptions.
|
4
|
+
class Subscription
|
5
|
+
|
6
|
+
attr_reader :name, :callback
|
7
|
+
|
8
|
+
# Initializes a new subscription with a given name and callback.
|
9
|
+
# @param [String] name the name of this subscription
|
10
|
+
# @param [#call] callback the callback for this subscription
|
11
|
+
def initialize(name, callback)
|
12
|
+
@name = name.to_s
|
13
|
+
@callback = callback
|
14
|
+
@_subscribe_keys = @name.to_s.split(":")
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the list of subscription sublevel keys.
|
18
|
+
# @return [Array<String>] the list of sublevel keys
|
19
|
+
def to_subscribe_keys
|
20
|
+
@_subscribe_keys
|
21
|
+
end
|
22
|
+
|
23
|
+
# Invokes the callback, returning whatever it returns.
|
24
|
+
# @param [Hash] context the callback context.
|
25
|
+
def call(context)
|
26
|
+
@callback.call context
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_proc
|
30
|
+
proc { |ctx| call ctx }
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Tavern
|
2
|
+
# Tavern::Subscriptions implements a simple tree-like structure for handling subscriptions, facilitating
|
3
|
+
# the efficient depth-first processing of callbacks based on a given path.
|
4
|
+
#
|
5
|
+
# Each level of the tree is composed of some callbacks as well as a set of sublevels, each represented by
|
6
|
+
# part of a subscription namespace. Namely, given a key of "a:b:c", there will be four levels:
|
7
|
+
#
|
8
|
+
# 1. The root level - Currently, nothing subscribes to this level
|
9
|
+
# 2. The "a" level - this is nested under the root level.
|
10
|
+
# 3. The "b" level - this is nested under the "a" level.
|
11
|
+
# 4. The "c" level - this is nested under the "b" level.
|
12
|
+
#
|
13
|
+
# When an event is published to "a:b:c", the top level Subscriptions instance will recursively ``#call`` the
|
14
|
+
# child levels, breaking if at any point a subscription returns false. Ideally, this means that for a normal
|
15
|
+
# event, we will get events (if present) invoked on all four levels, allowing a nice and structured event
|
16
|
+
# dispatch.
|
17
|
+
class Subscriptions
|
18
|
+
|
19
|
+
# Creates a new subscription with an empty list of subscriptions and an empty subkeys mapping.
|
20
|
+
def initialize
|
21
|
+
@subscriptions = []
|
22
|
+
@subkeys = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Given a specified context, calls all nested matching subcontexts and then invokes the
|
26
|
+
# callbacks on this level, breaking if it encounters false (like terminators in AS callbacks).
|
27
|
+
#
|
28
|
+
# This means that if your subscription returns false, it will halt further callbacks. Also,
|
29
|
+
# It also means that dispatching events are depth first based on the key. E.g., given a callback
|
30
|
+
# key of "a:b:c", the callbacks for "a:b:c" will be called first followed by those for "a:b" and "a".
|
31
|
+
#
|
32
|
+
# @param [Hash] context the given context for this publish call
|
33
|
+
# @option context [Array<String>] :path_parts an array of the current children keys to dispatch on
|
34
|
+
def call(context = {})
|
35
|
+
path_parts = context[:path_parts].dup
|
36
|
+
# Call the sublevel, breaking when a value returns false
|
37
|
+
if path_parts.any? and (subkey = @subkeys[path_parts.shift])
|
38
|
+
result = subkey.call context.merge(:path_parts => path_parts)
|
39
|
+
return result if result == false
|
40
|
+
end
|
41
|
+
# Iterate over the subscriptions, breaking when one of them returns false
|
42
|
+
@subscriptions.each do |subscription|
|
43
|
+
return false if subscription.call(context) == false
|
44
|
+
end
|
45
|
+
# Otherwise, return nil
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds a new subscription to this subscriptions list.
|
50
|
+
# @param [Subscription] subscription the new subscription to add
|
51
|
+
def add(subscription)
|
52
|
+
@subscriptions << subscription
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes a given subscription from this level.
|
56
|
+
# @param [Subscription] subscription the old subscription to remove
|
57
|
+
def delete(subscription)
|
58
|
+
@subscriptions.delete subscription
|
59
|
+
end
|
60
|
+
|
61
|
+
# Gets the sublevel with the given key, initializing a new
|
62
|
+
# sublevel if it is as of yet unknown.
|
63
|
+
# @param [String] key the key of the given sublevel
|
64
|
+
# @return [Subscriptions] the returned subscription level
|
65
|
+
def sublevel(key)
|
66
|
+
@subkeys[key] ||= Subscriptions.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# Given a list of subkeys, will return the association sublevel.
|
70
|
+
# @param [Array<String>] parts the list of path-parts to reach the desired sublevel.
|
71
|
+
# @return [Subscriptions] the given sublevel subscriptions object.
|
72
|
+
def sublevel_at(*parts)
|
73
|
+
parts.flatten.inject(self) do |level, part|
|
74
|
+
level.sublevel part
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
data/lib/tavern.rb
ADDED
data/spec/hub_spec.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tavern::Hub do
|
4
|
+
|
5
|
+
let(:hub) { Tavern::Hub.new }
|
6
|
+
|
7
|
+
let(:subscriber_klass) do
|
8
|
+
Class.new do
|
9
|
+
|
10
|
+
def published
|
11
|
+
@published ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(ctx)
|
15
|
+
published << ctx
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:ctx_from_proc) { [] }
|
22
|
+
|
23
|
+
let(:subscriber_proc) do
|
24
|
+
proc { |ctx| ctx_from_proc << ctx }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'the primary hub' do
|
28
|
+
|
29
|
+
it 'should let you query if something is the primary hub'
|
30
|
+
|
31
|
+
it 'should run the load hook when the hub is changed'
|
32
|
+
|
33
|
+
it 'should unset it when changing the hub'
|
34
|
+
|
35
|
+
it 'should set it to primary when setting it to the hub value'
|
36
|
+
|
37
|
+
it 'always set the default to be primary'
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#subscribe' do
|
42
|
+
|
43
|
+
it 'should let you subscribe to a top level item'
|
44
|
+
|
45
|
+
it 'should let you subscribe to a nested item'
|
46
|
+
|
47
|
+
it 'should let you pass an object'
|
48
|
+
|
49
|
+
it 'should let you pass a block'
|
50
|
+
|
51
|
+
it 'should return a subscription'
|
52
|
+
|
53
|
+
it 'should automatically subscribe to lower level nested events'
|
54
|
+
|
55
|
+
it 'should raise an error when subscribing with an object that does not provide call'
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#unsubscribe' do
|
60
|
+
|
61
|
+
it 'should remove an object from the subscription pool'
|
62
|
+
|
63
|
+
it 'should return the subscription'
|
64
|
+
|
65
|
+
it 'should do nothing with a blank item'
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#publish' do
|
70
|
+
|
71
|
+
let(:nested_a) { subscriber_klass.new }
|
72
|
+
let(:nested_b) { subscriber_klass.new }
|
73
|
+
let(:nested_c) { subscriber_klass.new }
|
74
|
+
let(:top_level_a) { subscriber_klass.new }
|
75
|
+
let(:top_level_b) { subscriber_klass.new }
|
76
|
+
|
77
|
+
before :each do
|
78
|
+
hub.subscribe 'hello', top_level_a
|
79
|
+
hub.subscribe 'hello:world', nested_a
|
80
|
+
hub.subscribe 'foo', top_level_b
|
81
|
+
hub.subscribe 'foo:bar', nested_b
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should add the path parts for a top level call' do
|
85
|
+
mock(top_level_a).call(hash_including(:path_parts => %w()))
|
86
|
+
hub.publish 'hello', {}
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should add the path parts for a nested call' do
|
90
|
+
mock(top_level_a).call(hash_including(:path_parts => %w(world)))
|
91
|
+
mock(nested_a).call(hash_including(:path_parts => %w()))
|
92
|
+
hub.publish 'hello:world', {}
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should unpack path keys if provided' do
|
96
|
+
mock(top_level_a).call(hash_including(:model_name => 'world'))
|
97
|
+
mock(nested_a).call(hash_including(:model_name => 'world'))
|
98
|
+
hub.publish 'hello:world', :path_keys => [nil, :model_name]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should add the full path to the publish' do
|
102
|
+
mock(top_level_a).call(hash_including(:full_path => 'hello:world'))
|
103
|
+
mock(nested_a).call(hash_including(:full_path => 'hello:world'))
|
104
|
+
hub.publish 'hello:world', {}
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should notify all subscriptions under the path'
|
108
|
+
|
109
|
+
it 'should not notify unmatched subscriptions on a simple case'
|
110
|
+
|
111
|
+
it 'should not notify unmatched subscriptions on a nested case'
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tavern.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/tavern/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Darcy Laycock"]
|
6
|
+
gem.email = ["sutto@sutto.net"]
|
7
|
+
gem.description = %q{Tavern implements simple pub / sub systems for Rails applications with a simple, extendable architecture and minimal api surface area.}
|
8
|
+
gem.summary = %q{Simple pubsub for Ruby apps.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "tavern"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Tavern::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activesupport', '~> 3.0'
|
19
|
+
gem.add_development_dependency 'rake'
|
20
|
+
gem.add_development_dependency 'rr'
|
21
|
+
gem.add_development_dependency 'rspec', '~> 2.0'
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tavern
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Darcy Laycock
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rr
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.0'
|
78
|
+
description: Tavern implements simple pub / sub systems for Rails applications with
|
79
|
+
a simple, extendable architecture and minimal api surface area.
|
80
|
+
email:
|
81
|
+
- sutto@sutto.net
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .rspec
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- lib/tavern.rb
|
93
|
+
- lib/tavern/hub.rb
|
94
|
+
- lib/tavern/mock_hub.rb
|
95
|
+
- lib/tavern/subscription.rb
|
96
|
+
- lib/tavern/subscriptions.rb
|
97
|
+
- lib/tavern/version.rb
|
98
|
+
- spec/hub_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
- tavern.gemspec
|
101
|
+
homepage: ''
|
102
|
+
licenses: []
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 1.8.21
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Simple pubsub for Ruby apps.
|
125
|
+
test_files:
|
126
|
+
- spec/hub_spec.rb
|
127
|
+
- spec/spec_helper.rb
|