heed 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0345d1edb02b2cf539eec0ab3dd83d53b64ae55b
4
+ data.tar.gz: 6e49bd332ad4e1e77980a745ec3fa369fe2e263f
5
+ SHA512:
6
+ metadata.gz: 4ed74d4af4b784f28e102e3f9828aec13772078d5e4a2555019c1476385a6ecb85e8a9651f4b1b9060dd662673e419ebf60c3ff9ea8460101a45340f1093e03b
7
+ data.tar.gz: b5f84d357dad9563f3eca222f0dbcca63096dffea597640e4779f0ea2a16b11e8539b38a1e2b07b40bd531e73eccff6360ec0b871744f3a822b50cfaa305d069
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.3
6
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hark.gemspec
4
+ gemspec
data/History.md ADDED
@@ -0,0 +1,26 @@
1
+ # History
2
+
3
+ ## 0.0.7
4
+
5
+ * Change gem name to 'heed'
6
+
7
+ ## 0.0.6
8
+
9
+ * remove Kernel#to_hark
10
+
11
+ ## 0.0.5
12
+
13
+ * ruby 1.8.7 compat
14
+
15
+ ## 0.0.4
16
+
17
+ * Adds Kernel#hearken
18
+ * Adds coveralls & History
19
+
20
+ ## 0.0.3
21
+
22
+ * Adds ruby < 1.9 support
23
+
24
+ ## 0.0.1 - 0.0.2
25
+
26
+ * Initial release, slims API down to Kernel#hark
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ian White
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,332 @@
1
+ # Hark
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/ianwhite-hark.png)](https://rubygems.org/gems/ianwhite-hark)
4
+ [![Build Status](https://travis-ci.org/ianwhite/hark.png)](https://travis-ci.org/ianwhite/hark)
5
+ [![Dependency Status](https://gemnasium.com/ianwhite/hark.png)](https://gemnasium.com/ianwhite/hark)
6
+ [![Code Climate](https://codeclimate.com/github/ianwhite/hark.png)](https://codeclimate.com/github/ianwhite/hark)
7
+ [![Coverage Status](https://coveralls.io/repos/ianwhite/hark/badge.png)](https://coveralls.io/r/ianwhite/hark)
8
+
9
+ Create a ad-hoc listeners with hark.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'ianwhite-hark', :require => 'hark'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install ianwhite-hark
24
+
25
+ ## What & Why?
26
+
27
+ **hark** enables you to create a 'listener' object very easily. It's for programming in the *hexagonal* or *tell, don't ask* style.
28
+ The consumers of hark listeners don't know anything about hark. Because hark makes it easy to create ad-hoc object, it's easy to get
29
+ started with a tell-dont-ask style, in rails controllers for example. For more detail see the 'Rationale' section.
30
+
31
+ ## Usage
32
+
33
+ ### Create a listener
34
+
35
+ To create a listener object use `hark`.
36
+
37
+ You can pass a symbol and block
38
+
39
+ hark :created do |user|
40
+ redirect_to(user, notice: "You have signed up!")
41
+ end
42
+
43
+ The following methods are more suitable for a listener with multiple messages.
44
+
45
+ A hash with callables as values
46
+
47
+ hark(
48
+ created: ->(user) { redirect_to(user, notice: "You have signed up!") },
49
+ invalid: ->(user) { @user = user; render "new" }
50
+ )
51
+
52
+ # assuming some methods for rendering and redirecting exist on the controller
53
+ hark(created: method(:redirect_to_user), invalid: method(:render_new))
54
+
55
+ Or, a 'respond_to' style block
56
+
57
+ hark do |on|
58
+ on.created {|user| redirect_to(user, notice: "You have signed up!") }
59
+ on.invalid {|user| @user = user; render "new" }
60
+ end
61
+
62
+ ### Strict & lax listeners
63
+
64
+ By default, hark listeners are 'strict', they will only respond to the methods defined on them.
65
+
66
+ You create a 'lax' listener, responding to any message, by sending the `lax` message.
67
+
68
+ listener = hark(:foo) { "Foo" }
69
+
70
+ listener.bar
71
+ # => NoMethodError: undefined method `bar' for #<Hark::StrictListener:0x007fc91a03e568>
72
+
73
+ listener = listener.lax
74
+ listener.bar
75
+ # => []
76
+
77
+ To make a strict listener send the `strict` message.
78
+
79
+ ### Combining listeners
80
+
81
+ Here are some ways of combining listeners.
82
+
83
+ # redirect listener
84
+ listener = hark(created: method(:redirect_to_user))
85
+
86
+ Add a message
87
+
88
+ listener = listener.hark :created do |user|
89
+ WelomeMailer.send_email(user)
90
+ end
91
+
92
+ Combine with another listener
93
+
94
+ logger = listener.hark(created: ->(u) { logger.info "User #{u} created" } )
95
+ listener = listener.hark(logger)
96
+
97
+ Combine with any object that support the same protocol
98
+
99
+ logger = UserLogger.new # responds to :created
100
+ listener = listener.hark(logger)
101
+
102
+ Turn any object into a listener, adding new methods as we go
103
+
104
+ hark UserLogger.new do |on|
105
+ on.created {|user| Emailer.send_welcom_email(user) }
106
+ end
107
+
108
+ Now, when listener is sent #created, all create handlers are called.
109
+
110
+ ### Sugar: `Kernel#hearken`
111
+
112
+ Because of the precedence of the block operator, constructing ad-hoc listeners requires
113
+ you to insert some parens, which might be seen as unsightly, e.g:
114
+
115
+ seller.request_valuation(item, (hark do |on|
116
+ on.valuation_requested {|valuation| redirect_to valuation}
117
+ on.invalid_item {|item| redirect_to item, error: "Item not evaluable" }
118
+ end))
119
+
120
+ You may use Kernerl#hearken to create an ad-hoc listener using a passed block as follows
121
+
122
+ seller.hearken :request_valuation, item do |on|
123
+ on.valuation_requested {|valuation| redirect_to valuation}
124
+ on.invalid_item {|item| redirect_to item, error: "Item not evaluable" }
125
+ end
126
+
127
+ If you want to combine listeners with an ad-hoc block, you may pass a 0-arity block that is
128
+ yielded as the listener
129
+
130
+ seller.hearken :request_valuation, item do
131
+ hark valuation_notifier do |on|
132
+ on.valuation_requested {|valuation| redirect_to valuation}
133
+ on.invalid_item {|item| redirect_to item, error: "Item not evaluable" }
134
+ end
135
+ end
136
+
137
+ ### Return value
138
+
139
+ Using the return value of a listener is not encouraged. Hark is designed for a *tell, don't ask*
140
+ style of coding. That said the return value of a hark listener is an array of its handlers return values.
141
+
142
+ a = hark(:foo) { 'a' }
143
+ b = Object.new.tap {|o| o.singleton_class.send(:define_method, :foo) { 'b' } }
144
+ c = hark(foo: -> { 'c' }, bar: -> { 'c bar' })
145
+
146
+ a.foo # => ["a"]
147
+ hark(a,b).foo # => ["a", "b"]
148
+ hark(a,b,c).foo # => ["a", "b", "c"]
149
+
150
+ ### Immutable
151
+
152
+ Hark listeners are immutable and `#lax`, `#strict`, and `#hark` all return new listeners.
153
+
154
+ ## Rationale
155
+
156
+ When programming in the 'tell-dont-ask' or 'hexagonal' style, program flow is managed by passing listener, or
157
+ response, objects to service objects, which call back depending on what happened. This allows logic that is concerned with the caller's domain to remain isolated from the service object.
158
+
159
+ The idea behind **hark** is that there should be little ceremony involved in the listener/response mechanics, and
160
+ that simple listeners can easily be refactored into objects in their own right, without changing the protocols between
161
+ the calling and servcie objects.
162
+
163
+ To that end, service objects should not know anything other than the listener/response protocol, and shouldn't have to 'publish' their
164
+ results beyond a simple method call.
165
+
166
+ As a simple example, a user creation service object defines a response protocol as follows:
167
+
168
+ * created_user(user) _the user was succesfully created_
169
+ * invalid_user(user) _the user couldn't be created because it was invalid_
170
+
171
+ The UserCreator object's main method will have some code as follows:
172
+
173
+ if # some logic that means the user params were valid and we could persist the user
174
+ response.created_user(user)
175
+ else
176
+ response.invalid_user(user)
177
+ end
178
+
179
+ Let's say a controller is calling this, and you are using hark. In the beginning you would do something like this:
180
+
181
+ def create
182
+ user_creator.call(user_params, hark do |on|
183
+ on.created_user {|user| redirect_to user, notice: "Welome!" }
184
+ on.invalid_user {|user| @user = user; render "new" }
185
+ end)
186
+ end
187
+
188
+ This keeps the controller's handling of the user creation nicely separate from the saving of the user creator.
189
+
190
+ Then, a requirement comes in to log the creation of users. The first attempt might be this:
191
+
192
+ def create
193
+ user_creator.call(user_params, hark do |on|
194
+ on.created_user do |user|
195
+ redirect_to user, notice: "Welome!"
196
+ logger.info "User #{user} created"
197
+ end
198
+ on.invalid_user {|user| @user = user; render "new" }
199
+ end
200
+ end
201
+
202
+ Then a requirement comes in to email users on succesful creation, there's an UserEmailer that responds
203
+ to the same protocol. Also, the UX team want to log invalid users.
204
+
205
+ There's quite a lot going on now, we can tie it up as follows:
206
+
207
+ def create
208
+ response = hark(ui_response, UserEmailer.new, ux_team_response)
209
+ user_creator.call user_params, response
210
+ end
211
+
212
+ # UserEmailer responds to #created_user(user)
213
+
214
+ def ui_response
215
+ hark do |on|
216
+ on.created_user {|user| redirect_to user, notice: "Welome!" }
217
+ on.invalid_user {|user| @user = user; render "new" }
218
+ end
219
+ end
220
+
221
+ def ux_team_response
222
+ hark(:invalid_user) {|user| logger.info("User invalid: #{user}") }
223
+ end
224
+
225
+ If some of the response code gets hairy, we can easily swap out hark ad-hoc objects for 'proper' ones.
226
+ For example, the UI response might get a bit hairy, and so we make a new object.
227
+
228
+ def create
229
+ response = hark(UiResponse.new(self), UserEmailer.new, ux_team_response)
230
+ user_creator.call user_params, response
231
+ end
232
+
233
+ class UiResponse < SimpleDelegator
234
+ def created_user user
235
+ if request.format.json?
236
+ # ...
237
+ else
238
+ # ...
239
+ end
240
+ end
241
+
242
+ def invalid_user user
243
+ # ...
244
+ end
245
+ end
246
+
247
+ Note that throughout this process we didn't have to modify the UserCreator code, even when we transitioned
248
+ to/from hark for different repsonses/styles.
249
+
250
+ ### Testing your listeners
251
+
252
+ Don't pay any attention to hark when you're testing, hark is just a utility to create listeners, and so what
253
+ you should be testing is the protocol.
254
+
255
+ For example the service object tests will test functionality that pertains to the actual creation of the user,
256
+ and will test that the correct message is sent to the response in those circumstances. Whereas the controller tests
257
+ will mock out the service object, and test what happens when the service object sends the messages to the response as
258
+ dictated by the protocol.
259
+
260
+ describe UserCreator do
261
+ let(:service) { described_class.new }
262
+
263
+ describe "#call params, response" do
264
+ subject { service.call params, response }
265
+
266
+ let(:response) { double }
267
+
268
+ context "when the user succesfully saves"
269
+ let(:params) { {name: "created user", # and other successful user params }
270
+
271
+ it "sends #created_user to the response with the created user" do
272
+ response.should_receive(:created_user) do |user|
273
+ user.name.should == "created user"
274
+ end
275
+ subject
276
+ end
277
+ end
278
+
279
+ context "when the user succesfully saves"
280
+ let(:params) { {name: "invalid user", # and invalid user params }
281
+
282
+ it "sends #invalid_user to the response with the created user" do
283
+ response.should_receive(:invalid_user) do |user|
284
+ # test that the object passed is the invalid user
285
+ end
286
+ subject
287
+ end
288
+ end
289
+ end
290
+ end
291
+
292
+ describe NewUserController do
293
+ before { controller.stub(user_creator: user_creator) } # or some other sensible way of injecting a fake user_creator
294
+
295
+ let(:user_creator) { double "User creator" }
296
+ let(:user) { double "A user" }
297
+
298
+ context "when the user_creator is succesful" do
299
+ before do
300
+ user_creator.stub :call do |params, response|
301
+ response.created_user(user)
302
+ end
303
+ end
304
+
305
+ it "should redirect to the user"
306
+
307
+ it "should email the user"
308
+
309
+ it "should log the creation of the user"
310
+ end
311
+
312
+ context "when the user_creator says the params are invalid" do
313
+ before do
314
+ user_creator.stub :call do |params, response|
315
+ response.invalid_user(user)
316
+ end
317
+ end
318
+
319
+ it "should render new with the user"
320
+
321
+ it "should log something for the UX team"
322
+ end
323
+ end
324
+
325
+
326
+ ## Contributing
327
+
328
+ 1. Fork it
329
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
330
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
331
+ 4. Push to the branch (`git push origin my-new-feature`)
332
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/hark.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hark/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "heed"
8
+ spec.version = Hark::VERSION
9
+ spec.authors = ["Ian White"]
10
+ spec.email = ["ian.w.white@gmail.com"]
11
+ spec.description = %q{Create ad-hoc listener objects with impunity}
12
+ spec.summary = %q{Hark is a gem that enables writing code in a "hexagonal architecture" or "tell don't ask" style}
13
+ spec.homepage = "http://github.com/ianwhite/hark"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+
25
+ if RUBY_VERSION > "1.9"
26
+ spec.add_development_dependency "coveralls"
27
+ end
28
+ end
data/lib/hark.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "hark/version"
2
+ require "hark/ad_hoc"
3
+ require "hark/dispatcher"
4
+ require "hark/listener"
5
+ require "hark/core_ext"
6
+
7
+ module Hark
8
+ def self.from(*args, &block)
9
+ Listener.new(*args, &block)
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ module Hark
2
+ # AdHoc is a tiny class to facilitate creating an ad-hoc object that from either a hash or proc.
3
+ #
4
+ # Eg. from a hash:
5
+ #
6
+ # handler = AdHoc.new(success: (o)-> { o.great_success }, failure: (o)-> { o.failed } )
7
+ #
8
+ # Eg. from a 'response' style block:
9
+ #
10
+ # handler = AdHoc.new do |on|
11
+ # on.success {|o| o.great_success }
12
+ # on.failure {|o| o.failed }
13
+ # end
14
+ #
15
+ # Eg. adding methods after creation
16
+ #
17
+ # obj = AdHoc.new
18
+ # obj.add_method!(:foo) { "bar" }
19
+ #
20
+ # All blocks keep their original binding. This makes AdHoc suitable for creating
21
+ # ad-hoc responses from controller type objects.
22
+ #
23
+ class AdHoc
24
+ def self.new hash = {}, &proc
25
+ super().tap do |ad_hoc|
26
+ AddMethodsFromProc.new(proc, ad_hoc) if block_given?
27
+ hash.each {|method, body| ad_hoc.add_method!(method, &body) }
28
+ end
29
+ end
30
+
31
+ def add_method!(method, &body)
32
+ singleton_class = class << self; self; end
33
+ singleton_class.send(:define_method, method) {|*args, &block| body.call(*args, &block) }
34
+ end
35
+
36
+ class AddMethodsFromProc
37
+ def initialize proc, ad_hoc
38
+ @ad_hoc = ad_hoc
39
+ proc.call(self)
40
+ end
41
+
42
+ def method_missing method, *, &body
43
+ @ad_hoc.add_method!(method, &body)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,10 @@
1
+ module Kernel
2
+ def hark *args, &block
3
+ Hark.from *args, &block
4
+ end
5
+
6
+ def hearken method, *args, &block
7
+ listener = (block.arity == 1) ? hark(&block) : block.call
8
+ send method, *args + [listener]
9
+ end
10
+ end
@@ -0,0 +1,50 @@
1
+ module Hark
2
+ class Dispatcher
3
+ # from(:success) do
4
+ # "success"
5
+ # end
6
+ #
7
+ # from(success: ->{ "success" })
8
+ #
9
+ # from do |on|
10
+ # on.success { "success" }
11
+ # end
12
+ #
13
+ def self.from(*args, &block)
14
+ if block
15
+ args << (args.last.is_a?(Symbol) ? {args.pop => block} : block)
16
+ end
17
+
18
+ new args.map{|o| to_handler(o) }.flatten.freeze
19
+ end
20
+
21
+ def self.to_handler object
22
+ case object
23
+ when Listener then object.dispatcher.handlers
24
+ when Dispatcher then object.handlers
25
+ when Hash then AdHoc.new(object)
26
+ when Proc then AdHoc.new(&object)
27
+ else object
28
+ end
29
+ end
30
+
31
+ attr_reader :handlers
32
+
33
+ def initialize handlers
34
+ @handlers = handlers
35
+ freeze
36
+ end
37
+
38
+ def handles? method
39
+ handlers.any? {|handler| handler.respond_to?(method) }
40
+ end
41
+
42
+ def handle method, *args, &block
43
+ results = []
44
+ handlers.each do |handler|
45
+ results << handler.send(method, *args, &block) if handler.respond_to?(method)
46
+ end
47
+ results
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ module Hark
2
+ # A Listener holds a dispatcher, which it dispatches messages to
3
+ #
4
+ # A listener is by default a 'strict' listener, it will raise NoMethodError if
5
+ # it is sent a message it doesn't know how to handle.
6
+ #
7
+ # A listener can be turned into a 'lax' listener, by sending it the #lax message.
8
+ # A lax listener will silently swallow any unknown messages.
9
+ class Listener
10
+ def self.new *args, &block
11
+ self == Listener ? StrictListener.new(*args, &block) : super(*args, &block)
12
+ end
13
+
14
+ attr_reader :dispatcher
15
+
16
+ def initialize(*args, &block)
17
+ @dispatcher = Dispatcher.from(*args, &block)
18
+ freeze
19
+ end
20
+
21
+ def strict
22
+ StrictListener.new dispatcher
23
+ end
24
+
25
+ def lax
26
+ LaxListener.new dispatcher
27
+ end
28
+
29
+ def hark *args, &block
30
+ self.class.new dispatcher, *args, &block
31
+ end
32
+ end
33
+
34
+ class StrictListener < Listener
35
+ def respond_to?(method, *args)
36
+ super || dispatcher.handles?(method)
37
+ end
38
+
39
+ def method_missing *args, &block
40
+ results = dispatcher.handle(*args, &block)
41
+ if results.length > 0
42
+ results
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end
48
+
49
+ class LaxListener < Listener
50
+ def respond_to? *args
51
+ true
52
+ end
53
+
54
+ def method_missing *args, &block
55
+ dispatcher.handle(*args, &block)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Hark
2
+ VERSION = "0.0.7"
3
+ end
data/spec/hark_spec.rb ADDED
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+ require 'hark'
3
+
4
+ describe Hark do
5
+ let(:transcript) { [] }
6
+
7
+ class PlainListener < Struct.new(:transcript)
8
+ def success(value)
9
+ transcript.push [:succeeded, value]
10
+ end
11
+
12
+ def failure(value)
13
+ transcript.push [:failed, value]
14
+ end
15
+ end
16
+
17
+ shared_examples_for "a success/failure listener" do
18
+ describe "success" do
19
+ before { listener.success(42) }
20
+ it { transcript.should == [[:succeeded, 42]] }
21
+ end
22
+
23
+ describe "failure" do
24
+ before { listener.failure(54) }
25
+ it { transcript.should == [[:failed, 54]] }
26
+ end
27
+ end
28
+
29
+ shared_examples_for "a strict listener" do
30
+ it { strict_listener.should_not respond_to(:unknown) }
31
+ it { expect{ strict_listener.unknown }.to raise_error(NoMethodError) }
32
+ end
33
+
34
+ shared_examples_for "a lax listener" do
35
+ it { lax_listener.should respond_to(:unknown) }
36
+ it { lax_listener.unknown.should == [] }
37
+ end
38
+
39
+ shared_examples_for "a success/failure hark listener" do
40
+ it_should_behave_like "a success/failure listener"
41
+ it_should_behave_like "a strict listener" do
42
+ let(:strict_listener) { listener }
43
+ end
44
+
45
+ context "when made lax" do
46
+ let(:lax_listener) { listener.lax }
47
+ it_should_behave_like "a lax listener"
48
+
49
+ context "and made strict again" do
50
+ let(:strict_listener) { lax_listener.strict }
51
+ it_should_behave_like "a strict listener"
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "A plain (non hark) listener object" do
57
+ let(:listener) { PlainListener.new(transcript) }
58
+
59
+ it_should_behave_like "a success/failure listener"
60
+ it_should_behave_like "a strict listener" do
61
+ let(:strict_listener) { listener }
62
+ end
63
+ end
64
+
65
+ describe "hark with respond_to style block" do
66
+ let(:listener) do
67
+ hark do |on|
68
+ on.success {|v| transcript.push [:succeeded, v] }
69
+ on.failure {|v| transcript.push [:failed, v] }
70
+ end
71
+ end
72
+
73
+ it_should_behave_like "a success/failure hark listener"
74
+ end
75
+
76
+ describe "hark with callables" do
77
+ let(:listener) do
78
+ hark :success => lambda{|v| transcript.push [:succeeded, v] }, :failure => lambda{|v| transcript.push [:failed, v] }
79
+ end
80
+
81
+ it_should_behave_like "a success/failure hark listener"
82
+ end
83
+
84
+ describe "hark built up in steps" do
85
+ let(:listener) do
86
+ l = hark
87
+ l = l.hark(:success) {|v| transcript.push [:succeeded, v] }
88
+ l = l.hark(:failure) {|v| transcript.push [:failed, v] }
89
+ end
90
+
91
+ it_should_behave_like "a success/failure hark listener"
92
+ end
93
+
94
+ describe "#hark(object)" do
95
+ let(:listener) { hark PlainListener.new(transcript) }
96
+
97
+ it_should_behave_like "a success/failure hark listener"
98
+ end
99
+
100
+ describe "combine two listeners together" do
101
+ let(:logger) { hark(:signup_user) {|user| transcript << "User #{user} signed up" } }
102
+ let(:emailer) { hark(:signup_user) {|user| transcript << "Emailed #{user}" } }
103
+
104
+ shared_examples_for "combined listeners" do
105
+ before { listener.signup_user("Fred") }
106
+
107
+ it { transcript.should == ["User Fred signed up", "Emailed Fred"] }
108
+ end
109
+
110
+ it_behaves_like "combined listeners" do
111
+ let(:listener) { logger.hark(emailer) }
112
+ end
113
+
114
+ it_behaves_like "combined listeners" do
115
+ let(:listener) { hark(logger, emailer) }
116
+ end
117
+
118
+ it_behaves_like "combined listeners" do
119
+ let(:listener) do
120
+ hark logger do |on|
121
+ on.signup_user {|user| transcript << "Emailed #{user}" }
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "lax/strict is preserved on #hark" do
128
+ it { hark.lax.hark.should be_a Hark::LaxListener }
129
+ it { hark.strict.hark.should be_a Hark::StrictListener }
130
+ end
131
+
132
+ describe "when methods return falsy" do
133
+ let(:listener) { hark(:foo) { false } }
134
+
135
+ it { expect{ listener.foo }.to_not raise_error }
136
+ it { listener.foo.should == [false] }
137
+ end
138
+
139
+ describe "#hearken :method" do
140
+ let(:object) do
141
+ Object.new.tap do |obj|
142
+ class << obj
143
+ def foo arg1, arg2, listener
144
+ listener.foo(arg1)
145
+ listener.bar(arg2)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ context "with 1 arity block" do
152
+ it "sends :method with an ad-hoc listener created from the block" do
153
+ object.hearken :foo, "ONE", "TWO" do |on|
154
+ on.foo {|a| transcript << [:foo, a] }
155
+ on.bar {|a| transcript << [:bar, a] }
156
+ end
157
+ transcript.should == [[:foo, "ONE"], [:bar, "TWO"]]
158
+ end
159
+ end
160
+
161
+ context "with 0 arity block" do
162
+ it "sends :method with listener created by yielding to the block" do
163
+ foo = hark(:foo) {|a| transcript << [:foo, a] }
164
+
165
+ object.hearken :foo, "ONE", "TWO" do
166
+ hark(foo, :bar) {|a| transcript << [:bar, a] }
167
+ end
168
+ transcript.should == [[:foo, "ONE"], [:bar, "TWO"]]
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.dirname('../lib')
2
+
3
+ require 'rspec'
4
+
5
+ begin
6
+ require 'coveralls'
7
+ Coveralls.wear!
8
+ rescue LoadError
9
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Ian White
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Create ad-hoc listener objects with impunity
70
+ email:
71
+ - ian.w.white@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .coveralls.yml
77
+ - .gitignore
78
+ - .travis.yml
79
+ - Gemfile
80
+ - History.md
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - hark.gemspec
85
+ - lib/hark.rb
86
+ - lib/hark/ad_hoc.rb
87
+ - lib/hark/core_ext.rb
88
+ - lib/hark/dispatcher.rb
89
+ - lib/hark/listener.rb
90
+ - lib/hark/version.rb
91
+ - spec/hark_spec.rb
92
+ - spec/spec_helper.rb
93
+ homepage: http://github.com/ianwhite/hark
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.0.6
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Hark is a gem that enables writing code in a "hexagonal architecture" or
117
+ "tell don't ask" style
118
+ test_files:
119
+ - spec/hark_spec.rb
120
+ - spec/spec_helper.rb