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 CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- use_case (0.6.0)
15
+ use_case (0.8.0)
16
16
 
17
17
  GEM
18
18
  remote: http://rubygems.org/
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 |condition|
50
- redirect_to(login_path) and return if condition.is_a?(UserLoggedInPrecondition)
51
- flash[:error] = "You're not allowed to do that"
52
- redirect_to project_path
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 UserLoggedInPrecondition
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
- params will either be a `Hash` or an instance of whatever you passed to
210
- `input_class`. The method should return `true/false`. If it raises, the outcome
211
- of the use case will call the `pre_condition_failed` block with the raised
212
- error. If it fails, the `pre_condition_failed` block will be called with the
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
 
@@ -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 @pre_condition if block_given?
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)
@@ -24,5 +24,5 @@
24
24
  #++
25
25
 
26
26
  module UseCase
27
- VERSION = "0.7.0"
27
+ VERSION = "0.8.0"
28
28
  end
@@ -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 "yields and returns failed pre-condition" do
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 { |pc| yielded_pc = pc }
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
- it "gets pre_condition without block" do
107
- outcome = UseCase::PreConditionFailed.new(nil, 42)
108
- assert_equal 42, outcome.pre_condition_failed
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
 
@@ -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 |pc|
39
- assert_equal UserLoggedInPrecondition, pc.class
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 |pc|
48
- assert_equal ProjectAdminPrecondition, pc.class
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 |pc|
57
- assert_equal RuntimeError, pc.class
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.7.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-05 00:00:00.000000000 Z
12
+ date: 2013-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest