use_case 0.3.0 → 0.4.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 +1 -1
- data/Readme.md +112 -10
- data/lib/use_case/version.rb +1 -1
- data/lib/use_case.rb +1 -1
- data/test/sample_use_case.rb +2 -2
- data/test.rb +2 -2
- metadata +2 -2
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -117,7 +117,7 @@ class UserLoggedInPrecondition
|
|
117
117
|
|
118
118
|
# A pre-condition must define this method
|
119
119
|
# Params is an instance of NewRepositoryInput
|
120
|
-
def
|
120
|
+
def satisfied?(params)
|
121
121
|
!@user.nil?
|
122
122
|
end
|
123
123
|
end
|
@@ -129,13 +129,13 @@ class ProjectAdminPrecondition
|
|
129
129
|
@user = user
|
130
130
|
end
|
131
131
|
|
132
|
-
def
|
132
|
+
def satisfied?(params)
|
133
133
|
@auth.can_admin?(@user, params.project)
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
137
|
# The business logic. Here we can safely assume that all pre-conditions are
|
138
|
-
#
|
138
|
+
# satisfied, and that input is valid and has the correct type.
|
139
139
|
class CreateRepositoryCommand
|
140
140
|
def initialize(user)
|
141
141
|
@user = user
|
@@ -164,6 +164,23 @@ class CreateRepository
|
|
164
164
|
end
|
165
165
|
```
|
166
166
|
|
167
|
+
## The use case pipeline at a glance
|
168
|
+
|
169
|
+
This is the high-level overview of how `UseCase` strings up a pipeline
|
170
|
+
for you to plug in various kinds of business logic:
|
171
|
+
|
172
|
+
```
|
173
|
+
User input (-> input sanitation) (-> pre-conditions) (-> builder) (-> validations) -> command
|
174
|
+
```
|
175
|
+
|
176
|
+
* Start with a hash of user input
|
177
|
+
* Optionally wrap this in an object that performs type-coercion,
|
178
|
+
enforces types etc. By default, input will be wrapped in an `OpenStruct`
|
179
|
+
* Optionally run pre-conditions on the santized input
|
180
|
+
* Optionally refine input by running it through a pre-execution "builder"
|
181
|
+
* Optionally (refined) input through one or more validators
|
182
|
+
* Execute command with (refined) input
|
183
|
+
|
167
184
|
## Input sanitation
|
168
185
|
|
169
186
|
In your `UseCase` instance (typically in the constructor), you can call the
|
@@ -176,6 +193,15 @@ solution for input sanitation and some level of type-safety. If you provide a
|
|
176
193
|
`Virtus` backed class as `input_class` you will get an instance of that class as
|
177
194
|
`params` in pre-conditions and commands.
|
178
195
|
|
196
|
+
## Pre-conditions
|
197
|
+
|
198
|
+
A pre-condition is any object that responds to `satisfied?(params)` where
|
199
|
+
params will either be a `Hash` or an instance of whatever you passed to
|
200
|
+
`input_class`. The method should return `true/false`. If it raises, the outcome
|
201
|
+
of the use case will call the `pre_condition_failed` block with the raised
|
202
|
+
error. If it fails, the `pre_condition_failed` block will be called with the
|
203
|
+
pre-condition instance that failed.
|
204
|
+
|
179
205
|
## Validations
|
180
206
|
|
181
207
|
The validator uses `ActiveModel::Validations`, so any Rails validation can go in
|
@@ -191,14 +217,90 @@ Because `UseCase::Validation` is not a required part of `UseCase`, and people
|
|
191
217
|
may want to control their own dependencies, `activemodel` is _not_ a hard
|
192
218
|
dependency. To use this feature, `gem install activemodel`.
|
193
219
|
|
194
|
-
##
|
220
|
+
## Builders
|
195
221
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
222
|
+
When user input has passed input sanitation and pre-conditions have
|
223
|
+
been satisfied, you can optionally pipe input through a "builder"
|
224
|
+
before handing it over to validations and the command.
|
225
|
+
|
226
|
+
The builder should be an object with a `build` method. The method will
|
227
|
+
be called with santized input. The return value from `build` will be
|
228
|
+
passed on to validators and the command.
|
229
|
+
|
230
|
+
Builders can be useful if you want to run validations on a domain
|
231
|
+
object rather than directly on "dumb" input.
|
232
|
+
|
233
|
+
### Example
|
234
|
+
|
235
|
+
In a Rails application, the builder is useful to wrap user input in an
|
236
|
+
unsaved `ActiveRecord` instance. The unsaved object will be run
|
237
|
+
through the validators, and (if found valid), the command can save it
|
238
|
+
and perform additional tasks that you possibly do with `ActiveRecord`
|
239
|
+
observers now.
|
240
|
+
|
241
|
+
This example also shows how to express uniqueness validators when you
|
242
|
+
move validations out of your `ActiveRecord` models.
|
243
|
+
|
244
|
+
```rb
|
245
|
+
require "activemodel"
|
246
|
+
require "virtus"
|
247
|
+
require "use_case"
|
248
|
+
|
249
|
+
class User < ActiveRecord::Base
|
250
|
+
def uniq?
|
251
|
+
user = User.where("lower(name) = ?", name).first
|
252
|
+
user.nil? || user == self
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
UserValidator = UseCase::Validator.define do
|
257
|
+
validates_presence_of :name
|
258
|
+
validate :uniqueness
|
259
|
+
|
260
|
+
def uniqueness
|
261
|
+
errors.add(:name, "is taken") if !uniq?
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class NewUserInput
|
266
|
+
include Virtus
|
267
|
+
attribute :name, String
|
268
|
+
end
|
269
|
+
|
270
|
+
class NewUserCommand
|
271
|
+
def execute(user)
|
272
|
+
user.save!
|
273
|
+
Mailer.user_signup(user).deliver
|
274
|
+
user
|
275
|
+
end
|
276
|
+
|
277
|
+
def build(params)
|
278
|
+
User.new(:name => params.name)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class CreateUser
|
283
|
+
include UseCase
|
284
|
+
|
285
|
+
def initialize
|
286
|
+
input_class(NewUserInput)
|
287
|
+
validator(UserValidator)
|
288
|
+
cmd = NewUserCommand.new
|
289
|
+
builder(cmd) # Use the command as a builder too
|
290
|
+
command(cmd)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Usage:
|
295
|
+
outcome = CreateUser.new.execute(:name => "Chris")
|
296
|
+
outcome.success? #=> true
|
297
|
+
outcome.result #=> #<User name: "Chris">
|
298
|
+
```
|
299
|
+
|
300
|
+
### Note
|
301
|
+
|
302
|
+
I'm not thrilled by `builder` as a name/concept. Suggestions for a
|
303
|
+
better name is welcome.
|
202
304
|
|
203
305
|
## Commands
|
204
306
|
|
data/lib/use_case/version.rb
CHANGED
data/lib/use_case.rb
CHANGED
@@ -71,7 +71,7 @@ module UseCase
|
|
71
71
|
def verify_pre_conditions(input)
|
72
72
|
pre_conditions.each do |pc|
|
73
73
|
begin
|
74
|
-
return PreConditionFailed.new(self, pc) if !pc.
|
74
|
+
return PreConditionFailed.new(self, pc) if !pc.satisfied?(input)
|
75
75
|
rescue Exception => err
|
76
76
|
return PreConditionFailed.new(self, err)
|
77
77
|
end
|
data/test/sample_use_case.rb
CHANGED
@@ -56,12 +56,12 @@ end
|
|
56
56
|
|
57
57
|
class UserLoggedInPrecondition
|
58
58
|
def initialize(user); @user = user; end
|
59
|
-
def
|
59
|
+
def satisfied?(params); @user && @user.id == 42; end
|
60
60
|
end
|
61
61
|
|
62
62
|
class ProjectAdminPrecondition
|
63
63
|
def initialize(user); @user = user; end
|
64
|
-
def
|
64
|
+
def satisfied?(params); @user.can_admin?; end
|
65
65
|
end
|
66
66
|
|
67
67
|
class CreateRepositoryCommand
|
data/test.rb
CHANGED
@@ -40,7 +40,7 @@ class UserLoggedInPrecondition
|
|
40
40
|
@user = user
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
43
|
+
def satisfied?(params)
|
44
44
|
@user[:id] == 42
|
45
45
|
end
|
46
46
|
end
|
@@ -50,7 +50,7 @@ class ProjectAdminPrecondition
|
|
50
50
|
@user = user
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
53
|
+
def satisfied?(params)
|
54
54
|
@user[:name] == params.name
|
55
55
|
end
|
56
56
|
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.4.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-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|