use_case 0.7.0 → 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/Gemfile.lock +1 -1
- data/Readme.md +80 -11
- data/lib/use_case/outcome.rb +27 -1
- data/lib/use_case/version.rb +1 -1
- data/test/use_case/outcome_test.rb +67 -7
- data/test/use_case_test.rb +6 -6
- metadata +2 -2
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -46,10 +46,12 @@ class RepositoryController < ApplicationController
|
|
46
46
|
def create
|
47
47
|
outcome = CreateRepository.new(self, current_user).execute(params)
|
48
48
|
|
49
|
-
outcome.pre_condition_failed do |
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
outcome.pre_condition_failed do |f|
|
50
|
+
f.when(:user_required) { redirect_to(login_path) }
|
51
|
+
f.otherwise do
|
52
|
+
flash[:error] = "You're not allowed to do that"
|
53
|
+
redirect_to project_path
|
54
|
+
end
|
53
55
|
end
|
54
56
|
|
55
57
|
outcome.failure do |model|
|
@@ -109,7 +111,7 @@ end
|
|
109
111
|
# This is often implemented as a controller filter in many Rails apps.
|
110
112
|
# Unfortunately that means we have to duplicate the check when exposing the use
|
111
113
|
# case in other contexts (e.g. a stand-alone API app, console API etc).
|
112
|
-
class
|
114
|
+
class UserRequired
|
113
115
|
# The constructor is only used by us and can look and do whever we want
|
114
116
|
def initialize(user)
|
115
117
|
@user = user
|
@@ -205,12 +207,79 @@ solution for input sanitation and some level of type-safety. If you provide a
|
|
205
207
|
|
206
208
|
## Pre-conditions
|
207
209
|
|
208
|
-
A pre-condition is any object that responds to `satisfied?(params)` where
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
pre-condition instance that failed.
|
210
|
+
A pre-condition is any object that responds to `satisfied?(params)` where params
|
211
|
+
will either be a `Hash` or an instance of whatever you passed to `input_class`.
|
212
|
+
The method should return `true/false`. If it raises, the outcome of the use case
|
213
|
+
will call the `pre_condition_failed` block with the raised error. If it fails,
|
214
|
+
the `pre_condition_failed` block will be called with a failure object wrapping
|
215
|
+
the pre-condition instance that failed.
|
216
|
+
|
217
|
+
The wrapper failure object provides three methods of interest:
|
218
|
+
|
219
|
+
### `when`
|
220
|
+
|
221
|
+
The when method allows you to associate a block of code with a specific
|
222
|
+
pre-condition. The block is called with the pre-condition instance if that
|
223
|
+
pre-condition fails. Because the pre-condition class may not be explicitly
|
224
|
+
available in contexts where you want to use `when`, a symbolic representation is
|
225
|
+
used.
|
226
|
+
|
227
|
+
If you have the following two pre-conditions:
|
228
|
+
|
229
|
+
* `UserRequired`
|
230
|
+
* `ProjectAdminRequired`
|
231
|
+
|
232
|
+
Then you can use `when(:user_required) { |condition ... }` and
|
233
|
+
`when(:project_admin_required) { |condition ... }`. If you want control over how
|
234
|
+
a class name is symbolized, make the class implement `symbol`, i.e.:
|
235
|
+
|
236
|
+
```js
|
237
|
+
class UserRequired
|
238
|
+
def self.symbol; :user_plz; end
|
239
|
+
def initialize(user); @user = user; end
|
240
|
+
def satisfied?(params); !@user.nil?; end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Then:
|
244
|
+
|
245
|
+
outcome = use_case.execute(params)
|
246
|
+
|
247
|
+
outcome.pre_condition_failed do |f|
|
248
|
+
f.when(:user_plz) { |c| puts "Needs moar user" }
|
249
|
+
# ...
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
### `otherwise`
|
254
|
+
|
255
|
+
`otherwise` is a catch-all that is called if no calls to `when` mention the
|
256
|
+
offending pre-condition:
|
257
|
+
|
258
|
+
|
259
|
+
```js
|
260
|
+
class CreateProject
|
261
|
+
include UseCase
|
262
|
+
|
263
|
+
def initialize(user)
|
264
|
+
add_pre_condition(UserRequired.new(user))
|
265
|
+
add_pre_condition(AdminRequired.new(user))
|
266
|
+
step(CreateProjectCommand.new(user))
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Then:
|
271
|
+
|
272
|
+
outcome = CreateProject.new(current_user).execute(params)
|
273
|
+
|
274
|
+
outcome.pre_condition_failed do |f|
|
275
|
+
f.when(:user_required) { |c| puts "Needs moar user" }
|
276
|
+
f.otherwise { |c| puts "#{c.name} pre-condition failed" }
|
277
|
+
end
|
278
|
+
```
|
279
|
+
### `pre_condition`
|
280
|
+
|
281
|
+
If you want to roll your own flow control, simply get the offending
|
282
|
+
pre-condition from this method.
|
214
283
|
|
215
284
|
## Validations
|
216
285
|
|
data/lib/use_case/outcome.rb
CHANGED
@@ -63,12 +63,13 @@ module UseCase
|
|
63
63
|
def initialize(use_case = nil, pre_condition = nil)
|
64
64
|
super(use_case)
|
65
65
|
@pre_condition = pre_condition
|
66
|
+
@failure = PreConditionFailure.new(@pre_condition)
|
66
67
|
end
|
67
68
|
|
68
69
|
def pre_condition_failed?; true; end
|
69
70
|
|
70
71
|
def pre_condition_failed
|
71
|
-
yield @
|
72
|
+
yield @failure if block_given?
|
72
73
|
@pre_condition
|
73
74
|
end
|
74
75
|
|
@@ -77,6 +78,31 @@ module UseCase
|
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
81
|
+
class PreConditionFailure
|
82
|
+
attr_reader :pre_condition
|
83
|
+
def initialize(pre_condition); @pre_condition = pre_condition; end
|
84
|
+
|
85
|
+
def when(symbol, &block)
|
86
|
+
raise Exception.new("Cannot call when after otherwise") if @otherwise
|
87
|
+
if symbol == class_symbol
|
88
|
+
@called = true
|
89
|
+
yield(@pre_condition)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def otherwise(&block)
|
94
|
+
@otherwise = true
|
95
|
+
yield(@pre_condition) if !@called
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def class_symbol
|
100
|
+
klass = @pre_condition.class
|
101
|
+
return klass.symbol if klass.respond_to?(:symbol)
|
102
|
+
klass.name.gsub(/([^A-Z])([A-Z])/, '\1_\2').gsub(/[:_]+/, "_").downcase.to_sym
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
80
106
|
class FailedOutcome < Outcome
|
81
107
|
def initialize(use_case = nil, errors = nil)
|
82
108
|
super(use_case)
|
data/lib/use_case/version.rb
CHANGED
@@ -25,6 +25,10 @@
|
|
25
25
|
require "test_helper"
|
26
26
|
require "use_case/outcome"
|
27
27
|
|
28
|
+
class MyPreCondition
|
29
|
+
def self.symbol; :something; end
|
30
|
+
end
|
31
|
+
|
28
32
|
describe UseCase::Outcome do
|
29
33
|
it "exposes use case" do
|
30
34
|
use_case = 42
|
@@ -93,19 +97,75 @@ describe UseCase::Outcome do
|
|
93
97
|
refute outcome.success?
|
94
98
|
end
|
95
99
|
|
96
|
-
it "
|
100
|
+
it "returns failed pre-condition" do
|
97
101
|
pre_condition = 42
|
98
|
-
yielded_pc = nil
|
99
102
|
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
100
|
-
returned_pc = outcome.pre_condition_failed
|
103
|
+
returned_pc = outcome.pre_condition_failed
|
101
104
|
|
102
|
-
assert_equal pre_condition, yielded_pc
|
103
105
|
assert_equal pre_condition, returned_pc
|
104
106
|
end
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
108
|
+
describe "yielded wrapper" do
|
109
|
+
it "has flow control API" do
|
110
|
+
yielded = false
|
111
|
+
pre_condition = Array.new
|
112
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
113
|
+
|
114
|
+
returned_pc = outcome.pre_condition_failed do |f|
|
115
|
+
f.when(:array) { |pc| yielded = pc }
|
116
|
+
end
|
117
|
+
|
118
|
+
assert_equal yielded, pre_condition
|
119
|
+
end
|
120
|
+
|
121
|
+
it "does not call non-matching block" do
|
122
|
+
yielded = nil
|
123
|
+
pre_condition = Array.new
|
124
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
125
|
+
|
126
|
+
returned_pc = outcome.pre_condition_failed do |f|
|
127
|
+
f.when(:something) { |pc| yielded = pc }
|
128
|
+
end
|
129
|
+
|
130
|
+
assert_nil yielded
|
131
|
+
end
|
132
|
+
|
133
|
+
it "matches by class symbol" do
|
134
|
+
yielded = false
|
135
|
+
pre_condition = MyPreCondition.new
|
136
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
137
|
+
|
138
|
+
returned_pc = outcome.pre_condition_failed do |f|
|
139
|
+
f.when(:something) { |pc| yielded = pc }
|
140
|
+
end
|
141
|
+
|
142
|
+
assert_equal yielded, pre_condition
|
143
|
+
end
|
144
|
+
|
145
|
+
it "yields to otherwise if no match" do
|
146
|
+
yielded = false
|
147
|
+
pre_condition = MyPreCondition.new
|
148
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
149
|
+
|
150
|
+
returned_pc = outcome.pre_condition_failed do |f|
|
151
|
+
f.when(:nothing) { |pc| yielded = 42 }
|
152
|
+
f.otherwise { |pc| yielded = pc }
|
153
|
+
end
|
154
|
+
|
155
|
+
assert_equal yielded, pre_condition
|
156
|
+
end
|
157
|
+
|
158
|
+
it "raises if calling when after otherwise" do
|
159
|
+
pre_condition = MyPreCondition.new
|
160
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
161
|
+
|
162
|
+
assert_raises(Exception) do
|
163
|
+
returned_pc = outcome.pre_condition_failed do |f|
|
164
|
+
f.otherwise { |pc| yielded = pc }
|
165
|
+
f.when(:nothing) { |pc| yielded = 42 }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
109
169
|
end
|
110
170
|
end
|
111
171
|
|
data/test/use_case_test.rb
CHANGED
@@ -35,8 +35,8 @@ describe UseCase do
|
|
35
35
|
it "fails first pre-condition; no user logged in" do
|
36
36
|
outcome = CreateRepository.new(nil).execute({})
|
37
37
|
|
38
|
-
outcome.pre_condition_failed do |
|
39
|
-
assert_equal UserLoggedInPrecondition,
|
38
|
+
outcome.pre_condition_failed do |f|
|
39
|
+
assert_equal UserLoggedInPrecondition, f.pre_condition.class
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -44,8 +44,8 @@ describe UseCase do
|
|
44
44
|
@logged_in_user.can_admin = false
|
45
45
|
outcome = CreateRepository.new(@logged_in_user).execute({})
|
46
46
|
|
47
|
-
outcome.pre_condition_failed do |
|
48
|
-
assert_equal ProjectAdminPrecondition,
|
47
|
+
outcome.pre_condition_failed do |f|
|
48
|
+
assert_equal ProjectAdminPrecondition, f.pre_condition.class
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -53,8 +53,8 @@ describe UseCase do
|
|
53
53
|
def @logged_in_user.id; raise "Oops!"; end
|
54
54
|
outcome = CreateRepository.new(@logged_in_user).execute({})
|
55
55
|
|
56
|
-
outcome.pre_condition_failed do |
|
57
|
-
assert_equal RuntimeError,
|
56
|
+
outcome.pre_condition_failed do |f|
|
57
|
+
assert_equal RuntimeError, f.pre_condition.class
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: use_case
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
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: 2013-04-
|
12
|
+
date: 2013-04-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|