f_service 0.1.1 → 0.3.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: 6cb29d87cf5c970b2b185ed2c3da41020f40122e2d0857af068302fb32e55400
4
+ data.tar.gz: 27a89660bb6d1e65d537f5d6617cc8175f600ab71efca79f65875a1b6adf0c97
5
5
  SHA512:
6
- metadata.gz: 0430b236523a17dae5c2afeb3db37d8354fbdce15f96789121ec184a41004242ccce40bd2a73d742f49a5312efa8dda9e6da207ceff41ef9eafabd03794e88d9
7
- data.tar.gz: 3d7c7439d7283f69d494c8e402b2c08f906073c4089d7e031e6b7a00e40b66577ce7aac860b8ed49378335d4be9f867dfeb5a982a2fd6cd9bcb1b34a1094e839
6
+ metadata.gz: 2b444953aee676163a2b076d814b3cc801b0808a30660c4edd2f19172754d58c41a7fcb3129a52cf95c1a0fdb8546419a55a8101b6882459673cd647d520f660
7
+ data.tar.gz: 0e2f10e1ad6cf4ba7ba2375b2302661429721b7bf4023afcb1559909205eb7e8f013efbb3da07c4b7926b2e967592c31a6c930ca6d6e69b195bba97342407b69
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "09:00"
8
+ open-pull-requests-limit: 10
@@ -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.6, 2.7, 3.0, 3.1]
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,22 @@ 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
20
+
21
+ RSpec/ContextWording:
22
+ Prefixes:
23
+ - and
24
+ - but
25
+ - when
26
+ - with
27
+ - without
28
+
29
+ RSpec/ExampleLength:
30
+ Max: 20
31
+
32
+ RSpec/NestedGroups:
33
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -4,16 +4,52 @@ 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.3.0
14
+ ### Added
15
+ - Added Rspec Helper `#mock_service` #41;
16
+ - Added Rspec Matcher `#have_succeed_with` and `#have_failed_with` #41;
17
+ - Added `Success()`, `Failure()`, `Check()`, `Try()` now can be multipe types #41;
18
+ - Changed Depreacate `Result#type` method #41;
19
+ - Changed Deprecate method `#then` for Success and Failure classes #40
20
+ - Added RSpec support for mock and match results #35
21
+ - Deprecate method `#then` for Success and Failure classes #40;
22
+ - Removed deprecated method `#on` #33
23
+ - Changed Capture just one callback per result #30;
24
+
25
+ ## 0.2.0
26
+ ### Added
27
+ - Add `and_then` as alias for `then` (partially fix [#23](https://github.com/Fretadao/f_service/issues/23)).
28
+ - Add `catch` to `Failure` and `Success`. It acts as an inverted `then` and has a `or_else` alias.
29
+ - Add support to custom `data` property to be passed when calling `Base#Check`.
30
+ - Add support to multiple type checks on `Result#on_success` and `Result#on_failure` hooks.
31
+ - Yields result type on blocks (`then`, `on_success` and `on_failure`).
32
+ - Add type check on `Result#on_success` and `Result#on_failure` hooks.
33
+ - Add method `Base#Try`. It wraps exceptions in Failures.
34
+ - Add method `Base#Check`. It converts booleans to Results.
35
+ - Add methods `#Success(type, data:)` and `#Failure(type, data:)` on `FService::Base`.
36
+ These methods allow defining the type and value of the Result object.
37
+ - Allow adding types on `Result`s.
38
+ - Add `#on_success` and `#on_failure` hooks on Result objects.
39
+ - Link to Changelog on gemspec.
40
+
41
+ ### Changed
42
+ - **[Deprecation]** Mark `Base#result` as deprecated. They will be removed on the next release. Use the `Base#Check` instead.
43
+ - **[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.
44
+ - **[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
45
 
10
46
  ## 0.1.1
11
47
  ### Added
12
48
  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`;
49
+ - Result based services
50
+ - Type check on results
51
+ - Pattern matching with `#call`ables
52
+ - Safe chaining calls with `#then`
17
53
 
18
54
  ## 0.1.0
19
- - **Yanked**
55
+ - **Yanked**
data/Gemfile CHANGED
@@ -6,6 +6,8 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  group :development, :test do
9
+ gem 'pry'
10
+ gem 'pry-nav'
9
11
  gem 'rake', '~> 13.0.0'
10
12
  gem 'rubocop', '~> 0.82.0', require: false
11
13
  gem 'rubocop-rspec', require: false
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.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -9,22 +9,31 @@ GEM
9
9
  ast (2.4.0)
10
10
  backport (1.1.2)
11
11
  benchmark (0.1.0)
12
+ coderay (1.1.3)
12
13
  diff-lcs (1.3)
13
- docile (1.3.2)
14
+ docile (1.3.5)
14
15
  e2mmap (0.1.0)
15
16
  jaro_winkler (1.5.4)
16
17
  maruku (0.7.3)
17
- mini_portile2 (2.4.0)
18
- nokogiri (1.10.9)
19
- mini_portile2 (~> 2.4.0)
18
+ method_source (1.0.0)
19
+ mini_portile2 (2.8.1)
20
+ nokogiri (1.14.3)
21
+ mini_portile2 (~> 2.8.0)
22
+ racc (~> 1.4)
20
23
  parallel (1.19.1)
21
24
  parser (2.7.1.1)
22
25
  ast (~> 2.4.0)
26
+ pry (0.14.1)
27
+ coderay (~> 1.1)
28
+ method_source (~> 1.0)
29
+ pry-nav (1.0.0)
30
+ pry (>= 0.9.10, < 0.15)
31
+ racc (1.6.2)
23
32
  rainbow (3.0.0)
24
33
  rake (13.0.1)
25
34
  reverse_markdown (1.4.0)
26
35
  nokogiri
27
- rexml (3.2.4)
36
+ rexml (3.2.5)
28
37
  rspec (3.9.0)
29
38
  rspec-core (~> 3.9.0)
30
39
  rspec-expectations (~> 3.9.0)
@@ -49,10 +58,12 @@ GEM
49
58
  rubocop-rspec (1.38.1)
50
59
  rubocop (>= 0.68.1)
51
60
  ruby-progressbar (1.10.1)
52
- simplecov (0.18.2)
61
+ simplecov (0.21.2)
53
62
  docile (~> 1.1)
54
63
  simplecov-html (~> 0.11)
55
- simplecov-html (0.12.0)
64
+ simplecov_json_formatter (~> 0.1)
65
+ simplecov-html (0.12.3)
66
+ simplecov_json_formatter (0.1.2)
56
67
  solargraph (0.38.6)
57
68
  backport (~> 1.1)
58
69
  benchmark
@@ -78,6 +89,8 @@ PLATFORMS
78
89
  DEPENDENCIES
79
90
  bundler (~> 2.0)
80
91
  f_service!
92
+ pry
93
+ pry-nav
81
94
  rake (~> 13.0.0)
82
95
  rspec (~> 3.0)
83
96
  rubocop (~> 0.82.0)
@@ -87,4 +100,4 @@ DEPENDENCIES
87
100
  yard
88
101
 
89
102
  BUNDLED WITH
90
- 2.0.2
103
+ 2.2.32
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 list of types which represents that result 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, :invalid_attribute) if @name.nil?
51
68
 
52
69
  user = UserRepository.create(name: @name)
53
- if user.valid?
54
- success(status: "User successfully created!", data: user)
70
+ if user.save
71
+ Success(: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,92 @@ 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:
98
120
 
99
121
  ```ruby
100
122
  class UsersController < BaseController
101
123
  def create
102
- User::Create.(user_params).on(
103
- success: ->(value) { return json_success(value) },
104
- failure: ->(error) { return json_error(error) }
105
- )
124
+ User::Create.(user_params)
125
+ .on_success { |value| return json_success(value) }
126
+ .on_failure { |error| return json_error(error) }
106
127
  end
107
128
  end
108
129
  ```
109
- > You can use any object that responds to #call, not only Lambdas.
130
+
131
+ Or else it is possible to specify an unhandled option to ensure that the callback will process that message anyway the
132
+ error.
133
+
134
+ ```ruby
135
+ class UsersController < BaseController
136
+ def create
137
+ User::Create.(user_params)
138
+ .on_success(unhandled: true) { |value| return json_success(value) }
139
+ .on_failure(unhandled: true) { |error| return json_error(error) }
140
+ end
141
+ end
142
+ ```
143
+
144
+ ```ruby
145
+ class UsersController < BaseController
146
+ def create
147
+ User::Create.(user_params)
148
+ .on_success { |value| return json_success(value) }
149
+ .on_failure { |error| return json_error(error) }
150
+ end
151
+ end
152
+ ```
153
+
154
+ > You can ignore any of the callbacks, if you want to.
155
+
156
+ Going further, you can match the Result type, in case you want to handle them differently:
157
+
158
+ ```ruby
159
+ class UsersController < BaseController
160
+ def create
161
+ User::Create.(user_params)
162
+ .on_success(:user_created) { |value| return json_success(value) }
163
+ .on_success(:user_already_exists) { |value| return json_success(value) }
164
+ .on_failure(:invalid_data) { |error| return json_error(error) }
165
+ .on_failure(:critical_error) do |error|
166
+ MyLogger.report_failure(error)
167
+
168
+ return json_error(error)
169
+ end
170
+ end
171
+ end
172
+ ```
173
+
174
+ It's possible to provide multiple types to the hooks too. If the result type matches any of the given types,
175
+ the hook will run.
176
+
177
+ ```ruby
178
+ class UsersController < BaseController
179
+ def create
180
+ User::Create.(user_params)
181
+ .on_success(:user_created, :user_already_exists) { |value| return json_success(value) }
182
+ .on_failure(:invalid_data) { |error| return json_error(error) }
183
+ .on_failure(:critical_error) do |error|
184
+ MyLogger.report_failure(error)
185
+
186
+ return json_error(error)
187
+ end
188
+ end
189
+ end
190
+ ```
191
+
192
+ ### Types precedence
193
+
194
+ FService matches types from left to right, from more specific to more generic.
195
+ Example (:unprocessable_entity, :client_error, :http_response)
196
+ Then, result will match first :unprocessable_entity, after :client_error, after :http_response, then not matched.
110
197
 
111
198
  ### Chaining services
199
+
112
200
  Since all services return Results, you can chain service calls making a data pipeline.
113
201
  If some step fails, it will short circuit the call chain.
114
202
 
@@ -116,8 +204,8 @@ If some step fails, it will short circuit the call chain.
116
204
  class UsersController < BaseController
117
205
  def create
118
206
  result = User::Create.(user_params)
119
- .then { |user| User::Login.(user) }
120
- .then { |user| User::SendWelcomeEmail.(user) }
207
+ .and_then { |user| User::Login.(user) }
208
+ .and_then { |user| User::SendWelcomeEmail.(user) }
121
209
 
122
210
  if result.successful?
123
211
  json_success(result.value)
@@ -128,6 +216,102 @@ class UsersController < BaseController
128
216
  end
129
217
  ```
130
218
 
219
+ You can use the `.to_proc` method on FService::Base to avoid explicit inputs when chaining services:
220
+
221
+ ```ruby
222
+ class UsersController < BaseController
223
+ def create
224
+ result = User::Create.(user_params)
225
+ .and_then(&User::Login)
226
+ .and_then(&User::SendWelcomeEmail)
227
+ # ...
228
+ end
229
+ end
230
+ ```
231
+
232
+ ### `Check` and `Try`
233
+
234
+ You can use `Check` to converts a boolean to a Result, truthy values map to `Success`, and falsey values map to `Failures`:
235
+
236
+ ```ruby
237
+ Check(:math_works) { 1 < 2 }
238
+ # => #<Success @value=true, @types=[:math_works]>
239
+
240
+ Check(:math_works) { 1 > 2 }
241
+ # => #<Failure @error=false, @types=[:math_works]>
242
+ ```
243
+
244
+ `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
245
+ using the parameter `catch`.
246
+
247
+ ```ruby
248
+ class IHateEvenNumbers < FService::Base
249
+ def run
250
+ Try(:rand_int) do
251
+ n = rand(1..10)
252
+ raise "Yuck! It's a #{n}" if n.even?
253
+
254
+ n
255
+ end
256
+ end
257
+ end
258
+
259
+ IHateEvenNumbers.call
260
+ # => #<Success @value=9, @types=[:rand_int]>
261
+
262
+ IHateEvenNumbers.call
263
+ # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
264
+ ```
265
+
266
+ ## Testing
267
+
268
+ We provide some helpers and matchers to make ease to test code envolving Fservice services.
269
+
270
+ To make available in the system, in the file 'spec/spec_helper.rb' or 'spec/rails_helper.rb'
271
+
272
+ add the folowing require:
273
+
274
+ ```rb
275
+ require 'f_service/rspec'
276
+ ```
277
+
278
+ ### Mocking a result
279
+
280
+ ```
281
+ mock_service(Uer::Create)
282
+ # => Mocks a successful result with all values nil
283
+
284
+ mock_service(Uer::Create, result: :success)
285
+ # => Mocks a successful result with all values nil
286
+
287
+ mock_service(Uer::Create, result: :success, types: [:created, :success])
288
+ # => Mocks a successful result with type created
289
+
290
+ mock_service(Uer::Create, result: :success, types: :created, value: instance_spy(User))
291
+ # => Mocks a successful result with type created and a value
292
+
293
+ mock_service(Uer::Create, result: :failure)
294
+ # => Mocs a failure with all nil values
295
+
296
+ mock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error])
297
+ # => Mocs a failure with a failure type
298
+
299
+ mock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error], value: { name: ["can't be blank"] })
300
+ # => Mocs a failure with a failure type and an error value
301
+ ```
302
+
303
+ ### Matching a result
304
+
305
+ ```rb
306
+ expect(User::Create.(name: 'Joe')).to have_succeed_with(:created)
307
+
308
+ expect(User::Create.(name: 'Joe')).to have_succeed_with(:created).and_value(an_instance_of(User))
309
+
310
+ expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes)
311
+
312
+ expect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes).and_error({ name: ["can't be blank"] })
313
+ ```
314
+
131
315
  ## API Docs
132
316
 
133
317
  You can access the API docs [here](https://www.rubydoc.info/gems/f_service/).
data/f_service.gemspec CHANGED
@@ -7,8 +7,8 @@ require 'f_service/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'f_service'
9
9
  spec.version = FService::VERSION
10
- spec.authors = ['Matheus Richard']
11
- spec.email = ['matheusrichardt@gmail.com']
10
+ spec.authors = ['Fretadao Tech Team']
11
+ spec.email = ['tech@fretadao.com.br']
12
12
 
13
13
  spec.summary = 'A small, monad-based service class'
14
14
  spec.description = <<-DESCRIPTION
@@ -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.
@@ -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,138 @@ 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
+ from: caller[0]
106
+ )
107
+
108
+ Result::Success.new(data)
109
+ end
110
+
111
+ # Returns a successful result.
112
+ # You can optionally specify a list of types and a value for your result.
113
+ # You'll probably want to return this inside {#run}.
114
+ #
115
+ #
116
+ # @example
117
+ # def run
118
+ # Success()
119
+ # # => #<Success @value=nil, @types=[]>
120
+ #
121
+ # Success(:ok)
122
+ # # => #<Success @value=nil, @types=[:ok]>
123
+ #
124
+ # Success(data: 10)
125
+ # # => #<Success @value=10, @types=[]>
126
+ #
127
+ # Success(:ok, data: 10)
128
+ # # => #<Success @value=10, @types=[:ok]>
129
+ # end
130
+ #
131
+ # @param types the Result types
132
+ # @param data the result value
133
+ # @return [Result::Success] a successful result
134
+ def Success(*types, data: nil)
135
+ Result::Success.new(data, types)
136
+ end
137
+
138
+ # Returns a failed result.
139
+ # You can optionally specify types and a value for your result.
140
+ # You'll probably want to return this inside {#run}.
141
+ #
142
+ #
143
+ # @example
144
+ # def run
145
+ # Failure()
146
+ # # => #<Failure @error=nil, @types=[]>
147
+ #
148
+ # Failure(:not_a_number)
149
+ # # => #<Failure @error=nil, @types=[:not_a_number]>
150
+ #
151
+ # Failure(data: "10")
152
+ # # => #<Failure @error="10", @types=[]>
153
+ #
154
+ # Failure(:not_a_number, data: "10")
155
+ # # => #<Failure @error="10", @types=[:not_a_number]>
156
+ # end
157
+ #
158
+ # @param types the Result types
159
+ # @param data the result value
160
+ # @return [Result::Failure] a failed result
161
+ def Failure(*types, data: nil)
162
+ Result::Failure.new(data, types)
163
+ end
164
+
165
+ # Converts a boolean to a Result.
166
+ # Truthy values map to Success, and falsey values map to Failures.
167
+ # You can optionally provide a types for the result.
168
+ # The result value defaults as the evaluated value of the given block.
169
+ # If you want another value you can pass it through the `data:` argument.
170
+ #
171
+ # @example
172
+ # class CheckMathWorks < FService::Base
173
+ # def run
174
+ # Check(:math_works) { 1 < 2 }
175
+ # # => #<Success @value=true, @types=[:math_works]>
176
+ #
177
+ # Check(:math_works) { 1 > 2 }
178
+ # # => #<Failure @error=false, @types=[:math_works]>
179
+ #
180
+ # Check(:math_works, data: 1 + 2) { 1 > 2 }
181
+ # # => #<Failure @types=:math_works, @error=3>
182
+ # end
183
+ #
184
+ # Check(:math_works, data: 1 + 2) { 1 < 2 }
185
+ # # => #<Success @types=[:math_works], @value=3>
186
+ # end
187
+ # end
188
+ #
189
+ # @param types the Result types
190
+ # @return [Result::Success, Result::Failure] a Result from the boolean expression
191
+ def Check(*types, data: nil)
192
+ res = yield
193
+
194
+ final_data = data || res
195
+
196
+ res ? Success(*types, data: final_data) : Failure(*types, data: final_data)
197
+ end
198
+
199
+ # If the given block raises an exception, it wraps it in a Failure.
200
+ # Otherwise, maps the block value in a Success object.
201
+ # You can specify which exceptions to watch for.
202
+ # It's possible to provide a types for the result too.
203
+ #
204
+ # @example
205
+ # class IHateEvenNumbers < FService::Base
206
+ # def run
207
+ # Try(:rand_int) do
208
+ # n = rand(1..10)
209
+ # raise "Yuck! It's a #{n}" if n.even?
210
+ #
211
+ # n
212
+ # end
213
+ # end
214
+ # end
215
+ #
216
+ # IHateEvenNumbers.call
217
+ # # => #<Success @value=9, @types=[:rand_int]>
218
+ #
219
+ # IHateEvenNumbers.call
220
+ # # => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>
221
+ #
222
+ # @param types the Result types
223
+ # @param catch the exception list to catch
224
+ # @return [Result::Success, Result::Failure] a result from the boolean expression
225
+ def Try(*types, catch: StandardError)
226
+ res = yield
227
+
228
+ Success(*types, data: res)
229
+ rescue *catch => e
230
+ Failure(*types, data: e)
78
231
  end
79
232
 
80
233
  # Returns a failed operation.
@@ -93,12 +246,19 @@ module FService
93
246
  # end
94
247
  # end
95
248
  #
96
- # @return [FService::Result::Failure] - a failed operation
249
+ # @deprecated Use {#Failure} instead.
250
+ # @return [Result::Failure] a failed operation
97
251
  def failure(data = nil)
98
- FService::Result::Failure.new(data)
252
+ FService.deprecate!(
253
+ name: "#{self.class}##{__method__}",
254
+ alternative: '#Failure',
255
+ from: caller[0]
256
+ )
257
+
258
+ Result::Failure.new(data)
99
259
  end
100
260
 
101
- # Return either {FService::Result::Failure Success} or {FService::Result::Failure Failure}
261
+ # Return either {Result::Failure Success} or {Result::Failure Failure}
102
262
  # given the condition.
103
263
  #
104
264
  # @example
@@ -120,8 +280,15 @@ module FService
120
280
  # end
121
281
  # end
122
282
  #
123
- # @return [FService::Result::Success, FService::Result::Failure]
283
+ # @deprecated Use {#Check} instead.
284
+ # @return [Result::Success, Result::Failure]
124
285
  def result(condition, data = nil)
286
+ FService.deprecate!(
287
+ name: "#{self.class}##{__method__}",
288
+ alternative: '#Check',
289
+ from: caller[0]
290
+ )
291
+
125
292
  condition ? success(data) : failure(data)
126
293
  end
127
294
  end
@@ -7,24 +7,54 @@ module FService
7
7
  #
8
8
  # @abstract
9
9
  class Base
10
- %i[initialize then successful? failed? value value! error].each do |method_name|
10
+ attr_reader :types
11
+
12
+ %i[and_then successful? failed? value value! error].each do |method_name|
11
13
  define_method(method_name) do |*_args|
12
14
  raise NotImplementedError, "called #{method_name} on class Result::Base"
13
15
  end
14
16
  end
15
17
 
16
- # "Pattern matching"-like method for results.
17
- # It will run the success path if Result is a Success.
18
- # Otherwise, it will run the failure path.
18
+ # You usually shouldn't call this directly. See {FService::Base#Failure} and {FService::Base#Success}.
19
+ def initialize(types = [])
20
+ @handled = false
21
+ @types = types
22
+ @matching_types = []
23
+ end
24
+
25
+ # Implements old attribute type. Its deprecated in favor of using types.
26
+ def type
27
+ FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#types', from: caller[0])
28
+
29
+ types.size == 1 ? types.first : Array(@matching_types).first
30
+ end
31
+
32
+ # This hook runs if the result is successful.
33
+ # Can receive one or more types to be checked before running the given block.
34
+ #
35
+ # @example
36
+ # class UsersController < BaseController
37
+ # def update
38
+ # User::Update.(user: user)
39
+ # .on_success(:type, :type2) { return json_success({ status: :ok }) } # run only if type matches
40
+ # .on_success { |value| return json_success(value) }
41
+ # .on_failure { |error| return json_error(error) } # this won't run
42
+ # end
19
43
  #
44
+ # private
45
+ #
46
+ # def user
47
+ # @user ||= User.find_by!(slug: params[:slug])
48
+ # end
49
+ # end
20
50
  #
21
51
  # @example
22
52
  # class UsersController < BaseController
23
53
  # def update
24
- # User::Update.(user: user).on(
25
- # success: ->(value) { return json_success(value) },
26
- # failure: ->(error) { return json_error(error) }
27
- # )
54
+ # User::Update.(user: user)
55
+ # .on_success(:type, :type2) { return json_success({ status: :ok }) } # run only if type matches
56
+ # .on_success(unhandled: true) { |value| return json_success(value) }
57
+ # .on_failure(unhandled: true) { |error| return json_error(error) } # this won't run
28
58
  # end
29
59
  #
30
60
  # private
@@ -34,15 +64,96 @@ module FService
34
64
  # end
35
65
  # end
36
66
  #
37
- # @param success [#call] a lambda (or anything that responds to #call) to run on success
38
- # @param failure [#call] a lambda (or anything that responds to #call) to run on failure
67
+ # @yieldparam value value of the failure object
68
+ # @yieldparam type type of the failure object
69
+ # @return [Success, Failure] the original Result object
39
70
  # @api public
40
- def on(success:, failure:)
41
- if successful?
42
- success.call(value)
43
- else
44
- failure.call(error)
71
+ def on_success(*target_types, unhandled: false)
72
+ if successful? && unhandled? && expected_type?(target_types, unhandled: unhandled)
73
+ match_types(target_types)
74
+ yield(*to_ary)
75
+ @handled = true
76
+ freeze
45
77
  end
78
+
79
+ self
80
+ end
81
+
82
+ # This hook runs if the result is failed.
83
+ # Can receive one or more types to be checked before running the given block.
84
+ #
85
+ # @example
86
+ # class UsersController < BaseController
87
+ # def update
88
+ # User::Update.(user: user)
89
+ # .on_success { |value| return json_success(value) } # this won't run
90
+ # .on_failure(:type, :type2) { |error| return json_error(error) } # runs only if type matches
91
+ # .on_failure { |error| return json_error(error) }
92
+ # end
93
+ #
94
+ # private
95
+ #
96
+ # def user
97
+ # @user ||= User.find_by!(slug: params[:slug])
98
+ # end
99
+ # end
100
+ #
101
+ # @example
102
+ # class UsersController < BaseController
103
+ # def update
104
+ # User::Update.(user: user)
105
+ # .on_success(:unhandled: true) { |value| return json_success(value) } # this won't run
106
+ # .on_failure(:type, :type2) { |error| return json_error(error) } # runs only if type matches
107
+ # .on_failure(:unhandled: true) { |error| return json_error(error) }
108
+ # end
109
+ #
110
+ # private
111
+ #
112
+ # def user
113
+ # @user ||= User.find_by!(slug: params[:slug])
114
+ # end
115
+ # end
116
+ #
117
+ # @yieldparam value value of the failure object
118
+ # @yieldparam type type of the failure object
119
+ # @return [Success, Failure] the original Result object
120
+ # @api public
121
+ def on_failure(*target_types, unhandled: false)
122
+ if failed? && unhandled? && expected_type?(target_types, unhandled: unhandled)
123
+ match_types(target_types)
124
+ yield(*to_ary)
125
+ @handled = true
126
+ freeze
127
+ end
128
+
129
+ self
130
+ end
131
+
132
+ # Splits the result object into its components.
133
+ #
134
+ # @return [Array] value and type of the result object
135
+ def to_ary
136
+ data = successful? ? value : error
137
+
138
+ [data, @matching_types.first]
139
+ end
140
+
141
+ private
142
+
143
+ def handled?
144
+ @handled
145
+ end
146
+
147
+ def unhandled?
148
+ !handled?
149
+ end
150
+
151
+ def expected_type?(target_types, unhandled:)
152
+ target_types.empty? || unhandled || target_types.any? { |target_type| types.include?(target_type) }
153
+ end
154
+
155
+ def match_types(target_types)
156
+ @matching_types = target_types.empty? ? types : target_types & types
46
157
  end
47
158
  end
48
159
  end
@@ -8,18 +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] types
14
+ # @return [Object] the provided types for the result. Defaults to nil.
11
15
  # @api public
12
16
  class Failure < Result::Base
13
- # Returns the provided error
14
17
  attr_reader :error
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, types = [])
24
+ super(types)
21
25
  @error = error
22
- freeze
23
26
  end
24
27
 
25
28
  # Returns false.
@@ -55,6 +58,33 @@ module FService
55
58
  raise Result::Error, 'Failure objects do not have value'
56
59
  end
57
60
 
61
+ # Returns the current error to the given block.
62
+ # Use this to chain multiple service calls (since all services return Results).
63
+ # It works just like the `.and_then` method, but only runs if the result is a Failure.
64
+ #
65
+ #
66
+ # @example
67
+ # class UpdateUserOnExternalService
68
+ # attribute :user_params
69
+ #
70
+ # def run
71
+ # check_api_status
72
+ # .and_then { update_user }
73
+ # .or_else { create_update_worker }
74
+ # end
75
+ #
76
+ # private
77
+ # # some code
78
+ # end
79
+ #
80
+ # @yieldparam error pass {#error} to a block
81
+ # @yieldparam type pass {#type} to a block
82
+ def catch
83
+ yield(*to_ary)
84
+ end
85
+
86
+ alias or_else catch
87
+
58
88
  # Returns itself to the given block.
59
89
  # Use this to chain multiple service calls (since all services return Results).
60
90
  # It will short circuit your service call chain.
@@ -64,8 +94,8 @@ module FService
64
94
  # class UsersController < BaseController
65
95
  # def create
66
96
  # 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) }
97
+ # .and_then { |user| User::SendWelcomeEmail.(user: user) }
98
+ # .and_then { |user| User::Login.(user: user) }
69
99
  #
70
100
  # if result.successful?
71
101
  # json_success(result.value)
@@ -76,10 +106,16 @@ module FService
76
106
  # end
77
107
  #
78
108
  # @return [self]
79
- def then
109
+ def and_then
80
110
  self
81
111
  end
82
112
 
113
+ # See #and_then
114
+ def then
115
+ FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#and_then', from: caller[0])
116
+ and_then
117
+ end
118
+
83
119
  # Outputs a string representation of the object
84
120
  #
85
121
  #
@@ -7,18 +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] types
13
+ # @return [Object] the provided types for the result. Defaults to nil.
10
14
  # @api public
11
15
  class Success < Result::Base
12
- # Returns the provided value.
13
16
  attr_reader :value
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, types = [])
23
+ super(types)
20
24
  @value = value
21
- freeze
22
25
  end
23
26
 
24
27
  # Returns true.
@@ -63,8 +66,8 @@ module FService
63
66
  # class UsersController < BaseController
64
67
  # def create
65
68
  # result = User::Create.(user_params)
66
- # .then { |user| User::SendWelcomeEmail.(user: user) }
67
- # .then { |user| User::Login.(user: user) }
69
+ # .and_then { |user| User::SendWelcomeEmail.(user: user) }
70
+ # .and_then { |user| User::Login.(user: user) }
68
71
  #
69
72
  # if result.successful?
70
73
  # json_success(result.value)
@@ -74,11 +77,45 @@ module FService
74
77
  # end
75
78
  # end
76
79
  #
77
- # @yieldparam [result] value pass {#value} to a block
78
- def then
79
- yield value
80
+ # @yieldparam value pass {#value} to a block
81
+ # @yieldparam types pass {#types} to a block
82
+ def and_then
83
+ yield(*to_ary)
80
84
  end
81
85
 
86
+ # See #and_then
87
+ def then(&block)
88
+ FService.deprecate!(name: "#{self.class}##{__method__}", alternative: '#and_then', from: caller[0])
89
+
90
+ and_then(&block)
91
+ end
92
+
93
+ # Returns itself to the given block.
94
+ # Use this to chain multiple actions or service calls (only valid when they return a Result).
95
+ # It works just like the `.and_then` method, but only runs if service is a Failure.
96
+ #
97
+ #
98
+ # @example
99
+ # class UpdateUserOnExternalService
100
+ # attribute :user_params
101
+ #
102
+ # def run
103
+ # check_api_status
104
+ # .and_then { update_user }
105
+ # .or_else { create_update_worker }
106
+ # end
107
+ #
108
+ # private
109
+ # # some code
110
+ # end
111
+ #
112
+ # @return [self]
113
+ def catch
114
+ self
115
+ end
116
+
117
+ alias or_else catch
118
+
82
119
  # Outputs a string representation of the object
83
120
  #
84
121
  #
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Methods to mock a FService result from a service call.
4
+ module FServiceResultHelpers
5
+ # Create an Fservice result Success or Failure.
6
+ def f_service_result(result, value = nil, types = [])
7
+ if result == :success
8
+ FService::Result::Success.new(value, Array(types))
9
+ else
10
+ FService::Result::Failure.new(value, Array(types))
11
+ end
12
+ end
13
+
14
+ # Mock a Fservice service call returning a result.
15
+ def mock_service(service, result: :success, value: nil, type: :not_passed, types: [])
16
+ result_types = Array(types)
17
+
18
+ if type != :not_passed
19
+ alternative = "mock_service(..., types: [#{type.inspect}])"
20
+ name = 'mock_service'
21
+ FService.deprecate_argument_name(name: name, argument_name: :type, alternative: alternative, from: caller[0])
22
+ result_types = Array(type)
23
+ end
24
+
25
+ service_result = f_service_result(result, value, result_types)
26
+ allow(service).to receive(:call).and_return(service_result)
27
+ end
28
+ end
29
+
30
+ RSpec.configure do |config|
31
+ config.include FServiceResultHelpers
32
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers/result'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ RSpec::Matchers.define :have_failed_with do |*expected_types|
6
+ match do |actual|
7
+ matched = actual.is_a?(FService::Result::Failure) && actual.types == expected_types
8
+
9
+ matched &&= actual.error == @expected_error if defined?(@expected_error)
10
+
11
+ matched
12
+ end
13
+
14
+ chain :and_error do |expected_error|
15
+ @expected_error = expected_error
16
+ end
17
+
18
+ failure_message do |actual|
19
+ if actual.is_a?(FService::Result::Failure)
20
+ message = "expected failure's types '#{actual.types.inspect}' to be equal '#{expected_types.inspect}'"
21
+ if defined?(@expected_error)
22
+ has_description = @expected_error.respond_to?(:description)
23
+ message += " and error '#{actual.error.inspect}' to be "
24
+ message += has_description ? @expected_error.description : "equal '#{@expected_error.inspect}'"
25
+ end
26
+
27
+ message
28
+ else
29
+ "result '#{actual.inspect}' is not a Failure object"
30
+ end
31
+ end
32
+ end
33
+
34
+ RSpec::Matchers.define :have_succeed_with do |*expected_types|
35
+ match do |actual|
36
+ matched = actual.is_a?(FService::Result::Success) && actual.types == expected_types
37
+
38
+ matched &&= values_match?(@expected_value, actual.value) if defined?(@expected_value)
39
+
40
+ matched
41
+ end
42
+
43
+ chain :and_value do |expected_value|
44
+ @expected_value = expected_value
45
+ end
46
+
47
+ failure_message do |actual|
48
+ if actual.is_a?(FService::Result::Success)
49
+ message = "expected success's types '#{actual.types.inspect}' to be equal '#{expected_types.inspect}'"
50
+ if defined?(@expected_value)
51
+ has_description = @expected_value.respond_to?(:description)
52
+ message += " and value '#{actual.value.inspect}' to be "
53
+ message += has_description ? @expected_value.description : "equal '#{@expected_value.inspect}'"
54
+ end
55
+
56
+ message
57
+ else
58
+ "result '#{actual.inspect}' is not a Success object"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'matchers/result'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/helpers'
4
+ require_relative 'support/matchers'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rspec/support'
@@ -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.3.0'
6
6
  end
data/lib/f_service.rb CHANGED
@@ -6,4 +6,27 @@ 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:, from: nil)
13
+ warn_message = ["\n[DEPRECATED] #{name} is deprecated; "]
14
+ warn_message << ["called from #{from}; "] unless from.nil?
15
+ warn_message << "use #{alternative} instead. "
16
+ warn_message << 'It will be removed on the next release.'
17
+
18
+ warn warn_message.join("\n")
19
+ end
20
+
21
+ # Marks an argument as deprecated
22
+ #
23
+ # @api private
24
+ def self.deprecate_argument_name(name:, argument_name:, alternative:, from: nil)
25
+ warn_message = ["\n[DEPRECATED] #{name} passing #{argument_name.inspect} is deprecated; "]
26
+ warn_message << ["called from #{from}; "] unless from.nil?
27
+ warn_message << "use #{alternative} instead. "
28
+ warn_message << 'It will be removed on the next release.'
29
+
30
+ warn warn_message.join("\n")
31
+ end
9
32
  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.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - Matheus Richard
7
+ - Fretadao Tech Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2023-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -29,11 +29,12 @@ description: |2
29
29
  The goal is to make services simpler, safer and more composable.
30
30
  It uses the Result monad for handling operations.
31
31
  email:
32
- - matheusrichardt@gmail.com
32
+ - tech@fretadao.com.br
33
33
  executables: []
34
34
  extensions: []
35
35
  extra_rdoc_files: []
36
36
  files:
37
+ - ".github/.dependabot.yml"
37
38
  - ".github/workflows/tests-and-linter.yml"
38
39
  - ".gitignore"
39
40
  - ".rubocop.yml"
@@ -52,7 +53,14 @@ files:
52
53
  - lib/f_service/result/base.rb
53
54
  - lib/f_service/result/failure.rb
54
55
  - lib/f_service/result/success.rb
56
+ - lib/f_service/rspec.rb
57
+ - lib/f_service/rspec/support.rb
58
+ - lib/f_service/rspec/support/helpers.rb
59
+ - lib/f_service/rspec/support/helpers/result.rb
60
+ - lib/f_service/rspec/support/matchers.rb
61
+ - lib/f_service/rspec/support/matchers/result.rb
55
62
  - lib/f_service/version.rb
63
+ - logo.png
56
64
  homepage: https://github.com/Fretadao/f_service
57
65
  licenses:
58
66
  - MIT
@@ -60,6 +68,7 @@ metadata:
60
68
  homepage_uri: https://github.com/Fretadao/f_service
61
69
  source_code_uri: https://github.com/Fretadao/f_service
62
70
  documentation_uri: https://www.rubydoc.info/gems/f_service
71
+ changelog_uri: https://github.com/Fretadao/f_service/blob/master/CHANGELOG.md
63
72
  post_install_message:
64
73
  rdoc_options: []
65
74
  require_paths:
@@ -75,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
84
  - !ruby/object:Gem::Version
76
85
  version: '0'
77
86
  requirements: []
78
- rubygems_version: 3.0.3
87
+ rubygems_version: 3.3.5
79
88
  signing_key:
80
89
  specification_version: 4
81
90
  summary: A small, monad-based service class