use_case 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|