f_service 0.3.1 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +11 -6
- data/README.md +145 -83
- data/f_service.gemspec +13 -3
- data/lib/f_service/base.rb +0 -92
- data/lib/f_service/result/base.rb +0 -7
- data/lib/f_service/result/failure.rb +0 -6
- data/lib/f_service/result/success.rb +0 -7
- data/lib/f_service/rspec/support/helpers/result.rb +2 -11
- data/lib/f_service/version.rb +1 -1
- metadata +3 -15
- data/.github/.dependabot.yml +0 -8
- data/.github/workflows/tests-and-linter.yml +0 -28
- data/.gitignore +0 -58
- data/.rubocop.yml +0 -38
- data/Gemfile +0 -27
- data/Gemfile.lock +0 -122
- data/Rakefile +0 -11
- data/bin/console +0 -15
- data/bin/setup +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e6e4ea7905315820d4149138a6d0acfbcb013cde481b34cb0cbd95247aef439
|
|
4
|
+
data.tar.gz: 7abcb86eaf3080a9c1e8d6bfaca8b5c8a70a932ba0e57fd55e9bd88f479d538c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36ec32dab3e5b223996963426298949403bf606fccd1d58c26362d1c38de8e46a5717f4639264709aee03d11b78b960f270e4db3b003f9688b57b2e0456bb958
|
|
7
|
+
data.tar.gz: 42d233650b876ca35f68b99d2985c4044b1e2989779e9817c9ea20677ecd3bf696e2d22bf7c9c0238b1803ccf74501af7e65ff1ccd70c4878b11be941218fba1
|
data/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,18 @@ 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
|
-
##
|
|
8
|
-
<!-- ### Added -->
|
|
9
|
-
<!-- ### Changed -->
|
|
10
|
-
<!-- ### Removed -->
|
|
11
|
-
---
|
|
7
|
+
## [0.4.0](https://github.com/Fretadao/f_service/compare/v0.3.1...v0.4.0) (2026-08-19)
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* Failure#then and Success#then were removed; use #and_then, which has always been their alias. Base#success, Base#failure and Base#result were removed; use #Success, #Failure and #Check. Result#type was removed; use #types. The mock_service `type:` argument was removed; use `types:` with an array.
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* remove deprecated Failure#then, Success#then and the 0.2.0 leftovers ([#63](https://github.com/Fretadao/f_service/issues/63)) ([69b4e80](https://github.com/Fretadao/f_service/commit/69b4e8099cfdb9850ecab656779436ac9cf16d09))
|
|
17
|
+
|
|
18
|
+
## 0.3.1
|
|
14
19
|
### Added
|
|
15
20
|
- Drop Support to Ruby 2.6 and 2.7
|
|
16
21
|
- Add Support to Ruby 3.2 and 3.3
|
data/README.md
CHANGED
|
@@ -23,9 +23,14 @@ It uses the Result monad for handling operations.
|
|
|
23
23
|
|
|
24
24
|
Add this line to your application's Gemfile:
|
|
25
25
|
|
|
26
|
+
<!-- x-release-please-start-version -->
|
|
26
27
|
```ruby
|
|
27
|
-
gem 'f_service'
|
|
28
|
+
gem 'f_service', '~> 0.4.0'
|
|
28
29
|
```
|
|
30
|
+
<!-- x-release-please-end-version -->
|
|
31
|
+
|
|
32
|
+
> The version above is kept current automatically on every release; pin to
|
|
33
|
+
> whichever version you prefer.
|
|
29
34
|
|
|
30
35
|
And then execute:
|
|
31
36
|
|
|
@@ -64,11 +69,12 @@ You can optionally specify a list of types which represents that result and a va
|
|
|
64
69
|
class User::Create < FService::Base
|
|
65
70
|
# ...
|
|
66
71
|
def run
|
|
67
|
-
return Failure(:
|
|
72
|
+
return Failure(:invalid_name, data: { name: ["can't be blank"] }) if @name.nil?
|
|
68
73
|
|
|
69
74
|
user = UserRepository.create(name: @name)
|
|
75
|
+
|
|
70
76
|
if user.save
|
|
71
|
-
Success(:
|
|
77
|
+
Success(:created, data: user)
|
|
72
78
|
else
|
|
73
79
|
Failure(:creation_failed, data: user.errors)
|
|
74
80
|
end
|
|
@@ -78,6 +84,11 @@ end
|
|
|
78
84
|
|
|
79
85
|
> Remember, you **have** to return an `FService::Result` at the end of your services.
|
|
80
86
|
|
|
87
|
+
> Always give your results a type. It says what happened, not just whether it worked, so
|
|
88
|
+
> callers can branch on the reason and a failure is readable in a log without opening the
|
|
89
|
+
> service. Pass the payload in `data:`, including on failures — otherwise `#error` is `nil`
|
|
90
|
+
> and whoever serializes it has nothing to show.
|
|
91
|
+
|
|
81
92
|
### Using your service
|
|
82
93
|
|
|
83
94
|
To run your service, use the method `#call` provided by `FService::Base`. We like to use the [implicit call](https://stackoverflow.com/a/19108981/8650655), but you can use it in the form you like most.
|
|
@@ -112,7 +123,7 @@ class UsersController < BaseController
|
|
|
112
123
|
end
|
|
113
124
|
```
|
|
114
125
|
|
|
115
|
-
> Note that you're not limited to using services inside controllers. They're just PORO's (
|
|
126
|
+
> Note that you're not limited to using services inside controllers. They're just PORO's (Plain Old Ruby Objects), so you can use in controllers, models, etc. (even other services!).
|
|
116
127
|
|
|
117
128
|
### Pattern matching
|
|
118
129
|
|
|
@@ -121,70 +132,65 @@ The code above could be rewritten using the `#on_success` and `#on_failure` hook
|
|
|
121
132
|
```ruby
|
|
122
133
|
class UsersController < BaseController
|
|
123
134
|
def create
|
|
124
|
-
User::Create
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
User::Create
|
|
136
|
+
.call(user_params)
|
|
137
|
+
.on_success { |user| return json_success(user) }
|
|
138
|
+
.on_failure { |errors| return json_error(errors) }
|
|
127
139
|
end
|
|
128
140
|
end
|
|
129
141
|
```
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
error.
|
|
143
|
+
> You can ignore any of the callbacks, if you want to.
|
|
133
144
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
```
|
|
145
|
+
Once you start matching specific types (below), a result whose type none of the hooks
|
|
146
|
+
mention falls through untouched. Pass `unhandled: true` to run a callback for exactly those
|
|
147
|
+
leftovers:
|
|
143
148
|
|
|
144
149
|
```ruby
|
|
145
150
|
class UsersController < BaseController
|
|
146
151
|
def create
|
|
147
|
-
User::Create
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
User::Create
|
|
153
|
+
.call(user_params)
|
|
154
|
+
.on_success(:created) { |user| return json_success(user) }
|
|
155
|
+
.on_failure(unhandled: true) { |errors| return json_error(errors) }
|
|
150
156
|
end
|
|
151
157
|
end
|
|
152
158
|
```
|
|
153
159
|
|
|
154
|
-
> You can ignore any of the callbacks, if you want to.
|
|
155
|
-
|
|
156
160
|
Going further, you can match the Result type, in case you want to handle them differently:
|
|
157
161
|
|
|
158
162
|
```ruby
|
|
159
163
|
class UsersController < BaseController
|
|
160
164
|
def create
|
|
161
|
-
User::Create
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
User::Create
|
|
166
|
+
.call(user_params)
|
|
167
|
+
.on_success(:created) { |user| return json_success(user) }
|
|
168
|
+
.on_failure(:invalid_name) { |errors| return json_error(errors) }
|
|
169
|
+
.on_failure(:creation_failed) do |errors|
|
|
170
|
+
MyLogger.report_failure(errors)
|
|
171
|
+
|
|
172
|
+
return json_error(errors)
|
|
173
|
+
end
|
|
170
174
|
end
|
|
171
175
|
end
|
|
172
176
|
```
|
|
173
177
|
|
|
174
178
|
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.
|
|
179
|
+
the hook will run. Say the service also answers `Success(:already_exists, data: user)` when the
|
|
180
|
+
name is taken — both outcomes are fine for the caller:
|
|
176
181
|
|
|
177
182
|
```ruby
|
|
178
183
|
class UsersController < BaseController
|
|
179
184
|
def create
|
|
180
|
-
User::Create
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
User::Create
|
|
186
|
+
.call(user_params)
|
|
187
|
+
.on_success(:created, :already_exists) { |user| return json_success(user) }
|
|
188
|
+
.on_failure(:invalid_name) { |errors| return json_error(errors) }
|
|
189
|
+
.on_failure(:creation_failed) do |errors|
|
|
190
|
+
MyLogger.report_failure(errors)
|
|
191
|
+
|
|
192
|
+
return json_error(errors)
|
|
193
|
+
end
|
|
188
194
|
end
|
|
189
195
|
end
|
|
190
196
|
```
|
|
@@ -206,9 +212,10 @@ If some step fails, it will short circuit the call chain.
|
|
|
206
212
|
```ruby
|
|
207
213
|
class UsersController < BaseController
|
|
208
214
|
def create
|
|
209
|
-
result = User::Create
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
result = User::Create
|
|
216
|
+
.call(user_params)
|
|
217
|
+
.and_then { |user| User::Login.(user) }
|
|
218
|
+
.and_then { |user| User::SendWelcomeEmail.(user) }
|
|
212
219
|
|
|
213
220
|
if result.successful?
|
|
214
221
|
json_success(result.value)
|
|
@@ -224,17 +231,23 @@ You can use the `.to_proc` method on FService::Base to avoid explicit inputs whe
|
|
|
224
231
|
```ruby
|
|
225
232
|
class UsersController < BaseController
|
|
226
233
|
def create
|
|
227
|
-
result = User::Create
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
result = User::Create
|
|
235
|
+
.call(user_params)
|
|
236
|
+
.and_then(&User::Login)
|
|
237
|
+
.and_then(&User::SendWelcomeEmail)
|
|
230
238
|
# ...
|
|
231
239
|
end
|
|
232
240
|
end
|
|
233
241
|
```
|
|
234
242
|
|
|
243
|
+
> **Coming from 0.2.x?** `Success#then` and `Failure#then` were deprecated in 0.3.0 and
|
|
244
|
+
> removed in 0.4.0. Use `#and_then`, which has always been their alias and behaves
|
|
245
|
+
> identically. Note that `#then` also exists on every Ruby object since 2.6, so a leftover
|
|
246
|
+
> call does not raise — it silently yields the Result itself instead of its value.
|
|
247
|
+
|
|
235
248
|
### `Check` and `Try`
|
|
236
249
|
|
|
237
|
-
You can use `Check` to
|
|
250
|
+
You can use `Check` to convert a boolean into a Result: truthy values map to `Success`, falsey values to `Failure`.
|
|
238
251
|
|
|
239
252
|
```ruby
|
|
240
253
|
Check(:math_works) { 1 < 2 }
|
|
@@ -248,73 +261,116 @@ Check(:math_works) { 1 > 2 }
|
|
|
248
261
|
using the parameter `catch`.
|
|
249
262
|
|
|
250
263
|
```ruby
|
|
251
|
-
class
|
|
264
|
+
class Number::DrawOdd < FService::Base
|
|
252
265
|
def run
|
|
253
|
-
Try(:
|
|
254
|
-
|
|
255
|
-
raise "Yuck! It's a #{
|
|
266
|
+
Try(:drawn_number) do
|
|
267
|
+
drawn_number = rand(1..10)
|
|
268
|
+
raise "Yuck! It's a #{drawn_number}" if drawn_number.even?
|
|
256
269
|
|
|
257
|
-
|
|
270
|
+
drawn_number
|
|
258
271
|
end
|
|
259
272
|
end
|
|
260
273
|
end
|
|
261
274
|
|
|
262
|
-
|
|
263
|
-
# => #<Success @value=9, @types=[:
|
|
275
|
+
Number::DrawOdd.call
|
|
276
|
+
# => #<Success @value=9, @types=[:drawn_number]>
|
|
264
277
|
|
|
265
|
-
|
|
266
|
-
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:
|
|
278
|
+
Number::DrawOdd.call
|
|
279
|
+
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:drawn_number]>
|
|
267
280
|
```
|
|
268
281
|
|
|
269
282
|
## Testing
|
|
270
283
|
|
|
271
|
-
We provide
|
|
284
|
+
We provide helpers and matchers to make it easier to test code involving FService services.
|
|
272
285
|
|
|
273
|
-
To make available
|
|
286
|
+
To make them available, add the following require to `spec/spec_helper.rb` or
|
|
287
|
+
`spec/rails_helper.rb`:
|
|
274
288
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
```rb
|
|
289
|
+
```ruby
|
|
278
290
|
require 'f_service/rspec'
|
|
279
291
|
```
|
|
280
292
|
|
|
281
293
|
### Mocking a result
|
|
282
294
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
`mock_service` stubs the service's `#call` and returns the Result you describe. It is a stub,
|
|
296
|
+
not a message expectation: it does not assert that the service was called, so add your own
|
|
297
|
+
`expect(...).to have_received(:call)` when that is the point of the test.
|
|
286
298
|
|
|
287
|
-
|
|
288
|
-
|
|
299
|
+
```ruby
|
|
300
|
+
mock_service(User::Create)
|
|
301
|
+
# => stubs a successful result, with no types and a nil value
|
|
289
302
|
|
|
290
|
-
mock_service(
|
|
291
|
-
# =>
|
|
303
|
+
mock_service(User::Create, result: :success, types: [:created])
|
|
304
|
+
# => stubs a Success typed :created
|
|
292
305
|
|
|
293
|
-
mock_service(
|
|
294
|
-
# =>
|
|
306
|
+
mock_service(User::Create, result: :success, types: [:created], value: instance_spy(User))
|
|
307
|
+
# => stubs a Success typed :created carrying a value
|
|
295
308
|
|
|
296
|
-
mock_service(
|
|
297
|
-
# =>
|
|
309
|
+
mock_service(User::Create, result: :failure, types: [:invalid_name])
|
|
310
|
+
# => stubs a Failure typed :invalid_name, with a nil error
|
|
298
311
|
|
|
299
|
-
mock_service(
|
|
300
|
-
|
|
312
|
+
mock_service(
|
|
313
|
+
User::Create,
|
|
314
|
+
result: :failure,
|
|
315
|
+
types: [:invalid_name],
|
|
316
|
+
value: { name: ["can't be blank"] }
|
|
317
|
+
)
|
|
318
|
+
# => stubs a Failure typed :invalid_name carrying an error
|
|
319
|
+
```
|
|
301
320
|
|
|
302
|
-
|
|
303
|
-
|
|
321
|
+
> `value:` fills `#value` on a Success and `#error` on a Failure — it is the payload either
|
|
322
|
+
> way. `types:` also accepts a bare symbol, but an array reads consistently. The deprecated
|
|
323
|
+
> singular `type:` argument was removed in 0.4.0.
|
|
324
|
+
|
|
325
|
+
Need the Result object itself rather than a stub — to pass it around in a unit test, say?
|
|
326
|
+
`f_service_result` builds one:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
result = f_service_result(:failure, { name: ["can't be blank"] }, [:invalid_name])
|
|
330
|
+
# => #<Failure @error={name: ["can't be blank"]}, @types=[:invalid_name]>
|
|
304
331
|
```
|
|
305
332
|
|
|
306
333
|
### Matching a result
|
|
307
334
|
|
|
308
|
-
```
|
|
335
|
+
```ruby
|
|
309
336
|
expect(User::Create.(name: 'Joe')).to have_succeed_with(:created)
|
|
310
337
|
|
|
311
338
|
expect(User::Create.(name: 'Joe')).to have_succeed_with(:created).and_value(an_instance_of(User))
|
|
312
339
|
|
|
313
|
-
expect(User::Create.(name: nil)).to have_failed_with(:
|
|
340
|
+
expect(User::Create.(name: nil)).to have_failed_with(:invalid_name)
|
|
341
|
+
|
|
342
|
+
expect(User::Create.(name: nil))
|
|
343
|
+
.to have_failed_with(:invalid_name).and_error({ name: ["can't be blank"] })
|
|
344
|
+
|
|
345
|
+
expect(User::Create.(name: nil))
|
|
346
|
+
.to have_failed_with(:invalid_name).and_error(a_hash_including(name: ["can't be blank"]))
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
> The matchers compare the type list for **equality**, not inclusion. A service answering
|
|
350
|
+
> `Success(:created, :persisted)` is matched by `have_succeed_with(:created, :persisted)` —
|
|
351
|
+
> `have_succeed_with(:created)` alone fails. Name every type the result carries.
|
|
352
|
+
|
|
353
|
+
Putting it together, a spec for the service built above:
|
|
314
354
|
|
|
315
|
-
|
|
355
|
+
```ruby
|
|
356
|
+
RSpec.describe User::Create do
|
|
357
|
+
subject(:create_user) { described_class.call(name: name) }
|
|
358
|
+
|
|
359
|
+
context 'when the name is given' do
|
|
360
|
+
let(:name) { 'Joe' }
|
|
361
|
+
|
|
362
|
+
it { is_expected.to have_succeed_with(:created).and_value(an_instance_of(User)) }
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
context 'when the name is missing' do
|
|
366
|
+
let(:name) { nil }
|
|
316
367
|
|
|
317
|
-
|
|
368
|
+
it 'fails naming the offending attribute' do
|
|
369
|
+
expect(create_user)
|
|
370
|
+
.to have_failed_with(:invalid_name).and_error(a_hash_including(:name))
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
318
374
|
```
|
|
319
375
|
|
|
320
376
|
## API Docs
|
|
@@ -325,11 +381,17 @@ You can access the API docs [here](https://www.rubydoc.info/gems/f_service/).
|
|
|
325
381
|
|
|
326
382
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that allows you to experiment.
|
|
327
383
|
|
|
328
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
384
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
385
|
+
|
|
386
|
+
Releases are automated: the version, the `CHANGELOG.md`, the git tag and the push to
|
|
387
|
+
[rubygems.org](https://rubygems.org) are all derived from
|
|
388
|
+
[Conventional Commits](https://www.conventionalcommits.org/) by release-please. There
|
|
389
|
+
is no version to edit and no release command to run by hand — see
|
|
390
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
329
391
|
|
|
330
392
|
## Contributing
|
|
331
393
|
|
|
332
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/Fretadao/f_service.
|
|
394
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Fretadao/f_service. Read [CONTRIBUTING.md](CONTRIBUTING.md) first — it covers the commit conventions the release automation depends on.
|
|
333
395
|
|
|
334
396
|
## License
|
|
335
397
|
|
data/f_service.gemspec
CHANGED
|
@@ -26,10 +26,20 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/f_service'
|
|
27
27
|
spec.metadata['changelog_uri'] = 'https://github.com/Fretadao/f_service/blob/master/CHANGELOG.md'
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
29
|
+
# Ship only what a consumer of the gem needs: the library, the docs and the
|
|
30
|
+
# licence. Everything else tracked in the repository is development tooling and
|
|
31
|
+
# was being packaged until now — CI workflows, git hooks, the Gemfile, and so on.
|
|
32
|
+
development_only = %r{
|
|
33
|
+
^(bin|test|spec|features)/ # console/setup scripts and test suites
|
|
34
|
+
| ^\.git # .gitignore, .github/, .githooks/
|
|
35
|
+
| ^\.rubocop # linter configuration
|
|
36
|
+
| ^(Gemfile|Rakefile) # development entrypoints
|
|
37
|
+
| ^CONTRIBUTING # contributor documentation
|
|
38
|
+
| release-please # release automation configuration
|
|
39
|
+
}x
|
|
40
|
+
|
|
31
41
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
32
|
-
`git ls-files -z`.split("\x0").
|
|
42
|
+
`git ls-files -z`.split("\x0").grep_v(development_only)
|
|
33
43
|
end
|
|
34
44
|
spec.bindir = 'exe'
|
|
35
45
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
data/lib/f_service/base.rb
CHANGED
|
@@ -78,36 +78,6 @@ module FService
|
|
|
78
78
|
raise NotImplementedError, 'Services must implement #run'
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
# Returns a successful operation.
|
|
82
|
-
# You'll probably want to return this inside {#run}.
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# @example
|
|
86
|
-
# class User::ValidateAge < FService::Base
|
|
87
|
-
# def initialize(age:)
|
|
88
|
-
# @age = age
|
|
89
|
-
# end
|
|
90
|
-
#
|
|
91
|
-
# def run
|
|
92
|
-
# return failure(status: 'No age given!', data: @age) if age.blank?
|
|
93
|
-
# return failure(status: 'Too young!', data: @age) if age < 18
|
|
94
|
-
#
|
|
95
|
-
# success(status: 'Valid age.', data: @age)
|
|
96
|
-
# end
|
|
97
|
-
# end
|
|
98
|
-
#
|
|
99
|
-
# @deprecated Use {#Success} instead.
|
|
100
|
-
# @return [Result::Success] a successful operation
|
|
101
|
-
def success(data = nil)
|
|
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
81
|
# Returns a successful result.
|
|
112
82
|
# You can optionally specify a list of types and a value for your result.
|
|
113
83
|
# You'll probably want to return this inside {#run}.
|
|
@@ -229,67 +199,5 @@ module FService
|
|
|
229
199
|
rescue *catch => e
|
|
230
200
|
Failure(*types, data: e)
|
|
231
201
|
end
|
|
232
|
-
|
|
233
|
-
# Returns a failed operation.
|
|
234
|
-
# You'll probably want to return this inside {#run}.
|
|
235
|
-
# @example
|
|
236
|
-
# class User::ValidateAge < FService::Base
|
|
237
|
-
# def initialize(age:)
|
|
238
|
-
# @age = age
|
|
239
|
-
# end
|
|
240
|
-
#
|
|
241
|
-
# def run
|
|
242
|
-
# return failure(status: 'No age given!', data: @age) if age.blank?
|
|
243
|
-
# return failure(status: 'Too young!', data: @age) if age < 18
|
|
244
|
-
#
|
|
245
|
-
# success(status: 'Valid age.', data: @age)
|
|
246
|
-
# end
|
|
247
|
-
# end
|
|
248
|
-
#
|
|
249
|
-
# @deprecated Use {#Failure} instead.
|
|
250
|
-
# @return [Result::Failure] a failed operation
|
|
251
|
-
def failure(data = nil)
|
|
252
|
-
FService.deprecate!(
|
|
253
|
-
name: "#{self.class}##{__method__}",
|
|
254
|
-
alternative: '#Failure',
|
|
255
|
-
from: caller[0]
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
Result::Failure.new(data)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Return either {Result::Failure Success} or {Result::Failure Failure}
|
|
262
|
-
# given the condition.
|
|
263
|
-
#
|
|
264
|
-
# @example
|
|
265
|
-
# class YearIsLeap < FService::Base
|
|
266
|
-
# def initialize(year:)
|
|
267
|
-
# @year = year
|
|
268
|
-
# end
|
|
269
|
-
#
|
|
270
|
-
# def run
|
|
271
|
-
# return failure(status: 'No year given!', data: @year) if @year.nil?
|
|
272
|
-
#
|
|
273
|
-
# result(leap?, @year)
|
|
274
|
-
# end
|
|
275
|
-
#
|
|
276
|
-
# private
|
|
277
|
-
#
|
|
278
|
-
# def leap?
|
|
279
|
-
# ((@year % 4).zero? && @year % 100 != 0) || (@year % 400).zero?
|
|
280
|
-
# end
|
|
281
|
-
# end
|
|
282
|
-
#
|
|
283
|
-
# @deprecated Use {#Check} instead.
|
|
284
|
-
# @return [Result::Success, Result::Failure]
|
|
285
|
-
def result(condition, data = nil)
|
|
286
|
-
FService.deprecate!(
|
|
287
|
-
name: "#{self.class}##{__method__}",
|
|
288
|
-
alternative: '#Check',
|
|
289
|
-
from: caller[0]
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
condition ? success(data) : failure(data)
|
|
293
|
-
end
|
|
294
202
|
end
|
|
295
203
|
end
|
|
@@ -22,13 +22,6 @@ module FService
|
|
|
22
22
|
@matching_types = []
|
|
23
23
|
end
|
|
24
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
25
|
# This hook runs if the result is successful.
|
|
33
26
|
# Can receive one or more types to be checked before running the given block.
|
|
34
27
|
#
|
|
@@ -110,12 +110,6 @@ module FService
|
|
|
110
110
|
self
|
|
111
111
|
end
|
|
112
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
|
-
|
|
119
113
|
# Outputs a string representation of the object
|
|
120
114
|
#
|
|
121
115
|
#
|
|
@@ -83,13 +83,6 @@ module FService
|
|
|
83
83
|
yield(*to_ary)
|
|
84
84
|
end
|
|
85
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
86
|
# Returns itself to the given block.
|
|
94
87
|
# Use this to chain multiple actions or service calls (only valid when they return a Result).
|
|
95
88
|
# It works just like the `.and_then` method, but only runs if service is a Failure.
|
|
@@ -12,17 +12,8 @@ module FServiceResultHelpers
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# Mock a Fservice service call returning a result.
|
|
15
|
-
def mock_service(service, result: :success, value: nil,
|
|
16
|
-
|
|
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)
|
|
15
|
+
def mock_service(service, result: :success, value: nil, types: [])
|
|
16
|
+
service_result = f_service_result(result, value, Array(types))
|
|
26
17
|
allow(service).to receive(:call).and_return(service_result)
|
|
27
18
|
end
|
|
28
19
|
end
|
data/lib/f_service/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: f_service
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fretadao Tech Team
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: |2
|
|
14
13
|
FService is a small gem that provides a base class for your services (aka operations).
|
|
@@ -20,18 +19,9 @@ executables: []
|
|
|
20
19
|
extensions: []
|
|
21
20
|
extra_rdoc_files: []
|
|
22
21
|
files:
|
|
23
|
-
- ".github/.dependabot.yml"
|
|
24
|
-
- ".github/workflows/tests-and-linter.yml"
|
|
25
|
-
- ".gitignore"
|
|
26
|
-
- ".rubocop.yml"
|
|
27
22
|
- CHANGELOG.md
|
|
28
|
-
- Gemfile
|
|
29
|
-
- Gemfile.lock
|
|
30
23
|
- LICENSE
|
|
31
24
|
- README.md
|
|
32
|
-
- Rakefile
|
|
33
|
-
- bin/console
|
|
34
|
-
- bin/setup
|
|
35
25
|
- f_service.gemspec
|
|
36
26
|
- lib/f_service.rb
|
|
37
27
|
- lib/f_service/base.rb
|
|
@@ -55,7 +45,6 @@ metadata:
|
|
|
55
45
|
source_code_uri: https://github.com/Fretadao/f_service
|
|
56
46
|
documentation_uri: https://www.rubydoc.info/gems/f_service
|
|
57
47
|
changelog_uri: https://github.com/Fretadao/f_service/blob/master/CHANGELOG.md
|
|
58
|
-
post_install_message:
|
|
59
48
|
rdoc_options: []
|
|
60
49
|
require_paths:
|
|
61
50
|
- lib
|
|
@@ -70,8 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
70
59
|
- !ruby/object:Gem::Version
|
|
71
60
|
version: '0'
|
|
72
61
|
requirements: []
|
|
73
|
-
rubygems_version: 3.
|
|
74
|
-
signing_key:
|
|
62
|
+
rubygems_version: 3.6.9
|
|
75
63
|
specification_version: 4
|
|
76
64
|
summary: A small, monad-based service class
|
|
77
65
|
test_files: []
|
data/.github/.dependabot.yml
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
name: Ruby
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
strategy:
|
|
13
|
-
matrix:
|
|
14
|
-
ruby: [3.0, 3.1, 3.2, 3.3]
|
|
15
|
-
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v2
|
|
18
|
-
- name: Set up Ruby ${{ matrix.ruby }}
|
|
19
|
-
uses: ruby/setup-ruby@v1
|
|
20
|
-
with:
|
|
21
|
-
ruby-version: ${{ matrix.ruby }}
|
|
22
|
-
- name: Build and test with Rake
|
|
23
|
-
run: |
|
|
24
|
-
gem install bundler
|
|
25
|
-
bundle install --jobs 4 --retry 3
|
|
26
|
-
bundle exec rake
|
|
27
|
-
- name: Rubocop Linter Action
|
|
28
|
-
run: bundle exec rubocop --parallel
|
data/.gitignore
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
*.gem
|
|
2
|
-
*.rbc
|
|
3
|
-
/.config
|
|
4
|
-
/coverage/
|
|
5
|
-
/InstalledFiles
|
|
6
|
-
/pkg/
|
|
7
|
-
/spec/reports/
|
|
8
|
-
/spec/examples.txt
|
|
9
|
-
/test/tmp/
|
|
10
|
-
/test/version_tmp/
|
|
11
|
-
/tmp/
|
|
12
|
-
|
|
13
|
-
# Used by dotenv library to load environment variables.
|
|
14
|
-
# .env
|
|
15
|
-
|
|
16
|
-
# Ignore Byebug command history file.
|
|
17
|
-
.byebug_history
|
|
18
|
-
|
|
19
|
-
## Specific to RubyMotion:
|
|
20
|
-
.dat*
|
|
21
|
-
.repl_history
|
|
22
|
-
build/
|
|
23
|
-
*.bridgesupport
|
|
24
|
-
build-iPhoneOS/
|
|
25
|
-
build-iPhoneSimulator/
|
|
26
|
-
|
|
27
|
-
## Specific to RubyMotion (use of CocoaPods):
|
|
28
|
-
#
|
|
29
|
-
# We recommend against adding the Pods directory to your .gitignore. However
|
|
30
|
-
# you should judge for yourself, the pros and cons are mentioned at:
|
|
31
|
-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
32
|
-
#
|
|
33
|
-
# vendor/Pods/
|
|
34
|
-
|
|
35
|
-
## Documentation cache and generated files:
|
|
36
|
-
/.yardoc/
|
|
37
|
-
/_yardoc/
|
|
38
|
-
/doc/
|
|
39
|
-
/rdoc/
|
|
40
|
-
|
|
41
|
-
## Environment normalization:
|
|
42
|
-
/.bundle/
|
|
43
|
-
/vendor/bundle
|
|
44
|
-
/lib/bundler/man/
|
|
45
|
-
|
|
46
|
-
# for a library or gem, you might want to ignore these files since the code is
|
|
47
|
-
# intended to run in multiple environments; otherwise, check them in:
|
|
48
|
-
# Gemfile.lock
|
|
49
|
-
# .ruby-version
|
|
50
|
-
# .ruby-gemset
|
|
51
|
-
|
|
52
|
-
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
53
|
-
.rvmrc
|
|
54
|
-
|
|
55
|
-
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
|
56
|
-
# .rubocop-https?--*
|
|
57
|
-
|
|
58
|
-
.rspec_status
|
data/.rubocop.yml
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
require:
|
|
2
|
-
- rubocop-rspec
|
|
3
|
-
|
|
4
|
-
AllCops:
|
|
5
|
-
TargetRubyVersion: 3.0.0
|
|
6
|
-
NewCops: enable
|
|
7
|
-
|
|
8
|
-
Layout/LineLength:
|
|
9
|
-
Max: 120
|
|
10
|
-
|
|
11
|
-
Metrics/BlockLength:
|
|
12
|
-
Exclude:
|
|
13
|
-
- "spec/**/*"
|
|
14
|
-
|
|
15
|
-
Style/DocumentationMethod:
|
|
16
|
-
Enabled: true
|
|
17
|
-
|
|
18
|
-
Naming/MethodName:
|
|
19
|
-
Exclude:
|
|
20
|
-
- lib/f_service/base.rb
|
|
21
|
-
|
|
22
|
-
RSpec/ContextWording:
|
|
23
|
-
Prefixes:
|
|
24
|
-
- and
|
|
25
|
-
- but
|
|
26
|
-
- when
|
|
27
|
-
- with
|
|
28
|
-
- without
|
|
29
|
-
|
|
30
|
-
RSpec/ExampleLength:
|
|
31
|
-
Max: 20
|
|
32
|
-
|
|
33
|
-
RSpec/NestedGroups:
|
|
34
|
-
Enabled: false
|
|
35
|
-
|
|
36
|
-
##### RUBYGEMS #####
|
|
37
|
-
Gemspec/RequireMFA:
|
|
38
|
-
Enabled: false
|
data/Gemfile
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
source 'https://rubygems.org'
|
|
4
|
-
|
|
5
|
-
# Specify your gem's dependencies in f_service.gemspec
|
|
6
|
-
gemspec
|
|
7
|
-
|
|
8
|
-
group :development, :test do
|
|
9
|
-
gem 'pry'
|
|
10
|
-
gem 'pry-nav'
|
|
11
|
-
gem 'rake', '~> 13.0.0'
|
|
12
|
-
gem 'rubocop', '~> 1.60.2', require: false
|
|
13
|
-
gem 'rubocop-rspec', require: false
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
group :docs do
|
|
17
|
-
gem 'yard'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
group :optional do
|
|
21
|
-
gem 'solargraph'
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
group :test do
|
|
25
|
-
gem 'rspec', '~> 3.0'
|
|
26
|
-
gem 'simplecov', require: false
|
|
27
|
-
end
|
data/Gemfile.lock
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
f_service (0.3.1)
|
|
5
|
-
|
|
6
|
-
GEM
|
|
7
|
-
remote: https://rubygems.org/
|
|
8
|
-
specs:
|
|
9
|
-
ast (2.4.2)
|
|
10
|
-
backport (1.2.0)
|
|
11
|
-
benchmark (0.3.0)
|
|
12
|
-
coderay (1.1.3)
|
|
13
|
-
diff-lcs (1.3)
|
|
14
|
-
docile (1.3.5)
|
|
15
|
-
e2mmap (0.1.0)
|
|
16
|
-
jaro_winkler (1.5.6)
|
|
17
|
-
json (2.7.1)
|
|
18
|
-
kramdown (2.4.0)
|
|
19
|
-
rexml
|
|
20
|
-
kramdown-parser-gfm (1.1.0)
|
|
21
|
-
kramdown (~> 2.0)
|
|
22
|
-
language_server-protocol (3.17.0.3)
|
|
23
|
-
method_source (1.0.0)
|
|
24
|
-
mini_portile2 (2.8.6)
|
|
25
|
-
nokogiri (1.16.5)
|
|
26
|
-
mini_portile2 (~> 2.8.2)
|
|
27
|
-
racc (~> 1.4)
|
|
28
|
-
parallel (1.24.0)
|
|
29
|
-
parser (3.3.0.5)
|
|
30
|
-
ast (~> 2.4.1)
|
|
31
|
-
racc
|
|
32
|
-
pry (0.14.1)
|
|
33
|
-
coderay (~> 1.1)
|
|
34
|
-
method_source (~> 1.0)
|
|
35
|
-
pry-nav (1.0.0)
|
|
36
|
-
pry (>= 0.9.10, < 0.15)
|
|
37
|
-
racc (1.7.3)
|
|
38
|
-
rainbow (3.1.1)
|
|
39
|
-
rake (13.0.1)
|
|
40
|
-
regexp_parser (2.9.0)
|
|
41
|
-
reverse_markdown (2.1.1)
|
|
42
|
-
nokogiri
|
|
43
|
-
rexml (3.2.8)
|
|
44
|
-
strscan (>= 3.0.9)
|
|
45
|
-
rspec (3.9.0)
|
|
46
|
-
rspec-core (~> 3.9.0)
|
|
47
|
-
rspec-expectations (~> 3.9.0)
|
|
48
|
-
rspec-mocks (~> 3.9.0)
|
|
49
|
-
rspec-core (3.9.1)
|
|
50
|
-
rspec-support (~> 3.9.1)
|
|
51
|
-
rspec-expectations (3.9.1)
|
|
52
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
53
|
-
rspec-support (~> 3.9.0)
|
|
54
|
-
rspec-mocks (3.9.1)
|
|
55
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
56
|
-
rspec-support (~> 3.9.0)
|
|
57
|
-
rspec-support (3.9.2)
|
|
58
|
-
rubocop (1.60.2)
|
|
59
|
-
json (~> 2.3)
|
|
60
|
-
language_server-protocol (>= 3.17.0)
|
|
61
|
-
parallel (~> 1.10)
|
|
62
|
-
parser (>= 3.3.0.2)
|
|
63
|
-
rainbow (>= 2.2.2, < 4.0)
|
|
64
|
-
regexp_parser (>= 1.8, < 3.0)
|
|
65
|
-
rexml (>= 3.2.5, < 4.0)
|
|
66
|
-
rubocop-ast (>= 1.30.0, < 2.0)
|
|
67
|
-
ruby-progressbar (~> 1.7)
|
|
68
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
|
69
|
-
rubocop-ast (1.30.0)
|
|
70
|
-
parser (>= 3.2.1.0)
|
|
71
|
-
rubocop-capybara (2.20.0)
|
|
72
|
-
rubocop (~> 1.41)
|
|
73
|
-
rubocop-factory_bot (2.25.1)
|
|
74
|
-
rubocop (~> 1.41)
|
|
75
|
-
rubocop-rspec (2.26.1)
|
|
76
|
-
rubocop (~> 1.40)
|
|
77
|
-
rubocop-capybara (~> 2.17)
|
|
78
|
-
rubocop-factory_bot (~> 2.22)
|
|
79
|
-
ruby-progressbar (1.13.0)
|
|
80
|
-
simplecov (0.21.2)
|
|
81
|
-
docile (~> 1.1)
|
|
82
|
-
simplecov-html (~> 0.11)
|
|
83
|
-
simplecov_json_formatter (~> 0.1)
|
|
84
|
-
simplecov-html (0.12.3)
|
|
85
|
-
simplecov_json_formatter (0.1.2)
|
|
86
|
-
solargraph (0.41.2)
|
|
87
|
-
backport (~> 1.1)
|
|
88
|
-
benchmark
|
|
89
|
-
bundler (>= 1.17.2)
|
|
90
|
-
e2mmap
|
|
91
|
-
jaro_winkler (~> 1.5)
|
|
92
|
-
kramdown (~> 2.3)
|
|
93
|
-
kramdown-parser-gfm (~> 1.1)
|
|
94
|
-
parser (~> 3.0)
|
|
95
|
-
reverse_markdown (>= 1.0.5, < 3)
|
|
96
|
-
rubocop (>= 0.52)
|
|
97
|
-
thor (~> 1.0)
|
|
98
|
-
tilt (~> 2.0)
|
|
99
|
-
yard (~> 0.9, >= 0.9.24)
|
|
100
|
-
strscan (3.1.0)
|
|
101
|
-
thor (1.3.0)
|
|
102
|
-
tilt (2.3.0)
|
|
103
|
-
unicode-display_width (2.5.0)
|
|
104
|
-
yard (0.9.36)
|
|
105
|
-
|
|
106
|
-
PLATFORMS
|
|
107
|
-
ruby
|
|
108
|
-
|
|
109
|
-
DEPENDENCIES
|
|
110
|
-
f_service!
|
|
111
|
-
pry
|
|
112
|
-
pry-nav
|
|
113
|
-
rake (~> 13.0.0)
|
|
114
|
-
rspec (~> 3.0)
|
|
115
|
-
rubocop (~> 1.60.2)
|
|
116
|
-
rubocop-rspec
|
|
117
|
-
simplecov
|
|
118
|
-
solargraph
|
|
119
|
-
yard
|
|
120
|
-
|
|
121
|
-
BUNDLED WITH
|
|
122
|
-
2.2.32
|
data/Rakefile
DELETED
data/bin/console
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'bundler/setup'
|
|
5
|
-
require 'f_service'
|
|
6
|
-
|
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
-
|
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
-
# require "pry"
|
|
12
|
-
# Pry.start
|
|
13
|
-
|
|
14
|
-
require 'irb'
|
|
15
|
-
IRB.start(__FILE__)
|