f_service 0.1.1 → 0.2.0

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: 2a994a8e96a8aa3f6db41ba6f9dcb358a716b2c1cdce8e82c8b92f70458e941c
4
- data.tar.gz: 99906aece6b382536fcda4ccb21e3874ccc755d6dfc2418fe98dd2019504513d
3
+ metadata.gz: cb3dfcc7d2f09fe4bd1cabca95a9520b06badf64e1d8fd7a238e0b904cdbbfe9
4
+ data.tar.gz: 4f71a8ecfb48c4b928b7240d63a63469a36f46635b081f221d064b0603d73be7
5
5
  SHA512:
6
- metadata.gz: 0430b236523a17dae5c2afeb3db37d8354fbdce15f96789121ec184a41004242ccce40bd2a73d742f49a5312efa8dda9e6da207ceff41ef9eafabd03794e88d9
7
- data.tar.gz: 3d7c7439d7283f69d494c8e402b2c08f906073c4089d7e031e6b7a00e40b66577ce7aac860b8ed49378335d4be9f867dfeb5a982a2fd6cd9bcb1b34a1094e839
6
+ metadata.gz: 1e3378dc3e210fd41aa994270792eb5a2dbf003f351964d81e08c4505404bc90b765fd33c4d49d39893a83d64dded6d1a5efa72bc4699ec23d7cd6273535e22d
7
+ data.tar.gz: 175330f3f2fbccdcc9b74f93b69ffa0a4002699139b63d19208da97327722346b154f83d46bde3f71bf23016ef23065727cb5c876fb45ed076375652da70e5b7
@@ -11,12 +11,12 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby: [2.4, 2.5, 2.6, 2.7]
14
+ ruby: [2.5, 2.6, 2.7, '3.0']
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v2
18
18
  - name: Set up Ruby ${{ matrix.ruby }}
19
- uses: actions/setup-ruby@v1
19
+ uses: ruby/setup-ruby@v1
20
20
  with:
21
21
  ruby-version: ${{ matrix.ruby }}
22
22
  - name: Build and test with Rake
data/.rubocop.yml CHANGED
@@ -12,4 +12,8 @@ Metrics/BlockLength:
12
12
  - "spec/**/*"
13
13
 
14
14
  Style/DocumentationMethod:
15
- Enabled: true
15
+ Enabled: true
16
+
17
+ Naming/MethodName:
18
+ Exclude:
19
+ - lib/f_service/base.rb
data/CHANGELOG.md CHANGED
@@ -4,16 +4,40 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## Unreleased
8
- _Nothing here_
7
+ ## Unreleased (master)
8
+ <!-- ### Added -->
9
+ <!-- ### Changed -->
10
+ <!-- ### Removed -->
11
+ ---
12
+
13
+ ## 0.2.0
14
+ ### Added
15
+ - Add `and_catch` as alias for `then` (partially fix [#23](https://github.com/Fretadao/f_service/issues/23)).
16
+ - Add `catch` to `Failure` and `Success`. It acts as an inverted `then` and has a `or_else` alias.
17
+ - Add support to custom `data` property to be passed when calling `Base#Check`.
18
+ - Add support to multiple type checks on `Result#on_success` and `Result#on_failure` hooks.
19
+ - Yields result type on blocks (`then`, `on_success` and `on_failure`).
20
+ - Add type check on `Result#on_success` and `Result#on_failure` hooks.
21
+ - Add method `Base#Try`. It wraps exceptions in Failures.
22
+ - Add method `Base#Check`. It converts booleans to Results.
23
+ - Add methods `#Success(type, data:)` and `#Failure(type, data:)` on `FService::Base`.
24
+ These methods allow defining the type and value of the Result object.
25
+ - Allow adding types on `Result`s.
26
+ - Add `#on_success` and `#on_failure` hooks on Result objects.
27
+ - Link to Changelog on gemspec.
28
+
29
+ ### Changed
30
+ - **[Deprecation]** Mark `Base#result` as deprecated. They will be removed on the next release. Use the `Base#Check` instead.
31
+ - **[Deprecation]** Mark `Base#success` and `Base#failure` as deprecated. They will be removed on the next release. Use the `Base#Success` and `Base#Failure` instead.
32
+ - **[Deprecation]** Mark `Result#on` as deprecated. It will be removed on the next release. Use the`Result#on_success` and/or `Result#on_failure` hooks instead.
9
33
 
10
34
  ## 0.1.1
11
35
  ### Added
12
36
  First usable version with:
13
- - Result based services;
14
- - Type check on results;
15
- - Pattern matching with `#call`ables;
16
- - Safe chaining calls with `#then`;
37
+ - Result based services
38
+ - Type check on results
39
+ - Pattern matching with `#call`ables
40
+ - Safe chaining calls with `#then`
17
41
 
18
42
  ## 0.1.0
19
- - **Yanked**
43
+ - **Yanked**
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- f_service (0.1.1)
4
+ f_service (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,16 +10,18 @@ GEM
10
10
  backport (1.1.2)
11
11
  benchmark (0.1.0)
12
12
  diff-lcs (1.3)
13
- docile (1.3.2)
13
+ docile (1.3.5)
14
14
  e2mmap (0.1.0)
15
15
  jaro_winkler (1.5.4)
16
16
  maruku (0.7.3)
17
- mini_portile2 (2.4.0)
18
- nokogiri (1.10.9)
19
- mini_portile2 (~> 2.4.0)
17
+ mini_portile2 (2.5.0)
18
+ nokogiri (1.11.1)
19
+ mini_portile2 (~> 2.5.0)
20
+ racc (~> 1.4)
20
21
  parallel (1.19.1)
21
22
  parser (2.7.1.1)
22
23
  ast (~> 2.4.0)
24
+ racc (1.5.2)
23
25
  rainbow (3.0.0)
24
26
  rake (13.0.1)
25
27
  reverse_markdown (1.4.0)
@@ -49,10 +51,12 @@ GEM
49
51
  rubocop-rspec (1.38.1)
50
52
  rubocop (>= 0.68.1)
51
53
  ruby-progressbar (1.10.1)
52
- simplecov (0.18.2)
54
+ simplecov (0.21.2)
53
55
  docile (~> 1.1)
54
56
  simplecov-html (~> 0.11)
55
- simplecov-html (0.12.0)
57
+ simplecov_json_formatter (~> 0.1)
58
+ simplecov-html (0.12.3)
59
+ simplecov_json_formatter (0.1.2)
56
60
  solargraph (0.38.6)
57
61
  backport (~> 1.1)
58
62
  benchmark
@@ -87,4 +91,4 @@ DEPENDENCIES
87
91
  yard
88
92
 
89
93
  BUNDLED WITH
90
- 2.0.2
94
+ 2.1.4
data/README.md CHANGED
@@ -1,6 +1,19 @@
1
- ![CI](https://github.com/Fretadao/f_service/workflows/Ruby/badge.svg)
2
-
3
- # FService
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Fretadao/f_service/master/logo.png" height="150">
3
+
4
+ <h1 align="center">FService</h1>
5
+
6
+ <p align="center">
7
+ <i>Simpler, safer and more composable operations</i>
8
+ <br>
9
+ <br>
10
+ <img src="https://img.shields.io/gem/v/f_service">
11
+ <img src="https://github.com/Fretadao/f_service/workflows/Ruby/badge.svg">
12
+ <a href="https://github.com/Fretadao/f_service/blob/master/LICENSE">
13
+ <img src="https://img.shields.io/github/license/Fretadao/f_service.svg" alt="License">
14
+ </a>
15
+ </p>
16
+ </p>
4
17
 
5
18
  FService is a small gem that provides a base class for your services (aka operations).
6
19
  The goal is to make services simpler, safer, and more composable.
@@ -23,7 +36,9 @@ Or install it yourself as:
23
36
  $ gem install f_service
24
37
 
25
38
  ## Usage
39
+
26
40
  ### Creating your service
41
+
27
42
  To start using it, you have to create your service class inheriting from FService::Base.
28
43
 
29
44
  ```ruby
@@ -32,6 +47,7 @@ end
32
47
  ```
33
48
 
34
49
  Now, define your initializer to setup data.
50
+
35
51
  ```ruby
36
52
  class User::Create < FService::Base
37
53
  def initialize(name:)
@@ -41,23 +57,25 @@ end
41
57
  ```
42
58
 
43
59
  The next step is writing the `#run` method, which is where the work should be done.
44
- Use the methods `#success` and `#failure` to handle your return values. The return can be any value.
60
+ Use the methods `#Success` and `#Failure` to handle your return values.
61
+ You can optionally specify a type and a value for your result.
45
62
 
46
63
  ```ruby
47
64
  class User::Create < FService::Base
48
65
  # ...
49
66
  def run
50
- return failure("No name given") if @name.nil?
67
+ return Failure(:no_name) if @name.nil?
51
68
 
52
69
  user = UserRepository.create(name: @name)
53
70
  if user.valid?
54
- success(status: "User successfully created!", data: user)
71
+ Success(:created, data: user)
55
72
  else
56
- failure(status: "User could not be created!", data: user.errors)
73
+ Failure(:creation_failed, data: user.errors)
57
74
  end
58
75
  end
59
76
  end
60
77
  ```
78
+
61
79
  > Remember, you **have** to return an `FService::Result` at the end of your services.
62
80
 
63
81
  ### Using your service
@@ -70,7 +88,9 @@ User::Create.(name: name)
70
88
  User::Create.call(name: name)
71
89
  ```
72
90
 
73
- > We do **not** recommend manually initializing your service because it **will not** type check your result (and you could lose nice features like [pattern matching](#pattern-matching) and [service chaining](#chaining-services))!
91
+ > We do **not** recommend manually initializing and running your service because it **will not**
92
+ > type check your result (and you could lose nice features like [pattern
93
+ > matching](#pattern-matching) and [service chaining](#chaining-services))!
74
94
 
75
95
  ### Using the result
76
96
 
@@ -91,24 +111,63 @@ class UsersController < BaseController
91
111
  end
92
112
  end
93
113
  ```
114
+
94
115
  > Note that you're not limited to using services inside controllers. They're just PORO's (Play Old Ruby Objects), so you can use in controllers, models, etc. (even other services!).
95
116
 
96
117
  ### Pattern matching
97
- The code above could be rewritten using the `#on` matcher too. It works similar to pattern matching:
118
+
119
+ The code above could be rewritten using the `#on_success` and `#on_failure` hooks. They work similar to pattern matching:
120
+
121
+ ```ruby
122
+ class UsersController < BaseController
123
+ def create
124
+ User::Create.(user_params)
125
+ .on_success { |value| return json_success(value) }
126
+ .on_failure { |error| return json_error(error) }
127
+ end
128
+ end
129
+ ```
130
+
131
+ > You can ignore any of the callbacks, if you want to.
132
+
133
+ Going further, you can match the Result type, in case you want to handle them differently:
98
134
 
99
135
  ```ruby
100
136
  class UsersController < BaseController
101
137
  def create
102
- User::Create.(user_params).on(
103
- success: ->(value) { return json_success(value) },
104
- failure: ->(error) { return json_error(error) }
105
- )
138
+ User::Create.(user_params)
139
+ .on_success(:user_created) { |value| return json_success(value) }
140
+ .on_success(:user_already_exists) { |value| return json_success(value) }
141
+ .on_failure(:invalid_data) { |error| return json_error(error) }
142
+ .on_failure(:critical_error) do |error|
143
+ MyLogger.report_failure(error)
144
+
145
+ return json_error(error)
146
+ end
147
+ end
148
+ end
149
+ ```
150
+
151
+ It's possible to provide multiple types to the hooks too. If the result type matches any of the given types,
152
+ the hook will run.
153
+
154
+ ```ruby
155
+ class UsersController < BaseController
156
+ def create
157
+ User::Create.(user_params)
158
+ .on_success(:user_created, :user_already_exists) { |value| return json_success(value) }
159
+ .on_failure(:invalid_data) { |error| return json_error(error) }
160
+ .on_failure(:critical_error) do |error|
161
+ MyLogger.report_failure(error)
162
+
163
+ return json_error(error)
164
+ end
106
165
  end
107
166
  end
108
167
  ```
109
- > You can use any object that responds to #call, not only Lambdas.
110
168
 
111
169
  ### Chaining services
170
+
112
171
  Since all services return Results, you can chain service calls making a data pipeline.
113
172
  If some step fails, it will short circuit the call chain.
114
173
 
@@ -116,8 +175,8 @@ If some step fails, it will short circuit the call chain.
116
175
  class UsersController < BaseController
117
176
  def create
118
177
  result = User::Create.(user_params)
119
- .then { |user| User::Login.(user) }
120
- .then { |user| User::SendWelcomeEmail.(user) }
178
+ .and_then { |user| User::Login.(user) }
179
+ .and_then { |user| User::SendWelcomeEmail.(user) }
121
180
 
122
181
  if result.successful?
123
182
  json_success(result.value)
@@ -128,6 +187,53 @@ class UsersController < BaseController
128
187
  end
129
188
  ```
130
189
 
190
+ You can use the `.to_proc` method on FService::Base to avoid explicit inputs when chaining services:
191
+
192
+ ```ruby
193
+ class UsersController < BaseController
194
+ def create
195
+ result = User::Create.(user_params)
196
+ .and_then(&User::Login)
197
+ .and_then(&User::SendWelcomeEmail)
198
+ # ...
199
+ end
200
+ end
201
+ ```
202
+
203
+ ### `Check` and `Try`
204
+
205
+ You can use `Check` to converts a boolean to a Result, truthy values map to `Success`, and falsey values map to `Failures`:
206
+
207
+ ```ruby
208
+ Check(:math_works) { 1 < 2 }
209
+ # => #<Success @value=true, @type=:math_works>
210
+
211
+ Check(:math_works) { 1 > 2 }
212
+ # => #<Failure @error=false, @type=:math_works>
213
+ ```
214
+
215
+ `Try` transforms an exception into a `Failure` if some exception is raised for the given block. You can specify which exception class to watch for
216
+ using the parameter `catch`.
217
+
218
+ ```ruby
219
+ class IHateEvenNumbers < FService::Base
220
+ def run
221
+ Try(:rand_int) do
222
+ n = rand(1..10)
223
+ raise "Yuck! It's a #{n}" if n.even?
224
+
225
+ n
226
+ end
227
+ end
228
+ end
229
+
230
+ IHateEvenNumbers.call
231
+ # => #<Success @value=9, @type=:rand_int>
232
+
233
+ IHateEvenNumbers.call
234
+ # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @type=:rand_int>
235
+ ```
236
+
131
237
  ## API Docs
132
238
 
133
239
  You can access the API docs [here](https://www.rubydoc.info/gems/f_service/).
data/f_service.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.metadata['homepage_uri'] = spec.homepage
24
24
  spec.metadata['source_code_uri'] = 'https://github.com/Fretadao/f_service'
25
25
  spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/f_service'
26
- # spec.metadata['changelog_uri'] = "TODO: Put your gem's CHANGELOG.md URL here."
26
+ spec.metadata['changelog_uri'] = 'https://github.com/Fretadao/f_service/blob/master/CHANGELOG.md'
27
27
 
28
28
  # Specify which files should be added to the gem when it is released.
29
29
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
data/lib/f_service.rb CHANGED
@@ -6,4 +6,12 @@ require_relative 'f_service/base'
6
6
  #
7
7
  # @api public
8
8
  module FService
9
+ # Marks a method as deprecated
10
+ #
11
+ # @api private
12
+ def self.deprecate!(name:, alternative:)
13
+ warn "[DEPRECATED] #{name} is deprecated; " \
14
+ "use #{alternative} instead. " \
15
+ 'It will be removed on the next release.'
16
+ end
9
17
  end
@@ -10,20 +10,44 @@ module FService
10
10
  #
11
11
  # @abstract
12
12
  class Base
13
- # Initializes and runs a new service.
14
- #
15
- # @example
16
- # User::UpdateName.(user: user, new_name: new_name)
17
- # # or
18
- # User::UpdateName.call(user: user, new_name: new_name)
19
- #
20
- # @note this method shouldn't be overridden in the subclasses
21
- # @return [FService::Result::Success, FService::Result::Failure]
22
- def self.call(*params)
23
- result = new(*params).run
24
- raise(FService::Error, 'Services must return a Result') unless result.is_a? Result::Base
13
+ class << self
14
+ # Initializes and runs a new service.
15
+ #
16
+ # @example
17
+ # User::UpdateName.(user: user, new_name: new_name)
18
+ # # or
19
+ # User::UpdateName.call(user: user, new_name: new_name)
20
+ #
21
+ # @note this method shouldn't be overridden in the subclasses
22
+ # @return [Result::Success, Result::Failure]
23
+ def call(*args)
24
+ result = new(*args).run
25
+ raise(FService::Error, 'Services must return a Result') unless result.is_a? Result::Base
26
+
27
+ result
28
+ end
25
29
 
26
- result
30
+ ruby2_keywords :call if respond_to?(:ruby2_keywords, true)
31
+
32
+ # Allows running a service without explicit giving params.
33
+ # This is useful when chaining services or mapping inputs to be processed.
34
+ #
35
+ # @example
36
+ # # Assuming all classes here subclass FService::Base:
37
+ #
38
+ # User::Create
39
+ # .and_then(&User::Login)
40
+ # .and_then(&SendWelcomeEmail)
41
+ #
42
+ # # Mapping inputs:
43
+ #
44
+ # [{ n:1 }, { n: 2 }].map(&DoubleNumber).map(&:value)
45
+ # # => [2, 4]
46
+ #
47
+ # @return [Proc]
48
+ def to_proc
49
+ proc { |args| call(**args) }
50
+ end
27
51
  end
28
52
 
29
53
  # This method is where the main work of your service must be.
@@ -38,18 +62,18 @@ module FService
38
62
  # end
39
63
  #
40
64
  # def run
41
- # return failure('Missing user') if user.nil?
65
+ # return Failure(:missing_user) if user.nil?
42
66
  #
43
67
  # if @user.update(name: @new_name)
44
- # success(status: 'User successfully updated!', data: user)
68
+ # Success(:created, data: user)
45
69
  # else
46
- # failure(status: 'User could not be updated.', data: user.errors)
70
+ # Failure(:creation_failed, data: user.errors)
47
71
  # end
48
72
  # end
49
73
  # end
50
74
  #
51
75
  # @note this method SHOULD be overridden in the subclasses
52
- # @return [FService::Result::Success, FService::Result::Failure]
76
+ # @return [Result::Success, Result::Failure]
53
77
  def run
54
78
  raise NotImplementedError, 'Services must implement #run'
55
79
  end
@@ -72,9 +96,137 @@ module FService
72
96
  # end
73
97
  # end
74
98
  #
75
- # @return [FService::Result::Success] - a successful operation
99
+ # @deprecated Use {#Success} instead.
100
+ # @return [Result::Success] a successful operation
76
101
  def success(data = nil)
77
- FService::Result::Success.new(data)
102
+ FService.deprecate!(
103
+ name: "#{self.class}##{__method__}",
104
+ alternative: '#Success'
105
+ )
106
+
107
+ Result::Success.new(data)
108
+ end
109
+
110
+ # Returns a successful result.
111
+ # You can optionally specify a type and a value for your result.
112
+ # You'll probably want to return this inside {#run}.
113
+ #
114
+ #
115
+ # @example
116
+ # def run
117
+ # Success()
118
+ # # => #<Success @value=nil, @type=nil>
119
+ #
120
+ # Success(:ok)
121
+ # # => #<Success @value=nil, @type=:ok>
122
+ #
123
+ # Success(data: 10)
124
+ # # => #<Success @value=10, @type=nil>
125
+ #
126
+ # Success(:ok, data: 10)
127
+ # # => #<Success @value=10, @type=:ok>
128
+ # end
129
+ #
130
+ # @param type the Result type
131
+ # @param data the result value
132
+ # @return [Result::Success] a successful result
133
+ def Success(type = nil, data: nil)
134
+ Result::Success.new(data, type)
135
+ end
136
+
137
+ # Returns a failed result.
138
+ # You can optionally specify a type and a value for your result.
139
+ # You'll probably want to return this inside {#run}.
140
+ #
141
+ #
142
+ # @example
143
+ # def run
144
+ # Failure()
145
+ # # => #<Failure @error=nil, @type=nil>
146
+ #
147
+ # Failure(:not_a_number)
148
+ # # => #<Failure @error=nil, @type=:not_a_number>
149
+ #
150
+ # Failure(data: "10")
151
+ # # => #<Failure @error="10", @type=nil>
152
+ #
153
+ # Failure(:not_a_number, data: "10")
154
+ # # => #<Failure @error="10", @type=:not_a_number>
155
+ # end
156
+ #
157
+ # @param type the Result type
158
+ # @param data the result value
159
+ # @return [Result::Failure] a failed result
160
+ def Failure(type = nil, data: nil)
161
+ Result::Failure.new(data, type)
162
+ end
163
+
164
+ # Converts a boolean to a Result.
165
+ # Truthy values map to Success, and falsey values map to Failures.
166
+ # You can optionally provide a type for the result.
167
+ # The result value defaults as the evaluated value of the given block.
168
+ # If you want another value you can pass it through the `data:` argument.
169
+ #
170
+ # @example
171
+ # class CheckMathWorks < FService::Base
172
+ # def run
173
+ # Check(:math_works) { 1 < 2 }
174
+ # # => #<Success @value=true, @type=:math_works>
175
+ #
176
+ # Check(:math_works) { 1 > 2 }
177
+ # # => #<Failure @error=false, @type=:math_works>
178
+ #
179
+ # Check(:math_works, data: 1 + 2) { 1 > 2 }
180
+ # # => #<Failure @type=:math_works, @error=3>
181
+ # end
182
+ #
183
+ # Check(:math_works, data: 1 + 2) { 1 < 2 }
184
+ # # => #<Success @type=:math_works, @value=3>
185
+ # end
186
+ # end
187
+ #
188
+ # @param type the Result type
189
+ # @return [Result::Success, Result::Failure] a Result from the boolean expression
190
+ def Check(type = nil, data: nil)
191
+ res = yield
192
+
193
+ final_data = data || res
194
+
195
+ res ? Success(type, data: final_data) : Failure(type, data: final_data)
196
+ end
197
+
198
+ # If the given block raises an exception, it wraps it in a Failure.
199
+ # Otherwise, maps the block value in a Success object.
200
+ # You can specify which exceptions to watch for.
201
+ # It's possible to provide a type for the result too.
202
+ #
203
+ # @example
204
+ # class IHateEvenNumbers < FService::Base
205
+ # def run
206
+ # Try(:rand_int) do
207
+ # n = rand(1..10)
208
+ # raise "Yuck! It's a #{n}" if n.even?
209
+ #
210
+ # n
211
+ # end
212
+ # end
213
+ # end
214
+ #
215
+ # IHateEvenNumbers.call
216
+ # # => #<Success @value=9, @type=:rand_int>
217
+ #
218
+ # IHateEvenNumbers.call
219
+ # # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @type=:rand_int>
220
+ #
221
+ # @param type the Result type
222
+ # @param catch the exception list to catch
223
+ # @return [Result::Success, Result::Failure] a result from the boolean expression
224
+ def Try(type = nil, catch: StandardError)
225
+ res = yield
226
+
227
+ Success(type, data: res)
228
+ rescue *catch => e
229
+ Failure(type, data: e)
78
230
  end
79
231
 
80
232
  # Returns a failed operation.
@@ -93,12 +245,18 @@ module FService
93
245
  # end
94
246
  # end
95
247
  #
96
- # @return [FService::Result::Failure] - a failed operation
248
+ # @deprecated Use {#Failure} instead.
249
+ # @return [Result::Failure] a failed operation
97
250
  def failure(data = nil)
98
- FService::Result::Failure.new(data)
251
+ FService.deprecate!(
252
+ name: "#{self.class}##{__method__}",
253
+ alternative: '#Failure'
254
+ )
255
+
256
+ Result::Failure.new(data)
99
257
  end
100
258
 
101
- # Return either {FService::Result::Failure Success} or {FService::Result::Failure Failure}
259
+ # Return either {Result::Failure Success} or {Result::Failure Failure}
102
260
  # given the condition.
103
261
  #
104
262
  # @example
@@ -120,8 +278,14 @@ module FService
120
278
  # end
121
279
  # end
122
280
  #
123
- # @return [FService::Result::Success, FService::Result::Failure]
281
+ # @deprecated Use {#Check} instead.
282
+ # @return [Result::Success, Result::Failure]
124
283
  def result(condition, data = nil)
284
+ FService.deprecate!(
285
+ name: "#{self.class}##{__method__}",
286
+ alternative: '#Check'
287
+ )
288
+
125
289
  condition ? success(data) : failure(data)
126
290
  end
127
291
  end
@@ -36,14 +36,93 @@ module FService
36
36
  #
37
37
  # @param success [#call] a lambda (or anything that responds to #call) to run on success
38
38
  # @param failure [#call] a lambda (or anything that responds to #call) to run on failure
39
+ # @deprecated Use {#on_success} and/or {#on_failure} instead.
39
40
  # @api public
40
41
  def on(success:, failure:)
42
+ FService.deprecate!(
43
+ name: "#{self.class}##{__method__}",
44
+ alternative: '#on_success and/or #on_failure'
45
+ )
46
+
41
47
  if successful?
42
48
  success.call(value)
43
49
  else
44
50
  failure.call(error)
45
51
  end
46
52
  end
53
+
54
+ # This hook runs if the result is successful.
55
+ # Can receive one or more types to be checked before running the given block.
56
+ #
57
+ # @example
58
+ # class UsersController < BaseController
59
+ # def update
60
+ # User::Update.(user: user)
61
+ # .on_success(:type, :type2) { return json_success({ status: :ok }) } # run only if type matches
62
+ # .on_success { |value| return json_success(value) }
63
+ # .on_failure { |error| return json_error(error) } # this won't run
64
+ # end
65
+ #
66
+ # private
67
+ #
68
+ # def user
69
+ # @user ||= User.find_by!(slug: params[:slug])
70
+ # end
71
+ # end
72
+ #
73
+ # @yieldparam value value of the failure object
74
+ # @yieldparam type type of the failure object
75
+ # @return [Success, Failure] the original Result object
76
+ # @api public
77
+ def on_success(*target_types)
78
+ yield(*to_ary) if successful? && expected_type?(target_types)
79
+
80
+ self
81
+ end
82
+
83
+ # This hook runs if the result is failed.
84
+ # Can receive one or more types to be checked before running the given block.
85
+ #
86
+ # @example
87
+ # class UsersController < BaseController
88
+ # def update
89
+ # User::Update.(user: user)
90
+ # .on_success { |value| return json_success(value) } # this won't run
91
+ # .on_failure(:type, :type2) { |error| return json_error(error) } # runs only if type matches
92
+ # .on_failure { |error| return json_error(error) }
93
+ # end
94
+ #
95
+ # private
96
+ #
97
+ # def user
98
+ # @user ||= User.find_by!(slug: params[:slug])
99
+ # end
100
+ # end
101
+ #
102
+ # @yieldparam value value of the failure object
103
+ # @yieldparam type type of the failure object
104
+ # @return [Success, Failure] the original Result object
105
+ # @api public
106
+ def on_failure(*target_types)
107
+ yield(*to_ary) if failed? && expected_type?(target_types)
108
+
109
+ self
110
+ end
111
+
112
+ # Splits the result object into its components.
113
+ #
114
+ # @return [Array] value and type of the result object
115
+ def to_ary
116
+ data = successful? ? value : error
117
+
118
+ [data, type]
119
+ end
120
+
121
+ private
122
+
123
+ def expected_type?(target_types)
124
+ target_types.include?(type) || target_types.empty?
125
+ end
47
126
  end
48
127
  end
49
128
  end
@@ -8,17 +8,21 @@ module FService
8
8
  # Represents a value of a failed operation.
9
9
  # The error field can contain any information you want.
10
10
  #
11
+ # @!attribute [r] error
12
+ # @return [Object] the provided error for the result
13
+ # @!attribute [r] type
14
+ # @return [Object] the provided type for the result. Defaults to nil.
11
15
  # @api public
12
16
  class Failure < Result::Base
13
- # Returns the provided error
14
- attr_reader :error
17
+ attr_reader :error, :type
15
18
 
16
19
  # Creates a failed operation.
17
- # You usually shouldn't call this directly. See {FService::Base#failure}.
20
+ # You usually shouldn't call this directly. See {FService::Base#Failure}.
18
21
  #
19
22
  # @param error [Object] failure value.
20
- def initialize(error)
23
+ def initialize(error, type = nil)
21
24
  @error = error
25
+ @type = type
22
26
  freeze
23
27
  end
24
28
 
@@ -55,6 +59,33 @@ module FService
55
59
  raise Result::Error, 'Failure objects do not have value'
56
60
  end
57
61
 
62
+ # Returns the current error to the given block.
63
+ # Use this to chain multiple service calls (since all services return Results).
64
+ # It works just like the `.and_then` method, but only runs if the result is a Failure.
65
+ #
66
+ #
67
+ # @example
68
+ # class UpdateUserOnExternalService
69
+ # attribute :user_params
70
+ #
71
+ # def run
72
+ # check_api_status
73
+ # .and_then { update_user }
74
+ # .or_else { create_update_worker }
75
+ # end
76
+ #
77
+ # private
78
+ # # some code
79
+ # end
80
+ #
81
+ # @yieldparam error pass {#error} to a block
82
+ # @yieldparam type pass {#type} to a block
83
+ def catch
84
+ yield(*to_ary)
85
+ end
86
+
87
+ alias or_else catch
88
+
58
89
  # Returns itself to the given block.
59
90
  # Use this to chain multiple service calls (since all services return Results).
60
91
  # It will short circuit your service call chain.
@@ -64,8 +95,8 @@ module FService
64
95
  # class UsersController < BaseController
65
96
  # def create
66
97
  # result = User::Create.(user_params) # if this fails the following calls won't run
67
- # .then { |user| User::SendWelcomeEmail.(user: user) }
68
- # .then { |user| User::Login.(user: user) }
98
+ # .and_then { |user| User::SendWelcomeEmail.(user: user) }
99
+ # .and_then { |user| User::Login.(user: user) }
69
100
  #
70
101
  # if result.successful?
71
102
  # json_success(result.value)
@@ -76,9 +107,10 @@ module FService
76
107
  # end
77
108
  #
78
109
  # @return [self]
79
- def then
110
+ def and_then
80
111
  self
81
112
  end
113
+ alias then and_then
82
114
 
83
115
  # Outputs a string representation of the object
84
116
  #
@@ -7,17 +7,21 @@ module FService
7
7
  # Represents a value of a successful operation.
8
8
  # The value field can contain any information you want.
9
9
  #
10
+ # @!attribute [r] value
11
+ # @return [Object] the provided value for the result
12
+ # @!attribute [r] type
13
+ # @return [Object] the provided type for the result. Defaults to nil.
10
14
  # @api public
11
15
  class Success < Result::Base
12
- # Returns the provided value.
13
- attr_reader :value
16
+ attr_reader :value, :type
14
17
 
15
18
  # Creates a successful operation.
16
- # You usually shouldn't call this directly. See {FService::Base#success}.
19
+ # You usually shouldn't call this directly. See {FService::Base#Success}.
17
20
  #
18
21
  # @param value [Object] success value.
19
- def initialize(value)
22
+ def initialize(value, type = nil)
20
23
  @value = value
24
+ @type = type
21
25
  freeze
22
26
  end
23
27
 
@@ -63,8 +67,8 @@ module FService
63
67
  # class UsersController < BaseController
64
68
  # def create
65
69
  # result = User::Create.(user_params)
66
- # .then { |user| User::SendWelcomeEmail.(user: user) }
67
- # .then { |user| User::Login.(user: user) }
70
+ # .and_then { |user| User::SendWelcomeEmail.(user: user) }
71
+ # .and_then { |user| User::Login.(user: user) }
68
72
  #
69
73
  # if result.successful?
70
74
  # json_success(result.value)
@@ -74,10 +78,38 @@ module FService
74
78
  # end
75
79
  # end
76
80
  #
77
- # @yieldparam [result] value pass {#value} to a block
78
- def then
79
- yield value
81
+ # @yieldparam value pass {#value} to a block
82
+ # @yieldparam type pass {#type} to a block
83
+ def and_then
84
+ yield(*to_ary)
80
85
  end
86
+ alias then and_then
87
+
88
+ # Returns itself to the given block.
89
+ # Use this to chain multiple actions or service calls (only valid when they return a Result).
90
+ # It works just like the `.and_then` method, but only runs if service is a Failure.
91
+ #
92
+ #
93
+ # @example
94
+ # class UpdateUserOnExternalService
95
+ # attribute :user_params
96
+ #
97
+ # def run
98
+ # check_api_status
99
+ # .and_then { update_user }
100
+ # .or_else { create_update_worker }
101
+ # end
102
+ #
103
+ # private
104
+ # # some code
105
+ # end
106
+ #
107
+ # @return [self]
108
+ def catch
109
+ self
110
+ end
111
+
112
+ alias or_else catch
81
113
 
82
114
  # Outputs a string representation of the object
83
115
  #
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FService
4
4
  # Current version of the gem
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
data/logo.png ADDED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: f_service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matheus Richard
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2021-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,6 +53,7 @@ files:
53
53
  - lib/f_service/result/failure.rb
54
54
  - lib/f_service/result/success.rb
55
55
  - lib/f_service/version.rb
56
+ - logo.png
56
57
  homepage: https://github.com/Fretadao/f_service
57
58
  licenses:
58
59
  - MIT
@@ -60,6 +61,7 @@ metadata:
60
61
  homepage_uri: https://github.com/Fretadao/f_service
61
62
  source_code_uri: https://github.com/Fretadao/f_service
62
63
  documentation_uri: https://www.rubydoc.info/gems/f_service
64
+ changelog_uri: https://github.com/Fretadao/f_service/blob/master/CHANGELOG.md
63
65
  post_install_message:
64
66
  rdoc_options: []
65
67
  require_paths:
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  - !ruby/object:Gem::Version
76
78
  version: '0'
77
79
  requirements: []
78
- rubygems_version: 3.0.3
80
+ rubygems_version: 3.1.4
79
81
  signing_key:
80
82
  specification_version: 4
81
83
  summary: A small, monad-based service class