light-services 2.1 → 2.2
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/.github/config/rubocop_linter_action.yml +3 -2
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +12 -12
- data/.ruby-version +1 -1
- data/Gemfile +8 -8
- data/Gemfile.lock +78 -75
- data/README.md +56 -278
- data/lib/light/services/base.rb +2 -0
- data/lib/light/services/version.rb +1 -1
- data/light-services.gemspec +5 -3
- metadata +23 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5ccceda02f284ebdd089f5b2ecc556e5a8cdea068a94d84e0b59d4cebb71d72
|
|
4
|
+
data.tar.gz: dd35a45b128b94f31cbf4b7bd84afce58fe50f416ef235dae181542c478f93de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45e62c84a24199d47085e3f6aaddec90612df913ace4a601be57c6cb8d1df473fdd231980de92b3b5f54b1043c5a6583a8fede03d56dfef14f5cca1261dadef2
|
|
7
|
+
data.tar.gz: ed4da976538c4ea372bfc1c8f8c5e137f4c9653a71e47cda425f8e06fe4c7d9d66ff3550cf2e9681aad2623286879bb4ee9d0bb1c0d37a6612c1bc236a0614f5
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "bundler"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -2,24 +2,24 @@ name: CI
|
|
|
2
2
|
on: push
|
|
3
3
|
|
|
4
4
|
jobs:
|
|
5
|
-
rubocop:
|
|
6
|
-
name: Rubocop
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
steps:
|
|
9
|
-
- name: Git Checkout
|
|
10
|
-
uses: actions/checkout@v2
|
|
11
|
-
|
|
12
|
-
- name: Rubocop
|
|
13
|
-
uses: andrewmcodes/rubocop-linter-action@v3.2.0
|
|
14
|
-
env:
|
|
15
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
5
|
+
# rubocop:
|
|
6
|
+
# name: Rubocop
|
|
7
|
+
# runs-on: ubuntu-latest
|
|
8
|
+
# steps:
|
|
9
|
+
# - name: Git Checkout
|
|
10
|
+
# uses: actions/checkout@v2
|
|
11
|
+
#
|
|
12
|
+
# - name: Rubocop
|
|
13
|
+
# uses: andrewmcodes/rubocop-linter-action@v3.2.0
|
|
14
|
+
# env:
|
|
15
|
+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
16
16
|
|
|
17
17
|
rspec:
|
|
18
18
|
name: RSpec
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
ruby: [3.0, 3.1, 3.2, head, debug]
|
|
22
|
+
ruby: [3.0, 3.1, 3.2, 3.3, head, debug]
|
|
23
23
|
steps:
|
|
24
24
|
- name: Git Checkout
|
|
25
25
|
uses: actions/checkout@v2
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.7
|
data/Gemfile
CHANGED
|
@@ -5,17 +5,17 @@ source "https://rubygems.org"
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
7
|
group :test do
|
|
8
|
-
gem "activerecord", "~>
|
|
9
|
-
gem "database_cleaner-active_record", "~> 2.
|
|
10
|
-
gem "sqlite3", "~>
|
|
8
|
+
gem "activerecord", "~> 8.1"
|
|
9
|
+
gem "database_cleaner-active_record", "~> 2.2"
|
|
10
|
+
gem "sqlite3", "~> 2.8"
|
|
11
11
|
|
|
12
12
|
gem "codecov", "~> 0.6.0"
|
|
13
|
-
gem "rake", "~> 13.
|
|
13
|
+
gem "rake", "~> 13.3"
|
|
14
14
|
gem "rspec", "~> 3.13"
|
|
15
15
|
gem "simplecov", "~> 0.21"
|
|
16
16
|
|
|
17
|
-
gem "rubocop", "~> 1.
|
|
18
|
-
gem "rubocop-performance", "~> 1.
|
|
19
|
-
gem "rubocop-rake", "~> 0.
|
|
20
|
-
gem "rubocop-rspec", "~>
|
|
17
|
+
gem "rubocop", "~> 1.81", require: false
|
|
18
|
+
gem "rubocop-performance", "~> 1.26", require: false
|
|
19
|
+
gem "rubocop-rake", "~> 0.7.0", require: false
|
|
20
|
+
gem "rubocop-rspec", "~> 3.8", require: false
|
|
21
21
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,131 +1,134 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
light-services (2.
|
|
4
|
+
light-services (2.2)
|
|
5
|
+
benchmark (>= 0.5)
|
|
5
6
|
|
|
6
7
|
GEM
|
|
7
8
|
remote: https://rubygems.org/
|
|
8
9
|
specs:
|
|
9
|
-
activemodel (
|
|
10
|
-
activesupport (=
|
|
11
|
-
activerecord (
|
|
12
|
-
activemodel (=
|
|
13
|
-
activesupport (=
|
|
10
|
+
activemodel (8.1.1)
|
|
11
|
+
activesupport (= 8.1.1)
|
|
12
|
+
activerecord (8.1.1)
|
|
13
|
+
activemodel (= 8.1.1)
|
|
14
|
+
activesupport (= 8.1.1)
|
|
14
15
|
timeout (>= 0.4.0)
|
|
15
|
-
activesupport (
|
|
16
|
+
activesupport (8.1.1)
|
|
16
17
|
base64
|
|
17
18
|
bigdecimal
|
|
18
|
-
concurrent-ruby (~> 1.0, >= 1.
|
|
19
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
19
20
|
connection_pool (>= 2.2.5)
|
|
20
21
|
drb
|
|
21
22
|
i18n (>= 1.6, < 2)
|
|
23
|
+
json
|
|
24
|
+
logger (>= 1.4.2)
|
|
22
25
|
minitest (>= 5.1)
|
|
23
|
-
|
|
24
|
-
tzinfo (~> 2.0)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
securerandom (>= 0.3)
|
|
27
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
|
+
uri (>= 0.13.1)
|
|
29
|
+
ast (2.4.3)
|
|
30
|
+
base64 (0.3.0)
|
|
31
|
+
benchmark (0.5.0)
|
|
32
|
+
bigdecimal (3.3.1)
|
|
28
33
|
codecov (0.6.0)
|
|
29
34
|
simplecov (>= 0.15, < 0.22)
|
|
30
|
-
concurrent-ruby (1.
|
|
31
|
-
connection_pool (2.4
|
|
32
|
-
database_cleaner-active_record (2.
|
|
35
|
+
concurrent-ruby (1.3.5)
|
|
36
|
+
connection_pool (2.5.4)
|
|
37
|
+
database_cleaner-active_record (2.2.2)
|
|
33
38
|
activerecord (>= 5.a)
|
|
34
|
-
database_cleaner-core (~> 2.0
|
|
39
|
+
database_cleaner-core (~> 2.0)
|
|
35
40
|
database_cleaner-core (2.0.1)
|
|
36
|
-
diff-lcs (1.
|
|
37
|
-
docile (1.4.
|
|
38
|
-
drb (2.2.
|
|
39
|
-
i18n (1.14.
|
|
41
|
+
diff-lcs (1.6.2)
|
|
42
|
+
docile (1.4.1)
|
|
43
|
+
drb (2.2.3)
|
|
44
|
+
i18n (1.14.7)
|
|
40
45
|
concurrent-ruby (~> 1.0)
|
|
41
|
-
json (2.
|
|
42
|
-
language_server-protocol (3.17.0.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
json (2.16.0)
|
|
47
|
+
language_server-protocol (3.17.0.5)
|
|
48
|
+
lint_roller (1.1.0)
|
|
49
|
+
logger (1.7.0)
|
|
50
|
+
mini_portile2 (2.8.9)
|
|
51
|
+
minitest (5.26.1)
|
|
52
|
+
parallel (1.27.0)
|
|
53
|
+
parser (3.3.10.0)
|
|
48
54
|
ast (~> 2.4.1)
|
|
49
55
|
racc
|
|
50
|
-
|
|
56
|
+
prism (1.6.0)
|
|
57
|
+
racc (1.8.1)
|
|
51
58
|
rainbow (3.1.1)
|
|
52
|
-
rake (13.
|
|
53
|
-
regexp_parser (2.
|
|
54
|
-
|
|
55
|
-
strscan (>= 3.0.9)
|
|
56
|
-
rspec (3.13.0)
|
|
59
|
+
rake (13.3.1)
|
|
60
|
+
regexp_parser (2.11.3)
|
|
61
|
+
rspec (3.13.2)
|
|
57
62
|
rspec-core (~> 3.13.0)
|
|
58
63
|
rspec-expectations (~> 3.13.0)
|
|
59
64
|
rspec-mocks (~> 3.13.0)
|
|
60
|
-
rspec-core (3.13.
|
|
65
|
+
rspec-core (3.13.6)
|
|
61
66
|
rspec-support (~> 3.13.0)
|
|
62
|
-
rspec-expectations (3.13.
|
|
67
|
+
rspec-expectations (3.13.5)
|
|
63
68
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
64
69
|
rspec-support (~> 3.13.0)
|
|
65
|
-
rspec-mocks (3.13.
|
|
70
|
+
rspec-mocks (3.13.7)
|
|
66
71
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
67
72
|
rspec-support (~> 3.13.0)
|
|
68
|
-
rspec-support (3.13.
|
|
69
|
-
rubocop (1.
|
|
73
|
+
rspec-support (3.13.6)
|
|
74
|
+
rubocop (1.81.7)
|
|
70
75
|
json (~> 2.3)
|
|
71
|
-
language_server-protocol (
|
|
76
|
+
language_server-protocol (~> 3.17.0.2)
|
|
77
|
+
lint_roller (~> 1.1.0)
|
|
72
78
|
parallel (~> 1.10)
|
|
73
79
|
parser (>= 3.3.0.2)
|
|
74
80
|
rainbow (>= 2.2.2, < 4.0)
|
|
75
|
-
regexp_parser (>=
|
|
76
|
-
|
|
77
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
|
81
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
82
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
78
83
|
ruby-progressbar (~> 1.7)
|
|
79
|
-
unicode-display_width (>= 2.4.0, <
|
|
80
|
-
rubocop-ast (1.
|
|
81
|
-
parser (>= 3.3.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
rubocop (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
rubocop (~> 1.
|
|
93
|
-
rubocop-capybara (~> 2.17)
|
|
94
|
-
rubocop-factory_bot (~> 2.22)
|
|
95
|
-
rubocop-rspec_rails (~> 2.28)
|
|
96
|
-
rubocop-rspec_rails (2.28.3)
|
|
97
|
-
rubocop (~> 1.40)
|
|
84
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
85
|
+
rubocop-ast (1.48.0)
|
|
86
|
+
parser (>= 3.3.7.2)
|
|
87
|
+
prism (~> 1.4)
|
|
88
|
+
rubocop-performance (1.26.1)
|
|
89
|
+
lint_roller (~> 1.1)
|
|
90
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
91
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
92
|
+
rubocop-rake (0.7.1)
|
|
93
|
+
lint_roller (~> 1.1)
|
|
94
|
+
rubocop (>= 1.72.1)
|
|
95
|
+
rubocop-rspec (3.8.0)
|
|
96
|
+
lint_roller (~> 1.1)
|
|
97
|
+
rubocop (~> 1.81)
|
|
98
98
|
ruby-progressbar (1.13.0)
|
|
99
|
+
securerandom (0.4.1)
|
|
99
100
|
simplecov (0.21.2)
|
|
100
101
|
docile (~> 1.1)
|
|
101
102
|
simplecov-html (~> 0.11)
|
|
102
103
|
simplecov_json_formatter (~> 0.1)
|
|
103
|
-
simplecov-html (0.
|
|
104
|
+
simplecov-html (0.13.2)
|
|
104
105
|
simplecov_json_formatter (0.1.4)
|
|
105
|
-
sqlite3 (
|
|
106
|
+
sqlite3 (2.8.0)
|
|
106
107
|
mini_portile2 (~> 2.8.0)
|
|
107
|
-
|
|
108
|
-
timeout (0.4.1)
|
|
108
|
+
timeout (0.4.4)
|
|
109
109
|
tzinfo (2.0.6)
|
|
110
110
|
concurrent-ruby (~> 1.0)
|
|
111
|
-
unicode-display_width (2.
|
|
111
|
+
unicode-display_width (3.2.0)
|
|
112
|
+
unicode-emoji (~> 4.1)
|
|
113
|
+
unicode-emoji (4.1.0)
|
|
114
|
+
uri (1.1.1)
|
|
112
115
|
|
|
113
116
|
PLATFORMS
|
|
114
117
|
ruby
|
|
115
118
|
|
|
116
119
|
DEPENDENCIES
|
|
117
|
-
activerecord (~>
|
|
120
|
+
activerecord (~> 8.1)
|
|
118
121
|
codecov (~> 0.6.0)
|
|
119
|
-
database_cleaner-active_record (~> 2.
|
|
122
|
+
database_cleaner-active_record (~> 2.2)
|
|
120
123
|
light-services!
|
|
121
|
-
rake (~> 13.
|
|
124
|
+
rake (~> 13.3)
|
|
122
125
|
rspec (~> 3.13)
|
|
123
|
-
rubocop (~> 1.
|
|
124
|
-
rubocop-performance (~> 1.
|
|
125
|
-
rubocop-rake (~> 0.
|
|
126
|
-
rubocop-rspec (~>
|
|
126
|
+
rubocop (~> 1.81)
|
|
127
|
+
rubocop-performance (~> 1.26)
|
|
128
|
+
rubocop-rake (~> 0.7.0)
|
|
129
|
+
rubocop-rspec (~> 3.8)
|
|
127
130
|
simplecov (~> 0.21)
|
|
128
|
-
sqlite3 (~>
|
|
131
|
+
sqlite3 (~> 2.8)
|
|
129
132
|
|
|
130
133
|
BUNDLED WITH
|
|
131
134
|
2.5.10
|
data/README.md
CHANGED
|
@@ -1,326 +1,104 @@
|
|
|
1
1
|
# 🚀 Light Services
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Light Services is a simple yet powerful way to organize your business logic. This Ruby gem helps you build services that are easy to test, maintain, and understand.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
2. [Usage](#usage)
|
|
8
|
-
1. [Arguments](#arguments)
|
|
9
|
-
2. [Steps](#steps)
|
|
10
|
-
3. [Outputs](#outputs)
|
|
11
|
-
4. [Context](#context)
|
|
12
|
-
3. [Complex Example](#complex-example)
|
|
13
|
-
4. [More Examples](#more-examples)
|
|
5
|
+

|
|
6
|
+
[](https://codecov.io/gh/light-ruby/light-services)
|
|
14
7
|
|
|
15
|
-
##
|
|
8
|
+
## Features
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
This gem was initially intended for internal use and has several issues:
|
|
28
|
-
|
|
29
|
-
1. The gem is not well-documented
|
|
30
|
-
1. The code lacks comments
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
|
|
34
|
-
Add this line to your application's Gemfile:
|
|
35
|
-
|
|
36
|
-
```ruby
|
|
37
|
-
gem "light-services", "~> 2.0"
|
|
38
|
-
```
|
|
10
|
+
- 🧩 **Simple**: Define your service as a class with `arguments`, `steps`, and `outputs`
|
|
11
|
+
- 🎢 **Transactions**: Automatically rollback database changes if any step fails
|
|
12
|
+
- 👵 **Inheritance**: Inherit from other services to reuse logic seamlessly
|
|
13
|
+
- 🚨 **Error Handling**: Collect errors from steps and handle them your way
|
|
14
|
+
- ⛓️ **Context**: Run multiple services sequentially within the same context
|
|
15
|
+
- 🤔 **Framework Agnostic**: Compatible with Rails, Hanami, or any Ruby framework
|
|
16
|
+
- 🏗️ **Modularity**: Isolate and test your services with ease
|
|
17
|
+
- 🐛 **100% Test Coverage**: Bugs are not welcome here!
|
|
18
|
+
- 🛡️ **Battle-Tested**: In production use since 2017
|
|
39
19
|
|
|
40
20
|
## Simple Example
|
|
41
21
|
|
|
42
|
-
### Send Notification
|
|
43
|
-
|
|
44
|
-
Create a basic service object that sends a notification to a user.
|
|
45
|
-
|
|
46
22
|
```ruby
|
|
47
|
-
class
|
|
23
|
+
class GreetService < Light::Services::Base
|
|
48
24
|
# Arguments
|
|
49
|
-
arg :
|
|
50
|
-
arg :
|
|
25
|
+
arg :name
|
|
26
|
+
arg :age
|
|
51
27
|
|
|
52
28
|
# Steps
|
|
53
|
-
step :
|
|
54
|
-
step :
|
|
55
|
-
|
|
56
|
-
|
|
29
|
+
step :build_message
|
|
30
|
+
step :send_message
|
|
31
|
+
|
|
57
32
|
# Outputs
|
|
58
|
-
output :
|
|
33
|
+
output :message
|
|
59
34
|
|
|
60
35
|
private
|
|
61
36
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
errors.add(:user, "isn't active")
|
|
37
|
+
def build_message
|
|
38
|
+
self.message = "Hello, #{name}! You are #{age} years old."
|
|
66
39
|
end
|
|
67
40
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
errors.add(:text, "must be present")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def send_notification
|
|
75
|
-
self.response = ExternalAPI.send_message(...)
|
|
76
|
-
rescue ExternalAPI::Error
|
|
77
|
-
errors.add(:base, "External API doesn't work")
|
|
41
|
+
def send_message
|
|
42
|
+
# Send logic goes here
|
|
78
43
|
end
|
|
79
44
|
end
|
|
80
45
|
```
|
|
81
46
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
### Arguments
|
|
47
|
+
## Advanced Example
|
|
85
48
|
|
|
86
|
-
Pass arguments into the service object as shown:
|
|
87
|
-
|
|
88
|
-
**How to define arguments:**
|
|
89
49
|
```ruby
|
|
90
|
-
class User::
|
|
91
|
-
# Required argument
|
|
92
|
-
arg :user, type: User
|
|
93
|
-
|
|
94
|
-
# Optional argument
|
|
95
|
-
arg :device, type: Device, optional: true
|
|
96
|
-
|
|
97
|
-
# Argument with default value
|
|
98
|
-
arg :text, type: :string, default: "Hello, how are you?"
|
|
99
|
-
|
|
100
|
-
# Argument with multiple allowed types
|
|
101
|
-
arg :retry, type: [TrueClass, FalseClass], default: false
|
|
102
|
-
|
|
103
|
-
# Argument which will be automatically passed into child components
|
|
104
|
-
arg :provider, type: Provider, context: true
|
|
105
|
-
end
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**How to pass arguments from a controller:**
|
|
109
|
-
```ruby
|
|
110
|
-
class UsersController
|
|
111
|
-
def send_notification
|
|
112
|
-
service = User::SendNotification.run(user: User.first, provider: Provider.first)
|
|
113
|
-
# ...
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Passing arguments and context from parent to child service object:**
|
|
119
|
-
```ruby
|
|
120
|
-
class User::Update
|
|
50
|
+
class User::ResetPassword < Light::Services::Base
|
|
121
51
|
# Arguments
|
|
122
|
-
arg :user, type: User,
|
|
52
|
+
arg :user, type: User, optional: true
|
|
53
|
+
arg :email, type: :string, optional: true
|
|
54
|
+
arg :send_email, type: :boolean, default: true
|
|
123
55
|
|
|
124
56
|
# Steps
|
|
125
|
-
|
|
126
|
-
step :
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# ...
|
|
131
|
-
|
|
132
|
-
def send_notification
|
|
133
|
-
User::SendNotification
|
|
134
|
-
.with(self) # Specifies the current service object as parent, passing all context arguments to a child service object
|
|
135
|
-
.run(text: "Your profile was updated") # No need to pass `user` as it's a context argument
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Steps
|
|
141
|
-
|
|
142
|
-
Steps are a bit more powerful than they appear.
|
|
143
|
-
|
|
144
|
-
```ruby
|
|
145
|
-
class User::Charge
|
|
146
|
-
# Run step only when condition meets
|
|
147
|
-
step :create_payment_account, unless: :payment_account?
|
|
148
|
-
|
|
149
|
-
# Run step only when condition meets
|
|
150
|
-
step :charge_credit_card, if: :pay_with_credit_card?
|
|
151
|
-
|
|
152
|
-
# Run step after other step
|
|
153
|
-
step :update_payment_account, after: :create_payment_account
|
|
154
|
-
|
|
155
|
-
# Or before
|
|
156
|
-
step :save_information, before: :log_action
|
|
157
|
-
end
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Outputs
|
|
161
|
-
|
|
162
|
-
Outputs are straightforward.
|
|
163
|
-
|
|
164
|
-
```ruby
|
|
165
|
-
class User::Charge
|
|
166
|
-
# Simple output
|
|
167
|
-
output :payment
|
|
168
|
-
|
|
169
|
-
# Output with initial value
|
|
170
|
-
output :items, default: []
|
|
171
|
-
end
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Context
|
|
175
|
-
|
|
176
|
-
The context specifies the relationship between parent and child service objects.
|
|
177
|
-
|
|
178
|
-
What the context does:
|
|
179
|
-
1. Tells the parent service object to pass context arguments to a child service object.
|
|
180
|
-
1. Informs the parent service object to also fail when the child service object fails (this is customizable).
|
|
181
|
-
|
|
182
|
-
```ruby
|
|
183
|
-
class User::Charge
|
|
184
|
-
# Arguments
|
|
185
|
-
arg :user, type: User, context: true
|
|
186
|
-
arg :cents, type: Integer
|
|
187
|
-
|
|
188
|
-
# ...
|
|
189
|
-
|
|
190
|
-
private
|
|
191
|
-
|
|
192
|
-
# ...
|
|
193
|
-
|
|
194
|
-
def send_notification
|
|
195
|
-
# Run service object w/o any context
|
|
196
|
-
User::SendNotification
|
|
197
|
-
.run(user: user, text: "...")
|
|
198
|
-
|
|
199
|
-
# Run service object and specify current one as a parent
|
|
200
|
-
User::SendNotification
|
|
201
|
-
.with(self)
|
|
202
|
-
.run(text: "...")
|
|
203
|
-
|
|
204
|
-
# Run service object with context but don't load errors from the child service object
|
|
205
|
-
service = User::SendNotification
|
|
206
|
-
.with(self, load_errors: false)
|
|
207
|
-
.run(text: "...")
|
|
208
|
-
|
|
209
|
-
if service.failed?
|
|
210
|
-
# That's ok. Process it somehow...
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Complex Example
|
|
217
|
-
|
|
218
|
-
### Record Creation
|
|
219
|
-
|
|
220
|
-
Explore a more intricate example of creating database records.
|
|
221
|
-
|
|
222
|
-
**Here is an example of controller (pretty thin, yeah? but we can make it even thinner):**
|
|
223
|
-
```ruby
|
|
224
|
-
class ContactsController < ApplicationController
|
|
225
|
-
# ...
|
|
226
|
-
|
|
227
|
-
def create
|
|
228
|
-
service = Contact::Create.run(service_args)
|
|
229
|
-
|
|
230
|
-
if service.success?
|
|
231
|
-
render locals: { contact: service.contact }, status: :ok
|
|
232
|
-
else
|
|
233
|
-
render "shared/errors", locals: { service: service }, status: :bad_request
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# ...
|
|
238
|
-
end
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Then, let's create a service object (no way, it couldn't be so simple):**
|
|
242
|
-
```ruby
|
|
243
|
-
class Contact::Create < CreateService
|
|
244
|
-
# We create alias just for a better readability
|
|
245
|
-
# so that we can call `service.contact` instead of `service.record`
|
|
246
|
-
alias contact record
|
|
247
|
-
|
|
248
|
-
private
|
|
249
|
-
|
|
250
|
-
def filtered_params
|
|
251
|
-
params.require(:contact).permit(:name, :phone)
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
**Let's check what logic we put into `CreateService`:**
|
|
257
|
-
```ruby
|
|
258
|
-
class CreateService < ApplicationService
|
|
259
|
-
# Arguments
|
|
260
|
-
arg :attributes, type: Hash, optional: true
|
|
57
|
+
step :validate
|
|
58
|
+
step :find_user, unless: :user?
|
|
59
|
+
step :generate_reset_token
|
|
60
|
+
step :save_reset_token
|
|
61
|
+
step :send_reset_email, if: :send_email?
|
|
261
62
|
|
|
262
63
|
# Outputs
|
|
263
|
-
output :
|
|
264
|
-
|
|
265
|
-
# Steps
|
|
266
|
-
step :initialize_record
|
|
267
|
-
step :assign_attributes
|
|
268
|
-
step :authorize
|
|
269
|
-
step :validate
|
|
270
|
-
step :save_record
|
|
64
|
+
output :user, type: User
|
|
65
|
+
output :reset_token, type: :string
|
|
271
66
|
|
|
272
67
|
private
|
|
273
68
|
|
|
274
|
-
def
|
|
275
|
-
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def assign_attributes
|
|
279
|
-
record.assign_attributes(filtered_params)
|
|
69
|
+
def validate
|
|
70
|
+
errors.add(:base, "user or email is required") if !user? && !email?
|
|
280
71
|
end
|
|
281
72
|
|
|
282
|
-
def
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
# Here is some Pundit logic 👇
|
|
286
|
-
authorize!(record, with_action: :create?)
|
|
73
|
+
def find_user
|
|
74
|
+
self.user = User.find_by("LOWER(email) = ?", email.downcase)
|
|
75
|
+
errors.add(:email, "not found") unless user
|
|
287
76
|
end
|
|
288
77
|
|
|
289
|
-
def
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
errors.copy_from(record)
|
|
78
|
+
def generate_reset_token
|
|
79
|
+
self.reset_token = SecureRandom.hex(32)
|
|
293
80
|
end
|
|
294
81
|
|
|
295
|
-
def
|
|
296
|
-
|
|
82
|
+
def save_reset_token
|
|
83
|
+
user.update!(
|
|
84
|
+
reset_password_token: reset_token,
|
|
85
|
+
reset_password_sent_at: Time.current,
|
|
86
|
+
)
|
|
87
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
88
|
+
errors.from_record(e.record)
|
|
297
89
|
end
|
|
298
90
|
|
|
299
|
-
def
|
|
300
|
-
|
|
91
|
+
def send_reset_email
|
|
92
|
+
Mailer::SendEmail
|
|
93
|
+
.with(self) # Call sub-service with the same context
|
|
94
|
+
.run(template: :reset_password, user:, reset_token:)
|
|
301
95
|
end
|
|
302
96
|
end
|
|
303
97
|
```
|
|
304
98
|
|
|
305
|
-
|
|
306
|
-
```ruby
|
|
307
|
-
class Team::Create < CreateService
|
|
308
|
-
alias team record
|
|
309
|
-
|
|
310
|
-
private
|
|
311
|
-
|
|
312
|
-
def filtered_params
|
|
313
|
-
params.require(:team).permit(:name)
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## More examples
|
|
319
|
-
|
|
320
|
-
You can find more examples here:
|
|
321
|
-
[https://github.com/light-ruby/light-services/tree/spec/data/services](https://github.com/light-ruby/light-services/tree/v2/spec/data/services)
|
|
99
|
+
## Documentation
|
|
322
100
|
|
|
323
|
-
|
|
101
|
+
You can find the full documentation at [light-services.kodkod.me](https://light-services.kodkod.me).
|
|
324
102
|
|
|
325
103
|
## License
|
|
326
104
|
|
data/lib/light/services/base.rb
CHANGED
data/light-services.gemspec
CHANGED
|
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Andrew Kodkod"]
|
|
9
9
|
spec.email = ["andrew@kodkod.me"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "
|
|
12
|
-
spec.description = "
|
|
13
|
-
spec.homepage = "https://
|
|
11
|
+
spec.summary = "Robust service architecture for Ruby frameworks"
|
|
12
|
+
spec.description = "Robust service architecture for Ruby frameworks"
|
|
13
|
+
spec.homepage = "https://light-services-docs.vercel.app/"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
|
16
16
|
|
|
@@ -30,4 +30,6 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
31
|
spec.require_paths = ["lib"]
|
|
32
32
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
33
|
+
|
|
34
|
+
spec.add_dependency "benchmark", ">= 0.5"
|
|
33
35
|
end
|
metadata
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: light-services
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '2.
|
|
4
|
+
version: '2.2'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kodkod
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: benchmark
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.5'
|
|
26
|
+
description: Robust service architecture for Ruby frameworks
|
|
14
27
|
email:
|
|
15
28
|
- andrew@kodkod.me
|
|
16
29
|
executables: []
|
|
@@ -18,6 +31,7 @@ extensions: []
|
|
|
18
31
|
extra_rdoc_files: []
|
|
19
32
|
files:
|
|
20
33
|
- ".github/config/rubocop_linter_action.yml"
|
|
34
|
+
- ".github/dependabot.yml"
|
|
21
35
|
- ".github/workflows/ci.yml"
|
|
22
36
|
- ".gitignore"
|
|
23
37
|
- ".rspec"
|
|
@@ -49,16 +63,15 @@ files:
|
|
|
49
63
|
- lib/light/services/settings/step.rb
|
|
50
64
|
- lib/light/services/version.rb
|
|
51
65
|
- light-services.gemspec
|
|
52
|
-
homepage: https://
|
|
66
|
+
homepage: https://light-services-docs.vercel.app/
|
|
53
67
|
licenses:
|
|
54
68
|
- MIT
|
|
55
69
|
metadata:
|
|
56
70
|
allowed_push_host: https://rubygems.org
|
|
57
|
-
homepage_uri: https://
|
|
71
|
+
homepage_uri: https://light-services-docs.vercel.app/
|
|
58
72
|
source_code_uri: https://github.com/light-ruby/light-services
|
|
59
73
|
changelog_uri: https://github.com/light-ruby/light-services/blob/master/CHANGELOG.md
|
|
60
74
|
rubygems_mfa_required: 'true'
|
|
61
|
-
post_install_message:
|
|
62
75
|
rdoc_options: []
|
|
63
76
|
require_paths:
|
|
64
77
|
- lib
|
|
@@ -73,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
86
|
- !ruby/object:Gem::Version
|
|
74
87
|
version: '0'
|
|
75
88
|
requirements: []
|
|
76
|
-
rubygems_version: 3.
|
|
77
|
-
signing_key:
|
|
89
|
+
rubygems_version: 3.6.9
|
|
78
90
|
specification_version: 4
|
|
79
|
-
summary:
|
|
91
|
+
summary: Robust service architecture for Ruby frameworks
|
|
80
92
|
test_files: []
|