rails_use_case 0.0.7 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +104 -19
- data/lib/rails/callable.rb +2 -2
- data/lib/rails/service.rb +3 -3
- data/lib/rails/use_case/outcome.rb +11 -2
- data/lib/rails/use_case.rb +44 -6
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ede9e2243fddfc8ed59a395ca97b7f14062c4180e27d1f956f793a435559e90
|
4
|
+
data.tar.gz: 3f56c4fe1f24f74af0226aa5adacbb1341ac042e52b0caf767d61005c8d04b9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e21f3ab1cc85466ec37bccfcc1dae363c478bb4d49a15bdcd5b1e093e5d63d270d5be3befccd66f41f0b81cd79015cdcf41f124f9321fa3d968f17300d7ba1a8
|
7
|
+
data.tar.gz: 34ec8bbeb3e95a0ffce45ef6945d859e71b3122b53c1a01b7d7ad122b9c2d884f681c67a47a355bd0905b4e19ca482defcd7ba572b2aaf209f29920911a53862
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Rails Use Case gem
|
2
2
|
|
3
|
-
Opinionated gem for UseCases and Services in
|
3
|
+
Opinionated gem for UseCases and Services in Rails to keep your Models and Controllers slim.
|
4
|
+
|
5
|
+
Read more: https://dev.to/phortx/pimp-your-rails-application-32d0
|
4
6
|
|
5
7
|
The purpose of a UseCase is to contain reusable high level business logic which would normally be
|
6
8
|
located in the controller. Examples are: Place an item in the cart, create a new user or delete a comment.
|
@@ -20,33 +22,71 @@ gem 'rails_use_case'
|
|
20
22
|
|
21
23
|
The purpose of a UseCase is to contain reusable high level business logic which would normally be
|
22
24
|
located in the controller. It defines a process via `step` definitions. A UseCase takes params
|
23
|
-
and has a outcome, which is
|
25
|
+
and has a outcome, which is either successful or failed. It doesn't have a configuration file and doesn't
|
24
26
|
log anything. Examples are: Place an item in the cart, create a new user or delete a comment.
|
25
27
|
|
28
|
+
The params should always passed as hash and are automatically assigned to instance variables.
|
29
|
+
|
30
|
+
Use Cases should be placed in the `app/use_cases/` directory and the file and class name should start with a verb like `create_blog_post.rb`.
|
31
|
+
|
32
|
+
|
33
|
+
### Steps
|
34
|
+
|
26
35
|
Steps are executed in the defined order. Only when a step succeeds (returns true) the next step will
|
27
36
|
be executed. Steps can be skipped via `if` and `unless`.
|
28
37
|
|
29
|
-
The
|
30
|
-
|
38
|
+
The step either provides the name of a method within the use case or a block.
|
39
|
+
When a block is given, it will be executed. Otherwise the framework will try to
|
40
|
+
call a method with the given name.
|
31
41
|
|
32
|
-
|
42
|
+
You can also have named inline steps: `step :foo, do: -> { ... }` which is
|
43
|
+
equivalent to `step { ... }` but with a given name. An existing method `foo`
|
44
|
+
will not be called in this case but rather the block be executed.
|
33
45
|
|
34
|
-
|
46
|
+
There are also two special steps: `success` and `failure`. Both will end the
|
47
|
+
step chain immediately. `success` will end the use case successfully (like there
|
48
|
+
would be no more steps). And `failure` respectively will end the use case with a
|
49
|
+
error. You should pass the error message and/or code via `message:` and/or
|
50
|
+
`code:` options.
|
51
|
+
|
52
|
+
|
53
|
+
### Failing
|
54
|
+
|
55
|
+
A UseCase fails when a step returns a falsy value or raises an exception.
|
56
|
+
|
57
|
+
For even better error handling, you should let a UseCase fail via the shortcut
|
58
|
+
`fail!()` which actually just raised an `UseCase::Error` but you can provide
|
59
|
+
some additional information. This way you can provide a human readable message
|
60
|
+
with error details and additionally you can pass an error code as symbol, which
|
61
|
+
allows the calling code to do error handling:
|
62
|
+
|
63
|
+
`fail!(message: 'You have called this wrong. Shame on you!', code: :missing_information)`.
|
64
|
+
|
65
|
+
The error_code can also passed as first argument to the `failure` step definition.
|
66
|
+
|
67
|
+
|
68
|
+
### Record
|
69
|
+
|
70
|
+
The UseCase should assign the main record to `@record`. Calling `save!` without argument will try to
|
71
|
+
save that record or raises an exception. Also the `@record` will automatically passed into the outcome.
|
35
72
|
|
36
73
|
|
37
74
|
### Example UseCase
|
38
75
|
|
39
76
|
```ruby
|
40
|
-
class
|
41
|
-
attr_accessor :title, :content, :author, :skip_notifications
|
77
|
+
class BlogPosts::Create < Rails::UseCase
|
78
|
+
attr_accessor :title, :content, :author, :skip_notifications, :publish
|
42
79
|
|
43
80
|
validates :title, presence: true
|
44
81
|
validates :content, presence: true
|
45
82
|
validates :author, presence: true
|
46
83
|
|
47
|
-
|
48
|
-
step
|
49
|
-
step
|
84
|
+
failure :access_denied, message: 'No permission', unless: -> { author.can_publish_blog_posts? }
|
85
|
+
step :build_post
|
86
|
+
step :save!
|
87
|
+
succeed unless: -> { publish }
|
88
|
+
step :publish, do: -> { record.publish! }
|
89
|
+
step :notify_subscribers, unless: -> { skip_notifications }
|
50
90
|
|
51
91
|
|
52
92
|
private def build_post
|
@@ -67,22 +107,67 @@ end
|
|
67
107
|
Example usage of that UseCase:
|
68
108
|
|
69
109
|
```ruby
|
70
|
-
result =
|
110
|
+
result = BlogPosts::Create.perform(
|
71
111
|
title: 'Super Awesome Stuff!',
|
72
112
|
content: 'Lorem Ipsum Dolor Sit Amet',
|
73
|
-
|
113
|
+
author: current_user,
|
74
114
|
skip_notifications: false
|
75
115
|
)
|
76
116
|
|
77
117
|
puts result.inspect
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
#
|
118
|
+
=> {
|
119
|
+
success: false, # Wether the UseCase ended successfully
|
120
|
+
record: BlogPost(...) # The value assigned to @record
|
121
|
+
errors: [], # List of validation errors
|
122
|
+
exception: Rails::UseCase::Error(...), # The exception raised by the UseCase
|
123
|
+
message: "...", # Error message
|
124
|
+
error_code: :save_failed # Error Code
|
125
|
+
}
|
84
126
|
```
|
85
127
|
|
128
|
+
- You can check whether a UseCase was successful via `result.success?`.
|
129
|
+
- You can access the value of `@record` via `result.record`.
|
130
|
+
- You can stop the UseCase process with a error message via throwing `Rails::UseCase::Error` exception.
|
131
|
+
|
132
|
+
|
133
|
+
### Working with the result
|
134
|
+
|
135
|
+
The `perform` method of a UseCase returns an outcome object which contains a
|
136
|
+
`code` field with the error code or `:success` otherwise. This comes handy when
|
137
|
+
using in controller actions for example and is a great way to delegate the
|
138
|
+
business logic part of a controller action to the respective UseCase.
|
139
|
+
Everything the controller has to do, is to setup the params and dispatch the
|
140
|
+
result.
|
141
|
+
|
142
|
+
Given the Example above, here is the same call within a controller action with
|
143
|
+
an case statement.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class BlogPostsController < ApplicationController
|
147
|
+
# ...
|
148
|
+
|
149
|
+
def create
|
150
|
+
parameters = {
|
151
|
+
title: params[:post][:title],
|
152
|
+
content: params[:post][:content],
|
153
|
+
publish: params[:post][:publish],
|
154
|
+
author: current_user
|
155
|
+
}
|
156
|
+
|
157
|
+
case BlogPosts::Create.perform(parameters).code
|
158
|
+
when :success then redirect_to(outcome.record)
|
159
|
+
when :access_denied then render(:new, flash: { error: "Access Denied!" })
|
160
|
+
when :foo then redirect_to('/')
|
161
|
+
else render(:new, flash: { error: outcome.message })
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# ...
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
However this is not rails specific and can be used in any context.
|
170
|
+
|
86
171
|
|
87
172
|
## Behavior
|
88
173
|
|
data/lib/rails/callable.rb
CHANGED
data/lib/rails/service.rb
CHANGED
@@ -70,7 +70,7 @@ module Rails
|
|
70
70
|
@logger = Logger.new(STDOUT)
|
71
71
|
|
72
72
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
73
|
-
"[@service_name] #{msg}"
|
73
|
+
"[#{@service_name}] #{msg}"
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -121,8 +121,8 @@ module Rails
|
|
121
121
|
|
122
122
|
|
123
123
|
# Allows call syntax on class level: SomeService.(some, args)
|
124
|
-
def self.call(
|
125
|
-
new.(
|
124
|
+
def self.call(...)
|
125
|
+
new.(...)
|
126
126
|
end
|
127
127
|
|
128
128
|
# Allows to use rails view helpers
|
@@ -4,19 +4,28 @@ module Rails
|
|
4
4
|
class UseCase
|
5
5
|
# Outcome of a UseCase
|
6
6
|
class Outcome
|
7
|
-
attr_reader :success, :errors, :record, :exception
|
7
|
+
attr_reader :success, :errors, :record, :exception, :message, :code
|
8
8
|
|
9
9
|
# Constructor.
|
10
10
|
# @param success [Boolean] Wether the UseCase was successful.
|
11
11
|
# @param errors [Array|nil] ActiveModel::Validations error.
|
12
12
|
# @param record [ApplicationRecord|nil] The main record of the use case.
|
13
13
|
# @param exception [Rails::UseCase::Error|nil] The error which was raised.
|
14
|
-
|
14
|
+
# @param message [String|nil] The error message.
|
15
|
+
# @param code [Symbol|String|nil] The error code.
|
16
|
+
# rubocop:disable Metrics/ParameterLists
|
17
|
+
def initialize(
|
18
|
+
success:, errors: nil, record: nil, exception: nil,
|
19
|
+
message: nil, code: nil
|
20
|
+
)
|
15
21
|
@success = success
|
16
22
|
@errors = errors
|
17
23
|
@record = record
|
18
24
|
@exception = exception
|
25
|
+
@message = message || exception&.message || errors&.full_messages
|
26
|
+
@code = success ? :success : code&.to_sym
|
19
27
|
end
|
28
|
+
# rubocop:enable Metrics/ParameterLists
|
20
29
|
|
21
30
|
|
22
31
|
# @return [Boolean] Whether the UseCase was successful.
|
data/lib/rails/use_case.rb
CHANGED
@@ -34,14 +34,32 @@ module Rails
|
|
34
34
|
# DSL to define a process step of the UseCase.
|
35
35
|
# You can use if/unless with a lambda in the options
|
36
36
|
# to conditionally skip the step.
|
37
|
+
#
|
37
38
|
# @param name [Symbol]
|
38
39
|
# @param options [Hash]
|
39
|
-
def self.step(name, options = {})
|
40
|
+
def self.step(name = :inline, options = {}, &block)
|
40
41
|
@steps ||= []
|
42
|
+
|
43
|
+
if block_given?
|
44
|
+
options[:do] = block
|
45
|
+
name = :inline
|
46
|
+
end
|
47
|
+
|
41
48
|
@steps << { name: name.to_sym, options: options }
|
42
49
|
end
|
43
50
|
|
44
51
|
|
52
|
+
def self.success(options = {})
|
53
|
+
step :success, options
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def self.failure(code = nil, options = {})
|
58
|
+
options[:code] = code || options[:code] || :failure
|
59
|
+
step :failure, options
|
60
|
+
end
|
61
|
+
|
62
|
+
|
45
63
|
# DSL to include a behavior.
|
46
64
|
# @param mod [Module]
|
47
65
|
def self.with(mod)
|
@@ -52,14 +70,32 @@ module Rails
|
|
52
70
|
# Will run the steps of the use case.
|
53
71
|
def process
|
54
72
|
self.class.steps.each do |step|
|
73
|
+
# Check wether to skip when :if or :unless are set.
|
55
74
|
next if skip_step?(step)
|
56
|
-
next if send(step[:name])
|
57
75
|
|
58
|
-
|
76
|
+
opts = step[:options]
|
77
|
+
name = step[:name]
|
78
|
+
|
79
|
+
# Handle failure and success steps.
|
80
|
+
return true if name == :success
|
81
|
+
|
82
|
+
fail!(code: opts[:code], message: opts[:message]) if name == :failure
|
83
|
+
|
84
|
+
# Run the lambda, when :do is set. Otherwise call the method.
|
85
|
+
next if opts[:do] ? instance_eval(&opts[:do]) : send(name)
|
86
|
+
|
87
|
+
# result is false, so we have a failure.
|
88
|
+
fail! code: :step_false, message: "Step '#{name}' returned false"
|
59
89
|
end
|
60
90
|
end
|
61
91
|
|
62
92
|
|
93
|
+
def fail!(code: nil, message: 'Failed')
|
94
|
+
@error_code = code
|
95
|
+
raise UseCase::Error, message
|
96
|
+
end
|
97
|
+
|
98
|
+
|
63
99
|
# Checks whether to skip a step.
|
64
100
|
# @param step [Hash]
|
65
101
|
def skip_step?(step)
|
@@ -93,7 +129,7 @@ module Rails
|
|
93
129
|
def break_when_invalid!
|
94
130
|
return true if valid?
|
95
131
|
|
96
|
-
|
132
|
+
fail! code: :validation_failed, message: errors.full_messages.join(', ')
|
97
133
|
end
|
98
134
|
|
99
135
|
|
@@ -119,7 +155,7 @@ module Rails
|
|
119
155
|
message: record.errors.full_messages.join(', ')
|
120
156
|
)
|
121
157
|
|
122
|
-
|
158
|
+
fail! code: :save_failed, message: errors.full_messages.join(', ')
|
123
159
|
end
|
124
160
|
|
125
161
|
|
@@ -140,7 +176,9 @@ module Rails
|
|
140
176
|
success: false,
|
141
177
|
record: @record,
|
142
178
|
errors: errors,
|
143
|
-
exception: error
|
179
|
+
exception: error,
|
180
|
+
message: error.message,
|
181
|
+
code: @error_code
|
144
182
|
)
|
145
183
|
end
|
146
184
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_use_case
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Klein
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.1.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 6.1.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: railties
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.1.3
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 6.1.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler-audit
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -195,7 +195,7 @@ homepage: https://github.com/phortx/rails-use-case
|
|
195
195
|
licenses:
|
196
196
|
- MIT
|
197
197
|
metadata: {}
|
198
|
-
post_install_message:
|
198
|
+
post_install_message:
|
199
199
|
rdoc_options: []
|
200
200
|
require_paths:
|
201
201
|
- lib
|
@@ -210,8 +210,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
210
|
- !ruby/object:Gem::Version
|
211
211
|
version: '0'
|
212
212
|
requirements: []
|
213
|
-
rubygems_version: 3.
|
214
|
-
signing_key:
|
213
|
+
rubygems_version: 3.1.2
|
214
|
+
signing_key:
|
215
215
|
specification_version: 4
|
216
216
|
summary: Rails UseCase and Service classes
|
217
217
|
test_files: []
|