use_case 0.3.0 → 0.4.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.3.0)
15
+ use_case (0.4.0)
16
16
 
17
17
  GEM
18
18
  remote: http://rubygems.org/
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 satiesfied?(params)
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 satiesfied?(params)
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
- # satiesfied, and that input is valid and has the correct type.
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
- ## Pre-conditions
220
+ ## Builders
195
221
 
196
- A pre-condition is any object that responds to `satiesfied?(params)` where
197
- params will either be a `Hash` or an instance of whatever you passed to
198
- `input_class`. The method should return `true/false`. If it raises, the outcome
199
- of the use case will call the `pre_condition_failed` block with the raised
200
- error. If it fails, the `pre_condition_failed` block will be called with the
201
- pre-condition instance that failed.
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
 
@@ -24,5 +24,5 @@
24
24
  #++
25
25
 
26
26
  module UseCase
27
- VERSION = "0.3.0"
27
+ VERSION = "0.4.0"
28
28
  end
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.satiesfied?(input)
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
@@ -56,12 +56,12 @@ end
56
56
 
57
57
  class UserLoggedInPrecondition
58
58
  def initialize(user); @user = user; end
59
- def satiesfied?(params); @user && @user.id == 42; end
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 satiesfied?(params); @user.can_admin?; end
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 satiesfied?(params)
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 satiesfied?(params)
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.3.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-01 00:00:00.000000000 Z
12
+ date: 2013-04-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest