simple_ruby_service 1.0.2 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +130 -80
- data/lib/simple_ruby_service/service.rb +7 -4
- data/lib/simple_ruby_service/version.rb +1 -1
- data/simple_ruby_service.gemspec +3 -2
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93055c8359bf7677e53e99e5cae18f128e17f643770b51e207cdfd4892bf1ac2
|
4
|
+
data.tar.gz: 916d16d46e8c43ca5815e40b940df3d768e9d22fec56990fa29fb4c0610235a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d68822b1a43f2a664c9e4b6f48a7b0a81de97686d346147e5b1d9bc6163e69d3df83a199908a82da6f2f694b704821aab54c019e35f35f3bfefed47c71499f42
|
7
|
+
data.tar.gz: cc1b27bbb4d6fd18dfb161d504787a4828a27a0babe4550f0864f1f9f9d740749a0e7983792b8e92d9f030ad7b1afa23be0220ee239939e4cf7cda3b11bb7d96
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.5 (22-Dec-21)
|
4
|
+
|
5
|
+
* Added setting to control automatically setting self.value with result of service methods
|
6
|
+
|
7
|
+
## 1.0.4 (02-Jul-21)
|
8
|
+
|
9
|
+
* Polished README (again)
|
10
|
+
|
3
11
|
## 1.0.3 (01-Jul-21)
|
4
12
|
|
5
|
-
*
|
13
|
+
* Polished README
|
6
14
|
|
7
15
|
## 1.0.2 (01-Jul-21)
|
8
16
|
|
data/README.md
CHANGED
@@ -3,13 +3,31 @@
|
|
3
3
|
[![Build Status](https://travis-ci.com/amazing-jay/simple_ruby_service.svg?branch=master)](https://travis-ci.com/amazing-jay/simple_ruby_service)
|
4
4
|
[![Test Coverage](https://codecov.io/gh/amazing-jay/simple_ruby_service/graph/badge.svg)](https://codecov.io/gh/amazing-jay/simple_ruby_service)
|
5
5
|
|
6
|
-
Simple Ruby Service is a lightweight framework for
|
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
|
|
@@ -37,10 +55,12 @@ Source code can be downloaded on GitHub
|
|
37
55
|
[github.com/amazing-jay/simple_ruby_service/tree/master](https://github.com/amazing-jay/simple_ruby_service/tree/master)
|
38
56
|
|
39
57
|
|
40
|
-
|
58
|
+
## Quick Start
|
41
59
|
|
42
60
|
See [Usage](https://github.com/amazing-jay/simple_ruby_service#usage) & [Creating Simple Ruby Services](https://github.com/amazing-jay/simple_ruby_service#creating-simple-ruby-services) for more information.
|
43
61
|
|
62
|
+
### How to refactor complex business logic with Simple Ruby Service
|
63
|
+
|
44
64
|
#### ::Before:: Vanilla Rails with a fat controller (a contrived example)
|
45
65
|
```ruby
|
46
66
|
# in app/controllers/some_controller.rb
|
@@ -51,56 +71,85 @@ class SomeController < ApplicationController
|
|
51
71
|
authorize! resource
|
52
72
|
resource.do_something
|
53
73
|
value = resource.do_something_related
|
74
|
+
raise unless resource.errors
|
54
75
|
render value
|
55
76
|
end
|
56
77
|
end
|
57
78
|
```
|
58
79
|
|
59
|
-
#### ::After:: Refactored using an
|
80
|
+
#### ::After:: Refactored using an Simple Ruby Service Object
|
81
|
+
```ruby
|
82
|
+
# in app/controllers/some_controller.rb
|
83
|
+
class SomeController < ApplicationController
|
84
|
+
def show
|
85
|
+
# NOTE: That's right... just one, readable line of code
|
86
|
+
render DoSomething.call!(params)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
#### ::Alternate After:: Refactored using a Simple Ruby Service
|
60
92
|
```ruby
|
61
93
|
# in app/controllers/some_controller.rb
|
62
94
|
class SomeController < ApplicationController
|
63
95
|
def show
|
64
|
-
# NOTE: Simple Ruby Service
|
65
|
-
render
|
96
|
+
# NOTE: Simple Ruby Service methods can be chained together
|
97
|
+
render SomeService.new(params)
|
98
|
+
.do_something
|
99
|
+
.do_something_related
|
100
|
+
.value
|
66
101
|
end
|
67
102
|
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### Taking a peek under the hood
|
106
|
+
|
107
|
+
`DoSomething.call!(params)` is deliberately designed to look and feel like `ActiveRecord::Base#save!`.
|
108
|
+
|
109
|
+
The following (simplified) implementation illustrates what happens under the hood:
|
68
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
|
+
```
|
69
122
|
|
123
|
+
### Anatomy of a Simple Ruby Service Object
|
124
|
+
```ruby
|
70
125
|
# in app/service_objects/do_something.rb
|
71
126
|
class DoSomething
|
72
127
|
include SimpleRubyService::ServiceObject
|
73
128
|
|
129
|
+
# `attribute` behaves similar to ActiveRecord::Base#attribute, but is not typed, or bound to persistant storage
|
74
130
|
attribute :id
|
75
131
|
attr_accessor :resource
|
76
132
|
|
77
|
-
#
|
133
|
+
# Validations are executed prior to the business logic encapsulated in `perform`
|
78
134
|
validate do
|
79
135
|
@resource ||= SomeModel.find(id)
|
80
136
|
authorize! resource
|
81
137
|
end
|
82
138
|
|
83
|
-
#
|
139
|
+
# The result of `perform` is automatically stored as the SO's `value`
|
84
140
|
def perform
|
85
|
-
resource.do_something
|
86
|
-
resource.do_something_related
|
141
|
+
resource.do_something
|
142
|
+
result = resource.do_something_related
|
143
|
+
|
144
|
+
# Adding any kind of error indicates failure
|
145
|
+
add_errors_from_object resource
|
146
|
+
result
|
87
147
|
end
|
88
148
|
end
|
89
149
|
```
|
90
150
|
|
91
|
-
|
151
|
+
### Anatomy of a Simple Ruby Service
|
92
152
|
```ruby
|
93
|
-
# in app/controllers/some_controller.rb
|
94
|
-
class SomeController < ApplicationController
|
95
|
-
def show
|
96
|
-
# NOTE: Simple Ruby Service methods can be chained together
|
97
|
-
render SomeService.new(params)
|
98
|
-
.do_something
|
99
|
-
.do_something_related
|
100
|
-
.value
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
153
|
# in app/services/do_something.rb
|
105
154
|
class SomeService
|
106
155
|
include SimpleRubyService::Service
|
@@ -108,25 +157,38 @@ class SomeService
|
|
108
157
|
attribute :id
|
109
158
|
attr_accessor :resource
|
110
159
|
|
111
|
-
#
|
160
|
+
# Similar to SOs, validations are executed prior to the first service method called
|
112
161
|
validate do
|
113
162
|
@resource ||= SomeModel.find(id)
|
114
163
|
authorize! @resource
|
115
164
|
end
|
116
165
|
|
166
|
+
# Unlike SOs, Services can define an arbitrary number of service methods with arbitrary names
|
117
167
|
service_methods do
|
118
168
|
def do_something
|
119
|
-
resource.
|
169
|
+
resource.do_something
|
120
170
|
end
|
121
171
|
|
122
|
-
#
|
172
|
+
# Unlike SOs, `value` must be explicitely set for Service methods
|
123
173
|
def do_something_related
|
124
174
|
self.value ||= resource.tap &:do_something_related
|
175
|
+
add_errors_from_object resource
|
125
176
|
end
|
126
177
|
end
|
127
178
|
end
|
128
179
|
```
|
129
180
|
|
181
|
+
## A special note about Simple Ruby Service Objects, Procs, and Ducktyping
|
182
|
+
|
183
|
+
Simple Ruby Service Objects respond to (`#call`) so they can stand in for Procs, i.e.:
|
184
|
+
```ruby
|
185
|
+
# in app/models/some_model.rb
|
186
|
+
class SomeModel < ApplicationRecord
|
187
|
+
validates :some_attribute, if: SomeServiceObject
|
188
|
+
[...]
|
189
|
+
```
|
190
|
+
_See [To bang!, or not to bang](https://github.com/amazing-jay/simple_ruby_service/tree/master#to-bang-or-not-to-bang) to learn about `.call!` vs. `.call`._
|
191
|
+
|
130
192
|
## Usage
|
131
193
|
|
132
194
|
### Service Objects
|
@@ -137,8 +199,6 @@ Service Object names should begin with a verb and should not include the words `
|
|
137
199
|
|
138
200
|
Also, only one operation should be made public, it should always be named `call`, and it should not accept arguments (except for an optional block).
|
139
201
|
|
140
|
-
_See [To bang!, or not to bang](https://github.com/amazing-jay/simple_ruby_service/tree/master#to-bang-or-not-to-bang) to learn about `.call!` vs. `.call`._
|
141
|
-
|
142
202
|
#### Short form (_recommended_)
|
143
203
|
|
144
204
|
```ruby
|
@@ -197,9 +257,6 @@ Unlike Service Objects, Service class names should begin with a noun (and may in
|
|
197
257
|
|
198
258
|
Also, any number of operations may be made public, any of these operations may be named `call`, and any of these operations may accept arguments.
|
199
259
|
|
200
|
-
_See [To bang!, or not to bang](https://github.com/amazing-jay/simple_ruby_service/tree/master#to-bang-or-not-to-bang) to learn about `.service_method_name!` vs. `.service_method_name`._
|
201
|
-
|
202
|
-
|
203
260
|
#### Short form
|
204
261
|
|
205
262
|
_not available for Services_
|
@@ -254,7 +311,7 @@ end
|
|
254
311
|
## Creating Simple Ruby Services
|
255
312
|
|
256
313
|
### Service Objects
|
257
|
-
To implement
|
314
|
+
To implement a Simple Ruby Service Object:
|
258
315
|
|
259
316
|
1. include `SimpleRubyService::ServiceObject`
|
260
317
|
2. declare attributes with the `attribute` keyword (class level DSL)
|
@@ -282,7 +339,7 @@ end
|
|
282
339
|
```
|
283
340
|
|
284
341
|
### Services
|
285
|
-
To implement
|
342
|
+
To implement a Simple Ruby Service:
|
286
343
|
|
287
344
|
1. include `SimpleRubyService::Service`
|
288
345
|
2. declare attributes with the `attribute` keyword (class level DSL)
|
@@ -319,19 +376,46 @@ class SomeService
|
|
319
376
|
end
|
320
377
|
```
|
321
378
|
|
322
|
-
|
379
|
+
### Workflows
|
380
|
+
Simple Ruby Services are inherently a good fit for workflows because they support chaining, i.e.:
|
323
381
|
|
324
|
-
|
382
|
+
```ruby
|
383
|
+
SomeService.new(params)
|
384
|
+
.do_something
|
385
|
+
.do_something_related
|
386
|
+
.value
|
387
|
+
```
|
325
388
|
|
326
|
-
|
389
|
+
But SOs can also implement various workflows with dependency injection:
|
327
390
|
|
328
|
-
|
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
|
+
```
|
329
401
|
|
330
|
-
|
402
|
+
## MISC
|
331
403
|
|
332
|
-
|
404
|
+
### To bang!, or not to bang
|
405
|
+
|
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`
|
333
412
|
|
334
|
-
|
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?
|
335
419
|
|
336
420
|
Use a `Service` when encapsulating related operations that share dependencies & validations.
|
337
421
|
|
@@ -342,9 +426,6 @@ i.e.:
|
|
342
426
|
|
343
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`._
|
344
428
|
|
345
|
-
### Atomicity
|
346
|
-
The framework does not include transaction support by default. You are responsible for wrapping with a transaction if atomicity is desired.
|
347
|
-
|
348
429
|
### Control Flow
|
349
430
|
Rescue exceptions that represent internal control flow and propogate the rest.
|
350
431
|
|
@@ -352,7 +433,7 @@ For example, if an internal call to User.create! is expected to always succeed,
|
|
352
433
|
|
353
434
|
Example::
|
354
435
|
```ruby
|
355
|
-
class DoSomethingDangerous < SimpleRubyService::
|
436
|
+
class DoSomethingDangerous < SimpleRubyService::ServiceObject
|
356
437
|
attribute :attr1, :attr2 # should include all params required to execute
|
357
438
|
validates_presence_of :attr1 # validate params to call
|
358
439
|
|
@@ -368,38 +449,6 @@ class DoSomethingDangerous < SimpleRubyService::ObjectBase
|
|
368
449
|
end
|
369
450
|
```
|
370
451
|
|
371
|
-
## Workflows
|
372
|
-
SOs often need to call other SOs in order to implement various workflows:
|
373
|
-
```ruby
|
374
|
-
class PerformSomeWorkflow < SimpleRubyService::ObjectBase
|
375
|
-
def perform
|
376
|
-
dependency = SimpleRubyService1.call!
|
377
|
-
result = SimpleRubyService2.call(dependency)
|
378
|
-
raise unless result.success?
|
379
|
-
SimpleRubyService3(dependency, result.value).call!
|
380
|
-
end
|
381
|
-
end
|
382
|
-
```
|
383
|
-
|
384
|
-
## MISC
|
385
|
-
|
386
|
-
### Attributes
|
387
|
-
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.
|
388
|
-
|
389
|
-
### To bang!, or not to bang
|
390
|
-
|
391
|
-
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.
|
392
|
-
|
393
|
-
Similar in pattern to `ActiveRecord#save!`, the bang version of each operation:
|
394
|
-
* raises `SimpleRubyService::Invalid` if `valid?` is falsey
|
395
|
-
* raises `SimpleRubyService::Failure` if the block provided returns a falsey value
|
396
|
-
* returns `@value`
|
397
|
-
|
398
|
-
Whereas, similar in pattern to `ActiveRecord#save`, the regular version of each operation:
|
399
|
-
* doesn't raise any exceptions
|
400
|
-
* passes the return value of the block provided to `#success?`
|
401
|
-
* returns self << _note: this is unlike `ActiveRecord#save`_
|
402
|
-
|
403
452
|
## Development
|
404
453
|
|
405
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.
|
@@ -416,6 +465,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
416
465
|
|
417
466
|
## DEVELOPMENT ROADMAP
|
418
467
|
|
419
|
-
1. Create a
|
420
|
-
2.
|
468
|
+
1. Create a class level DSL to stop before each Service method unless errors.empty?
|
469
|
+
2. Create a helper to dynamically generate default SOs for ActiveRecord models (`create`, `update`, and `destroy`) _(when used in a project that includes [ActiveRecord](https://github.com/rails/rails/tree/main/activerecord))_.
|
470
|
+
3. Consider isolating validation errors from execution errors (so that invalid? is not always true when failed? is true)
|
421
471
|
|
@@ -6,9 +6,13 @@ module SimpleRubyService
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
include ActiveModel::AttributeAssignment
|
8
8
|
include ActiveModel::Validations
|
9
|
+
include ActiveModel::Validations::Callbacks
|
9
10
|
|
10
11
|
included do
|
11
12
|
attr_accessor :value
|
13
|
+
|
14
|
+
class_attribute :set_value_when_service_methods_return
|
15
|
+
self.set_value_when_service_methods_return = true
|
12
16
|
end
|
13
17
|
|
14
18
|
class_methods do
|
@@ -32,7 +36,7 @@ module SimpleRubyService
|
|
32
36
|
# Class level DSL that wraps the methods defined in inherited classes.
|
33
37
|
def service_methods(&blk)
|
34
38
|
Module.new.tap do |m| # Using anonymous modules so that super can be used to extend service methods
|
35
|
-
m.module_eval
|
39
|
+
m.module_eval(&blk)
|
36
40
|
include m
|
37
41
|
|
38
42
|
m.instance_methods.each do |service_method|
|
@@ -41,7 +45,8 @@ module SimpleRubyService
|
|
41
45
|
# Returns self (for chainability).
|
42
46
|
# Evaluates validity prior to executing the block provided.
|
43
47
|
define_method service_method do |*args, **kwargs, &callback|
|
44
|
-
perform(service_method, *args, **kwargs, &callback) if valid?
|
48
|
+
result = perform(service_method, *args, **kwargs, &callback) if valid?
|
49
|
+
self.value = result if set_value_when_service_methods_return
|
45
50
|
|
46
51
|
self
|
47
52
|
end
|
@@ -130,5 +135,3 @@ module SimpleRubyService
|
|
130
135
|
end
|
131
136
|
end
|
132
137
|
end
|
133
|
-
|
134
|
-
|
data/simple_ruby_service.gemspec
CHANGED
@@ -10,8 +10,9 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.authors = ["Jay Crouch"]
|
11
11
|
spec.email = ["i.jaycrouch@gmail.com"]
|
12
12
|
|
13
|
-
spec.summary = 'Simple Ruby Service is a lightweight framework for
|
14
|
-
spec.description = 'Simple Ruby Service is a lightweight framework for
|
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. ' \
|
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,14 +1,14 @@
|
|
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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay Crouch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -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
|
308
|
-
|
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: []
|
@@ -358,6 +359,6 @@ requirements: []
|
|
358
359
|
rubygems_version: 3.0.9
|
359
360
|
signing_key:
|
360
361
|
specification_version: 4
|
361
|
-
summary: Simple Ruby Service is a lightweight framework for
|
362
|
-
|
362
|
+
summary: Simple Ruby Service is a lightweight framework for creating Services and
|
363
|
+
Service Objects (SOs) in Ruby.
|
363
364
|
test_files: []
|