Skip to content

Commit

Permalink
Spike.
Browse files Browse the repository at this point in the history
  • Loading branch information
jbarnette committed Mar 8, 2012
0 parents commit fab64a2
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
/.rbenv-version
/pkg
Gemfile.lock
15 changes: 15 additions & 0 deletions .kick
@@ -0,0 +1,15 @@
require "kicker/utils"

process do |files|
if files.any? { |f| /\.rb$/ =~ f }
execute "rake test"
end

files.clear
end

module Kicker::Utils
def log(message)
nil
end
end
12 changes: 12 additions & 0 deletions .travis.yml
@@ -0,0 +1,12 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- jruby-18mode
- jruby-19mode
- jruby-head
- rbx-18mode
- rbx-19mode
- ree
- ruby-head
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in watchable.gemspec
gemspec
125 changes: 125 additions & 0 deletions README.markdown
@@ -0,0 +1,125 @@
[![Build Status](https://secure.travis-ci.org/jbarnette/watchable.png)](http://travis-ci.org/jbarnette/watchable)

# Watchable

A simple event/notification mixin, reluctantly extracted to a gem.
This is code I've had floating around for a few years now, but I've
also incorporated a few extras from node.js' [EventEmitter][ee],
[jQuery][jq], and [Backbone.Events][be].

[ee]: http://nodejs.org/api/events.html#events_class_events_eventemitter
[jq]: http://api.jquery.com/on
[be]: http://documentcloud.github.com/backbone/#Events

## Example

### Fixtures

```ruby
require "watchable"

class Frob
include Watchable
end

class Callable
def call *args
p :called! => args
end
end

```

### Watching and Firing

Events can have any number of watchers. Each watcher will be called
in order, and any args provided when the event is fired will be passed
along. Watchers will most commonly be blocks, but any object that
responds to `call` can be used instead.

```ruby
frob = Frob.new

frob.on :twiddle do |name|
puts "#{name} twiddled the frob!"
end

frob.on :twiddle do |name|
puts "(not that there's anything wrong with that)"
end

frob.on :twiddle, Callable.new
frob.fire :twiddle, "John"
```

#### Result

John twiddled the frob!
(not that there's anything wrong with that)
{ :called! => ["John"] }

### Watching Once

Only want to be notified the first time something happens? `once` is
like `on`, but fickle.

```ruby
frob = Frob.new

frob.on :twiddle do
p :twiddled!
end

frob.fire :twiddle
frob.fire :twiddle
```

#### Result

:twiddled!

### Unwatching

Specific blocks or callable objects can be removed from an event's
watchers, or all the event's watchers can be removed.

```ruby
b = lambda {}
frob = Frob.new

frob.on :twiddle, &b

frob.off :twiddle, b # removes the 'b' watcher, same as frob.off :twiddle, &b
frob.off :twiddle # removes all watchers for the 'twiddle' event
```

## Compatibility

Watchable is actively developed against MRI Ruby 1.8.7 as a least common
denominator, but is widely tested against other Ruby versions and
implementations. Check the [travis-ci][] page for details.

[travis-ci]: http://travis-ci.org/jbarnette/watchable

## License (MIT)

Copyright 2012 John Barnette (john@jbarnette.com)

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 changes: 9 additions & 0 deletions Rakefile
@@ -0,0 +1,9 @@
require "bundler/gem_tasks"

desc "Run the tests."
task :test do
$: << "lib" << "test"
Dir["test/*_test.rb"].each { |f| require f[5..-4] }
end

task :default => :test
33 changes: 33 additions & 0 deletions lib/watchable.rb
@@ -0,0 +1,33 @@
module Watchable
def watchers
@watchers ||= Hash.new { |h, k| h[k] = [] }
end

def fire event, *args
watchers[event].each { |w| w && w.call(*args) }

self
end

def on event, callable = nil, &block
watchers[event] << (callable || block)

self
end

def once event, callable = nil, &block
wrapper = lambda do |*args|
off event, wrapper
(callable || block).call *args
end

on event, wrapper
end

def off event, callable = nil, &block
watcher = callable || block
watcher ? watchers[event].delete(watcher) : watchers[event].clear

self
end
end
110 changes: 110 additions & 0 deletions test/watchable_test.rb
@@ -0,0 +1,110 @@
require "minitest/autorun"
require "mocha"
require "watchable"

describe Watchable do
before do
@obj = Object.new
@obj.extend Watchable
end

it "has an empty list of watchers by default" do
assert @obj.watchers.empty?
end

it "returns an empty array of watchers for any event" do
assert_equal [], @obj.watchers[:foo]
end

describe :fire do
it "calls each watcher with optional args" do
@obj.on :foo, mock { expects(:call).with :bar, :baz }
@obj.fire :foo, :bar, :baz
end

it "calls multiple watchers in order" do
fires = sequence "fires"

@obj.on :foo, mock { expects(:call).in_sequence fires }
@obj.on :foo, mock { expects(:call).in_sequence fires }

@obj.fire :foo
end

it "ignores nil watchers" do
@obj.on :foo, nil
@obj.fire :foo
end

it "returns the watchable" do
assert_same @obj, @obj.fire(:foo)
end
end

describe :off do
it "can unregister a block" do
b = lambda {}

@obj.on :foo, &b
@obj.off :foo, &b

assert @obj.watchers[:foo].empty?
end

it "can unregister an object" do
b = lambda {}

@obj.on :foo, &b
@obj.off :foo, &b

assert @obj.watchers[:foo].empty?
end

it "can unregister all watchers for an event" do
@obj.on(:foo) {}
@obj.on(:foo) {}

assert_equal 2, @obj.watchers[:foo].size

@obj.off :foo
assert @obj.watchers[:foo].empty?
end

it "returns the watchable" do
assert_same @obj, @obj.off(:foo)
end
end

describe :on do
it "can register a block" do
b = lambda {}

@obj.on :foo, &b
assert_equal [b], @obj.watchers[:foo]
end

it "can register an object" do
b = lambda {}

@obj.on :foo, b
assert_equal [b], @obj.watchers[:foo]
end

it "returns the watchable" do
assert_same @obj, @obj.on(:foo) {}
end
end

describe :once do
it "registers a watcher that's only called on the first fire" do
@obj.once :foo, mock { expects :call }

@obj.fire :foo
@obj.fire :foo
end

it "returns the watchable" do
assert_same @obj, @obj.once(:foo) {}
end
end
end
16 changes: 16 additions & 0 deletions watchable.gemspec
@@ -0,0 +1,16 @@
Gem::Specification.new do |gem|
gem.authors = ["John Barnette"]
gem.email = ["john@jbarnette.com"]
gem.description = "A simple event mixin, reluctantly extracted to a gem."
gem.summary = "Watch an object for events."
gem.homepage = "https://github.com/jbarnette/watchable"

gem.files = `git ls-files`.split "\n"
gem.test_files = `git ls-files -- test/*`.split "\n"
gem.name = "watchable"
gem.require_paths = ["lib"]
gem.version = "0.0.0"

gem.add_development_dependency "minitest"
gem.add_development_dependency "mocha"
end

0 comments on commit fab64a2

Please sign in to comment.