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 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