rails_use_case 0.0.6 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54ea39257a8eca46fc85b8e8f6b7cd8523f953b9ec27dd25a3efe52f78f4fc18
4
- data.tar.gz: e9fb8c9f2302c110914fc071aba9646a665f02e8218abf80b5fe0e7d4a916f35
3
+ metadata.gz: 96f24873c4a93a2084c377f236b387605286fd7cbae9915fc53fd85d8f5b07c1
4
+ data.tar.gz: 7b2d97ab8d3ac97e3cbe5a9b78ec6f0e1590a8f7692bce3a9b207e5891d40577
5
5
  SHA512:
6
- metadata.gz: 2ebdad5670ad1543263dff016033b6158567220a6c65d982aa5f839c89ec9c635b7b421e10484439e8fff3ce23915250b5d11c649338e2862c09b46b592ab299
7
- data.tar.gz: ffb2d162f29baaf3574435b138748f1da4ae85414015fa0a9accc5de7e486b18f26ed5f0ca8daaf8d019f92337de59126df0f4ad5b07889c867c2c384156c209
6
+ metadata.gz: dca166f3929ce6782995814218a95cd9d485f2c83ce4f7cd0282d0aa7e3fc83d58c79a65a70a407c8157883f8d6833c371dfc2b64948ba8c9f74aec368b6fe79
7
+ data.tar.gz: 03b6342cdbd9c244a07ffd034d9873fff5baaef554dd69a1da07f406f4e6dff27da095f1210a4914398b7bf1ba55c005586707c9b59c3e35b467857507a90eb6
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Rails Use Case gem
2
2
 
3
- Opinionated gem for UseCases and Services in rails to keep models and controllers slim.
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 successfully or failed. It doesn't have a configuration file and doesn't
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 UseCase should assign the main record to `@record`. Calling save! without argument will try to
30
- save that record or raises an exception. Also the `@record` will automatically passed into the outcome.
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
- The params should always passed as hash and are automatically assigned to instance variables.
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
- 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`.
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
77
  class CreateBlogPost < Rails::UseCase
41
- attr_accessor :title, :content, :author, :skip_notifications
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
- step :build_post
48
- step :save!
49
- step :notify_subscribers, unless: -> { skip_notifications }
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
@@ -75,14 +115,21 @@ result = CreateBlogPost.perform(
75
115
  )
76
116
 
77
117
  puts result.inspect
78
- # => {
79
- # success: true,
80
- # record: BlogPost(...)
81
- # errors: [],
82
- # error: nil
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
+
86
133
 
87
134
  ## Behavior
88
135
 
@@ -8,8 +8,8 @@ module Rails
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  class_methods do
11
- def call(*args)
12
- new.call(*args)
11
+ def call(...)
12
+ new.call(...)
13
13
  end
14
14
 
15
15
  alias_method :perform, :call
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(*args)
125
- new.(*args)
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
- def initialize(success:, errors: nil, record: nil, exception: nil)
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 = code&.to_sym
19
27
  end
28
+ # rubocop:enable Metrics/ParameterLists
20
29
 
21
30
 
22
31
  # @return [Boolean] Whether the UseCase was successful.
@@ -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
- raise UseCase::Error, "Step #{step[:name]} returned false"
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
- raise UseCase::Error, errors.full_messages.join(', ')
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
- raise UseCase::Error, "#{record.class.name} is not valid"
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.6
4
+ version: 0.0.10
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: 2020-04-05 00:00:00.000000000 Z
11
+ date: 2021-10-04 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: 4.1.0
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: 4.1.0
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: 4.1.0
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: 4.1.0
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.0.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: []