use_case 0.1.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 +9 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/Rakefile +8 -0
- data/Readme.md +298 -0
- data/lib/use_case.rb +82 -0
- data/lib/use_case/outcome.rb +92 -0
- data/lib/use_case/validator.rb +52 -0
- data/lib/use_case/version.rb +28 -0
- data/test.rb +96 -0
- data/test/sample_use_case.rb +92 -0
- data/test/test_helper.rb +28 -0
- data/test/use_case/outcome_test.rb +144 -0
- data/test/use_case/validator_test.rb +51 -0
- data/test/use_case_test.rb +91 -0
- data/use_case.gemspec +19 -0
- metadata +97 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/solnic/virtus.git
|
3
|
+
revision: 97a161f19d20a961094112464075c284d68d35e4
|
4
|
+
specs:
|
5
|
+
virtus (1.0.0.beta1)
|
6
|
+
abstract_type (~> 0.0.5)
|
7
|
+
adamantium (~> 0.0.7)
|
8
|
+
backports (~> 3.0, >= 3.1.0)
|
9
|
+
coercible (~> 0.2)
|
10
|
+
descendants_tracker (~> 0.0.1)
|
11
|
+
|
12
|
+
PATH
|
13
|
+
remote: .
|
14
|
+
specs:
|
15
|
+
use_case (0.1.0)
|
16
|
+
|
17
|
+
GEM
|
18
|
+
remote: http://rubygems.org/
|
19
|
+
specs:
|
20
|
+
abstract_type (0.0.5)
|
21
|
+
backports (~> 3.0, >= 3.0.3)
|
22
|
+
activemodel (3.2.12)
|
23
|
+
activesupport (= 3.2.12)
|
24
|
+
builder (~> 3.0.0)
|
25
|
+
activesupport (3.2.12)
|
26
|
+
i18n (~> 0.6)
|
27
|
+
multi_json (~> 1.0)
|
28
|
+
adamantium (0.0.7)
|
29
|
+
backports (~> 3.0, >= 3.0.3)
|
30
|
+
ice_nine (~> 0.7.0)
|
31
|
+
backports (3.1.1)
|
32
|
+
builder (3.0.4)
|
33
|
+
coercible (0.2.0)
|
34
|
+
backports (~> 3.0, >= 3.1.0)
|
35
|
+
descendants_tracker (~> 0.0.1)
|
36
|
+
descendants_tracker (0.0.1)
|
37
|
+
i18n (0.6.4)
|
38
|
+
ice_nine (0.7.0)
|
39
|
+
minitest (4.7.0)
|
40
|
+
multi_json (1.6.1)
|
41
|
+
rake (10.0.3)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
activemodel
|
48
|
+
minitest (~> 4)
|
49
|
+
rake
|
50
|
+
use_case!
|
51
|
+
virtus!
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Gitorious AS
|
2
|
+
|
3
|
+
Christian Johansen (christian@gitorious.com)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# Use Case
|
2
|
+
|
3
|
+
Compose non-trivial business logic into use cases, that combine:
|
4
|
+
|
5
|
+
* Input parameter abstractions; type safety and coercion, and white-listing of
|
6
|
+
supported input for any given operation
|
7
|
+
* Pre-conditions: System-level conditions that must be met, e.g. "a user must be
|
8
|
+
logged in" etc.
|
9
|
+
* Input parameter validation: ActiveRecord-like validations as composable
|
10
|
+
objects. Combine specific sets of validation rules for the same input in
|
11
|
+
different contexts etc.
|
12
|
+
* Commands: Avoid defensive coding by performing the core actions in commands
|
13
|
+
that receive type-converted input, and are only executed when pre-conditions
|
14
|
+
are met and input validated.
|
15
|
+
|
16
|
+
## Example
|
17
|
+
|
18
|
+
At its very simplest, a use case wraps a command. A command is any Ruby object
|
19
|
+
with an `execute` method that receives one argument (input parameters). Because
|
20
|
+
UseCase is intended as a mechanism for implementing non-trivial use cases, the
|
21
|
+
following contrived example will seem particularly useless.
|
22
|
+
|
23
|
+
```rb
|
24
|
+
require "use_case"
|
25
|
+
|
26
|
+
class HelloWorldCommand
|
27
|
+
def execute(params)
|
28
|
+
puts "Hello, #{params[:place]}!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PrintHelloWorld
|
33
|
+
include UseCase
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
command(HelloWorldCommand.new)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Usage
|
41
|
+
PrintHelloWorld.new.execute(:place => "World")
|
42
|
+
```
|
43
|
+
|
44
|
+
Not that you are free to design your constructors any way you want.
|
45
|
+
|
46
|
+
## Useful example
|
47
|
+
|
48
|
+
A more useful example will use every aspect of a `UseCase`. Pre-conditions are
|
49
|
+
conditions not directly related to input parameters, and whose failure signifies
|
50
|
+
other forms of errors than simple validation errors. If you have a Rails
|
51
|
+
application that uses controller filters, then those are very likely good
|
52
|
+
candidates for pre-conditions.
|
53
|
+
|
54
|
+
The following example is a simplified use case from
|
55
|
+
[Gitorious](http://gitorious.org) where we want to create a new repository. To
|
56
|
+
do this, we need a user that can admin the project under which we want the new
|
57
|
+
repository to live.
|
58
|
+
|
59
|
+
This example illustrates how to solve common design challenges in Rails
|
60
|
+
applications, but that does not mean that `UseCase` is only useful to Rails
|
61
|
+
applications.
|
62
|
+
|
63
|
+
```rb
|
64
|
+
require "use_case"
|
65
|
+
require "virtus"
|
66
|
+
|
67
|
+
# Input parameters can be sanitized and pre-processed anyway you like. One nice
|
68
|
+
# way to go about it is to use Datamapper 2's Virtus gem to define a parameter
|
69
|
+
# set.
|
70
|
+
#
|
71
|
+
# This class uses Project.find to look up a project by id if project_id is
|
72
|
+
# provided and project is not. This is the only class that has
|
73
|
+
class NewRepositoryInput
|
74
|
+
include Virtus
|
75
|
+
attribute :name, String
|
76
|
+
attribute :description, String
|
77
|
+
attribute :project, Project
|
78
|
+
attribute :project_id, Integer
|
79
|
+
|
80
|
+
def project
|
81
|
+
@project ||= Project.find(@project_id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Validate new repositories. Extremely simplified example.
|
86
|
+
NewRepositoryValidator = UseCase::Validator.define do
|
87
|
+
validates_presence_of :name, :project
|
88
|
+
end
|
89
|
+
|
90
|
+
# This is often implemented as a controller filter in many Rails apps.
|
91
|
+
# Unfortunately that means we have to duplicate the check when exposing the use
|
92
|
+
# case in other contexts (e.g. a stand-alone API app, console API etc).
|
93
|
+
class UserLoggedInPrecondition
|
94
|
+
# The constructor is only used by us and can look and do whever we want
|
95
|
+
def initialize(user)
|
96
|
+
@user = user
|
97
|
+
end
|
98
|
+
|
99
|
+
# A pre-condition must define this method
|
100
|
+
# Params is an instance of NewRepositoryInput
|
101
|
+
def satiesfied?(params)
|
102
|
+
!@user.nil?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Another pre-condition that uses app-wide state
|
107
|
+
class ProjectAdminPrecondition
|
108
|
+
def initialize(auth, user)
|
109
|
+
@auth = auth
|
110
|
+
@user = user
|
111
|
+
end
|
112
|
+
|
113
|
+
def satiesfied?(params)
|
114
|
+
@auth.can_admin?(@user, params.project)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# The business logic. Here we can safely assume that all pre-conditions are
|
119
|
+
# satiesfied, and that input is valid and has the correct type.
|
120
|
+
class CreateRepositoryCommand
|
121
|
+
def initialize(user)
|
122
|
+
@user = user
|
123
|
+
end
|
124
|
+
|
125
|
+
# Params is an instance of NewRepositoryInput
|
126
|
+
def execute(params)
|
127
|
+
params.project.repositories.create(:name => params.name, :user => @user)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# The UseCase - this is just wiring together the various classes
|
132
|
+
class CreateRepository
|
133
|
+
include UseCase
|
134
|
+
|
135
|
+
# There's no contract to satiesfy with the constructor - design it to receive
|
136
|
+
# any dependencies you need.
|
137
|
+
def initialize(auth, user)
|
138
|
+
input_class(NewRepositoryInput)
|
139
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
140
|
+
pre_condition(ProjectAdminPrecondition.new(auth, user))
|
141
|
+
# Multiple validators can be added if needed
|
142
|
+
validator(NewRepositoryValidator)
|
143
|
+
command(CreateRepositoryCommand.new(user))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Finally, the actual usage. This example is a Rails controller
|
148
|
+
class RepositoryController < ApplicationController
|
149
|
+
include Gitorious::Authorization # Adds stuff like can_admin?(actor, thing)
|
150
|
+
|
151
|
+
# ...
|
152
|
+
|
153
|
+
def create
|
154
|
+
outcome = CreateRepository.new(self, current_user).execute(params)
|
155
|
+
|
156
|
+
outcome.pre_condition_failed do |condition|
|
157
|
+
redirect_to(login_path) and return if condition.is_a?(UserLoggedInPrecondition)
|
158
|
+
flash[:error] = "You're not allowed to do that"
|
159
|
+
redirect_to project_path
|
160
|
+
end
|
161
|
+
|
162
|
+
outcome.failure do |model|
|
163
|
+
# Render form with validation errors
|
164
|
+
render :new, :locals => { :repository => model }
|
165
|
+
end
|
166
|
+
|
167
|
+
outcome.success do |repository|
|
168
|
+
redirect_to(repository_path(repository))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
## Input sanitation
|
175
|
+
|
176
|
+
In your `UseCase` instance (typically in the constructor), you can call the
|
177
|
+
`input_class` method to specify which class is used to santize inputs. If you do
|
178
|
+
not use this, inputs are forwarded to pre-conditions and commands untouched
|
179
|
+
(i.e. as a `Hash`).
|
180
|
+
|
181
|
+
Datamapper 2's [Virtus](https://github.com/solnic/virtus) is a very promising
|
182
|
+
solution for input sanitation and some level of type-safety. If you provide a
|
183
|
+
`Virtus` backed class as `input_class` you will get an instance of that class as
|
184
|
+
`params` in pre-conditions and commands.
|
185
|
+
|
186
|
+
## Validations
|
187
|
+
|
188
|
+
The validator uses `ActiveModel::Validations`, so any Rails validation can go in
|
189
|
+
here. The main difference is that the validator is created as a stand-alone
|
190
|
+
object that can be used with any model instance. This design allows you to
|
191
|
+
define multiple context-sensitive validations for a single object.
|
192
|
+
|
193
|
+
You can of course provide your own validation if you want - any object that
|
194
|
+
defines `call(object)` and returns something that responds to `valid?` is good.
|
195
|
+
I am following the Datamapper project closely in this area.
|
196
|
+
|
197
|
+
Because `UseCase::Validation` is not a required part of `UseCase`, and people
|
198
|
+
may want to control their own dependencies, `activemodel` is _not_ a hard
|
199
|
+
dependency. To use this feature, `gem install activemodel`.
|
200
|
+
|
201
|
+
## Pre-conditions
|
202
|
+
|
203
|
+
A pre-condition is any object that responds to `satiesfied?(params)` where
|
204
|
+
params will either be a `Hash` or an instance of whatever you passed to
|
205
|
+
`input_class`. The method should return `true/false`. If it raises, the outcome
|
206
|
+
of the use case will call the `pre_condition_failed` block with the raised
|
207
|
+
error. If it fails, the `pre_condition_failed` block will be called with the
|
208
|
+
pre-condition instance that failed.
|
209
|
+
|
210
|
+
## Commands
|
211
|
+
|
212
|
+
A command is any Ruby object that defines an `execute(params)` method. Its
|
213
|
+
return value will be passed to the outcome's `success` block. Any errors raised
|
214
|
+
by this method is not rescued, so be sure to wrap `use_case.execute(params)` in
|
215
|
+
a rescue block if you're worried that it raises.
|
216
|
+
|
217
|
+
## Use cases
|
218
|
+
|
219
|
+
A use case simply glues together all the components. Define a class, include
|
220
|
+
`UseCase`, and configure the instance in the constructor. The constructor can
|
221
|
+
take any arguments you like, making this solution suitable for DI (dependency
|
222
|
+
injection) style designs.
|
223
|
+
|
224
|
+
The use case can optionally call `input_class` once, `pre_condition` multiple
|
225
|
+
times, and `validator` multiple times. It *must* call `command` once with the
|
226
|
+
command object.
|
227
|
+
|
228
|
+
## Outcomes
|
229
|
+
|
230
|
+
`UseCase#execute` returns an `Outcome`. You can use the outcome in primarily two
|
231
|
+
ways. The primary approach is one that takes blocks for the three situations:
|
232
|
+
`success(&block)`, `failure(&block)`, and `pre_condition_failed(&block)`. Only
|
233
|
+
one of these will ever be called. This allows you to declaratively describe
|
234
|
+
further flow in your program.
|
235
|
+
|
236
|
+
For use on the console and other situations, this style is not the most
|
237
|
+
convenient. For that reason each of the three methods above can also be called
|
238
|
+
without a block, and they always return something:
|
239
|
+
|
240
|
+
* `success` returns the command result
|
241
|
+
* `failure` returns the validation object (e.g. `failure.errors.inspect`)
|
242
|
+
* `pre_condition_failed` returns the pre-condition that failed, *or* an
|
243
|
+
exception object, if a pre-condition raised an exception.
|
244
|
+
|
245
|
+
In addition to these, the outcome object responds to `success?` and
|
246
|
+
`pre_condition_failed?`.
|
247
|
+
|
248
|
+
## Inspiration and design considerations
|
249
|
+
|
250
|
+
This small library is very much inspired by
|
251
|
+
[Mutations](http://github.com/cypriss/mutations). Nice as it is, I found it to
|
252
|
+
be a little limiting in terms of what kinds of commands it could comfortably
|
253
|
+
encapsulate. Treating everything as a hash of inputs makes it hard to do things
|
254
|
+
like "redirect if there's no user, render form if there are validation errors
|
255
|
+
and redirect to new object if successful".
|
256
|
+
|
257
|
+
As I started working on my own solution I quickly recognized the power in
|
258
|
+
separating input parameter type constraints/coercions from validation rules.
|
259
|
+
This is another area where UseCase differs from Mutations. UseCase is probably
|
260
|
+
slightly more "enterprise" than Mutations, but fits the kinds of problems I
|
261
|
+
intend to solve with it better than Mutations did.
|
262
|
+
|
263
|
+
## Testing
|
264
|
+
|
265
|
+
Using UseCase will allow you to test almost all logic completely without loading
|
266
|
+
Rails. In the example above, the input conversion is the only place that
|
267
|
+
directly touches any classes from the Rails application. The rest of the classes
|
268
|
+
work by the "data in, data out" principle, meaning you can easily test them with
|
269
|
+
any kind of object (which spares you of loading heavy ActiveRecord-bound models,
|
270
|
+
running opaque controller tets etc).
|
271
|
+
|
272
|
+
## License
|
273
|
+
|
274
|
+
UseCase is free software licensed under the MIT license.
|
275
|
+
|
276
|
+
```
|
277
|
+
The MIT License (MIT)
|
278
|
+
|
279
|
+
Copyright (C) 2013 Gitorious AS
|
280
|
+
|
281
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
282
|
+
of this software and associated documentation files (the "Software"), to deal
|
283
|
+
in the Software without restriction, including without limitation the rights
|
284
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
285
|
+
copies of the Software, and to permit persons to whom the Software is
|
286
|
+
furnished to do so, subject to the following conditions:
|
287
|
+
|
288
|
+
The above copyright notice and this permission notice shall be included in all
|
289
|
+
copies or substantial portions of the Software.
|
290
|
+
|
291
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
292
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
293
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
294
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
295
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
296
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
297
|
+
SOFTWARE.
|
298
|
+
```
|
data/lib/use_case.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "use_case/outcome"
|
26
|
+
require "use_case/validator"
|
27
|
+
require "ostruct"
|
28
|
+
|
29
|
+
module UseCase
|
30
|
+
def input_class(input_class)
|
31
|
+
@input_class = input_class
|
32
|
+
end
|
33
|
+
|
34
|
+
def validator(validator)
|
35
|
+
validators << validator
|
36
|
+
end
|
37
|
+
|
38
|
+
def pre_condition(pc)
|
39
|
+
pre_conditions << pc
|
40
|
+
end
|
41
|
+
|
42
|
+
def command(command)
|
43
|
+
@command = command
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(params)
|
47
|
+
input = @input_class && @input_class.new(params) || params
|
48
|
+
|
49
|
+
if outcome = verify_pre_conditions(input)
|
50
|
+
return outcome
|
51
|
+
end
|
52
|
+
|
53
|
+
if outcome = validate_params(input)
|
54
|
+
return outcome
|
55
|
+
end
|
56
|
+
|
57
|
+
SuccessfulOutcome.new(self, @command.execute(input))
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def verify_pre_conditions(input)
|
62
|
+
pre_conditions.each do |pc|
|
63
|
+
begin
|
64
|
+
return PreConditionFailed.new(self, pc) if !pc.satiesfied?(input)
|
65
|
+
rescue Exception => err
|
66
|
+
return PreConditionFailed.new(self, err)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_params(input)
|
73
|
+
validators.each do |validator|
|
74
|
+
result = validator.call(input)
|
75
|
+
return FailedOutcome.new(self, result) if !result.valid?
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def pre_conditions; @pre_conditions ||= []; end
|
81
|
+
def validators; @validators ||= []; end
|
82
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module UseCase
|
27
|
+
class Outcome
|
28
|
+
attr_reader :use_case
|
29
|
+
|
30
|
+
def initialize(use_case = nil)
|
31
|
+
@use_case = use_case
|
32
|
+
end
|
33
|
+
|
34
|
+
def pre_condition_failed?; false; end
|
35
|
+
def success?; false; end
|
36
|
+
def success; end
|
37
|
+
def pre_condition_failed; end
|
38
|
+
def failure; end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SuccessfulOutcome < Outcome
|
42
|
+
def initialize(use_case = nil, result = nil)
|
43
|
+
super(use_case)
|
44
|
+
@result = result
|
45
|
+
end
|
46
|
+
|
47
|
+
def success?; true; end
|
48
|
+
|
49
|
+
def success
|
50
|
+
yield @result if block_given?
|
51
|
+
@result
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#<UseCase::SuccessfulOutcome: #{@result}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class PreConditionFailed < Outcome
|
60
|
+
def initialize(use_case = nil, pre_condition = nil)
|
61
|
+
super(use_case)
|
62
|
+
@pre_condition = pre_condition
|
63
|
+
end
|
64
|
+
|
65
|
+
def pre_condition_failed?; true; end
|
66
|
+
|
67
|
+
def pre_condition_failed
|
68
|
+
yield @pre_condition if block_given?
|
69
|
+
@pre_condition
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"#<UseCase::PreConditionFailed: #{@pre_condition}>"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class FailedOutcome < Outcome
|
78
|
+
def initialize(use_case = nil, errors = nil)
|
79
|
+
super(use_case)
|
80
|
+
@errors = errors
|
81
|
+
end
|
82
|
+
|
83
|
+
def failure
|
84
|
+
yield @errors if block_given?
|
85
|
+
@errors
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
"#<UseCase::FailedOutcome: #{@errors}>"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "active_model"
|
26
|
+
|
27
|
+
module UseCase
|
28
|
+
module Validator
|
29
|
+
def self.define(&block)
|
30
|
+
klass = Class.new do
|
31
|
+
include ActiveModel::Validations
|
32
|
+
|
33
|
+
def initialize(target)
|
34
|
+
@target = target
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(name, *args, &block)
|
38
|
+
@target.send(name, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.call(object)
|
42
|
+
validator = new(object)
|
43
|
+
validator.valid?
|
44
|
+
validator
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
klass.instance_eval(&block)
|
49
|
+
klass
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module UseCase
|
27
|
+
VERSION = "0.1.0"
|
28
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "virtus"
|
2
|
+
require "use_case"
|
3
|
+
|
4
|
+
class Project
|
5
|
+
attr_reader :title
|
6
|
+
def initialize(title); @title = title; end
|
7
|
+
def self.find(id); new("<Project> My name is #{id}"); end
|
8
|
+
end
|
9
|
+
|
10
|
+
class User
|
11
|
+
attr_reader :name
|
12
|
+
def initialize(name); @name = name; end
|
13
|
+
def self.find(id); new("<User> My name is #{id}"); end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NewRepositoryInput
|
17
|
+
include Virtus
|
18
|
+
|
19
|
+
attribute :name, String
|
20
|
+
attribute :description, String
|
21
|
+
attribute :merge_requests_enabled, Boolean, :default => true
|
22
|
+
attribute :private_repository, Boolean, :default => true
|
23
|
+
|
24
|
+
attribute :user, User
|
25
|
+
attribute :user_id, Integer
|
26
|
+
attribute :project, Project
|
27
|
+
attribute :project_id, Integer
|
28
|
+
|
29
|
+
def project; @project ||= Project.find(@project_id); end
|
30
|
+
def user; @user ||= User.find(@user_id); end
|
31
|
+
end
|
32
|
+
|
33
|
+
class NewRepositoryValidator
|
34
|
+
include UseCase::Validator
|
35
|
+
validates_presence_of :name, :description, :merge_requests_enabled, :private_repository
|
36
|
+
end
|
37
|
+
|
38
|
+
class UserLoggedInPrecondition
|
39
|
+
def initialize(user)
|
40
|
+
@user = user
|
41
|
+
end
|
42
|
+
|
43
|
+
def satiesfied?(params)
|
44
|
+
@user[:id] == 42
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ProjectAdminPrecondition
|
49
|
+
def initialize(user)
|
50
|
+
@user = user
|
51
|
+
end
|
52
|
+
|
53
|
+
def satiesfied?(params)
|
54
|
+
@user[:name] == params.name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class CreateRepositoryCommand
|
59
|
+
def initialize(user)
|
60
|
+
@user = user
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute(params)
|
64
|
+
@user.merge(params)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class CreateRepository
|
69
|
+
include UseCase
|
70
|
+
|
71
|
+
def initialize(user)
|
72
|
+
input_class(NewRepositoryInput)
|
73
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
74
|
+
pre_condition(ProjectAdminPrecondition.new(user))
|
75
|
+
validator(NewRepositoryValidator)
|
76
|
+
command(CreateRepositoryCommand.new(user))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
### Example
|
81
|
+
|
82
|
+
outcome = CreateRepository.new({ :id => 42, :name => "Boy" }).execute({ :name => "Boy" })
|
83
|
+
|
84
|
+
outcome.precondition_failed do |pc|
|
85
|
+
puts "Pre-condition failed! #{pc}"
|
86
|
+
end
|
87
|
+
|
88
|
+
outcome.success do |result|
|
89
|
+
puts "Your request was successful! #{result}"
|
90
|
+
end
|
91
|
+
|
92
|
+
outcome.failure do |errors|
|
93
|
+
puts "There was a failure #{errors}"
|
94
|
+
end
|
95
|
+
|
96
|
+
puts outcome.to_s
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "virtus"
|
26
|
+
require "use_case"
|
27
|
+
|
28
|
+
class Model
|
29
|
+
attr_reader :id, :name
|
30
|
+
|
31
|
+
def initialize(id, name)
|
32
|
+
@id = id
|
33
|
+
@name = name
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s; "#<#{self.class.name}[id: #{id}, name: #{name}]>"; end
|
37
|
+
def self.find(id); new(id, "From #{self.class.name}.find"); end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Project < Model; end
|
41
|
+
class Repository < Model; end
|
42
|
+
|
43
|
+
class User < Model
|
44
|
+
def can_admin?; @can_admin; end
|
45
|
+
def can_admin=(ca); @can_admin = ca; end
|
46
|
+
end
|
47
|
+
|
48
|
+
class NewRepositoryInput
|
49
|
+
include Virtus
|
50
|
+
attribute :name, String
|
51
|
+
end
|
52
|
+
|
53
|
+
NewRepositoryValidator = UseCase::Validator.define do
|
54
|
+
validates_presence_of :name
|
55
|
+
end
|
56
|
+
|
57
|
+
class UserLoggedInPrecondition
|
58
|
+
def initialize(user); @user = user; end
|
59
|
+
def satiesfied?(params); @user && @user.id == 42; end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ProjectAdminPrecondition
|
63
|
+
def initialize(user); @user = user; end
|
64
|
+
def satiesfied?(params); @user.can_admin?; end
|
65
|
+
end
|
66
|
+
|
67
|
+
class CreateRepositoryCommand
|
68
|
+
def initialize(user); @user = user; end
|
69
|
+
def execute(params); Repository.new(1349, params.name); end
|
70
|
+
end
|
71
|
+
|
72
|
+
class CreateRepository
|
73
|
+
include UseCase
|
74
|
+
|
75
|
+
def initialize(user)
|
76
|
+
input_class(NewRepositoryInput)
|
77
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
78
|
+
pre_condition(ProjectAdminPrecondition.new(user))
|
79
|
+
validator(NewRepositoryValidator)
|
80
|
+
command(CreateRepositoryCommand.new(user))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ExplodingRepository
|
85
|
+
include UseCase
|
86
|
+
|
87
|
+
def initialize(user)
|
88
|
+
cmd = CreateRepositoryCommand.new(user)
|
89
|
+
def cmd.execute(params); raise "Crash!"; end
|
90
|
+
command(cmd)
|
91
|
+
end
|
92
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "bundler/setup"
|
26
|
+
require "minitest/autorun"
|
27
|
+
|
28
|
+
Bundler.require(:default, :test)
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case/outcome"
|
27
|
+
|
28
|
+
describe UseCase::Outcome do
|
29
|
+
it "exposes use case" do
|
30
|
+
use_case = 42
|
31
|
+
outcome = UseCase::Outcome.new(use_case)
|
32
|
+
|
33
|
+
assert_equal use_case, outcome.use_case
|
34
|
+
end
|
35
|
+
|
36
|
+
it "defaults to not failing and not being successful (noop)" do
|
37
|
+
outcome = UseCase::Outcome.new
|
38
|
+
outcome.success { fail "Shouldn't succeed" }
|
39
|
+
outcome.pre_condition_failed { fail "Shouldn't have failed pre-conditions" }
|
40
|
+
outcome.failure { fail "Shouldn't fail" }
|
41
|
+
|
42
|
+
refute outcome.pre_condition_failed?
|
43
|
+
refute outcome.success?
|
44
|
+
end
|
45
|
+
|
46
|
+
describe UseCase::SuccessfulOutcome do
|
47
|
+
it "exposes use case" do
|
48
|
+
use_case = { :id => 42 }
|
49
|
+
outcome = UseCase::SuccessfulOutcome.new(use_case)
|
50
|
+
|
51
|
+
assert_equal use_case, outcome.use_case
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not fail" do
|
55
|
+
outcome = UseCase::SuccessfulOutcome.new
|
56
|
+
outcome.pre_condition_failed { fail "Shouldn't have failed pre-conditions" }
|
57
|
+
outcome.failure { fail "Shouldn't fail" }
|
58
|
+
|
59
|
+
refute outcome.pre_condition_failed?
|
60
|
+
assert outcome.success?
|
61
|
+
end
|
62
|
+
|
63
|
+
it "yields and returns result" do
|
64
|
+
result = 42
|
65
|
+
yielded_result = nil
|
66
|
+
outcome = UseCase::SuccessfulOutcome.new(nil, result)
|
67
|
+
returned_result = outcome.success { |res| yielded_result = res }
|
68
|
+
|
69
|
+
assert_equal result, yielded_result
|
70
|
+
assert_equal result, returned_result
|
71
|
+
end
|
72
|
+
|
73
|
+
it "gets result without block" do
|
74
|
+
outcome = UseCase::SuccessfulOutcome.new(nil, 42)
|
75
|
+
assert_equal 42, outcome.success
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe UseCase::PreConditionFailed do
|
80
|
+
it "exposes use case" do
|
81
|
+
use_case = { :id => 42 }
|
82
|
+
outcome = UseCase::PreConditionFailed.new(use_case)
|
83
|
+
|
84
|
+
assert_equal use_case, outcome.use_case
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not succeed or fail" do
|
88
|
+
outcome = UseCase::PreConditionFailed.new
|
89
|
+
outcome.success { fail "Shouldn't succeed" }
|
90
|
+
outcome.failure { fail "Shouldn't fail" }
|
91
|
+
|
92
|
+
assert outcome.pre_condition_failed?
|
93
|
+
refute outcome.success?
|
94
|
+
end
|
95
|
+
|
96
|
+
it "yields and returns failed pre-condition" do
|
97
|
+
pre_condition = 42
|
98
|
+
yielded_pc = nil
|
99
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
100
|
+
returned_pc = outcome.pre_condition_failed { |pc| yielded_pc = pc }
|
101
|
+
|
102
|
+
assert_equal pre_condition, yielded_pc
|
103
|
+
assert_equal pre_condition, returned_pc
|
104
|
+
end
|
105
|
+
|
106
|
+
it "gets pre_condition without block" do
|
107
|
+
outcome = UseCase::PreConditionFailed.new(nil, 42)
|
108
|
+
assert_equal 42, outcome.pre_condition_failed
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe UseCase::FailedOutcome do
|
113
|
+
it "exposes use case" do
|
114
|
+
use_case = { :id => 42 }
|
115
|
+
outcome = UseCase::FailedOutcome.new(use_case)
|
116
|
+
|
117
|
+
assert_equal use_case, outcome.use_case
|
118
|
+
end
|
119
|
+
|
120
|
+
it "does not succeed or fail pre-conditions" do
|
121
|
+
outcome = UseCase::FailedOutcome.new
|
122
|
+
outcome.success { fail "Shouldn't succeed" }
|
123
|
+
outcome.pre_condition_failed { fail "Shouldn't fail pre-conditions" }
|
124
|
+
|
125
|
+
refute outcome.pre_condition_failed?
|
126
|
+
refute outcome.success?
|
127
|
+
end
|
128
|
+
|
129
|
+
it "yields and returns validation failure" do
|
130
|
+
failure = 42
|
131
|
+
yielded_result = nil
|
132
|
+
outcome = UseCase::FailedOutcome.new(nil, failure)
|
133
|
+
returned_result = outcome.failure { |result| yielded_result = result }
|
134
|
+
|
135
|
+
assert_equal failure, yielded_result
|
136
|
+
assert_equal failure, returned_result
|
137
|
+
end
|
138
|
+
|
139
|
+
it "gets failure without block" do
|
140
|
+
outcome = UseCase::FailedOutcome.new(nil, 42)
|
141
|
+
assert_equal 42, outcome.failure
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case/validator"
|
27
|
+
|
28
|
+
NewPersonValidator = UseCase::Validator.define do
|
29
|
+
validates_presence_of :name
|
30
|
+
end
|
31
|
+
|
32
|
+
class Person
|
33
|
+
attr_accessor :name
|
34
|
+
end
|
35
|
+
|
36
|
+
describe UseCase::Validator do
|
37
|
+
it "passes valid object" do
|
38
|
+
person = Person.new
|
39
|
+
person.name = "Christian"
|
40
|
+
result = NewPersonValidator.call(person)
|
41
|
+
|
42
|
+
assert result.valid?
|
43
|
+
end
|
44
|
+
|
45
|
+
it "fails invalid object" do
|
46
|
+
result = NewPersonValidator.call(Person.new)
|
47
|
+
|
48
|
+
refute result.valid?
|
49
|
+
assert_equal 1, result.errors.count
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case"
|
27
|
+
require "sample_use_case"
|
28
|
+
|
29
|
+
describe UseCase do
|
30
|
+
before do
|
31
|
+
@logged_in_user = User.new(42, "Christian")
|
32
|
+
@logged_in_user.can_admin = true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "fails first pre-condition; no user logged in" do
|
36
|
+
outcome = CreateRepository.new(nil).execute({})
|
37
|
+
|
38
|
+
outcome.pre_condition_failed do |pc|
|
39
|
+
assert_equal UserLoggedInPrecondition, pc.class
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "fails second pre-condition; user cannot admin" do
|
44
|
+
@logged_in_user.can_admin = false
|
45
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
46
|
+
|
47
|
+
outcome.pre_condition_failed do |pc|
|
48
|
+
assert_equal ProjectAdminPrecondition, pc.class
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "fails with error if pre-condition raises" do
|
53
|
+
def @logged_in_user.id; raise "Oops!"; end
|
54
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
55
|
+
|
56
|
+
outcome.pre_condition_failed do |pc|
|
57
|
+
assert_equal RuntimeError, pc.class
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fails on input validation" do
|
62
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
63
|
+
|
64
|
+
validation = outcome.failure do |v|
|
65
|
+
refute v.valid?
|
66
|
+
assert_equal 1, v.errors.count
|
67
|
+
assert v.errors[:name]
|
68
|
+
end
|
69
|
+
|
70
|
+
refute_nil validation
|
71
|
+
end
|
72
|
+
|
73
|
+
it "executes command" do
|
74
|
+
outcome = CreateRepository.new(@logged_in_user).execute({ :name => "My repository" })
|
75
|
+
|
76
|
+
result = outcome.success do |res|
|
77
|
+
assert_equal "My repository", res.name
|
78
|
+
assert res.is_a?(Repository)
|
79
|
+
end
|
80
|
+
|
81
|
+
refute_nil result
|
82
|
+
end
|
83
|
+
|
84
|
+
it "raises if command raises" do
|
85
|
+
use_case = ExplodingRepository.new(@logged_in_user)
|
86
|
+
|
87
|
+
assert_raises RuntimeError do
|
88
|
+
use_case.execute(nil)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/use_case.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "./lib/use_case/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "use_case"
|
5
|
+
s.version = UseCase::VERSION
|
6
|
+
s.author = "Christian Johansen"
|
7
|
+
s.email = "christian@gitorious.com"
|
8
|
+
s.homepage = "http://gitorious.org/gitorious/use_case"
|
9
|
+
s.summary = s.description = ""
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files test`.split("\n")
|
13
|
+
s.require_path = "lib"
|
14
|
+
|
15
|
+
s.add_development_dependency "minitest", "~> 4"
|
16
|
+
s.add_development_dependency "rake"
|
17
|
+
|
18
|
+
s.required_ruby_version = ">= 1.9.2"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: use_case
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Johansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ''
|
47
|
+
email: christian@gitorious.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE
|
55
|
+
- Rakefile
|
56
|
+
- Readme.md
|
57
|
+
- lib/use_case.rb
|
58
|
+
- lib/use_case/outcome.rb
|
59
|
+
- lib/use_case/validator.rb
|
60
|
+
- lib/use_case/version.rb
|
61
|
+
- test.rb
|
62
|
+
- test/sample_use_case.rb
|
63
|
+
- test/test_helper.rb
|
64
|
+
- test/use_case/outcome_test.rb
|
65
|
+
- test/use_case/validator_test.rb
|
66
|
+
- test/use_case_test.rb
|
67
|
+
- use_case.gemspec
|
68
|
+
homepage: http://gitorious.org/gitorious/use_case
|
69
|
+
licenses: []
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 1.9.2
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: ''
|
92
|
+
test_files:
|
93
|
+
- test/sample_use_case.rb
|
94
|
+
- test/test_helper.rb
|
95
|
+
- test/use_case/outcome_test.rb
|
96
|
+
- test/use_case/validator_test.rb
|
97
|
+
- test/use_case_test.rb
|