servactory 1.6.0 → 1.6.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd3981fb61af74a47cc5d5279a51796d78527c2b4b16c3bdb69f7bd94e32932f
4
- data.tar.gz: ca8ad9beca25c5dd04a9f7bb584ced4cef6b3f35d6d14c68c0d297ef817b8025
3
+ metadata.gz: 77500bca8d6ee7ee302e77b8b64eb840a2e004bece8743a2b246e49a49a42b24
4
+ data.tar.gz: dfff784e9b2787ac349d5e33f84f75a318814ecf64ac0df7915a500b2073f6ef
5
5
  SHA512:
6
- metadata.gz: e95bffde026a1b55bca088e15d86c2ec2b78ff4838be5f683c574e9ea1ed148a993a3075c4fb7b72493d3667b7280b4fe177edd748262bd77193425571aabb96
7
- data.tar.gz: c41af274f2c6c858f29cef27f7b9d0b61ebf106712f9a55ca739782f2bbb2e12758c51a4271901505fdd81028ec435b52308244a2d581ee8c82841b768169b7f
6
+ metadata.gz: 16091e0330a74cda1246f0a3a4ccdbc9ed572cba9c29d485e306c95c2405792e12bd9d03cbf13e1e17cf0c40bd4839ea997d75eac7f5f26a199eb8bc757af01a
7
+ data.tar.gz: 2d67bd5beff3c5a4d73a28a70662b3df52e61757fadc37f15751e745e28f9a3b18cd3af43d84684339debd58b6231ca70bdcbcc8b2af8c20ef76f9eb9e68c883
data/README.md CHANGED
@@ -5,439 +5,14 @@ A set of tools for building reliable services of any complexity.
5
5
  [![Gem version](https://img.shields.io/gem/v/servactory?logo=rubygems&logoColor=fff)](https://rubygems.org/gems/servactory)
6
6
  [![Release Date](https://img.shields.io/github/release-date/afuno/servactory)](https://github.com/afuno/servactory/releases)
7
7
 
8
- ## Table of contents
8
+ ## Documentation
9
9
 
10
- - [Requirements](#requirements)
11
- - [Getting started](#getting-started)
12
- - [Conventions](#conventions)
13
- - [Installation](#installation)
14
- - [Preparation](#preparation)
15
- - [Usage](#usage)
16
- - [Minimal example](#minimal-example)
17
- - [Call](#call)
18
- - [Result](#result)
19
- - [Input](#input)
20
- - [Type](#option-type)
21
- - [Required](#option-required)
22
- - [Internal](#option-internal)
23
- - [Internal name](#option-as)
24
- - [Array](#option-array)
25
- - [Inclusion](#option-inclusion)
26
- - [Must](#option-must)
27
- - [Output](#output)
28
- - [Internal](#internal)
29
- - [Make](#make)
30
- - [Failures](#failures)
31
- - [Inheritance](#inheritance)
32
- - [I18n](#i18n)
33
- - [Testing](#testing)
34
- - [Thanks](#thanks)
35
- - [Contributing](#contributing)
10
+ See [servactory.com](https://servactory.com) for documentation.
36
11
 
37
- ## Requirements
38
-
39
- - Ruby >= 2.7
40
-
41
- ## Getting started
42
-
43
- ### Conventions
44
-
45
- - Services are subclasses of `Servactory::Base` and are located in the `app/services` directory. It is common practice to create and inherit from `ApplicationService::Base`, which is a subclass of `Servactory::Base`.
46
- - Name services by what they do, not by what they accept. Try to use verbs in names. For example, `UsersService::Create` instead of `UsersService::Creation`.
47
-
48
- ### Installation
49
-
50
- Add this to `Gemfile`:
51
-
52
- ```ruby
53
- gem "servactory"
54
- ```
55
-
56
- And execute:
57
-
58
- ```shell
59
- bundle install
60
- ```
61
-
62
- ### Preparation
63
-
64
- As a first step, it is recommended to prepare the base class for further inheritance.
65
-
66
- #### ApplicationService::Errors
67
-
68
- ```ruby
69
- # app/services/application_service/errors.rb
70
-
71
- module ApplicationService
72
- module Errors
73
- class InputError < Servactory::Errors::InputError; end
74
- class OutputError < Servactory::Errors::OutputError; end
75
- class InternalError < Servactory::Errors::InternalError; end
76
-
77
- class Failure < Servactory::Errors::Failure; end
78
- end
79
- end
80
- ```
81
-
82
- #### ApplicationService::Base
83
-
84
- ```ruby
85
- # app/services/application_service/base.rb
86
-
87
- module ApplicationService
88
- class Base < Servactory::Base
89
- configuration do
90
- input_error_class ApplicationService::Errors::InputError
91
- output_error_class ApplicationService::Errors::OutputError
92
- internal_error_class ApplicationService::Errors::InternalError
93
-
94
- failure_class ApplicationService::Errors::Failure
95
- end
96
- end
97
- end
98
- ```
99
-
100
- ## Usage
101
-
102
- ### Minimal example
103
-
104
- ```ruby
105
- class MinimalService < ApplicationService::Base
106
- make :call
107
-
108
- private
109
-
110
- def call
111
- # ...
112
- end
113
- end
114
- ```
115
-
116
- [More examples](https://github.com/afuno/servactory/tree/main/examples/usual)
117
-
118
- ### Call
119
-
120
- Services can only be called via `.call` and `.call!` methods.
121
-
122
- The `.call` method will only fail if it catches an exception in the input arguments.
123
- Internal and output attributes, as well as methods for failures - all this will be collected in the result.
124
-
125
- The `.call!` method will fail if it catches any exception.
126
-
127
- #### Via .call
128
-
129
- ```ruby
130
- UsersService::Accept.call(user: User.first)
131
- ```
132
-
133
- #### Via .call!
134
-
135
- ```ruby
136
- UsersService::Accept.call!(user: User.first)
137
- ```
138
-
139
- ### Result
140
-
141
- All services have the result of their work. For example, in case of success this call:
142
-
143
- ```ruby
144
- service_result = UsersService::Accept.call!(user: User.first)
145
- ```
146
-
147
- Will return this:
148
-
149
- ```ruby
150
- #<Servactory::Result:0x0000000107ad9e88 @user="...">
151
- ```
152
-
153
- And then you can work with this result, for example, in this way:
154
-
155
- ```ruby
156
- Notification::SendJob.perform_later(service_result.user.id)
157
- ```
158
-
159
- ### Input
160
-
161
- #### Option `type`
162
-
163
- Always required to specify. May contain one or more classes.
164
-
165
- ```ruby
166
- class UsersService::Accept < ApplicationService::Base
167
- input :user, type: User
168
-
169
- # ...
170
- end
171
- ```
172
-
173
- ```ruby
174
- class ToggleService < ApplicationService::Base
175
- input :flag, type: [TrueClass, FalseClass]
176
-
177
- # ...
178
- end
179
- ```
180
-
181
- #### Option `required`
182
-
183
- By default, `required` is set to `true`.
184
-
185
- ```ruby
186
- class UsersService::Create < ApplicationService::Base
187
- input :first_name, type: String
188
- input :middle_name, type: String, required: false
189
- input :last_name, type: String
190
-
191
- # ...
192
- end
193
- ```
194
-
195
- #### Option `internal`
196
-
197
- By default, `internal` is set to `false`.
198
-
199
- ```ruby
200
- class UsersService::Accept < ApplicationService::Base
201
- input :user, type: User
202
-
203
- make :accept!
204
-
205
- private
206
-
207
- def accept!
208
- inputs.user.accept!
209
- end
210
- end
211
- ```
212
-
213
- ```ruby
214
- class UsersService::Accept < ApplicationService::Base
215
- input :user, type: User, internal: true
216
-
217
- make :accept!
218
-
219
- private
220
-
221
- def accept!
222
- user.accept!
223
- end
224
- end
225
- ```
226
-
227
- #### Option `as`
228
-
229
- This option changes the name of the input within the service.
230
-
231
- ```ruby
232
- class NotificationService::Create < ApplicationService::Base
233
- input :customer, as: :user, type: User
234
-
235
- output :notification, type: Notification
236
-
237
- make :create_notification!
238
-
239
- private
240
-
241
- def create_notification!
242
- self.notification = Notification.create!(user: inputs.user)
243
- end
244
- end
245
- ```
246
-
247
- #### Option `array`
248
-
249
- Using this option will mean that the input argument is an array, each element of which has the specified type.
250
-
251
- ```ruby
252
- class PymentsService::Send < ApplicationService::Base
253
- input :invoice_numbers, type: String, array: true
254
-
255
- # ...
256
- end
257
- ```
258
-
259
- #### Option `inclusion`
260
-
261
- ```ruby
262
- class EventService::Send < ApplicationService::Base
263
- input :event_name, type: String, inclusion: %w[created rejected approved]
264
-
265
- # ...
266
- end
267
- ```
268
-
269
- #### Option `must`
270
-
271
- Sometimes there are cases that require the implementation of a specific input attribute check. In such cases `must` can help.
272
-
273
- ```ruby
274
- class PymentsService::Send < ApplicationService::Base
275
- input :invoice_numbers,
276
- type: String,
277
- array: true,
278
- must: {
279
- be_6_characters: {
280
- is: ->(value:) { value.all? { |id| id.size == 6 } }
281
- }
282
- }
283
-
284
- # ...
285
- end
286
- ```
287
-
288
- ### Output
289
-
290
- ```ruby
291
- class NotificationService::Create < ApplicationService::Base
292
- input :user, type: User
293
-
294
- output :notification, type: Notification
295
-
296
- make :create_notification!
297
-
298
- private
299
-
300
- def create_notification!
301
- self.notification = Notification.create!(user: inputs.user)
302
- end
303
- end
304
- ```
305
-
306
- ### Internal
307
-
308
- ```ruby
309
- class NotificationService::Create < ApplicationService::Base
310
- input :user, type: User
311
-
312
- internal :inviter, type: User
313
-
314
- output :notification, type: Notification
315
-
316
- make :assign_inviter
317
- make :create_notification!
318
-
319
- private
320
-
321
- def assign_inviter
322
- self.inviter = user.inviter
323
- end
324
-
325
- def create_notification!
326
- self.notification = Notification.create!(user: inputs.user, inviter:)
327
- end
328
- end
329
- ```
330
-
331
- ### Make
332
-
333
- #### Minimal example
334
-
335
- ```ruby
336
- make :something
337
-
338
- def something
339
- # ...
340
- end
341
- ```
342
-
343
- #### Condition
344
-
345
- ```ruby
346
- make :something, if: -> { Settings.something.enabled }
347
-
348
- def something
349
- # ...
350
- end
351
- ```
352
-
353
- #### Several
354
-
355
- ```ruby
356
- make :assign_api_model
357
- make :perform_api_request
358
- make :process_result
359
-
360
- def assign_api_model
361
- self.api_model = APIModel.new
362
- end
363
-
364
- def perform_api_request
365
- self.response = APIClient.resource.create(api_model)
366
- end
367
-
368
- def process_result
369
- ARModel.create!(response)
370
- end
371
- ```
372
-
373
- ### Failures
374
-
375
- The methods that are used in `make` may fail. In order to more informatively provide information about this outside the service, the following methods were prepared.
376
-
377
- #### Fail
378
-
379
- ```ruby
380
- make :check!
381
-
382
- def check!
383
- return if inputs.invoice_number.start_with?("AA")
384
-
385
- fail!(message: "Invalid invoice number")
386
- end
387
- ```
388
-
389
- #### Fail for input
390
-
391
- ```ruby
392
- make :check!
393
-
394
- def check!
395
- return if inputs.invoice_number.start_with?("AA")
396
-
397
- fail_input!(:invoice_number, message: "Invalid invoice number")
398
- end
399
- ```
400
-
401
- #### Metadata
402
-
403
- ```ruby
404
- fail!(
405
- message: "Invalid invoice number",
406
- meta: {
407
- invoice_number: inputs.invoice_number
408
- }
409
- )
410
- ```
411
-
412
- ```ruby
413
- exception.detailed_message # => Invalid invoice number (ApplicationService::Errors::Failure)
414
- exception.message # => Invalid invoice number
415
- exception.type # => :fail
416
- exception.meta # => {:invoice_number=>"BB-7650AE"}
417
- ```
418
-
419
- ### Inheritance
420
-
421
- Inheritance between services is also supported.
422
-
423
- ## I18n
424
-
425
- All texts are stored in the localization file. All texts can be changed or supplemented by new locales.
426
-
427
- [See en.yml file](https://github.com/afuno/servactory/tree/main/config/locales/en.yml)
428
-
429
- ## Testing
430
-
431
- Testing Servactory services is the same as testing regular Ruby classes.
432
-
433
- ## Thanks
12
+ ## Contributing
434
13
 
435
- Thanks to [@sunny](https://github.com/sunny) for [Service Actor](https://github.com/sunny/actor).
14
+ This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./docs/pages/CONTRIBUTING.md) as well.
436
15
 
437
- ## Contributing
16
+ ## License
438
17
 
439
- 1. Fork it (https://github.com/afuno/servactory/fork);
440
- 2. Create your feature branch (`git checkout -b my-new-feature`);
441
- 3. Commit your changes (`git commit -am "Add some feature"`);
442
- 4. Push to the branch (`git push origin my-new-feature`);
443
- 5. Create a new Pull Request.
18
+ ViewComponent is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -18,6 +18,10 @@ module Servactory
18
18
  def failure_class(failure_class)
19
19
  Servactory.configuration.failure_class = failure_class
20
20
  end
21
+
22
+ def method_shortcuts(method_shortcuts)
23
+ Servactory.configuration.method_shortcuts.merge(method_shortcuts)
24
+ end
21
25
  end
22
26
  end
23
27
  end
@@ -6,7 +6,8 @@ module Servactory
6
6
  attr_accessor :input_error_class,
7
7
  :internal_error_class,
8
8
  :output_error_class,
9
- :failure_class
9
+ :failure_class,
10
+ :method_shortcuts
10
11
 
11
12
  def initialize
12
13
  @input_error_class = Servactory::Errors::InputError
@@ -14,6 +15,8 @@ module Servactory
14
15
  @output_error_class = Servactory::Errors::OutputError
15
16
 
16
17
  @failure_class = Servactory::Errors::Failure
18
+
19
+ @method_shortcuts = Servactory::Methods::Shortcuts::Collection.new
17
20
  end
18
21
  end
19
22
  end
@@ -20,6 +20,20 @@ module Servactory
20
20
  collection_of_methods << Method.new(name, **options)
21
21
  end
22
22
 
23
+ def method_missing(shortcut_name, *args, &block)
24
+ return super unless Servactory.configuration.method_shortcuts.include?(shortcut_name)
25
+
26
+ method_options = args.last.is_a?(Hash) ? args.pop : {}
27
+
28
+ args.each do |method_name|
29
+ make(:"#{shortcut_name}_#{method_name}", **method_options)
30
+ end
31
+ end
32
+
33
+ def respond_to_missing?(shortcut_name, *)
34
+ Servactory.configuration.method_shortcuts.include?(shortcut_name) || super
35
+ end
36
+
23
37
  def collection_of_methods
24
38
  @collection_of_methods ||= Collection.new
25
39
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Methods
5
+ module Shortcuts
6
+ class Collection
7
+ # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
8
+ extend Forwardable
9
+ def_delegators :@collection, :<<, :each, :merge, :include?
10
+
11
+ def initialize(*)
12
+ @collection = Set.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -34,7 +34,7 @@ module Servactory
34
34
  return false if condition.blank?
35
35
  return !Servactory::Utils.boolean?(condition) unless condition.is_a?(Proc)
36
36
 
37
- !condition.call(context)
37
+ !condition.call(context: context)
38
38
  end
39
39
  end
40
40
  end
@@ -4,7 +4,7 @@ module Servactory
4
4
  module VERSION
5
5
  MAJOR = 1
6
6
  MINOR = 6
7
- PATCH = 0
7
+ PATCH = 1
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servactory
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Sokolov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-24 00:00:00.000000000 Z
11
+ date: 2023-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -194,7 +194,6 @@ files:
194
194
  - lib/servactory/configuration/factory.rb
195
195
  - lib/servactory/configuration/setup.rb
196
196
  - lib/servactory/context/callable.rb
197
- - lib/servactory/context/configuration.rb
198
197
  - lib/servactory/context/dsl.rb
199
198
  - lib/servactory/context/store.rb
200
199
  - lib/servactory/context/workspace.rb
@@ -235,6 +234,7 @@ files:
235
234
  - lib/servactory/methods/collection.rb
236
235
  - lib/servactory/methods/dsl.rb
237
236
  - lib/servactory/methods/method.rb
237
+ - lib/servactory/methods/shortcuts/collection.rb
238
238
  - lib/servactory/methods/workbench.rb
239
239
  - lib/servactory/outputs/checks/base.rb
240
240
  - lib/servactory/outputs/checks/type.rb
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Servactory
4
- module Context
5
- class Configuration
6
- def input_error_class(input_error_class)
7
- Servactory.configuration.input_error_class = input_error_class
8
- end
9
-
10
- def output_error_class(output_error_class)
11
- Servactory.configuration.output_error_class = output_error_class
12
- end
13
-
14
- def internal_error_class(internal_error_class)
15
- Servactory.configuration.internal_error_class = internal_error_class
16
- end
17
-
18
- def failure_class(failure_class)
19
- Servactory.configuration.failure_class = failure_class
20
- end
21
- end
22
- end
23
- end