use_case 0.6.0 → 0.7.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/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