use_case 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Readme.md CHANGED
@@ -155,11 +155,12 @@ class CreateRepository
155
155
  # any dependencies you need.
156
156
  def initialize(auth, user)
157
157
  input_class(NewRepositoryInput)
158
- pre_condition(UserLoggedInPrecondition.new(user))
159
- pre_condition(ProjectAdminPrecondition.new(auth, user))
160
- # A command has 0, 1 or many validators (e.g. :validators => [...])
161
- # The use case can span multiple commands (see below)
162
- command(CreateRepositoryCommand.new(user), :validator => NewRepositoryValidator)
158
+ add_pre_condition(UserLoggedInPrecondition.new(user))
159
+ add_pre_condition(ProjectAdminPrecondition.new(auth, user))
160
+ # A step is comprised of a command with 0, 1 or many validators
161
+ # (e.g. :validators => [...])
162
+ # The use case can span multiple steps (see below)
163
+ step(CreateRepositoryCommand.new(user), :validator => NewRepositoryValidator)
163
164
  end
164
165
  end
165
166
  ```
@@ -170,17 +171,25 @@ This is the high-level overview of how `UseCase` strings up a pipeline
170
171
  for you to plug in various kinds of business logic:
171
172
 
172
173
  ```
173
- User input (-> input sanitation) (-> pre-conditions) [(-> builder) (-> validations) -> command]*
174
+ User input (-> input sanitation) (-> pre-conditions) -> steps
174
175
  ```
175
176
 
176
177
  1. Start with a hash of user input
177
178
  2. Optionally wrap this in an object that performs type-coercion,
178
179
  enforces types etc.
179
180
  3. Optionally run pre-conditions on the santized input
180
- 4. Optionally refine input by running it through a pre-execution "builder"
181
- 5. Optionally run (refined) input through one or more validators
182
- 6. Execute command(s) with (refined) input
183
- 7. Repeat steps 4-7 as necessary
181
+ 4. Execute steps. The initial step is fed the sanitized input, each
182
+ following command is fed the result from the previous step.
183
+
184
+ Each step is a pipeline in its own right:
185
+
186
+ ```
187
+ Step: (-> builder) (-> validations) -> command
188
+ ```
189
+
190
+ 1. Optionally refine input by running it through a pre-execution "builder"
191
+ 2. Optionally run (refined) input through one or more validators
192
+ 3. Execute command with (refined) input
184
193
 
185
194
  ## Input sanitation
186
195
 
@@ -222,27 +231,26 @@ dependency. To use this feature, `gem install activemodel`.
222
231
 
223
232
  ## Builders
224
233
 
225
- When user input has passed input sanitation and pre-conditions have
226
- been satisfied, you can optionally pipe input through a "builder"
227
- before handing it over to validations and a command.
234
+ When user input has passed input sanitation and pre-conditions have been
235
+ satisfied, you can optionally pipe input through a "builder" before handing it
236
+ over to validations and a command.
228
237
 
229
238
  The builder should be an object with a `build` or a `call` method (if it has
230
239
  both, `build` will be preferred). The method will be called with santized input.
231
240
  The return value will be passed on to validators and the commands.
232
241
 
233
- Builders can be useful if you want to run validations on a domain
234
- object rather than directly on "dumb" input.
242
+ Builders can be useful if you want to run validations on a domain object rather
243
+ than directly on "dumb" input.
235
244
 
236
245
  ### Example
237
246
 
238
- In a Rails application, the builder is useful to wrap user input in an
239
- unsaved `ActiveRecord` instance. The unsaved object will be run
240
- through the validators, and (if found valid), the command can save it
241
- and perform additional tasks that you possibly do with `ActiveRecord`
242
- observers now.
247
+ In a Rails application, the builder is useful to wrap user input in an unsaved
248
+ `ActiveRecord` instance. The unsaved object will be run through the validators,
249
+ and (if found valid), the command can save it and perform additional tasks that
250
+ you possibly do with `ActiveRecord` observers now.
243
251
 
244
- This example also shows how to express uniqueness validators when you
245
- move validations out of your `ActiveRecord` models.
252
+ This example also shows how to express uniqueness validators when you move
253
+ validations out of your `ActiveRecord` models.
246
254
 
247
255
  ```rb
248
256
  require "activemodel"
@@ -289,7 +297,7 @@ class CreateUser
289
297
  input_class(NewUserInput)
290
298
  cmd = NewUserCommand.new
291
299
  # Use the command as a builder too
292
- command(cmd, :builder => cmd, :validator => UserValidator)
300
+ step(cmd, :builder => cmd, :validator => UserValidator)
293
301
  end
294
302
  end
295
303
 
@@ -299,19 +307,59 @@ outcome.success? #=> true
299
307
  outcome.result #=> #<User name: "Chris">
300
308
  ```
301
309
 
310
+ If the command fails to execute due to validation errors, using the builder
311
+ allows us to access the partial object for re-rendering forms etc. Because this
312
+ is such a common scenario, the command will automatically be used as the builder
313
+ as well if there is no explicit `:builder` option, and the command responds to
314
+ `build`. This means that the command in the previous example could be written as
315
+ so:
316
+
317
+ ```rb
318
+ class CreateUser
319
+ include UseCase
320
+
321
+ def initialize
322
+ input_class(NewUserInput)
323
+ step(NewUserCommand.new, :validator => UserValidator)
324
+ end
325
+ end
326
+ ```
327
+
328
+ When calling `execute` on this use case, we can observe the following flow:
329
+
330
+ ```rb
331
+ # This
332
+ params = { :name => "Dude" }
333
+ CreateUser.new.execute(params)
334
+
335
+ # ...roughly expands to:
336
+ # (command is the command instance wired in the use case constructor)
337
+ input = NewUserInput.new(params)
338
+ prepared = command.build(input)
339
+
340
+ if UserValidator.call(prepared).valid?
341
+ command.execute(prepared)
342
+ end
343
+ ```
344
+
302
345
  ### Note
303
346
 
304
- I'm not thrilled by `builder` as a name/concept. Suggestions for a
305
- better name is welcome.
347
+ I'm not thrilled by `builder` as a name/concept. Suggestions for a better name
348
+ is welcome.
306
349
 
307
350
  ## Commands
308
351
 
309
- A command is any Ruby object that defines an `execute(params)` method. Its
352
+ A command is any Ruby object that defines an `execute(params)` method.
353
+ Alternately, it can be an object that responds to `call` (e.g. a lambda). Its
310
354
  return value will be passed to the outcome's `success` block. Any errors raised
311
355
  by this method is not rescued, so be sure to wrap `use_case.execute(params)` in
312
356
  a rescue block if you're worried that it raises. Better yet, detect known causes
313
357
  of exceptions in a pre-condition so you know that the command does not raise.
314
358
 
359
+ If the command responds to the `build` message and there is no explicitly
360
+ configured `:builder` for the current step, the command is also used as a
361
+ builder (see example above, under "Builders").
362
+
315
363
  ## Use cases
316
364
 
317
365
  A use case simply glues together all the components. Define a class, include
@@ -319,16 +367,16 @@ A use case simply glues together all the components. Define a class, include
319
367
  take any arguments you like, making this solution suitable for DI (dependency
320
368
  injection) style designs.
321
369
 
322
- The use case can optionally call `input_class` once, `pre_condition` multiple
323
- times, and `command` multiple times.
370
+ The use case can optionally call `input_class` once, `add_pre_condition`
371
+ multiple times, and `step` multiple times.
324
372
 
325
- When using multiple commands, input sanitation with the `input_class` is
373
+ When using multiple steps, input sanitation with the `input_class` is
326
374
  performed once only. Pre-conditions are also only checked once - before any
327
- commands are executed. The use case will then execute the commands:
375
+ steps are executed. The use case will then execute the steps:
328
376
 
329
377
  ```
330
- command_1: sanitizied_input -> (builder ->) (validators ->) command
331
- command_n: command_n-1 result -> (builder ->) (validators ->) command
378
+ step_1: sanitizied_input -> (builder ->) (validators ->) command
379
+ step_n: command_n-1 result -> (builder ->) (validators ->) command
332
380
  ```
333
381
 
334
382
  In other words, all commands except the first one will be executed with the
@@ -24,5 +24,5 @@
24
24
  #++
25
25
 
26
26
  module UseCase
27
- VERSION = "0.6.0"
27
+ VERSION = "0.7.0"
28
28
  end
data/lib/use_case.rb CHANGED
@@ -31,13 +31,13 @@ module UseCase
31
31
  @input_class = input_class
32
32
  end
33
33
 
34
- def pre_condition(pc)
34
+ def add_pre_condition(pc)
35
35
  pre_conditions << pc
36
36
  end
37
37
 
38
- def command(command, options = {})
39
- @commands ||= []
40
- @commands << {
38
+ def step(command, options = {})
39
+ @steps ||= []
40
+ @steps << {
41
41
  :command => command,
42
42
  :builder => options[:builder],
43
43
  :validators => Array(options[:validators] || options[:validator])
@@ -51,23 +51,27 @@ module UseCase
51
51
  return outcome
52
52
  end
53
53
 
54
- execute_commands(@commands, input)
54
+ execute_steps(@steps, input)
55
55
  end
56
56
 
57
57
  private
58
- def execute_commands(commands, params)
59
- result = commands.inject(params) do |input, command|
58
+ def execute_steps(steps, params)
59
+ result = steps.inject(params) do |input, step|
60
60
  begin
61
- input = prepare_input(input, command[:builder])
61
+ input = prepare_input(input, step)
62
62
  rescue Exception => err
63
63
  return PreConditionFailed.new(self, err)
64
64
  end
65
65
 
66
- if outcome = validate_params(input, command[:validators])
66
+ if outcome = validate_params(input, step[:validators])
67
67
  return outcome
68
68
  end
69
69
 
70
- command[:command].execute(input)
70
+ if step[:command].respond_to?(:execute)
71
+ step[:command].execute(input)
72
+ else
73
+ step[:command].call(input)
74
+ end
71
75
  end
72
76
 
73
77
  SuccessfulOutcome.new(self, result)
@@ -84,10 +88,11 @@ module UseCase
84
88
  nil
85
89
  end
86
90
 
87
- def prepare_input(input, builder)
88
- return input if !builder
91
+ def prepare_input(input, step)
92
+ builder = step[:builder] || step[:command]
89
93
  return builder.build(input) if builder.respond_to?(:build)
90
- builder.call(input)
94
+ return step[:builder].call(input) if step[:builder]
95
+ input
91
96
  end
92
97
 
93
98
  def validate_params(input, validators)
@@ -74,9 +74,9 @@ class CreateRepository
74
74
 
75
75
  def initialize(user)
76
76
  input_class(NewRepositoryInput)
77
- pre_condition(UserLoggedInPrecondition.new(user))
78
- pre_condition(ProjectAdminPrecondition.new(user))
79
- command(CreateRepositoryCommand.new(user), :validators => NewRepositoryValidator)
77
+ add_pre_condition(UserLoggedInPrecondition.new(user))
78
+ add_pre_condition(ProjectAdminPrecondition.new(user))
79
+ step(CreateRepositoryCommand.new(user), :validators => NewRepositoryValidator)
80
80
  end
81
81
  end
82
82
 
@@ -86,7 +86,7 @@ class ExplodingRepository
86
86
  def initialize(user)
87
87
  cmd = CreateRepositoryCommand.new(user)
88
88
  def cmd.execute(params); raise "Crash!"; end
89
- command(cmd)
89
+ step(cmd)
90
90
  end
91
91
  end
92
92
 
@@ -104,7 +104,7 @@ class CreateRepositoryWithBuilder
104
104
 
105
105
  def initialize(user)
106
106
  input_class(NewRepositoryInput)
107
- command(CreateRepositoryCommand.new(user), {
107
+ step(CreateRepositoryCommand.new(user), {
108
108
  :validators => NewRepositoryValidator,
109
109
  :builder => RepositoryBuilder
110
110
  })
@@ -116,13 +116,20 @@ class CreateRepositoryWithExplodingBuilder
116
116
 
117
117
  def initialize(user)
118
118
  input_class(NewRepositoryInput)
119
- command(CreateRepositoryCommand.new(user), :builder => self)
119
+ step(CreateRepositoryCommand.new(user), :builder => self)
120
120
  end
121
121
 
122
122
  def build; raise "Oops"; end
123
123
  end
124
124
 
125
125
  class PimpRepositoryCommand
126
+ def execute(repository)
127
+ repository.name += " (Pimped)"
128
+ repository
129
+ end
130
+ end
131
+
132
+ class PimpRepositoryCommandWithBuilder
126
133
  def build(repository)
127
134
  repository.id = 42
128
135
  repository
@@ -139,8 +146,8 @@ class CreatePimpedRepository
139
146
 
140
147
  def initialize(user)
141
148
  input_class(NewRepositoryInput)
142
- command(CreateRepositoryCommand.new(user))
143
- command(PimpRepositoryCommand.new)
149
+ step(CreateRepositoryCommand.new(user))
150
+ step(PimpRepositoryCommand.new)
144
151
  end
145
152
  end
146
153
 
@@ -149,9 +156,9 @@ class CreatePimpedRepository2
149
156
 
150
157
  def initialize(user)
151
158
  input_class(NewRepositoryInput)
152
- command(CreateRepositoryCommand.new(user), :builder => RepositoryBuilder)
153
- cmd = PimpRepositoryCommand.new
154
- command(cmd, :builder => cmd)
159
+ step(CreateRepositoryCommand.new(user), :builder => RepositoryBuilder)
160
+ cmd = PimpRepositoryCommandWithBuilder.new
161
+ step(cmd, :builder => cmd)
155
162
  end
156
163
  end
157
164
 
@@ -165,8 +172,26 @@ class CreatePimpedRepository3
165
172
 
166
173
  def initialize(user)
167
174
  input_class(NewRepositoryInput)
168
- command(CreateRepositoryCommand.new(user), :builder => RepositoryBuilder, :validator => NewRepositoryValidator)
169
- cmd = PimpRepositoryCommand.new
170
- command(cmd, :builder => cmd, :validators => [NewRepositoryValidator, PimpedRepositoryValidator])
175
+ cmd = PimpRepositoryCommandWithBuilder.new
176
+ step(CreateRepositoryCommand.new(user), :builder => RepositoryBuilder, :validator => NewRepositoryValidator)
177
+ step(cmd, :builder => cmd, :validators => [NewRepositoryValidator, PimpedRepositoryValidator])
178
+ end
179
+ end
180
+
181
+ class InlineCommand
182
+ include UseCase
183
+
184
+ def initialize
185
+ step(lambda { |params| params[:name] })
186
+ end
187
+ end
188
+
189
+ class ImplicitBuilder
190
+ include UseCase
191
+
192
+ def initialize(user)
193
+ input_class(NewRepositoryInput)
194
+ step(CreateRepositoryCommand.new(user), :builder => RepositoryBuilder)
195
+ step(PimpRepositoryCommandWithBuilder.new)
171
196
  end
172
197
  end
@@ -134,4 +134,18 @@ describe UseCase do
134
134
  refute outcome.success?
135
135
  assert_equal "You cannot win", outcome.failure.errors[:name].join
136
136
  end
137
+
138
+ it "calls command lambda" do
139
+ outcome = InlineCommand.new.execute({ :name => "Dissection" })
140
+
141
+ assert outcome.success?
142
+ assert_equal "Dissection", outcome.result
143
+ end
144
+
145
+ it "implicitly uses command as builder" do
146
+ outcome = ImplicitBuilder.new(@logged_in_user).execute({ :name => "Mr" })
147
+
148
+ assert_equal 42, outcome.result.id
149
+ assert_equal "Mr! (Pimped)", outcome.result.name
150
+ end
137
151
  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.6.0
4
+ version: 0.7.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-04 00:00:00.000000000 Z
12
+ date: 2013-04-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest