rails_use_case 0.0.9 → 0.0.10

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 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