sea_food 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1fadef17caded8e4d217abcf4cacb11a668661557b90945daec7c6248c3016d
4
- data.tar.gz: 07ba588fb09dd2488095aff9149840decba53751351929195ce21fc490257756
3
+ metadata.gz: '09caa9ace45be8850e8639249d6838094845be06718b0c70e384873a35612020'
4
+ data.tar.gz: 4b77831dc3d9decd91d67f735baea6bd3cfb27c0519d581d1e6988f6d8ff0194
5
5
  SHA512:
6
- metadata.gz: 43027c029c2c6b39625303d3ec828b5327c9231864902aef30cbde651229bb03b897b6bec31912c06e97234b3e8a8b9a944d80d75a3f15df185245322dcd72d9
7
- data.tar.gz: 4a4a56b87d4c8944da926dc59c26ed634ca398fd1127af54cb9df7141def7520f456b577c50f3e78268722fd82ba4c92ddd47ef5840bcfcd0ba946c16f5f0491
6
+ metadata.gz: 710db87b3d97c5cf54c3ad05599efd80a499a6d3610212a8c7289fa67d678eaf6213cf02f49801bf3fe86fb8713d2fed2d73d3a9b4b9eb6c8baa6d39b521c6b4
7
+ data.tar.gz: 49c68b7d88ef0526fa7f9762e0b5f264dd135a174287993b73e31e412dfc8ee225623c1c11bd4c966f50bb446459f83a604f31c46839c76368df51bff7b7ff83
@@ -0,0 +1,49 @@
1
+ name: Test Matrix
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+
15
+ strategy:
16
+ matrix:
17
+ gemfile: [5_2.gemfile, 6_1.gemfile, 7_0.gemfile, 7_1.gemfile, 7_2.gemfile]
18
+ ruby-version: [ '2.7', '3.0', '3.1', '3.3']
19
+ exclude:
20
+ - ruby-version: '2.7'
21
+ gemfile: '7_0.gemfile'
22
+ - ruby-version: '2.7'
23
+ gemfile: '7_1.gemfile'
24
+ - ruby-version: '2.7'
25
+ gemfile: '7_2.gemfile'
26
+ - ruby-version: '3.0'
27
+ gemfile: '5_2.gemfile'
28
+ - ruby-version: '3.0'
29
+ gemfile: '7_2.gemfile'
30
+ - ruby-version: '3.1'
31
+ gemfile: '5_2.gemfile'
32
+ - ruby-version: '3.3'
33
+ gemfile: '5_2.gemfile'
34
+
35
+ env:
36
+ BUNDLE_GEMFILE: spec/gemfiles/${{ matrix.gemfile }}
37
+ steps:
38
+ - name: Checkout code
39
+ uses: actions/checkout@v2
40
+
41
+ - name: Set up Ruby ${{ matrix.ruby-version }}
42
+ uses: ruby/setup-ruby@v1
43
+ with:
44
+ ruby-version: ${{ matrix.ruby-version }}
45
+ bundler-cache: true
46
+
47
+ - name: Run tests
48
+ run: |
49
+ bundle exec rspec
data/.rubocop.yml CHANGED
@@ -42,7 +42,7 @@ Metrics/BlockLength:
42
42
  - 'sea_food.gemspec'
43
43
  - config/**/*
44
44
  - spec/**/*
45
- ExcludedMethods:
45
+ AllowedMethods:
46
46
  - class_methods
47
47
 
48
48
  Metrics/BlockNesting:
@@ -97,9 +97,14 @@ Style/MissingRespondToMissing:
97
97
  Exclude:
98
98
  - 'lib/sea_food/service.rb'
99
99
 
100
- Style/MethodMissingSuper:
100
+ Lint/MissingSuper:
101
101
  Exclude:
102
102
  - 'lib/sea_food/service.rb'
103
+ - spec/**/*
103
104
 
104
105
  Naming/PredicateName:
105
- Enabled: false
106
+ Enabled: false
107
+
108
+ Lint/ConstantDefinitionInBlock:
109
+ Exclude:
110
+ - 'spec/**/*'
data/README.md CHANGED
@@ -21,7 +21,228 @@ Or install it yourself as:
21
21
 
22
22
  ## Usage
23
23
 
24
- TODO: Write usage instructions here
24
+ ### Service usage
25
+ ```ruby
26
+ class Invoices::ApproveService < SeaFood::Service
27
+ def initialize(invoice:, user:)
28
+ @invoice = invoice
29
+ @user = user
30
+ end
31
+
32
+ def call
33
+ fail!("You are not authorized to approve invoices") unless authorized?
34
+
35
+ @invoice.update!(status: :approved)
36
+ success(invoice: @invoice)
37
+ end
38
+ end
39
+
40
+ result = Invoices::ApproveService.call(invoice: invoice, user: user)
41
+ result.success?
42
+ #=> true
43
+ result.invoice
44
+ #=> <Invoice(...>
45
+ ```
46
+ If you want to enforce the interface and not allow users to use `call`
47
+ without arguments defined in the `initialize` you can add an initializer.
48
+ ```ruby
49
+ # initializers/sea_food.rb
50
+ SeaFood.configure do |config|
51
+ config.enforce_interface = true
52
+ end
53
+ ```
54
+ It will raise an `ArgumentError`.
55
+
56
+
57
+ ### Handling Success and Failure
58
+ #### Using success and fail
59
+ `success(data):` Marks the service result as successful and optionally provides data.
60
+ `fail(data):` Marks the service result as a failure but continues executing the call method.
61
+ `fail!(data):` Marks the service result as a failure and immediately exits the call method.
62
+ #### Example: Using fail followed by success
63
+ ```ruby
64
+ class TestFailService < SeaFood::Service
65
+ def initialize(email:)
66
+ @email = email
67
+ end
68
+
69
+ def call
70
+ fail(email: 'hi@example.com')
71
+ success(email: @email)
72
+ end
73
+ end
74
+
75
+ result = TestFailService.call(email: 'service@example.com')
76
+
77
+ puts result.success? # => true
78
+ puts result.email # => 'service@example.com'
79
+ ```
80
+ In this example:
81
+
82
+ The fail method sets the result to failure but allows the method to continue.
83
+ The subsequent success call overrides the failure, resulting in a successful outcome.
84
+
85
+ #### Example: Using fail! to Exit Early
86
+ ```ruby
87
+ class TestFailBangService < SeaFood::Service
88
+ def initialize(email:)
89
+ @email = email
90
+ end
91
+
92
+ def call
93
+ fail!(email: 'hi@example.com')
94
+ success(email: @email) # This line is not executed
95
+ end
96
+ end
97
+
98
+ result = TestFailBangService.call(email: 'service@example.com')
99
+
100
+ puts result.success? # => false
101
+ puts result.email # => 'hi@example.com'
102
+ ```
103
+
104
+ The `fail!` method immediately exits the call method.
105
+ The service result is a failure, and subsequent code in call is not executed.
106
+ Nested Services
107
+ You can call other services within a service. The behavior depends on whether you use `call` or `call!`.
108
+ #### Summary of call vs. call!
109
+ `call`: Executes the service and returns the result. Does not raise an exception if the service fails.
110
+
111
+ `call!`: Executes the service and raises an exception if the service fails. Useful for propagating failures in nested services.
112
+
113
+ #### Using call (Non-Bang Method)
114
+ Failures in nested services do not automatically propagate to the parent service.
115
+
116
+
117
+ ```ruby
118
+ class InnerService < SeaFood::Service
119
+ def initialize(email:)
120
+ @email = email
121
+ end
122
+
123
+ def call
124
+ fail!(email: @email)
125
+ end
126
+ end
127
+
128
+ class OuterService < SeaFood::Service
129
+ def initialize(email:)
130
+ @email = email
131
+ end
132
+
133
+ def call
134
+ InnerService.call(email: 'inner@example.com')
135
+ success(email: @email)
136
+ end
137
+ end
138
+
139
+ result = OuterService.call(email: 'outer@example.com')
140
+
141
+ puts result.success? # => true
142
+ puts result.email # => 'outer@example.com'
143
+ ```
144
+ *Explanation:*
145
+
146
+ `InnerService` fails using `fail!`.
147
+ `OuterService` calls `InnerService` using `call`.
148
+ Since `call` does not raise an exception on failure, `OuterService` continues and succeeds.
149
+
150
+ #### Using `call!` (Bang Method)
151
+ Failures in nested services propagate to the parent service when using `call!`.
152
+
153
+ ```ruby
154
+ class InnerService < SeaFood::Service
155
+ def initialize(email:)
156
+ @email = email
157
+ end
158
+
159
+ def call
160
+ fail!(email: @email)
161
+ end
162
+ end
163
+
164
+ class OuterService < SeaFood::Service
165
+ def initialize(email:)
166
+ @email = email
167
+ end
168
+
169
+ def call
170
+ InnerService.call!(email: 'inner@example.com')
171
+ success(email: @email) # This line is not executed
172
+ end
173
+ end
174
+
175
+ result = OuterService.call(email: 'outer@example.com')
176
+
177
+ puts result.success? # => false
178
+ puts result.email # => 'inner@example.com'
179
+ ```
180
+ *Explanation:*
181
+
182
+ `InnerService` fails using `fail!`.
183
+ `OuterService` calls `InnerService` using call!.
184
+ The failure from `InnerService` propagates, causing `OuterService` to fail.
185
+ #### Handling Failures in Nested Services
186
+ You can handle failures from nested services by checking their result.
187
+
188
+ ```ruby
189
+ class InnerService < SeaFood::Service
190
+ def initialize(value:)
191
+ @value = value
192
+ end
193
+
194
+ def call
195
+ if @value < 0
196
+ fail!(error: 'Negative value')
197
+ else
198
+ success(value: @value)
199
+ end
200
+ end
201
+ end
202
+
203
+ class OuterService < SeaFood::Service
204
+ def initialize(value:)
205
+ @value = value
206
+ end
207
+
208
+ def call
209
+ result = InnerService.call(value: @value)
210
+
211
+ if result.fail?
212
+ fail!(error: result.error)
213
+ else
214
+ success(value: result.value * 2)
215
+ end
216
+ end
217
+ end
218
+
219
+ result = OuterService.call(value: -1)
220
+
221
+ puts result.success? # => false
222
+ puts result.error # => 'Negative value'
223
+ ```
224
+ *Explanation:*
225
+
226
+ `OuterService` checks the result of `InnerService`.
227
+ If `InnerService` fails, `OuterService` handles it accordingly.
228
+
229
+ #### Accessing Result Data
230
+ The result object allows you to access data provided in success or fail calls using method syntax.
231
+
232
+ ```ruby
233
+ class ExampleService < SeaFood::Service
234
+ def call
235
+ success(message: 'Operation successful', value: 42)
236
+ end
237
+ end
238
+
239
+ result = ExampleService.call
240
+
241
+ puts result.success? # => true
242
+ puts result.message # => 'Operation successful'
243
+ puts result.value # => 42
244
+ ```
245
+
25
246
 
26
247
  ## Development
27
248
 
data/lib/sea_food/form.rb CHANGED
@@ -7,11 +7,11 @@ module SeaFood
7
7
  end
8
8
 
9
9
  def save(options = {})
10
- model.save(options) if valid?
10
+ model.save(**options) if valid?
11
11
  end
12
12
 
13
13
  def save!(options = {})
14
- model.save!(options) if valid?
14
+ model.save!(**options) if valid?
15
15
  end
16
16
 
17
17
  private
@@ -21,11 +21,18 @@ module SeaFood
21
21
  # @param args [Hash] Arguments to pass to the service.
22
22
  # @return [ServiceResult] The result of the service call.
23
23
  def call(params = {})
24
- service = new(params)
24
+ service = new(**params)
25
25
  service.call
26
26
  service.result || ServiceResult.new
27
27
  rescue ServiceError => e
28
- service.result
28
+ service.result || e.try(:result)
29
+ end
30
+
31
+ def call!(params = {})
32
+ result = call(params)
33
+ return result || ServiceResult.new unless result.fail?
34
+
35
+ raise ServiceError, result
29
36
  end
30
37
  end
31
38
 
@@ -112,7 +119,13 @@ module SeaFood
112
119
  end
113
120
  end
114
121
 
115
- class ServiceError < StandardError; end
122
+ class ServiceError < StandardError
123
+ attr_reader :result
124
+
125
+ def initialize(result)
126
+ @result = result
127
+ end
128
+ end
116
129
 
117
130
  private
118
131
 
@@ -1,3 +1,3 @@
1
1
  module SeaFood
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/sea_food.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = 'A Ruby gem for seamlessly integrating form and service object patterns.'
13
13
  spec.homepage = 'https://github.com/eagerworks/sea_food'
14
14
  spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
15
16
 
16
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -38,11 +39,11 @@ Gem::Specification.new do |spec|
38
39
  spec.add_dependency 'activesupport', '>= 5.2'
39
40
 
40
41
  spec.add_development_dependency 'activerecord', '>= 5.2'
41
- spec.add_development_dependency 'bundler', '~> 2.0'
42
+ spec.add_development_dependency 'bundler'
42
43
  spec.add_development_dependency 'database_cleaner-active_record', '~> 2.2.0'
43
44
  spec.add_development_dependency 'debug'
44
- spec.add_development_dependency 'rake', '~> 10.0'
45
+ spec.add_development_dependency 'rake'
45
46
  spec.add_development_dependency 'rspec', '~> 3.0'
46
- spec.add_development_dependency 'rubocop', '~> 0.80.0'
47
+ spec.add_development_dependency 'rubocop', '~> 1.0'
47
48
  spec.add_development_dependency 'sqlite3', '~> 1.5.0'
48
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sea_food
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Federico Aldunate
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-01 00:00:00.000000000 Z
11
+ date: 2024-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '2.0'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '2.0'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: database_cleaner-active_record
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -98,16 +98,16 @@ dependencies:
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '10.0'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '10.0'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 0.80.0
131
+ version: '1.0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 0.80.0
138
+ version: '1.0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: sqlite3
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -150,21 +150,20 @@ dependencies:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: 1.5.0
153
- description:
153
+ description:
154
154
  email:
155
155
  - federico.aldunatec@gmail.com
156
156
  executables: []
157
157
  extensions: []
158
158
  extra_rdoc_files: []
159
159
  files:
160
+ - ".github/workflows/ci.yml"
160
161
  - ".gitignore"
161
162
  - ".rspec"
162
163
  - ".rubocop.yml"
163
- - ".ruby-version"
164
164
  - ".travis.yml"
165
165
  - CODE_OF_CONDUCT.md
166
166
  - Gemfile
167
- - Gemfile.lock
168
167
  - LICENSE.txt
169
168
  - README.md
170
169
  - Rakefile
@@ -185,7 +184,7 @@ metadata:
185
184
  source_code_uri: https://github.com/eagerworks/sea_food/
186
185
  changelog_uri: https://github.com/eagerworks/sea_food/releases
187
186
  bug_tracker_uri: https://github.com/eagerworks/sea_food/issues
188
- post_install_message:
187
+ post_install_message:
189
188
  rdoc_options: []
190
189
  require_paths:
191
190
  - lib
@@ -193,15 +192,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
192
  requirements:
194
193
  - - ">="
195
194
  - !ruby/object:Gem::Version
196
- version: '0'
195
+ version: 2.7.0
197
196
  required_rubygems_version: !ruby/object:Gem::Requirement
198
197
  requirements:
199
198
  - - ">="
200
199
  - !ruby/object:Gem::Version
201
200
  version: '0'
202
201
  requirements: []
203
- rubygems_version: 3.1.4
204
- signing_key:
202
+ rubygems_version: 3.3.26
203
+ signing_key:
205
204
  specification_version: 4
206
205
  summary: A Ruby gem for seamlessly integrating form and service object patterns.
207
206
  test_files: []
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.7.2
data/Gemfile.lock DELETED
@@ -1,96 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- sea_food (0.1.0)
5
- activemodel (>= 5.2)
6
- activesupport (>= 5.2)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- activemodel (6.1.7.8)
12
- activesupport (= 6.1.7.8)
13
- activerecord (6.1.7.8)
14
- activemodel (= 6.1.7.8)
15
- activesupport (= 6.1.7.8)
16
- activesupport (6.1.7.8)
17
- concurrent-ruby (~> 1.0, >= 1.0.2)
18
- i18n (>= 1.6, < 2)
19
- minitest (>= 5.1)
20
- tzinfo (~> 2.0)
21
- zeitwerk (~> 2.3)
22
- ast (2.4.2)
23
- concurrent-ruby (1.3.4)
24
- database_cleaner-active_record (2.2.0)
25
- activerecord (>= 5.a)
26
- database_cleaner-core (~> 2.0.0)
27
- database_cleaner-core (2.0.1)
28
- debug (1.8.0)
29
- irb (>= 1.5.0)
30
- reline (>= 0.3.1)
31
- diff-lcs (1.5.1)
32
- i18n (1.14.5)
33
- concurrent-ruby (~> 1.0)
34
- io-console (0.7.2)
35
- irb (1.6.3)
36
- reline (>= 0.3.0)
37
- jaro_winkler (1.5.6)
38
- mini_portile2 (2.8.7)
39
- minitest (5.24.1)
40
- parallel (1.24.0)
41
- parser (3.3.4.2)
42
- ast (~> 2.4.1)
43
- racc
44
- racc (1.8.1)
45
- rainbow (3.1.1)
46
- rake (10.5.0)
47
- reline (0.5.9)
48
- io-console (~> 0.5)
49
- rexml (3.3.5)
50
- strscan
51
- rspec (3.13.0)
52
- rspec-core (~> 3.13.0)
53
- rspec-expectations (~> 3.13.0)
54
- rspec-mocks (~> 3.13.0)
55
- rspec-core (3.13.0)
56
- rspec-support (~> 3.13.0)
57
- rspec-expectations (3.13.1)
58
- diff-lcs (>= 1.2.0, < 2.0)
59
- rspec-support (~> 3.13.0)
60
- rspec-mocks (3.13.1)
61
- diff-lcs (>= 1.2.0, < 2.0)
62
- rspec-support (~> 3.13.0)
63
- rspec-support (3.13.1)
64
- rubocop (0.80.1)
65
- jaro_winkler (~> 1.5.1)
66
- parallel (~> 1.10)
67
- parser (>= 2.7.0.1)
68
- rainbow (>= 2.2.2, < 4.0)
69
- rexml
70
- ruby-progressbar (~> 1.7)
71
- unicode-display_width (>= 1.4.0, < 1.7)
72
- ruby-progressbar (1.13.0)
73
- sqlite3 (1.5.4)
74
- mini_portile2 (~> 2.8.0)
75
- strscan (3.1.0)
76
- tzinfo (2.0.6)
77
- concurrent-ruby (~> 1.0)
78
- unicode-display_width (1.6.1)
79
- zeitwerk (2.6.17)
80
-
81
- PLATFORMS
82
- ruby
83
-
84
- DEPENDENCIES
85
- activerecord (>= 5.2)
86
- bundler (~> 2.0)
87
- database_cleaner-active_record (~> 2.2.0)
88
- debug
89
- rake (~> 10.0)
90
- rspec (~> 3.0)
91
- rubocop (~> 0.80.0)
92
- sea_food!
93
- sqlite3 (~> 1.5.0)
94
-
95
- BUNDLED WITH
96
- 2.1.4