unobservable 0.6.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/TODO.rdoc CHANGED
@@ -1,5 +1,5 @@
1
1
  = TODO
2
2
 
3
- * Add support for singleton events
4
3
  * Figure out a decent way to restrict access to Unobservable::Event#call to encourage proper encapsulation.
4
+ * Move Unobservable::Event#handler_for to Unobservable::handler_for
5
5
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.1
1
+ 0.8.0
data/lib/unobservable.rb CHANGED
@@ -165,7 +165,12 @@ module Unobservable
165
165
  if block
166
166
  return block
167
167
  elsif args.size == 1
168
- return args[0]
168
+ candidate = args[0]
169
+ if candidate.respond_to?(:to_proc)
170
+ return candidate.to_proc
171
+ else
172
+ raise ArgumentError, "The argument does not respond to the #to_proc method"
173
+ end
169
174
  elsif args.size == 2
170
175
  return args[0].method(args[1])
171
176
  end
@@ -208,7 +213,11 @@ module Unobservable
208
213
  else
209
214
  # TODO: Add some form of error-handling
210
215
  @handlers.each do |h|
211
- h.call(*args, &block)
216
+ begin
217
+ h.call(*args, &block)
218
+ rescue Exception
219
+ # TODO: Should probably log when this happens
220
+ end
212
221
  end
213
222
 
214
223
  return true
data/spec/event_spec.rb CHANGED
@@ -2,5 +2,158 @@ require 'spec_helper'
2
2
 
3
3
  module Unobservable
4
4
 
5
+ describe Event do
6
+
7
+ let(:e) { Event.new }
8
+
9
+ describe "#handler_for" do
10
+ it "raises an ArgumentError when it does not receive any arguments" do
11
+ expect{ e.handler_for() }.to raise_error(ArgumentError)
12
+ end
13
+
14
+
15
+ it "raises an ArgumentError when it is receives an argument that cannot be converted into a Proc" do
16
+ expect{ e.handler_for(Object.new) }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it "allows a Proc to be used has an event handler" do
20
+ p = Proc.new {}
21
+ e.handler_for(p).should == p
22
+ end
23
+
24
+ it "allows a Block to be used as an event handler" do
25
+ p = Proc.new {}
26
+ e.handler_for(&p).should == p
27
+ end
28
+
29
+ it "can use a specified method on an object as an event handler" do
30
+ x = Object.new
31
+ e.handler_for(x, :class).should == x.method(:class)
32
+ end
33
+
34
+
35
+ it "raises an ArgumentError when it receives 3 or more arguments" do
36
+ expect{ e.handler_for(Proc.new, :foo, :bar) }.to raise_error(ArgumentError)
37
+ end
38
+
39
+
40
+ end
41
+
42
+
43
+ describe "#register" do
44
+
45
+ it "returns the event handler that was added to the event's list of handlers" do
46
+ handler = Proc.new {}
47
+ e.register(handler).should == handler
48
+ end
49
+
50
+ it "adds an event handler to the event's list of handlers" do
51
+ handler = Proc.new {}
52
+
53
+ expect do
54
+ e.register(handler)
55
+ end.to change{ e.handlers.size }.from(0).to(1)
56
+
57
+ e.handlers.should include(handler)
58
+ end
59
+
60
+ it "allows multiple event handlers to be registered to the same event" do
61
+ h1 = Proc.new { puts "one" }
62
+ h2 = Proc.new { puts "two" }
63
+
64
+ expect do
65
+ e.register h1
66
+ e.register h2
67
+ end.to change{ e.handlers.size }.from(0).to(2)
68
+
69
+ e.handlers.should include(h1)
70
+ e.handlers.should include(h2)
71
+
72
+ h1.should_not == h2
73
+ end
74
+
75
+
76
+ it "allows the same event handler to be registered multiple times" do
77
+ handler = Proc.new {}
78
+
79
+ expect do
80
+ 3.times { e.register handler }
81
+ end.to change{ e.handlers.size }.from(0).to(3)
82
+
83
+ e.handlers.each {|h| h.should == handler}
84
+ end
85
+
86
+ end
87
+
88
+
89
+ describe "#unregister" do
90
+
91
+ it "returns nil when asked to unregister a handler that was never registered" do
92
+ p = Proc.new {}
93
+ e.handlers.should_not include(p)
94
+ e.unregister(p).should be_nil
95
+ end
96
+
97
+ it "returns the handler that was unregistered" do
98
+ p = Proc.new {}
99
+ e.register p
100
+
101
+ e.handlers.should include(p)
102
+ expect do
103
+ e.unregister(p).should == p
104
+ end.to change{ e.handlers.size }.from(1).to(0)
105
+
106
+ e.handlers.should_not include(p)
107
+ end
108
+
109
+
110
+ it "only unregisters 1 occurrence of the specified event handler" do
111
+ p = Proc.new { "hello" }
112
+ 3.times { e.register p }
113
+
114
+ expect{ e.unregister p }.to change{ e.handlers.size }.from(3).to(2)
115
+
116
+ e.handlers.each{|h| h.should == p}
117
+ end
118
+
119
+ end
120
+
121
+
122
+
123
+ describe "#call" do
124
+ it "can be called when no event handlers have been registered" do
125
+ expect{ e.call }.to_not raise_error
126
+ expect{ e.call 1, 2, 3 }.to_not raise_error
127
+ end
128
+
129
+ it "passes its arguments to each event handler" do
130
+ 3.times do |i|
131
+ h = mock("handler #{i}")
132
+ h.should_receive(:some_method).with("arg1", "arg2")
133
+ e.register h, :some_method
134
+ end
135
+
136
+ e.call "arg1", "arg2"
137
+ end
138
+
139
+
140
+ it "should invoke each event handler even if one or more event handlers raise an exception." do
141
+ 3.times do |i|
142
+ h = mock("handler #{i}")
143
+ h.should_receive(:some_method).with("arg1", "arg2").and_raise(Exception)
144
+ e.register h, :some_method
145
+ end
146
+
147
+ expect{ e.call "arg1", "arg2" }.to_not raise_error
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
154
+
5
155
 
6
- end
156
+
157
+ end
158
+
159
+
data/spec/spec_helper.rb CHANGED
@@ -9,3 +9,34 @@ RSpec.configure do |config|
9
9
 
10
10
  end
11
11
 
12
+
13
+ module Unobservable
14
+ module SpecHelper
15
+
16
+ def module_with_instance_events(*names, &block)
17
+ m = Module.new do
18
+ include Unobservable::Support
19
+ attr_event *names
20
+ end
21
+
22
+ m.class_exec(&block) if block_given?
23
+
24
+ return m
25
+ end
26
+
27
+ def class_with_instance_events(*names, &block)
28
+ args = {superclass: Object}
29
+ args.merge!(names.pop) if names[-1].is_a? Hash
30
+
31
+ c = Class.new(args[:superclass]) do
32
+ include Unobservable::Support
33
+ attr_event *names
34
+ end
35
+
36
+ c.class_exec(&block) if block_given?
37
+
38
+ return c
39
+ end
40
+
41
+ end
42
+ end
@@ -1,72 +1,137 @@
1
1
  require 'spec_helper'
2
2
 
3
+ include Unobservable::SpecHelper
3
4
 
4
5
 
5
- describe Unobservable do
6
+ shared_examples_for "instance event container" do
7
+
8
+ context "Unobservable::Support is not included" do
9
+ it "does not have any instance events" do
10
+ c = described_class.new
11
+ Unobservable::instance_events_for(c, true).should be_empty
12
+ Unobservable::instance_events_for(c, false).should be_empty
13
+ end
14
+ end
15
+
16
+
17
+ context "Unobservable::Support is included" do
6
18
 
7
- describe "#instance_events_for" do
8
-
9
- it "raises a type error when it receives a non-Module" do
10
- expect do
11
- Unobservable.instance_events_for(Object.new)
12
- end.to raise_error(TypeError)
19
+ let(:mixin_module) do
20
+ module_with_instance_events :one, :two
13
21
  end
14
22
 
23
+ let(:instance_event_container) do
24
+ m = mixin_module
25
+ described_class.new do
26
+ include Unobservable::Support
27
+ include m
28
+
29
+ attr_event :three, :four
30
+ end
31
+ end
15
32
 
33
+ it "does not have any events by default" do
34
+ c = described_class.new { include Unobservable::Support }
35
+ Unobservable::instance_events_for(c, true).should be_empty
36
+ Unobservable::instance_events_for(c, false).should be_empty
37
+ end
38
+
39
+ it "knows which events have been defined explicitly" do
40
+ events = Unobservable::instance_events_for(instance_event_container, false)
41
+ events.size.should == 2
42
+ events.should include(:three)
43
+ events.should include(:four)
44
+ end
16
45
 
17
- it "returns an empty list when given a Module that does not support events" do
18
- plain_module = Module.new
19
- Unobservable.instance_events_for(plain_module, true).should be_empty
20
- Unobservable.instance_events_for(plain_module, false).should be_empty
46
+ it "inherits instance events defined by included Modules" do
47
+ events = Unobservable::instance_events_for(instance_event_container, true)
48
+ events.size.should == 4
49
+ events.should include(:one)
50
+ events.should include(:two)
51
+ events.should include(:three)
52
+ events.should include(:four)
21
53
  end
22
54
 
55
+ end
56
+
57
+ end
58
+
23
59
 
24
- it "returns an empty list when given a Module that does not have any instance events defined" do
25
- module_without_events = Module.new { include Unobservable::Support }
26
- Unobservable.instance_events_for(module_without_events, true).should be_empty
27
- Unobservable.instance_events_for(module_without_events, false).should be_empty
28
- end
60
+ describe Module do
61
+ it_behaves_like "instance event container"
62
+ end
63
+
64
+ describe Class do
65
+ it_behaves_like "instance event container"
66
+ end
67
+
68
+
69
+
70
+ describe Unobservable do
29
71
 
72
+ describe "#instance_events_for" do
30
73
 
74
+ it "raises TypeError when it receives a non-Module" do
75
+ expect{ Unobservable.instance_events_for(Object.new) }.to raise_error(TypeError)
76
+ end
31
77
 
32
- context "Module defines instance events" do
33
- let(:mixin_module) do
34
- Module.new do
35
- include Unobservable::Support
36
- attr_event :one, :two
37
- end
38
- end
39
-
40
- let(:module_with_events) do
41
- # Due to scoping weirdness, we need to place the mixin module
42
- # in a local variable first.
43
- m = mixin_module
44
- Module.new do
45
- include Unobservable::Support
46
- include m
47
- attr_event :three, :four
48
- end
49
- end
78
+ end
79
+
80
+
81
+ describe "#collect_instance_events_defined_by" do
82
+ let(:mixin_module) do
83
+ module_with_instance_events(:mixin_1, :mixin_2)
84
+ end
85
+
86
+ let(:baseclass) do
87
+ class_with_instance_events(:bc_1, :bc_2)
88
+ end
50
89
 
51
- it "returns instance events defined explicitly by the Module when all=false" do
52
- events = Unobservable::instance_events_for(module_with_events, false)
53
- events.size.should eq(2)
54
- events.should include(:three)
55
- events.should include(:four)
90
+ let(:subclass) do
91
+ m = mixin_module
92
+ class_with_instance_events(:sc_1, :sc_2, superclass: baseclass) do
93
+ include m
56
94
  end
95
+ end
96
+
97
+
98
+ it "only collects the instance events that the contributors define explicitly" do
99
+ events = Unobservable::collect_instance_events_defined_by([subclass])
100
+ events.size.should == 2
101
+ events.should include(:sc_1)
102
+ events.should include(:sc_2)
103
+ end
104
+
105
+ it "collects the instance events defined by each contributor" do
106
+ classes = (1..3).map{|i| class_with_instance_events("a#{i}", "b#{i}") }
107
+ modules = (1..3).map{|i| module_with_instance_events("c#{i}", "d#{i}") }
57
108
 
58
-
59
- it "returns instance events defined explicitly and through included Modules when all=true" do
60
- events = Unobservable::instance_events_for(module_with_events, true)
61
- events.size.should eq(4)
62
- events.should include(:one)
63
- events.should include(:two)
64
- events.should include(:three)
65
- events.should include(:four)
109
+ events = Unobservable::collect_instance_events_defined_by(classes + modules)
110
+ events.size.should == 12
111
+ (1..3).each do |i|
112
+ events.should include("a#{i}".to_sym)
113
+ events.should include("b#{i}".to_sym)
114
+ events.should include("c#{i}".to_sym)
115
+ events.should include("d#{i}".to_sym)
66
116
  end
117
+ end
118
+
119
+
120
+ it "does not repeat duplicate instance events" do
121
+ c1 = class_with_instance_events(:one, :two)
122
+ c2 = class_with_instance_events(:one, :three)
123
+ m1 = module_with_instance_events(:two, :three)
124
+ m2 = module_with_instance_events(:two)
67
125
 
126
+ events = Unobservable::collect_instance_events_defined_by([c1, c2, m1, m2])
127
+ events.size.should == 3
128
+ events.should include(:one)
129
+ events.should include(:two)
130
+ events.should include(:three)
68
131
  end
69
132
 
133
+
70
134
  end
71
135
 
72
- end
136
+ end
137
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unobservable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-04 00:00:00.000000000 Z
12
+ date: 2012-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: shoulda
@@ -127,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
127
  version: '0'
128
128
  segments:
129
129
  - 0
130
- hash: 2302660534203151431
130
+ hash: 2686370053034444533
131
131
  required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  none: false
133
133
  requirements: