unobservable 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: