simple_ruby_service 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +75 -57
- data/lib/simple_ruby_service/version.rb +1 -1
- data/simple_ruby_service.gemspec +2 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a993ee0ea8d8f8e40588b27286699aa0e4cf0196b530d864493921146339434b
|
4
|
+
data.tar.gz: d42d0b36699eea8126f56f4ca9fa5dda415cf16fe132b47da2bda7c13642c2ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 135157ecda108ae98dd379db2fb597e78fff0e5ce84dd8a663d064b0af44f5dfb0f259f9a893013c87a49ec0d6559281eda87f683d647d535924fa6a8a32cba4
|
7
|
+
data.tar.gz: 5f2f41813dddb117d7f2e3bb5aa40edcd180ea89901bce182d2e6f5e76eb3665d234a5a8ddb5e5fc3cff2cc15e5d34a544518921dd2e7f363bce245f25ee75bc
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -5,11 +5,29 @@
|
|
5
5
|
|
6
6
|
Simple Ruby Service is a lightweight framework for creating Services and Service Objects (SOs) in Ruby.
|
7
7
|
|
8
|
-
The framework
|
8
|
+
The framework makes Services and SOs look and feel like ActiveModels, complete with:
|
9
9
|
|
10
|
-
1.
|
11
|
-
2.
|
12
|
-
3.
|
10
|
+
1. Validations and robust error handling
|
11
|
+
2. Workflows and method chaining
|
12
|
+
3. Consistent interfaces
|
13
|
+
|
14
|
+
Additionally, Simple Ruby Service Objects can stand in for Procs, wherever Procs are expected (via ducktyping).
|
15
|
+
|
16
|
+
#### What problem does Simple Ruby Service solve?
|
17
|
+
|
18
|
+
Currently, most ruby developers roll their own services from scratch. As a result, most services are hastely built (in isolation), and this leads to inconsistant interfaces that are difficult to read. Also, error handling tends to vary wildly within an application, and support code tends to be implemented over and over again.
|
19
|
+
|
20
|
+
Simple Ruby Service addresses these problems and encourages succinct, idiomatic coding styles.
|
21
|
+
|
22
|
+
#### Should I be using Services & SOs in Ruby / Rails?
|
23
|
+
|
24
|
+
[LMGTFY](https://www.google.com/search?q=service+object+pattern+rails&rlz=1C5CHFA_enUS893US893&oq=service+object+pattern+rails) to learn more about Services & SOs.
|
25
|
+
|
26
|
+
**TLDR** - Fat models and fat controllers are bad! Services and Service Objects help you DRY things up.
|
27
|
+
|
28
|
+
#### How is a Service different from an SO?
|
29
|
+
|
30
|
+
An SO is just a Service that encapsulates a single operation (i.e. **one, and only one, responsibility**).
|
13
31
|
|
14
32
|
## Requirements
|
15
33
|
|
@@ -59,18 +77,18 @@ class SomeController < ApplicationController
|
|
59
77
|
end
|
60
78
|
```
|
61
79
|
|
62
|
-
#### ::After:: Refactored using an
|
80
|
+
#### ::After:: Refactored using an Simple Ruby Service Object
|
63
81
|
```ruby
|
64
82
|
# in app/controllers/some_controller.rb
|
65
83
|
class SomeController < ApplicationController
|
66
84
|
def show
|
67
|
-
# NOTE:
|
85
|
+
# NOTE: That's right... just one, readable line of code
|
68
86
|
render DoSomething.call!(params)
|
69
87
|
end
|
70
88
|
end
|
71
89
|
```
|
72
90
|
|
73
|
-
#### ::Alternate After:: Refactored using a Service
|
91
|
+
#### ::Alternate After:: Refactored using a Simple Ruby Service
|
74
92
|
```ruby
|
75
93
|
# in app/controllers/some_controller.rb
|
76
94
|
class SomeController < ApplicationController
|
@@ -86,14 +104,21 @@ end
|
|
86
104
|
|
87
105
|
### Taking a peek under the hood
|
88
106
|
|
89
|
-
|
90
|
-
- creates an instance of `DoSomething`
|
91
|
-
- initializes `instance.attributes` with `params`
|
92
|
-
- raises `SimpleRubyService::Invalid` if `instance.invalid?`
|
93
|
-
- sends `instance.call`
|
94
|
-
- raises `SimpleRubyService::Failed` if `instance.failed?`
|
95
|
-
- returns `instance.value` directly to the caller
|
107
|
+
`DoSomething.call!(params)` is deliberately designed to look and feel like `ActiveRecord::Base#save!`.
|
96
108
|
|
109
|
+
The following (simplified) implementation illustrates what happens under the hood:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
module SimpleRubyService::Object
|
113
|
+
def self.call!(params)
|
114
|
+
instance = new(params)
|
115
|
+
raise Invalid unless instance.valid?
|
116
|
+
self.value = instance.call
|
117
|
+
raise Invalid unless instance.failed?
|
118
|
+
value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
97
122
|
|
98
123
|
### Anatomy of a Simple Ruby Service Object
|
99
124
|
```ruby
|
@@ -101,6 +126,7 @@ Similar to `ActiveRecord::Base#save!`, `DoSomething.call!(params)`:
|
|
101
126
|
class DoSomething
|
102
127
|
include SimpleRubyService::ServiceObject
|
103
128
|
|
129
|
+
# `attribute` behaves similar to ActiveRecord::Base#attribute, but is not typed, or bound to persistant storage
|
104
130
|
attribute :id
|
105
131
|
attr_accessor :resource
|
106
132
|
|
@@ -350,19 +376,46 @@ class SomeService
|
|
350
376
|
end
|
351
377
|
```
|
352
378
|
|
353
|
-
|
379
|
+
### Workflows
|
380
|
+
Simple Ruby Services are inherently a good fit for workflows because they support chaining, i.e.:
|
354
381
|
|
355
|
-
|
382
|
+
```ruby
|
383
|
+
SomeService.new(params)
|
384
|
+
.do_something
|
385
|
+
.do_something_related
|
386
|
+
.value
|
387
|
+
```
|
356
388
|
|
357
|
-
|
389
|
+
But SOs can also implement various workflows with dependency injection:
|
358
390
|
|
359
|
-
|
391
|
+
```ruby
|
392
|
+
class PerformSomeWorkflow < SimpleRubyService::ServiceObject
|
393
|
+
def perform
|
394
|
+
dependency = SimpleRubyService1.call!
|
395
|
+
result = SimpleRubyService2.call(dependency)
|
396
|
+
raise unless result.success?
|
397
|
+
SimpleRubyService3(dependency, result.value).call!
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
360
401
|
|
361
|
-
|
402
|
+
## MISC
|
362
403
|
|
363
|
-
|
404
|
+
### To bang!, or not to bang
|
364
405
|
|
365
|
-
|
406
|
+
Use the bang! version of an operation whenever you expect the operation to succeed more often than fail, and you don't need to chain operations together.
|
407
|
+
|
408
|
+
Similar in pattern to `ActiveRecord#save!`, the bang version of each operation:
|
409
|
+
* raises `SimpleRubyService::Invalid` if `valid?` is falsey
|
410
|
+
* raises `SimpleRubyService::Failure` if the block provided returns a falsey value
|
411
|
+
* returns `@value`
|
412
|
+
|
413
|
+
Whereas, similar in pattern to `ActiveRecord#save`, the regular version of each operation:
|
414
|
+
* doesn't raise any exceptions
|
415
|
+
* passes the return value of the block provided to `#success?`
|
416
|
+
* returns self << _note: this is unlike `ActiveRecord#save`_
|
417
|
+
|
418
|
+
### Service or SO?
|
366
419
|
|
367
420
|
Use a `Service` when encapsulating related operations that share dependencies & validations.
|
368
421
|
|
@@ -373,9 +426,6 @@ i.e.:
|
|
373
426
|
|
374
427
|
_note: Things get fuzzy when operations share some, but not all, dependencies & validations. Use your best judgement when operation `A` and operation `B` are related but `A` acts on a `User` while `B` acts on both a `User` & a `Company`._
|
375
428
|
|
376
|
-
### Atomicity
|
377
|
-
The framework does not include transaction support by default. You are responsible for wrapping with a transaction if atomicity is desired.
|
378
|
-
|
379
429
|
### Control Flow
|
380
430
|
Rescue exceptions that represent internal control flow and propogate the rest.
|
381
431
|
|
@@ -383,7 +433,7 @@ For example, if an internal call to User.create! is expected to always succeed,
|
|
383
433
|
|
384
434
|
Example::
|
385
435
|
```ruby
|
386
|
-
class DoSomethingDangerous < SimpleRubyService::
|
436
|
+
class DoSomethingDangerous < SimpleRubyService::ServiceObject
|
387
437
|
attribute :attr1, :attr2 # should include all params required to execute
|
388
438
|
validates_presence_of :attr1 # validate params to call
|
389
439
|
|
@@ -399,38 +449,6 @@ class DoSomethingDangerous < SimpleRubyService::ObjectBase
|
|
399
449
|
end
|
400
450
|
```
|
401
451
|
|
402
|
-
## Workflows
|
403
|
-
SOs often need to call other SOs in order to implement various workflows:
|
404
|
-
```ruby
|
405
|
-
class PerformSomeWorkflow < SimpleRubyService::ObjectBase
|
406
|
-
def perform
|
407
|
-
dependency = SimpleRubyService1.call!
|
408
|
-
result = SimpleRubyService2.call(dependency)
|
409
|
-
raise unless result.success?
|
410
|
-
SimpleRubyService3(dependency, result.value).call!
|
411
|
-
end
|
412
|
-
end
|
413
|
-
```
|
414
|
-
|
415
|
-
## MISC
|
416
|
-
|
417
|
-
### Attributes
|
418
|
-
The `attribute` and `attributes` keywords behaves similar to [ActiveRecord::Base.attribute](https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/Attributes/ClassMethods.html), but they are not typed or bound to persistant storage.
|
419
|
-
|
420
|
-
### To bang!, or not to bang
|
421
|
-
|
422
|
-
Use the bang! version of an operation whenever you expect the operation to succeed more often than fail, and you don't need to chain operations together.
|
423
|
-
|
424
|
-
Similar in pattern to `ActiveRecord#save!`, the bang version of each operation:
|
425
|
-
* raises `SimpleRubyService::Invalid` if `valid?` is falsey
|
426
|
-
* raises `SimpleRubyService::Failure` if the block provided returns a falsey value
|
427
|
-
* returns `@value`
|
428
|
-
|
429
|
-
Whereas, similar in pattern to `ActiveRecord#save`, the regular version of each operation:
|
430
|
-
* doesn't raise any exceptions
|
431
|
-
* passes the return value of the block provided to `#success?`
|
432
|
-
* returns self << _note: this is unlike `ActiveRecord#save`_
|
433
|
-
|
434
452
|
## Development
|
435
453
|
|
436
454
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/simple_ruby_service.gemspec
CHANGED
@@ -11,7 +11,8 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ["i.jaycrouch@gmail.com"]
|
12
12
|
|
13
13
|
spec.summary = 'Simple Ruby Service is a lightweight framework for creating Services and Service Objects (SOs) in Ruby.'
|
14
|
-
spec.description = 'Simple Ruby Service is a lightweight framework for creating Services and Service Objects (SOs) in Ruby.
|
14
|
+
spec.description = 'Simple Ruby Service is a lightweight framework for creating Services and Service Objects (SOs) in Ruby. ' \
|
15
|
+
'The framework makes Services and SOs look and feel like ActiveModels, complete with: 1. validations and robust error handling; 2. workflows and method chaining; and 3. consistent interfaces. Additionally, Simple Ruby Service Objects can stand in for Procs, wherever Procs are expected (via ducktyping).'
|
15
16
|
spec.homepage = 'https://github.com/amazing-jay/simple_ruby_service'
|
16
17
|
spec.license = "MIT"
|
17
18
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_ruby_service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay Crouch
|
@@ -304,10 +304,11 @@ dependencies:
|
|
304
304
|
- - "~>"
|
305
305
|
- !ruby/object:Gem::Version
|
306
306
|
version: '3.13'
|
307
|
-
description: Simple Ruby Service is a lightweight framework for creating Services
|
308
|
-
and Service Objects (SOs) in Ruby. The framework
|
309
|
-
|
310
|
-
|
307
|
+
description: 'Simple Ruby Service is a lightweight framework for creating Services
|
308
|
+
and Service Objects (SOs) in Ruby. The framework makes Services and SOs look and
|
309
|
+
feel like ActiveModels, complete with: 1. validations and robust error handling;
|
310
|
+
2. workflows and method chaining; and 3. consistent interfaces. Additionally, Simple
|
311
|
+
Ruby Service Objects can stand in for Procs, wherever Procs are expected (via ducktyping).'
|
311
312
|
email:
|
312
313
|
- i.jaycrouch@gmail.com
|
313
314
|
executables: []
|