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