tusk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,8 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.testlib = 'minitest/autorun'
7
+ at.find_directories = ARGV unless ARGV.empty?
8
+ end
data/.gemtest ADDED
File without changes
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2012-07-18
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ .autotest
2
+ CHANGELOG.rdoc
3
+ Manifest.txt
4
+ README.markdown
5
+ Rakefile
6
+ lib/tusk.rb
7
+ lib/tusk/latch.rb
8
+ lib/tusk/observable/pg.rb
9
+ lib/tusk/observable/redis.rb
10
+ test/helper.rb
11
+ test/observable/test_pg.rb
12
+ test/observable/test_redis.rb
13
+ test/redis-test.conf
data/README.markdown ADDED
@@ -0,0 +1,206 @@
1
+ # tusk
2
+
3
+ * http://github.com/tenderlove/tusk
4
+
5
+ ## DESCRIPTION:
6
+
7
+ Tusk is a minimal pub / sub system with multiple observer strategies.
8
+ Tusk builds upon the Observer API from stdlib in order to provide a mostly
9
+ consistent API for building cross thread or process pub / sub systems.
10
+
11
+ Currently, Tusk supports Redis and PostgreSQL as message bus back ends.
12
+
13
+ ## FEATURES/PROBLEMS:
14
+
15
+ * Send message across processes
16
+ * Supports Redis as a message bus
17
+ * Supports PostgreSQL as a message bus
18
+
19
+ ## SYNOPSIS:
20
+
21
+ Here is an in-memory observer example:
22
+
23
+ ```ruby
24
+ require 'observer'
25
+
26
+ class Timer
27
+ include Observable
28
+
29
+ def tick
30
+ changed
31
+ notify_observers
32
+ end
33
+ end
34
+
35
+ class Listener
36
+ def update; puts "got update"; end
37
+ end
38
+
39
+ timer = Timer.new
40
+ timer.add_observer Listener.new
41
+ loop { timer.tick; sleep 1; }
42
+ ```
43
+
44
+
45
+ The down side of this example is that the Listener cannot be in a different
46
+ process. We can move the Listener to a different process by using the Redis
47
+ observable mixin and providing a redis connection:
48
+
49
+ ```ruby
50
+ require 'tusk/observable/redis'
51
+ require 'redis'
52
+
53
+ class Timer
54
+ include Tusk::Observable::Redis
55
+
56
+ def tick
57
+ changed
58
+ notify_observers
59
+ end
60
+
61
+ def connection
62
+ Thread.current[:redis] ||= ::Redis.new
63
+ end
64
+ end
65
+
66
+ class Listener
67
+ def update; puts "got update PID: #{$$}"; end
68
+ end
69
+
70
+ timer = Timer.new
71
+
72
+ fork {
73
+ timer.add_observer Listener.new
74
+ sleep
75
+ }
76
+
77
+ loop { timer.tick; sleep 1; }
78
+ ```
79
+
80
+ PostgreSQL can also be used as the message bus:
81
+
82
+ ```ruby
83
+ require 'tusk/observable/pg'
84
+ require 'pg'
85
+
86
+ class Timer
87
+ include Tusk::Observable::PG
88
+
89
+ def tick
90
+ changed
91
+ notify_observers
92
+ end
93
+
94
+ def connection
95
+ Thread.current[:pg] ||= ::PG::Connection.new :dbname => 'postgres'
96
+ end
97
+ end
98
+
99
+ class Listener
100
+ def update; puts "got update PID: #{$$}"; end
101
+ end
102
+
103
+ timer = Timer.new
104
+
105
+ fork {
106
+ timer.add_observer Listener.new
107
+ sleep
108
+ }
109
+
110
+ loop { timer.tick; sleep 1; }
111
+ ```
112
+
113
+ We can easily integrate Tusk with Active Record. Here is a User model that
114
+ sends notifications when a user is created:
115
+
116
+ ```ruby
117
+ require 'tusk/observable/pg'
118
+ class User < ActiveRecord::Base
119
+ attr_accessible :name
120
+
121
+ extend Tusk::Observable::PG
122
+
123
+ # After users are created, notify the message bus
124
+ after_create :notify_observers
125
+
126
+ # Listeners will use the table name as the bus channel
127
+ def self.channel
128
+ table_name
129
+ end
130
+
131
+ private
132
+
133
+ def notify_observers
134
+ self.class.changed
135
+ self.class.notify_observers
136
+ end
137
+ end
138
+ ```
139
+
140
+ The table name is used as the channel name where objects will listen. Here is
141
+ a producer script:
142
+
143
+ ```ruby
144
+ require 'user'
145
+ loop do
146
+ User.create!(:name => 'testing')
147
+ sleep 1
148
+ end
149
+ ```
150
+
151
+ Our consumer adds an observer to the User class:
152
+
153
+ ```ruby
154
+ require 'user'
155
+ class UserListener
156
+ def initialize
157
+ super
158
+ @last_id = 0
159
+ end
160
+
161
+ def update
162
+ users = User.where('id > ?', @last_id).sort_by(&:id)
163
+ @last_id = users.last.id
164
+ users.each { |u| p "user created: #{u.id}" }
165
+ end
166
+ end
167
+
168
+ User.add_observer UserListener.new
169
+ # Put the main thread to sleep
170
+ sleep
171
+ ```
172
+
173
+ Whenever a user gets created, our consumer listener will be notified.
174
+
175
+ ## REQUIREMENTS:
176
+
177
+ * PostgreSQL or Redis
178
+
179
+ ## INSTALL:
180
+
181
+ * gem install tusk
182
+
183
+ ## LICENSE:
184
+
185
+ (The MIT License)
186
+
187
+ Copyright (c) 2012 Aaron Patterson
188
+
189
+ Permission is hereby granted, free of charge, to any person obtaining
190
+ a copy of this software and associated documentation files (the
191
+ 'Software'), to deal in the Software without restriction, including
192
+ without limitation the rights to use, copy, modify, merge, publish,
193
+ distribute, sublicense, and/or sell copies of the Software, and to
194
+ permit persons to whom the Software is furnished to do so, subject to
195
+ the following conditions:
196
+
197
+ The above copyright notice and this permission notice shall be
198
+ included in all copies or substantial portions of the Software.
199
+
200
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
201
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
202
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
203
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
204
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
205
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
206
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugins.delete :rubyforge
7
+ Hoe.plugin :minitest
8
+ Hoe.plugin :gemspec # `gem install hoe-gemspec`
9
+ Hoe.plugin :git # `gem install hoe-git`
10
+
11
+ Hoe.spec 'tusk' do
12
+ developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
13
+ self.readme_file = 'README.markdown'
14
+ self.history_file = 'CHANGELOG.rdoc'
15
+ self.extra_rdoc_files = FileList['*.{rdoc,markdown}']
16
+
17
+ self.extra_dev_deps << ['pg', '~> 0.14.0']
18
+ end
19
+
20
+ # vim: syntax=ruby
data/lib/tusk.rb ADDED
@@ -0,0 +1,9 @@
1
+ ###
2
+ # Tusk contains observers with different message bus strategies.
3
+ #
4
+ # Tusk::Observers::Redis offers an Observer API with Redis as the
5
+ # message bus. Tusk::Observers::PG offers and Observer API with
6
+ # PostgreSQL as the message bus.
7
+ module Tusk
8
+ VERSION = '1.0.0'
9
+ end
data/lib/tusk/latch.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'monitor'
2
+
3
+ module Tusk
4
+ class Latch
5
+ def initialize(count = 1)
6
+ @count = count
7
+ @lock = Monitor.new
8
+ @cv = @lock.new_cond
9
+ end
10
+
11
+ def release
12
+ @lock.synchronize do
13
+ @count -= 1 if @count > 0
14
+ @cv.broadcast if @count.zero?
15
+ end
16
+ end
17
+
18
+ def await
19
+ @lock.synchronize do
20
+ @cv.wait_while { @count > 0 }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,170 @@
1
+ require 'thread'
2
+ require 'digest/md5'
3
+ require 'tusk/latch'
4
+
5
+ module Tusk
6
+ module Observable
7
+ ###
8
+ # An observer implementation for PostgreSQL. This module requires that
9
+ # your class implement a `connection` method that returns a database
10
+ # connection that this module can use.
11
+ #
12
+ # This observer works across processes.
13
+ #
14
+ # Example:
15
+ #
16
+ # require 'pg'
17
+ # require 'tusk/observable/pg'
18
+ #
19
+ # class Timer
20
+ # include Tusk::Observable::PG
21
+ #
22
+ # def tick
23
+ # changed
24
+ # notify_observers
25
+ # end
26
+ #
27
+ # def connection
28
+ # Thread.current[:conn] ||= ::PG::Connection.new :dbname => 'postgres'
29
+ # end
30
+ # end
31
+ #
32
+ # class Listener
33
+ # def update
34
+ # puts "got update"
35
+ # end
36
+ # end
37
+ #
38
+ # timer = Timer.new
39
+ #
40
+ # fork do
41
+ # timer.add_observer Listener.new
42
+ # sleep # put the process to sleep so it doesn't exit
43
+ # end
44
+ #
45
+ # loop do
46
+ # timer.tick
47
+ # sleep 1
48
+ # end
49
+ module PG
50
+ def self.extended klass
51
+ super
52
+
53
+ klass.instance_eval do
54
+ @sub_lock = Mutex.new
55
+ @observer_state = false
56
+ @subscribers = {}
57
+ @_listener = nil
58
+ @observing = Latch.new
59
+ end
60
+ end
61
+
62
+ attr_reader :subscribers
63
+
64
+ def initialize *args
65
+ super
66
+
67
+ @sub_lock = Mutex.new
68
+ @observer_state = false
69
+ @subscribers = {}
70
+ @_listener = nil
71
+ @observing = Latch.new
72
+ end
73
+
74
+ # Returns the number of observers associated with this object *in the
75
+ # current process*. If the object is observed across multiple processes,
76
+ # the returned count will not reflect the other processes.
77
+ def count_observers
78
+ @sub_lock.synchronize { subscribers.fetch(channel, {}).length }
79
+ end
80
+
81
+ # Remove all observers associated with this object *in the current
82
+ # process*. This method will not impact observers of this object in
83
+ # other processes.
84
+ def delete_observers
85
+ @sub_lock.synchronize { subscribers.delete channel }
86
+ end
87
+
88
+ # Returns true if this object's state has been changed since the last
89
+ # call to #notify_observers.
90
+ def changed?
91
+ @observer_state
92
+ end
93
+
94
+ # Set the changed state of this object. Notifications will be sent only
95
+ # if the changed +state+ is a truthy object.
96
+ def changed state = true
97
+ @observer_state = state
98
+ end
99
+
100
+ # If this object's #changed? state is true, this method will notify
101
+ # observing objects.
102
+ def notify_observers
103
+ return unless changed?
104
+
105
+ unwrap(connection).exec "NOTIFY #{channel}"
106
+
107
+ changed false
108
+ end
109
+
110
+ # Add +observer+ as an observer to this object. The +object+ will
111
+ # receive a notification when #changed? returns true and #notify_observers
112
+ # is called.
113
+ #
114
+ # +func+ method is called on +object+ when notifications are sent.
115
+ def add_observer object, func = :update
116
+ @sub_lock.synchronize do
117
+ subscribers.fetch(channel) { |k|
118
+ Thread.new {
119
+ start_listener
120
+ unwrap(connection).exec "LISTEN #{channel}"
121
+ @observing.release
122
+ }
123
+ subscribers[k] = {}
124
+ }[object] = func
125
+ end
126
+
127
+ @observing.await
128
+ end
129
+
130
+ # Remove +observer+ so that it will no longer receive notifications.
131
+ def delete_observer o
132
+ @sub_lock.synchronize do
133
+ subscribers.fetch(channel, {}).delete o
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def unwrap conn
140
+ if conn.respond_to? :exec
141
+ conn
142
+ else
143
+ # Yes, I am a terrible person. This pulls
144
+ # the connection out of AR connections.
145
+ conn.instance_eval { @connection }
146
+ end
147
+ end
148
+
149
+ def channel
150
+ "a" + Digest::MD5.hexdigest("#{self.class.name}#{object_id}")
151
+ end
152
+
153
+ def start_listener
154
+ return if @_listener
155
+
156
+ @_listener = Thread.new(unwrap(connection)) do |conn|
157
+ @observing.release
158
+
159
+ loop do
160
+ conn.wait_for_notify do |event, pid|
161
+ subscribers.fetch(event, []).dup.each do |listener, func|
162
+ listener.send func
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,170 @@
1
+ require 'securerandom'
2
+ require 'thread'
3
+ require 'tusk/latch'
4
+
5
+ module Tusk
6
+ module Observable
7
+ ###
8
+ # An observer implementation for Redis. This module requires that
9
+ # your class implement a `connection` method that returns a redis
10
+ # connection that this module can use.
11
+ #
12
+ # This observer works across processes.
13
+ #
14
+ # Example:
15
+ #
16
+ # require 'redis'
17
+ # require 'tusk/observable/redis'
18
+ #
19
+ # class Timer
20
+ # include Tusk::Observable::Redis
21
+ #
22
+ # def tick
23
+ # changed
24
+ # notify_observers
25
+ # end
26
+ #
27
+ # def connection
28
+ # Thread.current[:conn] ||= ::Redis.new
29
+ # end
30
+ # end
31
+ #
32
+ # class Listener
33
+ # def update
34
+ # puts "got update"
35
+ # end
36
+ # end
37
+ #
38
+ # timer = Timer.new
39
+ #
40
+ # fork do
41
+ # timer.add_observer Listener.new
42
+ # sleep # put the process to sleep so it doesn't exit
43
+ # end
44
+ #
45
+ # loop do
46
+ # timer.tick
47
+ # sleep 1
48
+ # end
49
+ module Redis
50
+ def self.extended klass
51
+ super
52
+
53
+ klass.instance_eval do
54
+ @sub_lock = Mutex.new
55
+ @observer_state = false
56
+ @subscribers = {}
57
+ @_listener = nil
58
+ @control_channel = SecureRandom.hex
59
+ end
60
+ end
61
+
62
+ attr_reader :subscribers, :control_channel
63
+
64
+ def initialize *args
65
+ super
66
+
67
+ @sub_lock = Mutex.new
68
+ @observer_state = false
69
+ @subscribers = {}
70
+ @_listener = nil
71
+ @control_channel = SecureRandom.hex
72
+ end
73
+
74
+ # Returns the number of observers associated with this object *in the
75
+ # current process*. If the object is observed across multiple processes,
76
+ # the returned count will not reflect the other processes.
77
+ def count_observers
78
+ @sub_lock.synchronize { subscribers.fetch(channel, {}).length }
79
+ end
80
+
81
+ # Remove all observers associated with this object *in the current
82
+ # process*. This method will not impact observers of this object in
83
+ # other processes.
84
+ def delete_observers
85
+ @sub_lock.synchronize { subscribers.delete channel }
86
+ connection.publish control_channel, 'quit'
87
+ end
88
+
89
+ # Returns true if this object's state has been changed since the last
90
+ # call to #notify_observers.
91
+ def changed?
92
+ @observer_state
93
+ end
94
+
95
+ # Set the changed state of this object. Notifications will be sent only
96
+ # if the changed +state+ is a truthy object.
97
+ def changed state = true
98
+ @observer_state = state
99
+ end
100
+
101
+ # If this object's #changed? state is true, this method will notify
102
+ # observing objects.
103
+ def notify_observers
104
+ return unless changed?
105
+ connection.publish channel, nil
106
+ changed false
107
+ end
108
+
109
+ # Add +observer+ as an observer to this object. The +object+ will
110
+ # receive a notification when #changed? returns true and #notify_observers
111
+ # is called.
112
+ #
113
+ # +func+ method is called on +object+ when notifications are sent.
114
+ def add_observer object, func = :update
115
+ observer_set = Latch.new
116
+ observing = Latch.new
117
+
118
+ @sub_lock.synchronize do
119
+ observing.release if subscribers.key? channel
120
+
121
+ subscribers.fetch(channel) { |k|
122
+ Thread.new {
123
+ observer_set.await
124
+ start_listener(observing)
125
+ }
126
+ subscribers[k] = {}
127
+ }[object] = func
128
+ end
129
+
130
+ observer_set.release
131
+ observing.await
132
+ end
133
+
134
+ # Remove +observer+ so that it will no longer receive notifications.
135
+ def delete_observer o
136
+ @sub_lock.synchronize do
137
+ subscribers.fetch(channel, {}).delete o
138
+ if subscribers.fetch(channel,{}).empty?
139
+ subscribers.delete channel
140
+ connection.publish control_channel, 'quit'
141
+ end
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def channel
148
+ "a" + Digest::MD5.hexdigest("#{self.class.name}#{object_id}")
149
+ end
150
+
151
+ def start_listener latch
152
+ connection.subscribe(channel, control_channel) do |on|
153
+ on.subscribe { |c| latch.release }
154
+
155
+ on.message do |c, message|
156
+ if c == control_channel && message == 'quit'
157
+ connection.unsubscribe
158
+ else
159
+ @sub_lock.synchronize do
160
+ subscribers.fetch(c, {}).each do |object,m|
161
+ object.send m
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,149 @@
1
+ require 'minitest/autorun'
2
+
3
+ module Tusk
4
+ class TestCase < MiniTest::Unit::TestCase
5
+ module ObserverTests
6
+ class QueueingObserver
7
+ def initialize q
8
+ @q = q
9
+ end
10
+
11
+ def update
12
+ @q.push :foo
13
+ end
14
+ end
15
+
16
+ def test_changed?
17
+ o = build_observable
18
+ refute o.changed?
19
+ o.changed
20
+ assert o.changed?
21
+ end
22
+
23
+ def test_changed
24
+ o = build_observable
25
+ refute o.changed?
26
+ o.changed false
27
+ refute o.changed?
28
+ o.changed
29
+ assert o.changed
30
+ end
31
+
32
+ def test_delete_observers
33
+ o = build_observable
34
+
35
+ q = Queue.new
36
+
37
+ o.add_observer QueueingObserver.new q
38
+ o.delete_observers
39
+ o.changed
40
+ o.notify_observers
41
+ assert q.empty?
42
+ end
43
+
44
+ def test_count_observers
45
+ o = build_observable
46
+ assert_equal 0, o.count_observers
47
+
48
+ q = Queue.new
49
+
50
+ o.add_observer QueueingObserver.new q
51
+ assert_equal 1, o.count_observers
52
+
53
+ o.add_observer QueueingObserver.new q
54
+ assert_equal 2, o.count_observers
55
+
56
+ o.delete_observers
57
+ assert_equal 0, o.count_observers
58
+ end
59
+
60
+ def test_observer_fires
61
+ o = build_observable
62
+ q = Queue.new
63
+
64
+ o.add_observer QueueingObserver.new q
65
+
66
+ o.changed
67
+ o.notify_observers
68
+
69
+ assert_equal :foo, q.pop
70
+ end
71
+
72
+ def test_multiple_observers
73
+ o = build_observable
74
+ q = Queue.new
75
+
76
+ o.add_observer QueueingObserver.new q
77
+ o.add_observer QueueingObserver.new q
78
+
79
+ o.changed
80
+ o.notify_observers
81
+
82
+ assert_equal :foo, q.pop
83
+ assert_equal :foo, q.pop
84
+ end
85
+
86
+ def test_observer_only_fires_on_change
87
+ o = build_observable
88
+ q = Queue.new
89
+
90
+ o.add_observer QueueingObserver.new q
91
+
92
+ o.notify_observers
93
+ assert q.empty?
94
+ end
95
+
96
+ def test_delete_observer
97
+ o = build_observable
98
+ q = Queue.new
99
+ observer = QueueingObserver.new q
100
+
101
+ o.add_observer observer
102
+
103
+ o.changed
104
+ o.notify_observers
105
+
106
+ assert_equal :foo, q.pop
107
+
108
+ o.delete_observer observer
109
+
110
+ o.changed
111
+ o.notify_observers
112
+
113
+ assert q.empty?
114
+ end
115
+
116
+ def test_delete_never_added
117
+ o = build_observable
118
+ q = Queue.new
119
+ observer = QueueingObserver.new q
120
+
121
+ o.delete_observer observer
122
+ o.changed
123
+ o.notify_observers
124
+
125
+ assert q.empty?
126
+ end
127
+
128
+ def test_no_connection
129
+ mod = observer_module
130
+ obj = Class.new { include mod }.new
131
+
132
+ assert_raises(NameError) do
133
+ obj.changed
134
+ obj.notify_observers
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def build_observable
141
+ raise NotImplementedError
142
+ end
143
+
144
+ def observer_module
145
+ raise NotImplementedError
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,57 @@
1
+ require 'pg'
2
+ require 'tusk/observable/pg'
3
+ require 'helper'
4
+
5
+ module Tusk
6
+ module Observable
7
+ class TestPg < TestCase
8
+ include ObserverTests
9
+
10
+ class Timer
11
+ include Tusk::Observable::PG
12
+
13
+ def tick
14
+ changed
15
+ notify_observers
16
+ end
17
+
18
+ def connection
19
+ Thread.current[:conn] ||= ::PG::Connection.new :dbname => 'postgres'
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def build_observable
26
+ Timer.new
27
+ end
28
+
29
+ def observer_module
30
+ Tusk::Observable::PG
31
+ end
32
+ end
33
+
34
+ class TestClassPg < TestCase
35
+ include ObserverTests
36
+
37
+ def build_observable
38
+ Class.new {
39
+ extend Tusk::Observable::PG
40
+
41
+ def self.tick
42
+ changed
43
+ notify_observers
44
+ end
45
+
46
+ def self.connection
47
+ Thread.current[:conn] ||= ::PG::Connection.new :dbname => 'postgres'
48
+ end
49
+ }
50
+ end
51
+
52
+ def observer_module
53
+ Tusk::Observable::PG
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,84 @@
1
+ require 'redis'
2
+ require 'tusk/observable/redis'
3
+ require 'helper'
4
+
5
+ module Tusk
6
+ module Observable
7
+ class TestRedis < TestCase
8
+ include ObserverTests
9
+
10
+ class Timer
11
+ include Tusk::Observable::Redis
12
+
13
+ def tick
14
+ changed
15
+ notify_observers
16
+ end
17
+
18
+ def connection
19
+ Thread.current[:redis] ||= ::Redis.new
20
+ end
21
+ end
22
+
23
+ class QueueingObserver
24
+ def initialize q
25
+ @q = q
26
+ end
27
+
28
+ def update
29
+ @q.push :foo
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def build_observable
36
+ Timer.new
37
+ end
38
+
39
+ def observer_module
40
+ Tusk::Observable::Redis
41
+ end
42
+ end
43
+
44
+ class TestClassRedis < TestCase
45
+ include ObserverTests
46
+
47
+ def build_observable
48
+ Class.new {
49
+ extend Tusk::Observable::Redis
50
+
51
+ def self.tick
52
+ changed
53
+ notify_observers
54
+ end
55
+
56
+ def self.connection
57
+ Thread.current[:redis] ||= ::Redis.new
58
+ end
59
+ }
60
+ end
61
+
62
+ def observer_module
63
+ Tusk::Observable::Redis
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ Dir.chdir(File.join(File.dirname(__FILE__), '..')) do
70
+ `redis-server redis-test.conf`
71
+ end
72
+
73
+ at_exit {
74
+ next if $!
75
+
76
+ exit_code = MiniTest::Unit.new.run(ARGV)
77
+
78
+ processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
79
+ pids = processes.map { |process| process.split(" ")[0] }
80
+ puts "Killing test redis server..."
81
+ pids.each { |pid| Process.kill("KILL", pid.to_i) }
82
+
83
+ exit exit_code
84
+ }
@@ -0,0 +1 @@
1
+ daemonize yes
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tusk
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Aaron Patterson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-07-22 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: minitest
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 21
29
+ segments:
30
+ - 2
31
+ - 11
32
+ version: "2.11"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: pg
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 39
44
+ segments:
45
+ - 0
46
+ - 14
47
+ - 0
48
+ version: 0.14.0
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rdoc
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 19
60
+ segments:
61
+ - 3
62
+ - 10
63
+ version: "3.10"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: hoe
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ hash: 25
75
+ segments:
76
+ - 2
77
+ - 13
78
+ version: "2.13"
79
+ type: :development
80
+ version_requirements: *id004
81
+ description: |-
82
+ Tusk is a minimal pub / sub system with multiple observer strategies.
83
+ Tusk builds upon the Observer API from stdlib in order to provide a mostly
84
+ consistent API for building cross thread or process pub / sub systems.
85
+
86
+ Currently, Tusk supports Redis and PostgreSQL as message bus back ends.
87
+ email:
88
+ - aaron@tenderlovemaking.com
89
+ executables: []
90
+
91
+ extensions: []
92
+
93
+ extra_rdoc_files:
94
+ - Manifest.txt
95
+ - CHANGELOG.rdoc
96
+ - README.markdown
97
+ files:
98
+ - .autotest
99
+ - CHANGELOG.rdoc
100
+ - Manifest.txt
101
+ - README.markdown
102
+ - Rakefile
103
+ - lib/tusk.rb
104
+ - lib/tusk/latch.rb
105
+ - lib/tusk/observable/pg.rb
106
+ - lib/tusk/observable/redis.rb
107
+ - test/helper.rb
108
+ - test/observable/test_pg.rb
109
+ - test/observable/test_redis.rb
110
+ - test/redis-test.conf
111
+ - .gemtest
112
+ homepage: http://github.com/tenderlove/tusk
113
+ licenses: []
114
+
115
+ post_install_message:
116
+ rdoc_options:
117
+ - --main
118
+ - README.markdown
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ hash: 3
136
+ segments:
137
+ - 0
138
+ version: "0"
139
+ requirements: []
140
+
141
+ rubyforge_project: tusk
142
+ rubygems_version: 1.8.22
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: Tusk is a minimal pub / sub system with multiple observer strategies
146
+ test_files:
147
+ - test/observable/test_pg.rb
148
+ - test/observable/test_redis.rb