the_help 1.4.2 → 1.5.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +122 -4
- data/lib/the_help/service.rb +22 -8
- data/lib/the_help/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fdee5c4c5d8c3c2d67bbb8abf7fb42379961924c59faa7f36da7575a085d325
|
4
|
+
data.tar.gz: 96464f2494174451d4581f984050fc258901b010a27556a3dc19a555ba2d99ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 800152351fd6ca49eab793748e48030cdb73f14c53f8ad4cd419be8a28e1fc20291a40948ee764744b924b53b7a96a0f13c109024cd1f5ebf0cb2bc09326c1bc
|
7
|
+
data.tar.gz: 3c11bcb7dbcf769a5045f3a42bcf52cec4b5a06ecee8015eda56554ae67d7f4604bb1df15914f09bf4c4991eb74201bc47120c675659f85e0c4c05b6cb576b2b
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# TheHelp
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
data/lib/the_help/service.rb
CHANGED
@@ -29,8 +29,8 @@ module TheHelp
|
|
29
29
|
# end
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# callback(:message_sent) do
|
33
|
-
# # do something really important
|
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
|
-
#
|
56
|
-
#
|
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
|
247
|
-
|
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
|
data/lib/the_help/version.rb
CHANGED
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
|
+
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-
|
11
|
+
date: 2018-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|