hanami-utils 2.0.0.alpha6 → 2.0.0.beta1
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/CHANGELOG.md +147 -1
- data/README.md +9 -18
- data/hanami-utils.gemspec +0 -1
- data/lib/hanami/utils/version.rb +1 -1
- metadata +2 -4
- data/lib/hanami/interactor.rb +0 -619
- data/lib/hanami/utils/basic_object.rb +0 -141
data/lib/hanami/interactor.rb
DELETED
@@ -1,619 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "hanami/utils/basic_object"
|
4
|
-
require "hanami/utils/class_attribute"
|
5
|
-
require "hanami/utils/hash"
|
6
|
-
|
7
|
-
module Hanami
|
8
|
-
# Hanami Interactor
|
9
|
-
#
|
10
|
-
# @since 0.3.5
|
11
|
-
module Interactor
|
12
|
-
# Result of an operation
|
13
|
-
#
|
14
|
-
# @since 0.3.5
|
15
|
-
class Result < Utils::BasicObject
|
16
|
-
# Concrete methods
|
17
|
-
#
|
18
|
-
# @since 0.3.5
|
19
|
-
# @api private
|
20
|
-
#
|
21
|
-
# @see Hanami::Interactor::Result#respond_to_missing?
|
22
|
-
METHODS = ::Hash[initialize: true,
|
23
|
-
success?: true,
|
24
|
-
successful?: true,
|
25
|
-
failure?: true,
|
26
|
-
fail!: true,
|
27
|
-
prepare!: true,
|
28
|
-
errors: true,
|
29
|
-
error: true].freeze
|
30
|
-
|
31
|
-
# Initialize a new result
|
32
|
-
#
|
33
|
-
# @param payload [Hash] a payload to carry on
|
34
|
-
#
|
35
|
-
# @return [Hanami::Interactor::Result]
|
36
|
-
#
|
37
|
-
# @since 0.3.5
|
38
|
-
# @api private
|
39
|
-
def initialize(payload = {})
|
40
|
-
@payload = payload
|
41
|
-
@errors = []
|
42
|
-
@success = true
|
43
|
-
end
|
44
|
-
|
45
|
-
# Checks if the current status is successful
|
46
|
-
#
|
47
|
-
# @return [TrueClass,FalseClass] the result of the check
|
48
|
-
#
|
49
|
-
# @since 0.8.1
|
50
|
-
def successful?
|
51
|
-
@success && errors.empty?
|
52
|
-
end
|
53
|
-
|
54
|
-
# @since 0.3.5
|
55
|
-
alias_method :success?, :successful?
|
56
|
-
|
57
|
-
# Checks if the current status is not successful
|
58
|
-
#
|
59
|
-
# @return [TrueClass,FalseClass] the result of the check
|
60
|
-
#
|
61
|
-
# @since 0.9.2
|
62
|
-
def failure?
|
63
|
-
!successful?
|
64
|
-
end
|
65
|
-
|
66
|
-
# Forces the status to be a failure
|
67
|
-
#
|
68
|
-
# @since 0.3.5
|
69
|
-
def fail!
|
70
|
-
@success = false
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns all the errors collected during an operation
|
74
|
-
#
|
75
|
-
# @return [Array] the errors
|
76
|
-
#
|
77
|
-
# @since 0.3.5
|
78
|
-
#
|
79
|
-
# @see Hanami::Interactor::Result#error
|
80
|
-
# @see Hanami::Interactor#call
|
81
|
-
# @see Hanami::Interactor#error
|
82
|
-
# @see Hanami::Interactor#error!
|
83
|
-
def errors
|
84
|
-
@errors.dup
|
85
|
-
end
|
86
|
-
|
87
|
-
# @since 0.5.0
|
88
|
-
# @api private
|
89
|
-
def add_error(*errors)
|
90
|
-
@errors << errors
|
91
|
-
@errors.flatten!
|
92
|
-
nil
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns the first errors collected during an operation
|
96
|
-
#
|
97
|
-
# @return [nil,String] the error, if present
|
98
|
-
#
|
99
|
-
# @since 0.3.5
|
100
|
-
#
|
101
|
-
# @see Hanami::Interactor::Result#errors
|
102
|
-
# @see Hanami::Interactor#call
|
103
|
-
# @see Hanami::Interactor#error
|
104
|
-
# @see Hanami::Interactor#error!
|
105
|
-
def error
|
106
|
-
errors.first
|
107
|
-
end
|
108
|
-
|
109
|
-
# Prepares the result before to be returned
|
110
|
-
#
|
111
|
-
# @param payload [Hash] an updated payload
|
112
|
-
#
|
113
|
-
# @since 0.3.5
|
114
|
-
# @api private
|
115
|
-
def prepare!(payload)
|
116
|
-
@payload.merge!(payload)
|
117
|
-
self
|
118
|
-
end
|
119
|
-
|
120
|
-
protected
|
121
|
-
|
122
|
-
# @since 0.3.5
|
123
|
-
# @api private
|
124
|
-
def method_missing(method_name, *)
|
125
|
-
@payload.fetch(method_name) { super }
|
126
|
-
end
|
127
|
-
|
128
|
-
# @since 0.3.5
|
129
|
-
# @api private
|
130
|
-
def respond_to_missing?(method_name, _include_all)
|
131
|
-
method_name = method_name.to_sym
|
132
|
-
METHODS[method_name] || @payload.key?(method_name)
|
133
|
-
end
|
134
|
-
|
135
|
-
# @since 0.3.5
|
136
|
-
# @api private
|
137
|
-
def __inspect
|
138
|
-
" @success=#{@success} @payload=#{@payload.inspect}"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Override for <tt>Module#included</tt>.
|
143
|
-
#
|
144
|
-
# @since 0.3.5
|
145
|
-
# @api private
|
146
|
-
def self.included(base)
|
147
|
-
super
|
148
|
-
|
149
|
-
base.class_eval do
|
150
|
-
extend ClassMethods
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Interactor legacy interface
|
155
|
-
#
|
156
|
-
# @since 0.3.5
|
157
|
-
module LegacyInterface
|
158
|
-
# Initialize an interactor
|
159
|
-
#
|
160
|
-
# It accepts arbitrary number of arguments.
|
161
|
-
# Developers can override it.
|
162
|
-
#
|
163
|
-
# @param args [Array<Object>] arbitrary number of arguments
|
164
|
-
#
|
165
|
-
# @return [Hanami::Interactor] the interactor
|
166
|
-
#
|
167
|
-
# @since 0.3.5
|
168
|
-
#
|
169
|
-
# @example Override #initialize
|
170
|
-
# require 'hanami/interactor'
|
171
|
-
#
|
172
|
-
# class UpdateProfile
|
173
|
-
# include Hanami::Interactor
|
174
|
-
#
|
175
|
-
# def initialize(user, params)
|
176
|
-
# @user = user
|
177
|
-
# @params = params
|
178
|
-
# end
|
179
|
-
#
|
180
|
-
# def call
|
181
|
-
# # ...
|
182
|
-
# end
|
183
|
-
# end
|
184
|
-
def initialize(*args, **kwargs)
|
185
|
-
super
|
186
|
-
ensure
|
187
|
-
@__result = ::Hanami::Interactor::Result.new
|
188
|
-
end
|
189
|
-
|
190
|
-
# Triggers the operation and return a result.
|
191
|
-
#
|
192
|
-
# All the instance variables will be available in the result.
|
193
|
-
#
|
194
|
-
# ATTENTION: This must be implemented by the including class.
|
195
|
-
#
|
196
|
-
# @return [Hanami::Interactor::Result] the result of the operation
|
197
|
-
#
|
198
|
-
# @raise [NoMethodError] if this isn't implemented by the including class.
|
199
|
-
#
|
200
|
-
# @example Expose instance variables in result payload
|
201
|
-
# require 'hanami/interactor'
|
202
|
-
#
|
203
|
-
# class Signup
|
204
|
-
# include Hanami::Interactor
|
205
|
-
# expose :user, :params
|
206
|
-
#
|
207
|
-
# def initialize(params)
|
208
|
-
# @params = params
|
209
|
-
# @foo = 'bar'
|
210
|
-
# end
|
211
|
-
#
|
212
|
-
# def call
|
213
|
-
# @user = UserRepository.new.create(@params)
|
214
|
-
# end
|
215
|
-
# end
|
216
|
-
#
|
217
|
-
# result = Signup.new(name: 'Luca').call
|
218
|
-
# result.failure? # => false
|
219
|
-
# result.successful? # => true
|
220
|
-
#
|
221
|
-
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
222
|
-
# result.params # => { :name=>"Luca" }
|
223
|
-
# result.foo # => raises NoMethodError
|
224
|
-
#
|
225
|
-
# @example Failed precondition
|
226
|
-
# require 'hanami/interactor'
|
227
|
-
#
|
228
|
-
# class Signup
|
229
|
-
# include Hanami::Interactor
|
230
|
-
# expose :user
|
231
|
-
#
|
232
|
-
# def initialize(params)
|
233
|
-
# @params = params
|
234
|
-
# end
|
235
|
-
#
|
236
|
-
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
237
|
-
# def call
|
238
|
-
# @user = UserRepository.new.create(@params)
|
239
|
-
# end
|
240
|
-
#
|
241
|
-
# private
|
242
|
-
# def valid?
|
243
|
-
# @params.valid?
|
244
|
-
# end
|
245
|
-
# end
|
246
|
-
#
|
247
|
-
# result = Signup.new(name: nil).call
|
248
|
-
# result.successful? # => false
|
249
|
-
# result.failure? # => true
|
250
|
-
#
|
251
|
-
# result.user # => #<User:0x007fa311105778 @id=nil @name="Luca">
|
252
|
-
#
|
253
|
-
# @example Bad usage
|
254
|
-
# require 'hanami/interactor'
|
255
|
-
#
|
256
|
-
# class Signup
|
257
|
-
# include Hanami::Interactor
|
258
|
-
#
|
259
|
-
# # Method #call is not defined
|
260
|
-
# end
|
261
|
-
#
|
262
|
-
# Signup.new.call # => NoMethodError
|
263
|
-
def call
|
264
|
-
_call { super }
|
265
|
-
end
|
266
|
-
|
267
|
-
private
|
268
|
-
|
269
|
-
# @since 0.3.5
|
270
|
-
# @api private
|
271
|
-
def _call
|
272
|
-
catch :fail do
|
273
|
-
validate!
|
274
|
-
yield
|
275
|
-
end
|
276
|
-
|
277
|
-
_prepare!
|
278
|
-
end
|
279
|
-
|
280
|
-
# @since 0.3.5
|
281
|
-
def validate!
|
282
|
-
fail! unless valid?
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
# Interactor interface
|
287
|
-
# @since 1.1.0
|
288
|
-
module Interface
|
289
|
-
# Triggers the operation and return a result.
|
290
|
-
#
|
291
|
-
# All the exposed instance variables will be available in the result.
|
292
|
-
#
|
293
|
-
# ATTENTION: This must be implemented by the including class.
|
294
|
-
#
|
295
|
-
# @return [Hanami::Interactor::Result] the result of the operation
|
296
|
-
#
|
297
|
-
# @raise [NoMethodError] if this isn't implemented by the including class.
|
298
|
-
#
|
299
|
-
# @example Expose instance variables in result payload
|
300
|
-
# require 'hanami/interactor'
|
301
|
-
#
|
302
|
-
# class Signup
|
303
|
-
# include Hanami::Interactor
|
304
|
-
# expose :user, :params
|
305
|
-
#
|
306
|
-
# def call(params)
|
307
|
-
# @params = params
|
308
|
-
# @foo = 'bar'
|
309
|
-
# @user = UserRepository.new.persist(User.new(params))
|
310
|
-
# end
|
311
|
-
# end
|
312
|
-
#
|
313
|
-
# result = Signup.new(name: 'Luca').call
|
314
|
-
# result.failure? # => false
|
315
|
-
# result.successful? # => true
|
316
|
-
#
|
317
|
-
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
318
|
-
# result.params # => { :name=>"Luca" }
|
319
|
-
# result.foo # => raises NoMethodError
|
320
|
-
#
|
321
|
-
# @example Failed precondition
|
322
|
-
# require 'hanami/interactor'
|
323
|
-
#
|
324
|
-
# class Signup
|
325
|
-
# include Hanami::Interactor
|
326
|
-
# expose :user
|
327
|
-
#
|
328
|
-
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
329
|
-
# def call(params)
|
330
|
-
# @user = User.new(params)
|
331
|
-
# @user = UserRepository.new.persist(@user)
|
332
|
-
# end
|
333
|
-
#
|
334
|
-
# private
|
335
|
-
# def valid?(params)
|
336
|
-
# params.valid?
|
337
|
-
# end
|
338
|
-
# end
|
339
|
-
#
|
340
|
-
# result = Signup.new.call(name: nil)
|
341
|
-
# result.successful? # => false
|
342
|
-
# result.failure? # => true
|
343
|
-
#
|
344
|
-
# result.user # => nil
|
345
|
-
#
|
346
|
-
# @example Bad usage
|
347
|
-
# require 'hanami/interactor'
|
348
|
-
#
|
349
|
-
# class Signup
|
350
|
-
# include Hanami::Interactor
|
351
|
-
#
|
352
|
-
# # Method #call is not defined
|
353
|
-
# end
|
354
|
-
#
|
355
|
-
# Signup.new.call # => NoMethodError
|
356
|
-
def call(*args, **kwargs)
|
357
|
-
@__result = ::Hanami::Interactor::Result.new
|
358
|
-
_call(*args, **kwargs) { super }
|
359
|
-
end
|
360
|
-
|
361
|
-
private
|
362
|
-
|
363
|
-
# @api private
|
364
|
-
# @since 1.1.0
|
365
|
-
def _call(*args, **kwargs)
|
366
|
-
catch :fail do
|
367
|
-
validate!(*args, **kwargs)
|
368
|
-
yield
|
369
|
-
end
|
370
|
-
|
371
|
-
_prepare!
|
372
|
-
end
|
373
|
-
|
374
|
-
# @since 1.1.0
|
375
|
-
def validate!(*args, **kwargs)
|
376
|
-
fail! unless valid?(*args, **kwargs)
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
private
|
381
|
-
|
382
|
-
# Checks if proceed with <tt>#call</tt> invocation.
|
383
|
-
# By default it returns <tt>true</tt>.
|
384
|
-
#
|
385
|
-
# Developers can override it.
|
386
|
-
#
|
387
|
-
# @return [TrueClass,FalseClass] the result of the check
|
388
|
-
#
|
389
|
-
# @since 0.3.5
|
390
|
-
def valid?(*)
|
391
|
-
true
|
392
|
-
end
|
393
|
-
|
394
|
-
# Fails and interrupts the current flow.
|
395
|
-
#
|
396
|
-
# @since 0.3.5
|
397
|
-
#
|
398
|
-
# @example
|
399
|
-
# require 'hanami/interactor'
|
400
|
-
#
|
401
|
-
# class CreateEmailTest
|
402
|
-
# include Hanami::Interactor
|
403
|
-
#
|
404
|
-
# def initialize(params)
|
405
|
-
# @params = params
|
406
|
-
# end
|
407
|
-
#
|
408
|
-
# def call
|
409
|
-
# persist_email_test!
|
410
|
-
# capture_screenshot!
|
411
|
-
# end
|
412
|
-
#
|
413
|
-
# private
|
414
|
-
# def persist_email_test!
|
415
|
-
# @email_test = EmailTestRepository.new.create(@params)
|
416
|
-
# end
|
417
|
-
#
|
418
|
-
# # IF THIS RAISES AN EXCEPTION WE FORCE A FAILURE
|
419
|
-
# def capture_screenshot!
|
420
|
-
# Screenshot.new(@email_test).capture!
|
421
|
-
# rescue
|
422
|
-
# fail!
|
423
|
-
# end
|
424
|
-
# end
|
425
|
-
#
|
426
|
-
# result = CreateEmailTest.new(account_id: 1).call
|
427
|
-
# result.successful? # => false
|
428
|
-
def fail!
|
429
|
-
@__result.fail!
|
430
|
-
throw :fail
|
431
|
-
end
|
432
|
-
|
433
|
-
# Logs an error without interrupting the flow.
|
434
|
-
#
|
435
|
-
# When used, the returned result won't be successful.
|
436
|
-
#
|
437
|
-
# @param message [String] the error message
|
438
|
-
#
|
439
|
-
# @return false
|
440
|
-
#
|
441
|
-
# @since 0.3.5
|
442
|
-
#
|
443
|
-
# @see Hanami::Interactor#error!
|
444
|
-
#
|
445
|
-
# @example
|
446
|
-
# require 'hanami/interactor'
|
447
|
-
#
|
448
|
-
# class CreateRecord
|
449
|
-
# include Hanami::Interactor
|
450
|
-
# expose :logger
|
451
|
-
#
|
452
|
-
# def initialize
|
453
|
-
# @logger = []
|
454
|
-
# end
|
455
|
-
#
|
456
|
-
# def call
|
457
|
-
# prepare_data!
|
458
|
-
# persist!
|
459
|
-
# sync!
|
460
|
-
# end
|
461
|
-
#
|
462
|
-
# private
|
463
|
-
# def prepare_data!
|
464
|
-
# @logger << __method__
|
465
|
-
# error "Prepare data error"
|
466
|
-
# end
|
467
|
-
#
|
468
|
-
# def persist!
|
469
|
-
# @logger << __method__
|
470
|
-
# error "Persist error"
|
471
|
-
# end
|
472
|
-
#
|
473
|
-
# def sync!
|
474
|
-
# @logger << __method__
|
475
|
-
# end
|
476
|
-
# end
|
477
|
-
#
|
478
|
-
# result = CreateRecord.new.call
|
479
|
-
# result.successful? # => false
|
480
|
-
#
|
481
|
-
# result.errors # => ["Prepare data error", "Persist error"]
|
482
|
-
# result.logger # => [:prepare_data!, :persist!, :sync!]
|
483
|
-
def error(message)
|
484
|
-
@__result.add_error message
|
485
|
-
false
|
486
|
-
end
|
487
|
-
|
488
|
-
# Logs an error and interrupts the flow.
|
489
|
-
#
|
490
|
-
# When used, the returned result won't be successful.
|
491
|
-
#
|
492
|
-
# @param message [String] the error message
|
493
|
-
#
|
494
|
-
# @since 0.3.5
|
495
|
-
#
|
496
|
-
# @see Hanami::Interactor#error
|
497
|
-
#
|
498
|
-
# @example
|
499
|
-
# require 'hanami/interactor'
|
500
|
-
#
|
501
|
-
# class CreateRecord
|
502
|
-
# include Hanami::Interactor
|
503
|
-
# expose :logger
|
504
|
-
#
|
505
|
-
# def initialize
|
506
|
-
# @logger = []
|
507
|
-
# end
|
508
|
-
#
|
509
|
-
# def call
|
510
|
-
# prepare_data!
|
511
|
-
# persist!
|
512
|
-
# sync!
|
513
|
-
# end
|
514
|
-
#
|
515
|
-
# private
|
516
|
-
# def prepare_data!
|
517
|
-
# @logger << __method__
|
518
|
-
# error "Prepare data error"
|
519
|
-
# end
|
520
|
-
#
|
521
|
-
# def persist!
|
522
|
-
# @logger << __method__
|
523
|
-
# error! "Persist error"
|
524
|
-
# end
|
525
|
-
#
|
526
|
-
# # THIS WILL NEVER BE INVOKED BECAUSE WE USE #error! IN #persist!
|
527
|
-
# def sync!
|
528
|
-
# @logger << __method__
|
529
|
-
# end
|
530
|
-
# end
|
531
|
-
#
|
532
|
-
# result = CreateRecord.new.call
|
533
|
-
# result.successful? # => false
|
534
|
-
#
|
535
|
-
# result.errors # => ["Prepare data error", "Persist error"]
|
536
|
-
# result.logger # => [:prepare_data!, :persist!]
|
537
|
-
def error!(message)
|
538
|
-
error(message)
|
539
|
-
fail!
|
540
|
-
end
|
541
|
-
|
542
|
-
# @since 0.3.5
|
543
|
-
# @api private
|
544
|
-
def _prepare!
|
545
|
-
@__result.prepare!(_exposures)
|
546
|
-
end
|
547
|
-
|
548
|
-
# @since 0.5.0
|
549
|
-
# @api private
|
550
|
-
def _exposures
|
551
|
-
::Hash[].tap do |result|
|
552
|
-
self.class.exposures.each do |name, ivar|
|
553
|
-
result[name] = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : nil
|
554
|
-
end
|
555
|
-
end
|
556
|
-
end
|
557
|
-
end
|
558
|
-
|
559
|
-
# @since 0.5.0
|
560
|
-
# @api private
|
561
|
-
module ClassMethods
|
562
|
-
# @since 0.5.0
|
563
|
-
# @api private
|
564
|
-
def self.extended(interactor)
|
565
|
-
interactor.class_eval do
|
566
|
-
include Utils::ClassAttribute
|
567
|
-
|
568
|
-
class_attribute :exposures
|
569
|
-
self.exposures = {}
|
570
|
-
end
|
571
|
-
end
|
572
|
-
|
573
|
-
def method_added(method_name)
|
574
|
-
super
|
575
|
-
return unless method_name == :call
|
576
|
-
|
577
|
-
if instance_method(:call).arity.zero?
|
578
|
-
prepend Hanami::Interactor::LegacyInterface
|
579
|
-
else
|
580
|
-
prepend Hanami::Interactor::Interface
|
581
|
-
end
|
582
|
-
end
|
583
|
-
|
584
|
-
# Exposes local instance variables into the returning value of <tt>#call</tt>
|
585
|
-
#
|
586
|
-
# @param instance_variable_names [Symbol,Array<Symbol>] one or more instance
|
587
|
-
# variable names
|
588
|
-
#
|
589
|
-
# @since 0.5.0
|
590
|
-
#
|
591
|
-
# @see Hanami::Interactor::Result
|
592
|
-
#
|
593
|
-
# @example Exposes instance variable
|
594
|
-
#
|
595
|
-
# class Signup
|
596
|
-
# include Hanami::Interactor
|
597
|
-
# expose :user
|
598
|
-
#
|
599
|
-
# def initialize(params)
|
600
|
-
# @params = params
|
601
|
-
# @user = User.new(@params[:user])
|
602
|
-
# end
|
603
|
-
#
|
604
|
-
# def call
|
605
|
-
# # ...
|
606
|
-
# end
|
607
|
-
# end
|
608
|
-
#
|
609
|
-
# result = Signup.new(user: { name: "Luca" }).call
|
610
|
-
#
|
611
|
-
# result.user # => #<User:0x007fa85c58ccd8 @name="Luca">
|
612
|
-
# result.params # => NoMethodError
|
613
|
-
def expose(*instance_variable_names)
|
614
|
-
instance_variable_names.each do |name|
|
615
|
-
exposures[name.to_sym] = "@#{name}"
|
616
|
-
end
|
617
|
-
end
|
618
|
-
end
|
619
|
-
end
|