base-service 0.1.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 +7 -0
- data/README.md +462 -0
- data/lib/base_service/version.rb +5 -0
- data/lib/base_service.rb +76 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 80c1f1f3a7747a5b86a3acd42fcc4f80fd0690530c5bb2a5a9e8cf473b8fd103
|
|
4
|
+
data.tar.gz: 5a2016293ca7765ade288a34c1488e6783f793735c8d7cc7e66d66b549a4c8ad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 96f4a2049c00564a96bd0bc934d7b89fe839481b50a5fe062e96a1a8915cee6faf0f0a831d5de5486237549516e641452f904c875a7ba17ec211b474b8af304c
|
|
7
|
+
data.tar.gz: 2dcbf079cb0e14de14c892c2ad5fdf048ed6b8df052d630a0be45151c5341e783f58e4e226c8ca53bf6283de18291190a028cfcd2492609fb0b93886eac6bac8
|
data/README.md
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# Base Service
|
|
2
|
+
|
|
3
|
+
[](https://github.com/nicolasva/base-service/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codeclimate.com/github/nicolasva/base-service)
|
|
5
|
+
[](https://rubygems.org/gems/base-service)
|
|
6
|
+
[](https://www.rubydoc.info/gems/base-service)
|
|
7
|
+
[](https://rubygems.org/gems/base-service)
|
|
8
|
+
|
|
9
|
+
`base-service` fournit une classe de base légère pour construire des services
|
|
10
|
+
Ruby avec une interface commune :
|
|
11
|
+
|
|
12
|
+
- les paramètres nommés deviennent des variables d'instance ;
|
|
13
|
+
- la logique métier est définie dans une méthode `call` sans argument ;
|
|
14
|
+
- les erreurs et les messages sont collectés pendant l'exécution ;
|
|
15
|
+
- chaque appel retourne un objet `Service::Result` ;
|
|
16
|
+
- des callbacks nommés peuvent être configurés grâce à
|
|
17
|
+
[`callback-collection`](https://github.com/nicolasva/callback-collection).
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Ajoutez la gem au `Gemfile` de l'application :
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem "base-service"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Puis installez les dépendances :
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Dans une application Ruby sans Bundler, chargez explicitement la gem :
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "base_service"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Rails charge normalement la gem automatiquement lorsqu'elle est déclarée dans
|
|
40
|
+
le `Gemfile`.
|
|
41
|
+
|
|
42
|
+
## Créer un service
|
|
43
|
+
|
|
44
|
+
Un service hérite de `Service::Base` et implémente une méthode publique `call`
|
|
45
|
+
sans argument :
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
class DoubleValueService < Service::Base
|
|
49
|
+
def call
|
|
50
|
+
return append_error(:missing_value, "Value is required") unless @value
|
|
51
|
+
|
|
52
|
+
append_message("Value doubled")
|
|
53
|
+
@value * 2
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Les arguments nommés transmis au service sont automatiquement disponibles sous
|
|
59
|
+
forme de variables d'instance. Ici, `value: 21` devient `@value`.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
result = DoubleValueService.call(value: 21)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Il n'est pas nécessaire d'écrire un constructeur dans chaque service.
|
|
66
|
+
|
|
67
|
+
## Résultat d'un appel
|
|
68
|
+
|
|
69
|
+
`MonService.call(...)` ne retourne pas directement la valeur métier. Il
|
|
70
|
+
retourne toujours un `Service::Result` :
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
result = DoubleValueService.call(value: 21)
|
|
74
|
+
|
|
75
|
+
result.result # => 42
|
|
76
|
+
result.successful? # => true
|
|
77
|
+
result.errors # => []
|
|
78
|
+
result.messages # => ["Value doubled"]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Méthode | Description |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `result.result` | Valeur retournée par la méthode `call` du service |
|
|
84
|
+
| `result.errors` | Tableau des erreurs ajoutées pendant l'exécution |
|
|
85
|
+
| `result.messages` | Tableau des messages ajoutés pendant l'exécution |
|
|
86
|
+
| `result.successful?` | `true` lorsque `result.errors` est vide |
|
|
87
|
+
|
|
88
|
+
L'objet `Service::Result` ainsi que ses tableaux `errors` et `messages` sont
|
|
89
|
+
gelés après l'exécution. La valeur métier contenue dans `result.result` n'est
|
|
90
|
+
pas gelée automatiquement.
|
|
91
|
+
|
|
92
|
+
## Ajouter des erreurs
|
|
93
|
+
|
|
94
|
+
La méthode protégée `append_error(type, message)` ajoute un objet
|
|
95
|
+
`Service::Error` au résultat :
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class CreateUserService < Service::Base
|
|
99
|
+
def call
|
|
100
|
+
unless @user.save
|
|
101
|
+
@user.errors.messages.each do |type, messages|
|
|
102
|
+
append_error(type, messages)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@user
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
result = CreateUserService.call(user: user)
|
|
113
|
+
|
|
114
|
+
result.successful? # => false si au moins une erreur a été ajoutée
|
|
115
|
+
|
|
116
|
+
error = result.errors.first
|
|
117
|
+
error.type # => :email
|
|
118
|
+
error.message # => ["has already been taken"]
|
|
119
|
+
error.caller_info # => emplacement où append_error a été appelé
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Le type et le message ne sont pas transformés par la gem. Ils peuvent donc
|
|
123
|
+
reprendre directement les valeurs fournies par Rails ou par le domaine métier.
|
|
124
|
+
|
|
125
|
+
`append_error` enregistre uniquement l'erreur : il ne lève pas d'exception et
|
|
126
|
+
n'arrête pas automatiquement le service. Utilisez `return` lorsque l'exécution
|
|
127
|
+
doit s'arrêter :
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
def call
|
|
131
|
+
unless valid?
|
|
132
|
+
append_error(:invalid, "The operation is invalid")
|
|
133
|
+
return nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
perform_operation
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Les exceptions levées par la logique métier, Active Record ou un callback ne
|
|
141
|
+
sont pas interceptées. Elles remontent normalement à l'appelant.
|
|
142
|
+
|
|
143
|
+
## Ajouter des messages
|
|
144
|
+
|
|
145
|
+
La méthode protégée `append_message(message)` ajoute une information non
|
|
146
|
+
bloquante au résultat :
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
class ImportUserService < Service::Base
|
|
150
|
+
def call
|
|
151
|
+
user = User.create!(@attributes)
|
|
152
|
+
append_message("User #{user.id} imported")
|
|
153
|
+
user
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
result = ImportUserService.call(attributes: { email: "ruby@example.com" })
|
|
160
|
+
|
|
161
|
+
result.successful? # => true
|
|
162
|
+
result.messages # => ["User 42 imported"]
|
|
163
|
+
result.result # => instance de User
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Ajouter un message ne modifie pas la valeur de `successful?`.
|
|
167
|
+
|
|
168
|
+
## Exemple avec un service Rails
|
|
169
|
+
|
|
170
|
+
Le service peut utiliser des modèles Active Record, des helpers Rails et des
|
|
171
|
+
méthodes privées comme n'importe quel objet Ruby :
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
module Billing
|
|
175
|
+
class DiscountedPriceService < Service::Base
|
|
176
|
+
include ActionView::Helpers::NumberHelper
|
|
177
|
+
|
|
178
|
+
def call
|
|
179
|
+
return 0 unless customer.discount_enabled?
|
|
180
|
+
return 0 unless customer.discount_percentage.present?
|
|
181
|
+
|
|
182
|
+
calculate_discounted_price
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def calculate_discounted_price
|
|
188
|
+
discount =
|
|
189
|
+
customer.discount_percentage.to_f / 100 * @original_price
|
|
190
|
+
|
|
191
|
+
return @original_price if discount < customer.minimum_discount.to_f
|
|
192
|
+
|
|
193
|
+
unit_price = catalog.price_for(product)
|
|
194
|
+
return @original_price if unit_price.blank?
|
|
195
|
+
|
|
196
|
+
if product.sold_in_whole_units?
|
|
197
|
+
(discount / unit_price).truncate * unit_price
|
|
198
|
+
else
|
|
199
|
+
discount
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def catalog
|
|
204
|
+
@catalog ||= @order.store.catalog
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def customer
|
|
208
|
+
@customer ||= @order.customer
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def product
|
|
212
|
+
@product ||= @order.products.first
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Appel du service :
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
result = Billing::DiscountedPriceService.call(
|
|
222
|
+
original_price: 10_000,
|
|
223
|
+
order: order
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if result.successful?
|
|
227
|
+
discounted_price = result.result
|
|
228
|
+
else
|
|
229
|
+
Rails.logger.error(result.errors.map(&:message))
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
L'instance du service reste mutable pendant l'exécution. Les mémorisations
|
|
234
|
+
avec `||=`, comme `@customer ||= ...`, fonctionnent donc normalement.
|
|
235
|
+
|
|
236
|
+
## Exemple de création d'une commande
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
module Orders
|
|
240
|
+
class CreateOrderService < Service::Base
|
|
241
|
+
def call
|
|
242
|
+
order = create_order
|
|
243
|
+
create_invoice_for(order)
|
|
244
|
+
order
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
|
|
249
|
+
def create_order
|
|
250
|
+
order = Order.new(
|
|
251
|
+
customer_id: @customer.id,
|
|
252
|
+
product_id: @product.id,
|
|
253
|
+
quantity: @quantity
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
unless order.save
|
|
257
|
+
order.errors.messages.each do |type, messages|
|
|
258
|
+
append_error(type, messages)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
order
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def create_invoice_for(order)
|
|
266
|
+
return unless order.persisted?
|
|
267
|
+
|
|
268
|
+
invoice = Invoice.new(
|
|
269
|
+
order: order,
|
|
270
|
+
customer: order.customer,
|
|
271
|
+
total: order.total
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return if invoice.save
|
|
275
|
+
|
|
276
|
+
append_error(
|
|
277
|
+
:invoice_creation_failed,
|
|
278
|
+
"Invoice could not be created for order #{order.id}"
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
result = Orders::CreateOrderService.call(
|
|
287
|
+
customer: customer,
|
|
288
|
+
product: product,
|
|
289
|
+
quantity: 2
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
order = result.result
|
|
293
|
+
|
|
294
|
+
if result.successful?
|
|
295
|
+
redirect_to order_path(order)
|
|
296
|
+
else
|
|
297
|
+
flash.now[:alert] = result.errors.map(&:message).join(", ")
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Le service retourne la commande dans `result.result`, même si des erreurs
|
|
302
|
+
ont été ajoutées. C'est `result.successful?` qui indique si l'exécution est
|
|
303
|
+
considérée comme réussie.
|
|
304
|
+
|
|
305
|
+
## Utilisation dans un autre service
|
|
306
|
+
|
|
307
|
+
Un service peut appeler un autre service. Il faut vérifier le
|
|
308
|
+
`Service::Result` retourné et récupérer explicitement sa valeur métier :
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
class CheckoutService < Service::Base
|
|
312
|
+
def call
|
|
313
|
+
order_result =
|
|
314
|
+
Orders::CreateOrderService.call(
|
|
315
|
+
customer: @customer,
|
|
316
|
+
product: @product,
|
|
317
|
+
quantity: @quantity
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
unless order_result.successful?
|
|
321
|
+
order_result.errors.each do |error|
|
|
322
|
+
append_error(error.type, error.message)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
return nil
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
order_result.result
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Les erreurs d'un sous-service ne sont pas automatiquement copiées dans le
|
|
334
|
+
service appelant. L'exemple ci-dessus les propage explicitement.
|
|
335
|
+
|
|
336
|
+
## Utilisation dans un contrôleur Rails
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
class OrdersController < ApplicationController
|
|
340
|
+
def create
|
|
341
|
+
result =
|
|
342
|
+
Orders::CreateOrderService.call(
|
|
343
|
+
customer: current_customer,
|
|
344
|
+
product: product,
|
|
345
|
+
quantity: params[:quantity]
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if result.successful?
|
|
349
|
+
redirect_to order_path(result.result), notice: "Order created"
|
|
350
|
+
else
|
|
351
|
+
flash.now[:alert] = result.errors.map(&:message).join(", ")
|
|
352
|
+
render :new, status: :unprocessable_entity
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Callbacks
|
|
359
|
+
|
|
360
|
+
Le bloc transmis à `.call` ou `.new` construit une collection de callbacks
|
|
361
|
+
immuable fournie par la gem `callback-collection` :
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
class NotifyUserService < Service::Base
|
|
365
|
+
def call
|
|
366
|
+
user = User.find(@user_id)
|
|
367
|
+
@callbacks&.respond_with(:success, user)
|
|
368
|
+
user
|
|
369
|
+
rescue ActiveRecord::RecordNotFound => error
|
|
370
|
+
append_error(:not_found, error.message)
|
|
371
|
+
@callbacks&.respond_with(:failure, error)
|
|
372
|
+
nil
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```ruby
|
|
378
|
+
result = NotifyUserService.call(user_id: 42) do |callbacks|
|
|
379
|
+
callbacks.success do |user|
|
|
380
|
+
UserMailer.notification(user).deliver_later
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
callbacks.failure do |error|
|
|
384
|
+
Rails.logger.warn(error.message)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Les callbacks ne sont pas exécutés automatiquement par `Service::Base`. Le
|
|
390
|
+
service choisit quand les appeler avec :
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
@callbacks.respond_with(:nom_du_callback, argument)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
L'opérateur `&.` permet de rendre le bloc de callbacks facultatif. Sans `&.`,
|
|
397
|
+
le service doit être appelé avec le callback attendu.
|
|
398
|
+
|
|
399
|
+
La collection est gelée après sa configuration. Un callback inconnu provoque
|
|
400
|
+
une `NoMethodError`, et une exception levée dans un callback remonte à
|
|
401
|
+
l'appelant.
|
|
402
|
+
|
|
403
|
+
Consultez la documentation de
|
|
404
|
+
[`callback-collection`](https://github.com/nicolasva/callback-collection) pour
|
|
405
|
+
les callbacks enregistrés par méthode et la compatibilité avec les Ractors.
|
|
406
|
+
|
|
407
|
+
## Instanciation manuelle
|
|
408
|
+
|
|
409
|
+
La forme recommandée est :
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
result = MyService.call(argument: value)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Elle équivaut à :
|
|
416
|
+
|
|
417
|
+
```ruby
|
|
418
|
+
service = MyService.new(argument: value)
|
|
419
|
+
result = service.execute
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Dans les deux cas, `execute` crée un nouveau contexte d'exécution, appelle la
|
|
423
|
+
méthode métier `call`, puis construit un `Service::Result`.
|
|
424
|
+
|
|
425
|
+
Évitez de réutiliser une même instance avec plusieurs appels à `execute`. La
|
|
426
|
+
forme `MyService.call(...)` crée une instance dédiée pour chaque exécution.
|
|
427
|
+
|
|
428
|
+
## Développement
|
|
429
|
+
|
|
430
|
+
```sh
|
|
431
|
+
bundle install
|
|
432
|
+
bundle exec rake
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
La tâche par défaut exécute les tests puis construit la gem dans `pkg/`.
|
|
436
|
+
|
|
437
|
+
## Publication sur RubyGems
|
|
438
|
+
|
|
439
|
+
Les versions sont publiées avec
|
|
440
|
+
[RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/).
|
|
441
|
+
Aucune clé API RubyGems ne doit être ajoutée aux secrets GitHub.
|
|
442
|
+
|
|
443
|
+
Avant la première publication, créez un **Pending Trusted Publisher** dans
|
|
444
|
+
votre profil RubyGems avec les paramètres suivants :
|
|
445
|
+
|
|
446
|
+
- gem : `base-service` ;
|
|
447
|
+
- propriétaire du dépôt : `nicolasva` ;
|
|
448
|
+
- dépôt : `base-service` ;
|
|
449
|
+
- workflow : `release.yml` ;
|
|
450
|
+
- environnement GitHub : `release`.
|
|
451
|
+
|
|
452
|
+
Publiez ensuite une version en créant un tag correspondant exactement à
|
|
453
|
+
`Service::VERSION` :
|
|
454
|
+
|
|
455
|
+
```sh
|
|
456
|
+
VERSION=$(ruby -Ilib -rbase_service/version -e 'print Service::VERSION')
|
|
457
|
+
git tag "v${VERSION}"
|
|
458
|
+
git push origin "v${VERSION}"
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Le workflow GitHub Actions construit alors la gem et la publie sur RubyGems.
|
|
462
|
+
RubyDoc génère automatiquement la documentation de la version publiée.
|
data/lib/base_service.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "callback_collection"
|
|
4
|
+
require_relative "base_service/version"
|
|
5
|
+
|
|
6
|
+
module Service
|
|
7
|
+
class Base
|
|
8
|
+
def initialize(**args, &block)
|
|
9
|
+
args.each { |key, value| instance_variable_set("@#{key}", value) }
|
|
10
|
+
@callbacks = ::CallbackCollection.new(&block) if block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.call(**args, &block)
|
|
14
|
+
new(**args, &block).execute
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute
|
|
18
|
+
@context = Context.new
|
|
19
|
+
result_payload = call
|
|
20
|
+
|
|
21
|
+
Result.new(result_payload, @context.errors, @context.messages)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def append_error(type, message)
|
|
27
|
+
@context.append_error(type, message, caller.first)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def append_message(message)
|
|
31
|
+
@context.append_message(message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Context
|
|
36
|
+
attr_reader :errors, :messages
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
@errors = []
|
|
40
|
+
@messages = []
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def append_error(type, message, caller_info = caller.first)
|
|
44
|
+
@errors << ::Service::Error.new(type, message, caller_info)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def append_message(message)
|
|
48
|
+
@messages << message
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Error
|
|
53
|
+
attr_reader :type, :message, :caller_info
|
|
54
|
+
|
|
55
|
+
def initialize(type, message, caller_info)
|
|
56
|
+
@type = type
|
|
57
|
+
@caller_info = caller_info
|
|
58
|
+
@message = message
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Result
|
|
63
|
+
attr_reader :result, :errors, :messages
|
|
64
|
+
|
|
65
|
+
def initialize(result, errors, messages)
|
|
66
|
+
@result = result
|
|
67
|
+
@errors = errors.dup.freeze
|
|
68
|
+
@messages = messages.dup.freeze
|
|
69
|
+
freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def successful?
|
|
73
|
+
@errors.empty?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: base-service
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nicolas Vandenbogaerde
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: callback-collection
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '7'
|
|
36
|
+
type: :development
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '5'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '7'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rake
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '13.0'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '13.0'
|
|
60
|
+
description: Build service objects with isolated execution contexts, immutable results,
|
|
61
|
+
and named callbacks.
|
|
62
|
+
executables: []
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/base_service.rb
|
|
68
|
+
- lib/base_service/version.rb
|
|
69
|
+
homepage: https://github.com/nicolasva/base-service
|
|
70
|
+
licenses: []
|
|
71
|
+
metadata:
|
|
72
|
+
rubygems_mfa_required: 'true'
|
|
73
|
+
documentation_uri: https://www.rubydoc.info/gems/base-service/0.1.1
|
|
74
|
+
source_code_uri: https://github.com/nicolasva/base-service
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.7'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 4.0.20
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: An immutable base class for Ruby service objects
|
|
92
|
+
test_files: []
|