use_case 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.md +76 -62
- data/lib/use_case/outcome.rb +3 -0
- data/lib/use_case/validator.rb +1 -1
- data/lib/use_case/version.rb +1 -1
- data/test/use_case/validator_test.rb +23 -0
- metadata +2 -2
data/Readme.md
CHANGED
@@ -11,65 +11,84 @@ Compose non-trivial business logic into use cases, that combine:
|
|
11
11
|
different contexts etc.
|
12
12
|
* Commands: Avoid defensive coding by performing the core actions in commands
|
13
13
|
that receive type-converted input, and are only executed when pre-conditions
|
14
|
-
are met and input validated.
|
14
|
+
are met and input is validated.
|
15
15
|
|
16
16
|
## Example
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
`UseCase` is designed to break up and keep non-trivial workflows understandable
|
19
|
+
and decoupled. As such, a trivial example would not illustrate what is good
|
20
|
+
about it. The following example is simplified, yet still has enough aspects to
|
21
|
+
show how `UseCase` helps you break things up.
|
22
|
+
|
23
|
+
Pre-conditions are conditions not directly related to input parameters alone,
|
24
|
+
and whose failure signifies other forms of errors than simple validation errors.
|
25
|
+
If you have a Rails application that uses controller filters, then those are
|
26
|
+
very likely good candidates for pre-conditions.
|
27
|
+
|
28
|
+
The following example is a simplified use case from
|
29
|
+
[Gitorious](http://gitorious.org) where we want to create a new repository. To
|
30
|
+
do this, we need a user that can admin the project under which we want the new
|
31
|
+
repository to live.
|
32
|
+
|
33
|
+
This example illustrates how to solve common design challenges in Rails
|
34
|
+
applications; that does not mean that `UseCase` is only useful to Rails
|
35
|
+
applications.
|
36
|
+
|
37
|
+
First, let's look at what your Rails controller will look like using a
|
38
|
+
`UseCase`:
|
22
39
|
|
23
40
|
```rb
|
24
|
-
|
41
|
+
class RepositoryController < ApplicationController
|
42
|
+
include Gitorious::Authorization # Adds stuff like can_admin?(actor, thing)
|
25
43
|
|
26
|
-
|
27
|
-
def execute(params)
|
28
|
-
puts "Hello, #{params[:place]}!"
|
29
|
-
end
|
30
|
-
end
|
44
|
+
# ...
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
def create
|
47
|
+
outcome = CreateRepository.new(self, current_user).execute(params)
|
34
48
|
|
35
|
-
|
36
|
-
|
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
|
53
|
+
end
|
54
|
+
|
55
|
+
outcome.failure do |model|
|
56
|
+
# Render form with validation errors
|
57
|
+
render :new, :locals => { :repository => model }
|
58
|
+
end
|
59
|
+
|
60
|
+
outcome.success do |repository|
|
61
|
+
redirect_to(repository_path(repository))
|
62
|
+
end
|
37
63
|
end
|
38
64
|
end
|
39
|
-
|
40
|
-
# Usage
|
41
|
-
PrintHelloWorld.new.execute(:place => "World")
|
42
65
|
```
|
43
66
|
|
44
|
-
|
67
|
+
Executing the use case in an `irb` session could look like this:
|
45
68
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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.
|
69
|
+
```rb
|
70
|
+
include Gitorious::Authorization
|
71
|
+
user = User.find_by_login("christian")
|
72
|
+
project = Project.find_by_name("gitorious")
|
73
|
+
outcome = CreateRepository.new(self, user).execute(:project => project,
|
74
|
+
:name => "use_case")
|
75
|
+
outcome.success? #=> true
|
76
|
+
outcome.result.name #=> "use_case"
|
77
|
+
```
|
58
78
|
|
59
|
-
|
60
|
-
applications, but that does not mean that `UseCase` is only useful to Rails
|
61
|
-
applications.
|
79
|
+
The code behind this use case follows:
|
62
80
|
|
63
81
|
```rb
|
64
82
|
require "use_case"
|
65
83
|
require "virtus"
|
66
84
|
|
67
|
-
# Input parameters can be sanitized and pre-processed
|
85
|
+
# Input parameters can be sanitized and pre-processed any way you like. One nice
|
68
86
|
# way to go about it is to use Datamapper 2's Virtus gem to define a parameter
|
69
87
|
# set.
|
70
88
|
#
|
71
89
|
# 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
|
90
|
+
# provided and project is not. This is the only class that directly touches
|
91
|
+
# classes from the Rails application.
|
73
92
|
class NewRepositoryInput
|
74
93
|
include Virtus
|
75
94
|
attribute :name, String
|
@@ -143,32 +162,6 @@ class CreateRepository
|
|
143
162
|
command(CreateRepositoryCommand.new(user))
|
144
163
|
end
|
145
164
|
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
165
|
```
|
173
166
|
|
174
167
|
## Input sanitation
|
@@ -269,6 +262,27 @@ work by the "data in, data out" principle, meaning you can easily test them with
|
|
269
262
|
any kind of object (which spares you of loading heavy ActiveRecord-bound models,
|
270
263
|
running opaque controller tets etc).
|
271
264
|
|
265
|
+
## Installation
|
266
|
+
|
267
|
+
$ gem install use_case
|
268
|
+
|
269
|
+
## Developing
|
270
|
+
|
271
|
+
$ bundle install
|
272
|
+
$ rake
|
273
|
+
|
274
|
+
## Contributing
|
275
|
+
|
276
|
+
* Clone repo
|
277
|
+
* Make changes
|
278
|
+
* Add test(s)
|
279
|
+
* Run tests
|
280
|
+
* If adding new abilities, add docs in Readme, or commit a working example
|
281
|
+
* Send patch, [pull request](http://github.com/cjohansen/use_case) or [merge request](http://gitorious.org/gitorious/use_case)
|
282
|
+
|
283
|
+
If you intend to add entirely new features, you might want to open an issue to
|
284
|
+
discuss it with me first.
|
285
|
+
|
272
286
|
## License
|
273
287
|
|
274
288
|
UseCase is free software licensed under the MIT license.
|
data/lib/use_case/outcome.rb
CHANGED
@@ -34,6 +34,7 @@ module UseCase
|
|
34
34
|
def pre_condition_failed?; false; end
|
35
35
|
def success?; false; end
|
36
36
|
def success; end
|
37
|
+
def result; end
|
37
38
|
def pre_condition_failed; end
|
38
39
|
def failure; end
|
39
40
|
end
|
@@ -51,6 +52,8 @@ module UseCase
|
|
51
52
|
@result
|
52
53
|
end
|
53
54
|
|
55
|
+
def result; @result; end
|
56
|
+
|
54
57
|
def to_s
|
55
58
|
"#<UseCase::SuccessfulOutcome: #{@result}>"
|
56
59
|
end
|
data/lib/use_case/validator.rb
CHANGED
data/lib/use_case/version.rb
CHANGED
@@ -29,6 +29,14 @@ NewPersonValidator = UseCase::Validator.define do
|
|
29
29
|
validates_presence_of :name
|
30
30
|
end
|
31
31
|
|
32
|
+
CustomValidator = UseCase::Validator.define do
|
33
|
+
validate :validate_custom
|
34
|
+
|
35
|
+
def validate_custom
|
36
|
+
errors.add(:name, "is not Dude") if name != "Dude"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
32
40
|
class Person
|
33
41
|
attr_accessor :name
|
34
42
|
end
|
@@ -48,4 +56,19 @@ describe UseCase::Validator do
|
|
48
56
|
refute result.valid?
|
49
57
|
assert_equal 1, result.errors.count
|
50
58
|
end
|
59
|
+
|
60
|
+
it "supports custom validators" do
|
61
|
+
result = CustomValidator.call(Person.new)
|
62
|
+
|
63
|
+
refute result.valid?
|
64
|
+
assert_equal 1, result.errors.count
|
65
|
+
end
|
66
|
+
|
67
|
+
it "passes custom validator" do
|
68
|
+
person = Person.new
|
69
|
+
person.name = "Dude"
|
70
|
+
result = CustomValidator.call(person)
|
71
|
+
|
72
|
+
assert result.valid?
|
73
|
+
end
|
51
74
|
end
|
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.2.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-03-
|
12
|
+
date: 2013-03-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|