the_help 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16fd1e45c719b154afcdca402e8c5a049fd521e9a11c6a1c54f7abc9fc3e13fc
4
- data.tar.gz: 9b9cac4ed3fdfcb4ad9f5359aa76f9a3298592017a9a7733be5d64d1712e6a0b
3
+ metadata.gz: 6fdee5c4c5d8c3c2d67bbb8abf7fb42379961924c59faa7f36da7575a085d325
4
+ data.tar.gz: 96464f2494174451d4581f984050fc258901b010a27556a3dc19a555ba2d99ab
5
5
  SHA512:
6
- metadata.gz: ea9b7907840050fa2017b2bc4d055f948524d52141c71d70dcbbd04800eb0a911ff2744f4278a777a6a4509f5273cb68f8c9685079b14cc027bdaa6ed5c687c5
7
- data.tar.gz: ca5962b02c675ae30a2de84c1c3b782ed56f1a672f0be39256af50273eca28fa6ac8e488e336ab705bf42ccc9501a70dec73ceaca0b774f52d4713d32181ba7d
6
+ metadata.gz: 800152351fd6ca49eab793748e48030cdb73f14c53f8ad4cd419be8a28e1fc20291a40948ee764744b924b53b7a96a0f13c109024cd1f5ebf0cb2bc09326c1bc
7
+ data.tar.gz: 3c11bcb7dbcf769a5045f3a42bcf52cec4b5a06ecee8015eda56554ae67d7f4604bb1df15914f09bf4c4991eb74201bc47120c675659f85e0c4c05b6cb576b2b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- the_help (1.4.2)
4
+ the_help (1.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # TheHelp
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/the_help`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ TheHelp is a framework for developing service objects in a way that encourages
4
+ adherence to the [Single Responsibility Principle][SRP] and [Tell Don't
5
+ Ask][TDA]
6
6
 
7
7
  ## Installation
8
8
 
@@ -22,7 +22,122 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- TODO: Write usage instructions here
25
+ Create subclasses of [`TheHelp::Service`][lib/the_help/service.rb] and call
26
+ them.
27
+
28
+ Make it easier to call a service by including
29
+ [`TheHelp::ServiceCaller`][lib/the_help/service_caller.rb].
30
+
31
+ ### Running Callbacks
32
+
33
+ This library encourages you to pass in callbacks to a service rather than
34
+ relying on a return value from the service call. For example:
35
+
36
+ ```ruby
37
+ class Foo < TheHelp::Service
38
+ authorization_policy allow_all: true
39
+
40
+ main do
41
+ call_service(GetSomeWidgets,
42
+ customer_id: 12345,
43
+ each_widget: method(:process_widget),
44
+ invalid_customer: method(:no_customer),
45
+ no_widgets_found: method(:no_widgets))
46
+ do_something_else
47
+ end
48
+
49
+ private
50
+
51
+ def process_widget(widget)
52
+ # do something with it
53
+ end
54
+
55
+ def invalid_customer
56
+ # handle this case
57
+ stop!
58
+ end
59
+
60
+ def no_widgets
61
+ # handle this case
62
+ end
63
+
64
+ def do_something_else
65
+ # ...
66
+ end
67
+ end
68
+ ```
69
+
70
+ When writing a service that accepts callbacks like this, do not simply run
71
+ `#call` on the callback that was passed in. Instead you must use the
72
+ `#run_callback` method. This ensures that, if the callback method you pass in
73
+ tries to halt the execution of the service, it will behave as expected.
74
+
75
+ In the above service, it is clear that the intention is to stop executing the
76
+ `Foo` service in the case where `GetSomeWidgets` reports back that the customer
77
+ was invalid. However, if `GetSomeWidgets` is implemented as:
78
+
79
+ ```ruby
80
+ class GetSomeWidgets < TheHelp::Service
81
+ input :customer_id
82
+ input :each_widget
83
+ input :invalid_customer
84
+ input :no_widgets_found
85
+
86
+ authorization_policy allow_all: true
87
+
88
+ main do
89
+ set_some_stuff_up
90
+ if customer_invalid?
91
+ invalid_customer.call
92
+ no_widgets_found.call
93
+ do_some_important_cleanup_for_invalid_customers
94
+ else
95
+ #...
96
+ end
97
+ end
98
+
99
+ #...
100
+ end
101
+ ```
102
+
103
+ then the problem is that the call to `#stop!` in the `Foo#invalid_customer`
104
+ callback will not just stop the `Foo` service, it will also stop the
105
+ `GetSomeWidgets` service at the point where the callback is executed (because it
106
+ uses `throw` behind the scenes.) This would cause the
107
+ `do_some_important_cleanup_for_invalid_customers` method to never be called.
108
+
109
+ You can protect yourself from this by implementing `GetSomeWidgets` like this,
110
+ instead:
111
+
112
+ ```ruby
113
+ class GetSomeWidgets < TheHelp::Service
114
+ input :customer_id
115
+ input :each_widget
116
+ input :invalid_customer
117
+ input :no_widgets_found
118
+
119
+ authorization_policy allow_all: true
120
+
121
+ main do
122
+ set_some_stuff_up
123
+ if customer_invalid?
124
+ run_callback(invalid_customer)
125
+ run_callback(no_widgets_found)
126
+ do_some_important_cleanup_for_invalid_customers
127
+ else
128
+ #...
129
+ end
130
+ end
131
+
132
+ #...
133
+ end
134
+ ```
135
+
136
+ This will ensure that callbacks only stop the service that provides them, not
137
+ the service that calls them. (If you really do need to allow the calling service
138
+ to stop the execution of the inner service, you could raise an exception or
139
+ throw a symbol other than `:stop`; but do so with caution, since it may have
140
+ unintended consequences further down the stack.)
26
141
 
27
142
  ## Development
28
143
 
@@ -41,3 +156,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
41
156
  ## Code of Conduct
42
157
 
43
158
  Everyone interacting in the TheHelp project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jwilger/the_help/blob/master/CODE_OF_CONDUCT.md).
159
+
160
+ [SRP]: https://en.wikipedia.org/wiki/Single_responsibility_principle
161
+ [TDA]: https://martinfowler.com/bliki/TellDontAsk.html
@@ -29,8 +29,8 @@ module TheHelp
29
29
  # end
30
30
  # end
31
31
  #
32
- # callback(:message_sent) do
33
- # # do something really important, I'm sure
32
+ # callback(:message_sent) do |message|
33
+ # # do something really important with `message`, I'm sure
34
34
  # end
35
35
  # end
36
36
  #
@@ -49,11 +49,12 @@ module TheHelp
49
49
  #
50
50
  # class SendWelcomeMessage < TheHelp::Service
51
51
  # input :user
52
- # input :success, default: ->() { }
52
+ # input :success, default: ->(message) { }
53
53
  #
54
54
  # main do
55
- # # whatever
56
- # success.call
55
+ # message = 'Hello, world!'
56
+ # # do something with message...
57
+ # run_callback(success, message)
57
58
  # end
58
59
  # end
59
60
  #
@@ -96,6 +97,7 @@ module TheHelp
96
97
  # service_result
97
98
  # #=> :the_service_result
98
99
  #
100
+ # @note See README section "Running Callbacks"
99
101
  class Service
100
102
  include ProvidesCallbacks
101
103
  include ServiceCaller
@@ -189,6 +191,7 @@ module TheHelp
189
191
  self.logger = logger
190
192
  self.not_authorized = not_authorized
191
193
  self.inputs = inputs
194
+ self.stop_caller = false
192
195
  end
193
196
 
194
197
  def call
@@ -199,6 +202,7 @@ module TheHelp
199
202
  main
200
203
  self.block_result = yield result if block_given?
201
204
  end
205
+ throw :stop if stop_caller
202
206
  return block_result if block_given?
203
207
  return result if result_set?
204
208
  self
@@ -206,7 +210,8 @@ module TheHelp
206
210
 
207
211
  private
208
212
 
209
- attr_accessor :context, :logger, :not_authorized, :block_result
213
+ attr_accessor :context, :logger, :not_authorized, :block_result,
214
+ :stop_caller
210
215
  attr_writer :result
211
216
  attr_reader :inputs
212
217
 
@@ -243,8 +248,8 @@ module TheHelp
243
248
  return if authorized?
244
249
  logger.warn("Unauthorized attempt to access #{self.class.name} " \
245
250
  "as #{context.inspect}")
246
- not_authorized.call(service: self.class, context: context)
247
- throw :stop
251
+ run_callback(not_authorized, service: self.class, context: context)
252
+ stop!
248
253
  end
249
254
 
250
255
  def stop!
@@ -259,5 +264,14 @@ module TheHelp
259
264
  def result_set?
260
265
  defined?(@result)
261
266
  end
267
+
268
+ def run_callback(callback, *args)
269
+ continue = false
270
+ continue = catch(:stop) do
271
+ callback.call(*args)
272
+ true
273
+ end
274
+ self.stop_caller ||= !continue
275
+ end
262
276
  end
263
277
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TheHelp
4
- VERSION = '1.4.2'
4
+ VERSION = '1.5.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: the_help
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Wilger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-31 00:00:00.000000000 Z
11
+ date: 2018-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake