observatory 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  doc
2
2
  .yardoc
3
3
  _site
4
+ pkg
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- observatory (0.0.1)
4
+ observatory (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
9
  bluecloth (2.1.0)
10
- rake (0.8.7)
11
- yard (0.6.8)
10
+ rake (0.9.0)
11
+ yard (0.7.1)
12
12
 
13
13
  PLATFORMS
14
14
  ruby
@@ -16,5 +16,5 @@ PLATFORMS
16
16
  DEPENDENCIES
17
17
  bluecloth (~> 2.1)
18
18
  observatory!
19
- rake (~> 0.8)
20
- yard (~> 0.6)
19
+ rake (~> 0.9)
20
+ yard (~> 0.7)
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ Rake::TestTask.new do |t|
12
12
  t.verbose = true
13
13
  end
14
14
 
15
- YARD::Rake::YardocTask.new do |t|
16
- t.files = ['lib/**/*.rb', 'app/**/*.rb', '-', 'LICENSE', 'HISTORY']
15
+ YARD::Rake::YardocTask.new do |t|
16
+ t.files = ['lib/**/*.rb', 'app/**/*.rb', '-', 'LICENSE', 'HISTORY']
17
17
  t.options = %w{--title Observatory -m markdown}
18
- end
18
+ end
@@ -1,33 +1,38 @@
1
1
  module Observatory
2
2
  # The Dispatcher is the central repository of all registered observers, and
3
3
  # is used by observables to send out signals to these observers.
4
- #
4
+ #
5
5
  # A dispatcher is not a singleton object, which means you may very well have
6
6
  # several dispatcher objects in your program, keeping track of different
7
7
  # stacks of observers and observables. Note that this requires you to pass
8
8
  # your dispatcher object around using dependency injection.
9
- #
9
+ #
10
10
  # ## For observables
11
- #
11
+ #
12
12
  # The stack of observers for any given signal is kept in {#observers}. When
13
13
  # using {#notify}, {#notify_until} or {#filter} all observers in the stack
14
14
  # will be called.
15
- #
15
+ #
16
16
  # ### Notification methods
17
- #
17
+ #
18
18
  # Observable objects may use the following methods to trigger their
19
19
  # observers:
20
- #
20
+ #
21
21
  # * {#notify} to call all observers.
22
22
  # * {#notify_until} to call observers until one stops the chain.
23
23
  # * {#filter} to let all observers alter a given value.
24
- #
24
+ #
25
25
  # ## For observers
26
- #
26
+ #
27
27
  # An object that observes another object is an observer, and it can
28
28
  # register itself with the {Dispatcher} to listen to a signal that
29
29
  # observable objects may issue.
30
- #
30
+ #
31
+ # An observer may be anything that is callable, but will usually
32
+ # be a method, block or Proc object. You may optionally specify an
33
+ # explicit priority for an observer, to make sure it gets called before
34
+ # or after other observers.
35
+ #
31
36
  # @example Using {#connect} to register a new observer
32
37
  # class Logger
33
38
  # def log(event)
@@ -36,10 +41,10 @@ module Observatory
36
41
  # end
37
42
  # logger = Logger.new
38
43
  # dispatcher.connect('post.publish', logger.method(:log))
39
- #
44
+ #
40
45
  # @example Using {#disconnect} to unregister an observer
41
46
  # dispatcher.disconnect('post.publish', logger.method(:log))
42
- #
47
+ #
43
48
  # @example Using {#notify} to let other objects know something has happened
44
49
  # class Post
45
50
  # include Observable
@@ -49,14 +54,14 @@ module Observatory
49
54
  # # do publication stuff here
50
55
  # end
51
56
  # end
52
- #
57
+ #
53
58
  # @example Using {#notify_until} to delegate saving a record to another object
54
59
  # class Post
55
60
  # def save
56
61
  # notify_until 'post.save', :title => title
57
62
  # end
58
63
  # end
59
- #
64
+ #
60
65
  # @example Using {#filter} to let observers modify the output of the title attribute
61
66
  # class Post
62
67
  # def title
@@ -71,17 +76,23 @@ module Observatory
71
76
  def initialize
72
77
  @observers = {}
73
78
  end
74
-
79
+
75
80
  # Register a observer for a given signal.
76
- #
81
+ #
77
82
  # Instead of adding a method or Proc object to the stack, you could
78
83
  # also use a block. Either the observer argument or the block is required.
79
- #
84
+ #
85
+ # Optionally, you could pass in an options hash as the last argument, that
86
+ # can specify an explicit priority. When omitted, an internal counter starting
87
+ # from 1 will be used. To make sure your observer is called last, specify
88
+ # a high, **positive** number. To make sure your observer is called first, specify
89
+ # a high, **negative** number.
90
+ #
80
91
  # @example Using a block as an observer
81
92
  # dispatcher.connect('post.publish') do |event|
82
93
  # puts "Post was published"
83
94
  # end
84
- #
95
+ #
85
96
  # @example Using a method as an observer
86
97
  # class Reporter
87
98
  # def log(event)
@@ -89,42 +100,98 @@ module Observatory
89
100
  # end
90
101
  # end
91
102
  # dispatcher.connect('post.publish', Reporter.new.method(:log))
92
- #
93
- # @param [String] signal is the name used by the observable to trigger
94
- # observers
95
- # @param [#call] observer is the Proc or method that will react to
96
- # an event issued by an observable.
103
+ #
104
+ # @example Determining observer call order using priority
105
+ # dispatcher.connect('pulp', :priority => 10) do
106
+ # puts "I dare you!"
107
+ # end
108
+ # dispatcher.connect('pulp', :priority => -10) do
109
+ # puts "I double-dare you!"
110
+ # end
111
+ # # output when "pulp" is triggered:
112
+ # "I double-dare you!"
113
+ # "I dare you!"
114
+ #
115
+ # @overload connect(signal, observer, options = {})
116
+ # @param [String] signal is the name used by the observable to trigger
117
+ # observers
118
+ # @param [#call] observer is the Proc or method that will react to
119
+ # an event issued by an observable.
120
+ # @param [Hash] options is an optional Hash of additional options.
121
+ # @option options [Fixnum] :priority is the priority of this observer
122
+ # in the stack of all observers for this signal. A higher number means
123
+ # lower priority. Negative numbers are allowed.
124
+ # @overload connect(signal, options = {}, &block)
125
+ # @param [String] signal is the name used by the observable to trigger
126
+ # observers
127
+ # @param [Hash] options is an optional Hash of additional options.
128
+ # @option options [Fixnum] :priority is the priority of this observer
129
+ # in the stack of all observers for this signal. A higher number means
130
+ # lower priority. Negative numbers are allowed.
97
131
  # @return [#call] the added observer
98
- def connect(signal, observer = nil, &block)
99
- if observer.nil?
100
- if block_given?
101
- observer = block
132
+ def connect(signal, *args, &block)
133
+ # ugly argument parsing.
134
+ # Make sure that there is either a block given, or that the second argument is
135
+ # something callable. If there is a block given, the second argument, if given,
136
+ # must be a Hash which defaults to an empty Hash. If there is no block given,
137
+ # the third optional argument must be Hash.
138
+ if block_given?
139
+ observer = block
140
+ if args.size == 1 && args.first.is_a?(Hash)
141
+ options = args.first
142
+ elsif args.size == 0
143
+ options = {}
102
144
  else
103
- raise ArgumentError, 'Use a block, method or proc to specify an observer'
145
+ raise ArgumentError, 'When given a block, #connect only expects a signal and options hash as arguments'
146
+ end
147
+ else
148
+ observer = args.shift
149
+ raise ArgumentError, 'Use a block, method or proc to specify an observer' unless observer.respond_to?(:call)
150
+ if args.any?
151
+ options = args.shift
152
+ raise ArgumentError, '#connect only expects a signal, method and options hash as arguments' unless options.is_a?(Hash) || args.any?
153
+ else
154
+ options = {}
104
155
  end
105
156
  end
157
+
158
+ observer_with_priority = {
159
+ :observer => observer,
160
+ :priority => (options[:priority] || next_internal_priority)
161
+ }
162
+
163
+ # Initialize the list of observers for this signal and add this observer
106
164
  observers[signal] ||= []
107
- observers[signal] << observer
165
+ observers[signal] << observer_with_priority
166
+
167
+ # Sort all observers on priority
168
+ observers[signal].sort! do |a,b|
169
+ a[:priority] <=> b[:priority]
170
+ end
171
+
172
+ observer
108
173
  end
109
174
 
110
175
  # Removes an observer from a signal stack, so it no longer gets triggered.
111
- #
176
+ #
112
177
  # @param [String] signal is the name of the stack to remove the observer
113
178
  # from.
114
179
  # @param [#call] observer is the original observer to remove.
115
180
  # @return [#call, nil] the removed observer or nil if it could not be found
116
181
  def disconnect(signal, observer)
117
182
  return nil unless observers.key?(signal)
118
- observers[signal].delete(observer)
183
+ observers[signal].delete_if do |observer_with_priority|
184
+ observer_with_priority[:observer] == observer
185
+ end
119
186
  end
120
187
 
121
188
  # Send out a signal to all registered observers using a new {Event}
122
189
  # instance. The {Event#signal} will be used to determine the stack of
123
190
  # {#observers} to use.
124
- #
191
+ #
125
192
  # Using {#notify} allows observers to take action at a given time during
126
193
  # program execution, such as logging important events.
127
- #
194
+ #
128
195
  # @param [Event]
129
196
  # @return [Event]
130
197
  def notify(event)
@@ -136,9 +203,9 @@ module Observatory
136
203
 
137
204
  # Same as {#notify}, but halt execution as soon as an observer has
138
205
  # indicated it has handled the event by returning a non-falsy value.
139
- #
206
+ #
140
207
  # An event that was acted upon by an observer will be marked as processed.
141
- #
208
+ #
142
209
  # @param [Event]
143
210
  # @see Event#process!
144
211
  # @return [Event]
@@ -151,10 +218,10 @@ module Observatory
151
218
 
152
219
  # Let all registered observers modify a given value. The observable can
153
220
  # then use the {Event#return_value} to get the filtered result back.
154
- #
221
+ #
155
222
  # You could use {#filter} to let observers modify arguments to a method
156
223
  # before continuing to work on them (just an example).
157
- #
224
+ #
158
225
  # @param [Event]
159
226
  # @param [Object] value
160
227
  # @return [Event]
@@ -168,8 +235,15 @@ module Observatory
168
235
 
169
236
  private
170
237
 
238
+ def next_internal_priority
239
+ @next_internal_priority ||= 0
240
+ @next_internal_priority += 1
241
+ end
242
+
171
243
  def each(signal, &block)
172
- (observers[signal] || []).each(&block)
244
+ (observers[signal] || []).each do |observer_with_priority|
245
+ yield observer_with_priority[:observer]
246
+ end
173
247
  end
174
248
  end
175
249
  end
@@ -1,3 +1,3 @@
1
1
  module Observatory
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
data/observatory.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ['Arjan van der Gaag']
10
10
  s.email = ['arjan@arjanvandergaag.nl']
11
- s.homepage = ""
11
+ s.homepage = 'http://avdgaag.github.com/observatory'
12
12
  s.summary = "A simple implementation of the observer pattern for Ruby programs."
13
13
  s.description = %q{Observatory is a simple gem to facilitate loosely-coupled communication between Ruby objects. It implements the observer design pattern so that your objects can publish events that other objects can subscribe to. Observatory provides some syntactic sugar and methods to notify events, filter values and allow observing objects to stop the filter chain. Observatory is inspired by the Event Dispatcher Symfony component.}
14
14
 
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
-
22
- s.add_development_dependency 'yard', '~>0.6'
21
+
22
+ s.add_development_dependency 'yard', '~>0.7'
23
23
  s.add_development_dependency 'bluecloth', '~>2.1'
24
- s.add_development_dependency 'rake', '~>0.8'
24
+ s.add_development_dependency 'rake', '~>0.9'
25
25
  end
@@ -5,42 +5,73 @@ class DispatcherTest < Test::Unit::TestCase
5
5
  @dispatcher = Dispatcher.new
6
6
  @method = method(:example_observer_method)
7
7
  end
8
-
8
+
9
9
  def example_observer_method
10
10
  # does nothing
11
11
  end
12
-
12
+
13
13
  def test_should_start_with_empty_list_of_observers
14
14
  assert_equal({}, @dispatcher.observers)
15
15
  end
16
-
16
+
17
17
  def test_connecting_an_observer_using_a_method
18
18
  @dispatcher.connect('signal', @method)
19
- assert @dispatcher.observers['signal'].include?(@method)
19
+ assert @dispatcher.observers['signal'].map { |o| o[:observer] }.include?(@method)
20
20
  end
21
-
22
- def test_connecting_an_oversver_using_a_block
21
+
22
+ def test_connecting_an_observer_using_a_block
23
23
  @dispatcher.connect('signal') do
24
24
  # does nothing
25
25
  end
26
26
  assert_equal 1, @dispatcher.observers['signal'].size
27
27
  end
28
-
28
+
29
+ def test_connecting_an_observer_with_a_priority
30
+ assert_nothing_raised { @dispatcher.connect('signal', @method, :priority => 5) }
31
+ end
32
+
33
+ def test_observers_are_called_in_order
34
+ guinea_pig = 'foo'
35
+ @dispatcher.connect('signal', :priority => 10) do
36
+ guinea_pig = guinea_pig.upcase!
37
+ end
38
+ @dispatcher.connect('signal', :priority => 5) do
39
+ guinea_pig << 'bar'
40
+ end
41
+ @dispatcher.notify(Event.new('observable', 'signal'))
42
+ assert_equal 'FOOBAR', guinea_pig
43
+ end
44
+
45
+ def test_observers_without_priority_remain_in_order_of_adding
46
+ guinea_pig = 'foo'
47
+ @dispatcher.connect('signal') do
48
+ guinea_pig << 'bar'
49
+ end
50
+ @dispatcher.connect('signal') do
51
+ guinea_pig << 'baz'
52
+ end
53
+ @dispatcher.connect('signal', :priority => -1) do
54
+ guinea_pig << 'qux'
55
+ end
56
+ @dispatcher.notify(Event.new('observable', 'signal'))
57
+ assert_equal 'fooquxbarbaz', guinea_pig
58
+ end
59
+
29
60
  def test_connecting_nothing_should_raise_exception
30
61
  assert_raise(ArgumentError) { @dispatcher.connect('signal') }
31
62
  end
32
-
63
+
33
64
  def test_disconnecting_a_new_observer_returns_nil
34
65
  assert_nil @dispatcher.disconnect('signal', @method)
35
66
  end
36
-
67
+
37
68
  def test_disconnecting_an_exisiting_observer_removes_it_from_stack
38
69
  @dispatcher.connect('signal', @method)
39
70
  assert_equal 1, @dispatcher.observers['signal'].size
40
71
  @dispatcher.disconnect('signal', @method)
41
72
  assert_equal 0, @dispatcher.observers['signal'].size
42
73
  end
43
-
74
+
44
75
  def test_notify_calls_all_observers
45
76
  flag1 = false
46
77
  flag2 = false
@@ -50,7 +81,7 @@ class DispatcherTest < Test::Unit::TestCase
50
81
  assert flag1
51
82
  assert flag2
52
83
  end
53
-
84
+
54
85
  def test_notify_calls_all_observers_in_order
55
86
  output = ''
56
87
  @dispatcher.connect('signal') { output << 'a' }
@@ -58,7 +89,7 @@ class DispatcherTest < Test::Unit::TestCase
58
89
  @dispatcher.notify(Event.new('observable', 'signal'))
59
90
  assert_equal('ab', output)
60
91
  end
61
-
92
+
62
93
  def test_using_notify_until_calls_all_observers_until_one_returns_true
63
94
  output = ''
64
95
  @dispatcher.connect('signal') { output << 'a'; true }
@@ -66,11 +97,11 @@ class DispatcherTest < Test::Unit::TestCase
66
97
  @dispatcher.notify_until(Event.new('observable', 'signal'))
67
98
  assert_equal('a', output)
68
99
  end
69
-
100
+
70
101
  def test_using_filter_uses_original_value_as_default_return_value
71
102
  assert_equal 'foo', @dispatcher.filter(Event.new('observable', 'signal'), 'foo').return_value
72
103
  end
73
-
104
+
74
105
  def test_using_filter_uses_adjusted_value_as_default_return_value
75
106
  @dispatcher.connect('signal') { |e,v| v.upcase }
76
107
  assert_equal 'FOO', @dispatcher.filter(Event.new('observable', 'signal'), 'foo').return_value
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: observatory
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Arjan van der Gaag
@@ -15,27 +15,26 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-01 00:00:00 Z
18
+ date: 2011-05-26 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: yard
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
21
+ type: :development
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
25
24
  requirements:
26
25
  - - ~>
27
26
  - !ruby/object:Gem::Version
28
- hash: 7
27
+ hash: 5
29
28
  segments:
30
29
  - 0
31
- - 6
32
- version: "0.6"
33
- type: :development
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: bluecloth
30
+ - 7
31
+ version: "0.7"
32
+ requirement: *id001
37
33
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
34
+ name: yard
35
+ - !ruby/object:Gem::Dependency
36
+ type: :development
37
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
38
  none: false
40
39
  requirements:
41
40
  - - ~>
@@ -45,23 +44,24 @@ dependencies:
45
44
  - 2
46
45
  - 1
47
46
  version: "2.1"
48
- type: :development
49
- version_requirements: *id002
50
- - !ruby/object:Gem::Dependency
51
- name: rake
47
+ requirement: *id002
52
48
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
49
+ name: bluecloth
50
+ - !ruby/object:Gem::Dependency
51
+ type: :development
52
+ version_requirements: &id003 !ruby/object:Gem::Requirement
54
53
  none: false
55
54
  requirements:
56
55
  - - ~>
57
56
  - !ruby/object:Gem::Version
58
- hash: 27
57
+ hash: 25
59
58
  segments:
60
59
  - 0
61
- - 8
62
- version: "0.8"
63
- type: :development
64
- version_requirements: *id003
60
+ - 9
61
+ version: "0.9"
62
+ requirement: *id003
63
+ prerelease: false
64
+ name: rake
65
65
  description: Observatory is a simple gem to facilitate loosely-coupled communication between Ruby objects. It implements the observer design pattern so that your objects can publish events that other objects can subscribe to. Observatory provides some syntactic sugar and methods to notify events, filter values and allow observing objects to stop the filter chain. Observatory is inspired by the Event Dispatcher Symfony component.
66
66
  email:
67
67
  - arjan@arjanvandergaag.nl
@@ -93,7 +93,7 @@ files:
93
93
  - test/observer_test.rb
94
94
  - test/test_helper.rb
95
95
  - watch_tests.rb
96
- homepage: ""
96
+ homepage: http://avdgaag.github.com/observatory
97
97
  licenses: []
98
98
 
99
99
  post_install_message: