servactory 1.6.0 → 1.6.2

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: dd3981fb61af74a47cc5d5279a51796d78527c2b4b16c3bdb69f7bd94e32932f
4
- data.tar.gz: ca8ad9beca25c5dd04a9f7bb584ced4cef6b3f35d6d14c68c0d297ef817b8025
3
+ metadata.gz: 20ac0dd1681f9d793073d7431a4532fa7776cb42bd547d7cb5be494b8f8916ee
4
+ data.tar.gz: 29d031bfae1c5ddd205540549ea4a98f23eb4195a2da27184300546d3b45602a
5
5
  SHA512:
6
- metadata.gz: e95bffde026a1b55bca088e15d86c2ec2b78ff4838be5f683c574e9ea1ed148a993a3075c4fb7b72493d3667b7280b4fe177edd748262bd77193425571aabb96
7
- data.tar.gz: c41af274f2c6c858f29cef27f7b9d0b61ebf106712f9a55ca739782f2bbb2e12758c51a4271901505fdd81028ec435b52308244a2d581ee8c82841b768169b7f
6
+ metadata.gz: a3ab392a96e2c2b583dfdf9de499b80308eae14f012a6f086118fa174eb9a2a7bf6d3760ae2c8ca6bca7bb62855ca491210ee995517886ad3939a8b0865db958
7
+ data.tar.gz: ac463a3a4fa01a93bbb42b7e1b326168fa9954bcc4a06e035fa804619cb45e497882a6bcb18385a1614b1d197f174353286aa3d92d02e1726c043c55ee942391
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 = 2
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.2
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