rails_use_case 0.0.9 → 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: 56f84c351e857b7fd1ac1963d18138f96608376c4952fe3a12e8e7db2dbc0152
4
- data.tar.gz: 46de14ac109e227a1a36dfdbb7f75a4e0595652c3d0e59189788cbbe8ea1ccd3
3
+ metadata.gz: 96f24873c4a93a2084c377f236b387605286fd7cbae9915fc53fd85d8f5b07c1
4
+ data.tar.gz: 7b2d97ab8d3ac97e3cbe5a9b78ec6f0e1590a8f7692bce3a9b207e5891d40577
5
5
  SHA512:
6
- metadata.gz: 990f15747a27962ea59d7428416c8b525cf297e5e18f592fbaf3feeed8d4eecfe231bc7608acaa6516bc6273973ab651639a272c8b885f8b672fc4cb9b699ecf
7
- data.tar.gz: '009f04d247c31452fec07815ff7be8c6af90ba3e5e03ef08f3ac4fd4b87cfcd4103cf44963ffc2a09f8791e8f9430327dd7db98f0d2e11d06feba3b646cbabd1'
6
+ metadata.gz: dca166f3929ce6782995814218a95cd9d485f2c83ce4f7cd0282d0aa7e3fc83d58c79a65a70a407c8157883f8d6833c371dfc2b64948ba8c9f74aec368b6fe79
7
+ data.tar.gz: 03b6342cdbd9c244a07ffd034d9873fff5baaef554dd69a1da07f406f4e6dff27da095f1210a4914398b7bf1ba55c005586707c9b59c3e35b467857507a90eb6
data/README.md CHANGED
@@ -25,30 +25,68 @@ located in the controller. It defines a process via `step` definitions. A UseCas
25
25
  and has a outcome, which is either successful or failed. It doesn't have a configuration file and doesn't
26
26
  log anything. Examples are: Place an item in the cart, create a new user or delete a comment.
27
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
+
28
35
  Steps are executed in the defined order. Only when a step succeeds (returns true) the next step will
29
36
  be executed. Steps can be skipped via `if` and `unless`.
30
37
 
31
- The UseCase should assign the main record to `@record`. Calling save! without argument will try to
32
- 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.
33
41
 
34
- 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.
35
45
 
36
- 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.
37
72
 
38
73
 
39
74
  ### Example UseCase
40
75
 
41
76
  ```ruby
42
77
  class CreateBlogPost < Rails::UseCase
43
- attr_accessor :title, :content, :author, :skip_notifications
78
+ attr_accessor :title, :content, :author, :skip_notifications, :publish
44
79
 
45
80
  validates :title, presence: true
46
81
  validates :content, presence: true
47
82
  validates :author, presence: true
48
83
 
49
- step :build_post
50
- step :save!
51
- 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 }
52
90
 
53
91
 
54
92
  private def build_post
@@ -77,12 +115,14 @@ result = CreateBlogPost.perform(
77
115
  )
78
116
 
79
117
  puts result.inspect
80
- # => {
81
- # success: true,
82
- # record: BlogPost(...)
83
- # errors: [],
84
- # error: nil
85
- # }
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
+ }
86
126
  ```
87
127
 
88
128
  - You can check whether a UseCase was successful via `result.success?`.
@@ -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.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Klein
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-13 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: 6.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: 6.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: 6.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: 6.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