rails_use_case 0.0.7 → 0.0.11
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.
- 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: []
|