simple-interactors 0.0.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 +53 -0
- data/lib/hanami/interactor.rb +617 -0
- data/lib/hanami/utils/basic_object.rb +141 -0
- data/lib/simple-interactors/version.rb +3 -0
- data/lib/simple-interactors.rb +6 -0
- data/simple-interactors.gemspec +26 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f7ff7d77b7cbbd6cf739e8ee2e00563500748a216908263cbe74eb4ab8032b41
|
4
|
+
data.tar.gz: 81488ca044bd1a3061a8936949ffb9ebb2b3c203fe3bb7b51fff01a8ddf10383
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 923eb61eca94d6dd0c0761f25446d820d786cec8c49190973de9cfecde23ff158ec7d751f523700ec2c7ff08d84b6cadf31608614a5d56e75ff4948eed978c05
|
7
|
+
data.tar.gz: 5efd10c8b01e2b8fcb137aea8402bbb0557edda3d652f91720956349eed9098789c38418142656522d673b3cf6a87a207a8ce9fc606228a653f4f2ff2f3fd80c
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# SimpleInteractors
|
2
|
+
|
3
|
+
The Hanami::Utils (https://github.com/hanami/utils) used to include an implementation of the interactor pattern. But it no longer does, see https://github.com/hanami/utils/issues/401
|
4
|
+
|
5
|
+
This gem includes the original Hanami Interactor and it is a drop by replacement. You should be able to just include this gem and continue to use Hanami::Interactor as you used to.
|
6
|
+
|
7
|
+
My future goal is to create an alternative implementation of Interactor that is independent of Hanami.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'simple-interactors'
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'hanami/interactor'
|
21
|
+
|
22
|
+
class YourClass
|
23
|
+
include Hanami::Interactor
|
24
|
+
|
25
|
+
def initialize(person:, current_user:)
|
26
|
+
@person = person
|
27
|
+
@current_user = current_user
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
# do your business logic here.
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class PeopleController < ApplicationController
|
38
|
+
|
39
|
+
def create
|
40
|
+
result = YourClass.new(person: person_params, current_user: current_user).call
|
41
|
+
|
42
|
+
if result.success?
|
43
|
+
redirect_to :index
|
44
|
+
else
|
45
|
+
render :new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
## Copyright
|
52
|
+
|
53
|
+
Copyright © 2022 Gonzalo Rodríguez-Baltanás Díaz – Released under MIT License
|
@@ -0,0 +1,617 @@
|
|
1
|
+
require "hanami/utils/basic_object"
|
2
|
+
require "hanami/utils/class_attribute"
|
3
|
+
require "hanami/utils/hash"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
# Hanami Interactor
|
7
|
+
#
|
8
|
+
# @since 0.3.5
|
9
|
+
module Interactor
|
10
|
+
# Result of an operation
|
11
|
+
#
|
12
|
+
# @since 0.3.5
|
13
|
+
class Result < Utils::BasicObject
|
14
|
+
# Concrete methods
|
15
|
+
#
|
16
|
+
# @since 0.3.5
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
# @see Hanami::Interactor::Result#respond_to_missing?
|
20
|
+
METHODS = ::Hash[initialize: true,
|
21
|
+
success?: true,
|
22
|
+
successful?: true,
|
23
|
+
failure?: true,
|
24
|
+
fail!: true,
|
25
|
+
prepare!: true,
|
26
|
+
errors: true,
|
27
|
+
error: true].freeze
|
28
|
+
|
29
|
+
# Initialize a new result
|
30
|
+
#
|
31
|
+
# @param payload [Hash] a payload to carry on
|
32
|
+
#
|
33
|
+
# @return [Hanami::Interactor::Result]
|
34
|
+
#
|
35
|
+
# @since 0.3.5
|
36
|
+
# @api private
|
37
|
+
def initialize(payload = {})
|
38
|
+
@payload = payload
|
39
|
+
@errors = []
|
40
|
+
@success = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if the current status is successful
|
44
|
+
#
|
45
|
+
# @return [TrueClass,FalseClass] the result of the check
|
46
|
+
#
|
47
|
+
# @since 0.8.1
|
48
|
+
def successful?
|
49
|
+
@success && errors.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
# @since 0.3.5
|
53
|
+
alias_method :success?, :successful?
|
54
|
+
|
55
|
+
# Checks if the current status is not successful
|
56
|
+
#
|
57
|
+
# @return [TrueClass,FalseClass] the result of the check
|
58
|
+
#
|
59
|
+
# @since 0.9.2
|
60
|
+
def failure?
|
61
|
+
!successful?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Forces the status to be a failure
|
65
|
+
#
|
66
|
+
# @since 0.3.5
|
67
|
+
def fail!
|
68
|
+
@success = false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns all the errors collected during an operation
|
72
|
+
#
|
73
|
+
# @return [Array] the errors
|
74
|
+
#
|
75
|
+
# @since 0.3.5
|
76
|
+
#
|
77
|
+
# @see Hanami::Interactor::Result#error
|
78
|
+
# @see Hanami::Interactor#call
|
79
|
+
# @see Hanami::Interactor#error
|
80
|
+
# @see Hanami::Interactor#error!
|
81
|
+
def errors
|
82
|
+
@errors.dup
|
83
|
+
end
|
84
|
+
|
85
|
+
# @since 0.5.0
|
86
|
+
# @api private
|
87
|
+
def add_error(*errors)
|
88
|
+
@errors << errors
|
89
|
+
@errors.flatten!
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the first errors collected during an operation
|
94
|
+
#
|
95
|
+
# @return [nil,String] the error, if present
|
96
|
+
#
|
97
|
+
# @since 0.3.5
|
98
|
+
#
|
99
|
+
# @see Hanami::Interactor::Result#errors
|
100
|
+
# @see Hanami::Interactor#call
|
101
|
+
# @see Hanami::Interactor#error
|
102
|
+
# @see Hanami::Interactor#error!
|
103
|
+
def error
|
104
|
+
errors.first
|
105
|
+
end
|
106
|
+
|
107
|
+
# Prepares the result before to be returned
|
108
|
+
#
|
109
|
+
# @param payload [Hash] an updated payload
|
110
|
+
#
|
111
|
+
# @since 0.3.5
|
112
|
+
# @api private
|
113
|
+
def prepare!(payload)
|
114
|
+
@payload.merge!(payload)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
# @since 0.3.5
|
121
|
+
# @api private
|
122
|
+
def method_missing(method_name, *)
|
123
|
+
@payload.fetch(method_name) { super }
|
124
|
+
end
|
125
|
+
|
126
|
+
# @since 0.3.5
|
127
|
+
# @api private
|
128
|
+
def respond_to_missing?(method_name, _include_all)
|
129
|
+
method_name = method_name.to_sym
|
130
|
+
METHODS[method_name] || @payload.key?(method_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @since 0.3.5
|
134
|
+
# @api private
|
135
|
+
def __inspect
|
136
|
+
" @success=#{@success} @payload=#{@payload.inspect}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Override for <tt>Module#included</tt>.
|
141
|
+
#
|
142
|
+
# @since 0.3.5
|
143
|
+
# @api private
|
144
|
+
def self.included(base)
|
145
|
+
super
|
146
|
+
|
147
|
+
base.class_eval do
|
148
|
+
extend ClassMethods
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Interactor legacy interface
|
153
|
+
#
|
154
|
+
# @since 0.3.5
|
155
|
+
module LegacyInterface
|
156
|
+
# Initialize an interactor
|
157
|
+
#
|
158
|
+
# It accepts arbitrary number of arguments.
|
159
|
+
# Developers can override it.
|
160
|
+
#
|
161
|
+
# @param args [Array<Object>] arbitrary number of arguments
|
162
|
+
#
|
163
|
+
# @return [Hanami::Interactor] the interactor
|
164
|
+
#
|
165
|
+
# @since 0.3.5
|
166
|
+
#
|
167
|
+
# @example Override #initialize
|
168
|
+
# require 'hanami/interactor'
|
169
|
+
#
|
170
|
+
# class UpdateProfile
|
171
|
+
# include Hanami::Interactor
|
172
|
+
#
|
173
|
+
# def initialize(user, params)
|
174
|
+
# @user = user
|
175
|
+
# @params = params
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# def call
|
179
|
+
# # ...
|
180
|
+
# end
|
181
|
+
# end
|
182
|
+
def initialize(*args, **kwargs)
|
183
|
+
super
|
184
|
+
ensure
|
185
|
+
@__result = ::Hanami::Interactor::Result.new
|
186
|
+
end
|
187
|
+
|
188
|
+
# Triggers the operation and return a result.
|
189
|
+
#
|
190
|
+
# All the instance variables will be available in the result.
|
191
|
+
#
|
192
|
+
# ATTENTION: This must be implemented by the including class.
|
193
|
+
#
|
194
|
+
# @return [Hanami::Interactor::Result] the result of the operation
|
195
|
+
#
|
196
|
+
# @raise [NoMethodError] if this isn't implemented by the including class.
|
197
|
+
#
|
198
|
+
# @example Expose instance variables in result payload
|
199
|
+
# require 'hanami/interactor'
|
200
|
+
#
|
201
|
+
# class Signup
|
202
|
+
# include Hanami::Interactor
|
203
|
+
# expose :user, :params
|
204
|
+
#
|
205
|
+
# def initialize(params)
|
206
|
+
# @params = params
|
207
|
+
# @foo = 'bar'
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# def call
|
211
|
+
# @user = UserRepository.new.create(@params)
|
212
|
+
# end
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# result = Signup.new(name: 'Luca').call
|
216
|
+
# result.failure? # => false
|
217
|
+
# result.successful? # => true
|
218
|
+
#
|
219
|
+
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
220
|
+
# result.params # => { :name=>"Luca" }
|
221
|
+
# result.foo # => raises NoMethodError
|
222
|
+
#
|
223
|
+
# @example Failed precondition
|
224
|
+
# require 'hanami/interactor'
|
225
|
+
#
|
226
|
+
# class Signup
|
227
|
+
# include Hanami::Interactor
|
228
|
+
# expose :user
|
229
|
+
#
|
230
|
+
# def initialize(params)
|
231
|
+
# @params = params
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
235
|
+
# def call
|
236
|
+
# @user = UserRepository.new.create(@params)
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# private
|
240
|
+
# def valid?
|
241
|
+
# @params.valid?
|
242
|
+
# end
|
243
|
+
# end
|
244
|
+
#
|
245
|
+
# result = Signup.new(name: nil).call
|
246
|
+
# result.successful? # => false
|
247
|
+
# result.failure? # => true
|
248
|
+
#
|
249
|
+
# result.user # => #<User:0x007fa311105778 @id=nil @name="Luca">
|
250
|
+
#
|
251
|
+
# @example Bad usage
|
252
|
+
# require 'hanami/interactor'
|
253
|
+
#
|
254
|
+
# class Signup
|
255
|
+
# include Hanami::Interactor
|
256
|
+
#
|
257
|
+
# # Method #call is not defined
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# Signup.new.call # => NoMethodError
|
261
|
+
def call
|
262
|
+
_call { super }
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
# @since 0.3.5
|
268
|
+
# @api private
|
269
|
+
def _call
|
270
|
+
catch :fail do
|
271
|
+
validate!
|
272
|
+
yield
|
273
|
+
end
|
274
|
+
|
275
|
+
_prepare!
|
276
|
+
end
|
277
|
+
|
278
|
+
# @since 0.3.5
|
279
|
+
def validate!
|
280
|
+
fail! unless valid?
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Interactor interface
|
285
|
+
# @since 1.1.0
|
286
|
+
module Interface
|
287
|
+
# Triggers the operation and return a result.
|
288
|
+
#
|
289
|
+
# All the exposed instance variables will be available in the result.
|
290
|
+
#
|
291
|
+
# ATTENTION: This must be implemented by the including class.
|
292
|
+
#
|
293
|
+
# @return [Hanami::Interactor::Result] the result of the operation
|
294
|
+
#
|
295
|
+
# @raise [NoMethodError] if this isn't implemented by the including class.
|
296
|
+
#
|
297
|
+
# @example Expose instance variables in result payload
|
298
|
+
# require 'hanami/interactor'
|
299
|
+
#
|
300
|
+
# class Signup
|
301
|
+
# include Hanami::Interactor
|
302
|
+
# expose :user, :params
|
303
|
+
#
|
304
|
+
# def call(params)
|
305
|
+
# @params = params
|
306
|
+
# @foo = 'bar'
|
307
|
+
# @user = UserRepository.new.persist(User.new(params))
|
308
|
+
# end
|
309
|
+
# end
|
310
|
+
#
|
311
|
+
# result = Signup.new(name: 'Luca').call
|
312
|
+
# result.failure? # => false
|
313
|
+
# result.successful? # => true
|
314
|
+
#
|
315
|
+
# result.user # => #<User:0x007fa311105778 @id=1 @name="Luca">
|
316
|
+
# result.params # => { :name=>"Luca" }
|
317
|
+
# result.foo # => raises NoMethodError
|
318
|
+
#
|
319
|
+
# @example Failed precondition
|
320
|
+
# require 'hanami/interactor'
|
321
|
+
#
|
322
|
+
# class Signup
|
323
|
+
# include Hanami::Interactor
|
324
|
+
# expose :user
|
325
|
+
#
|
326
|
+
# # THIS WON'T BE INVOKED BECAUSE #valid? WILL RETURN false
|
327
|
+
# def call(params)
|
328
|
+
# @user = User.new(params)
|
329
|
+
# @user = UserRepository.new.persist(@user)
|
330
|
+
# end
|
331
|
+
#
|
332
|
+
# private
|
333
|
+
# def valid?(params)
|
334
|
+
# params.valid?
|
335
|
+
# end
|
336
|
+
# end
|
337
|
+
#
|
338
|
+
# result = Signup.new.call(name: nil)
|
339
|
+
# result.successful? # => false
|
340
|
+
# result.failure? # => true
|
341
|
+
#
|
342
|
+
# result.user # => nil
|
343
|
+
#
|
344
|
+
# @example Bad usage
|
345
|
+
# require 'hanami/interactor'
|
346
|
+
#
|
347
|
+
# class Signup
|
348
|
+
# include Hanami::Interactor
|
349
|
+
#
|
350
|
+
# # Method #call is not defined
|
351
|
+
# end
|
352
|
+
#
|
353
|
+
# Signup.new.call # => NoMethodError
|
354
|
+
def call(*args, **kwargs)
|
355
|
+
@__result = ::Hanami::Interactor::Result.new
|
356
|
+
_call(*args, **kwargs) { super }
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
# @api private
|
362
|
+
# @since 1.1.0
|
363
|
+
def _call(*args, **kwargs)
|
364
|
+
catch :fail do
|
365
|
+
validate!(*args, **kwargs)
|
366
|
+
yield
|
367
|
+
end
|
368
|
+
|
369
|
+
_prepare!
|
370
|
+
end
|
371
|
+
|
372
|
+
# @since 1.1.0
|
373
|
+
def validate!(*args, **kwargs)
|
374
|
+
fail! unless valid?(*args, **kwargs)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
private
|
379
|
+
|
380
|
+
# Checks if proceed with <tt>#call</tt> invocation.
|
381
|
+
# By default it returns <tt>true</tt>.
|
382
|
+
#
|
383
|
+
# Developers can override it.
|
384
|
+
#
|
385
|
+
# @return [TrueClass,FalseClass] the result of the check
|
386
|
+
#
|
387
|
+
# @since 0.3.5
|
388
|
+
def valid?(*)
|
389
|
+
true
|
390
|
+
end
|
391
|
+
|
392
|
+
# Fails and interrupts the current flow.
|
393
|
+
#
|
394
|
+
# @since 0.3.5
|
395
|
+
#
|
396
|
+
# @example
|
397
|
+
# require 'hanami/interactor'
|
398
|
+
#
|
399
|
+
# class CreateEmailTest
|
400
|
+
# include Hanami::Interactor
|
401
|
+
#
|
402
|
+
# def initialize(params)
|
403
|
+
# @params = params
|
404
|
+
# end
|
405
|
+
#
|
406
|
+
# def call
|
407
|
+
# persist_email_test!
|
408
|
+
# capture_screenshot!
|
409
|
+
# end
|
410
|
+
#
|
411
|
+
# private
|
412
|
+
# def persist_email_test!
|
413
|
+
# @email_test = EmailTestRepository.new.create(@params)
|
414
|
+
# end
|
415
|
+
#
|
416
|
+
# # IF THIS RAISES AN EXCEPTION WE FORCE A FAILURE
|
417
|
+
# def capture_screenshot!
|
418
|
+
# Screenshot.new(@email_test).capture!
|
419
|
+
# rescue
|
420
|
+
# fail!
|
421
|
+
# end
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
# result = CreateEmailTest.new(account_id: 1).call
|
425
|
+
# result.successful? # => false
|
426
|
+
def fail!
|
427
|
+
@__result.fail!
|
428
|
+
throw :fail
|
429
|
+
end
|
430
|
+
|
431
|
+
# Logs an error without interrupting the flow.
|
432
|
+
#
|
433
|
+
# When used, the returned result won't be successful.
|
434
|
+
#
|
435
|
+
# @param message [String] the error message
|
436
|
+
#
|
437
|
+
# @return false
|
438
|
+
#
|
439
|
+
# @since 0.3.5
|
440
|
+
#
|
441
|
+
# @see Hanami::Interactor#error!
|
442
|
+
#
|
443
|
+
# @example
|
444
|
+
# require 'hanami/interactor'
|
445
|
+
#
|
446
|
+
# class CreateRecord
|
447
|
+
# include Hanami::Interactor
|
448
|
+
# expose :logger
|
449
|
+
#
|
450
|
+
# def initialize
|
451
|
+
# @logger = []
|
452
|
+
# end
|
453
|
+
#
|
454
|
+
# def call
|
455
|
+
# prepare_data!
|
456
|
+
# persist!
|
457
|
+
# sync!
|
458
|
+
# end
|
459
|
+
#
|
460
|
+
# private
|
461
|
+
# def prepare_data!
|
462
|
+
# @logger << __method__
|
463
|
+
# error "Prepare data error"
|
464
|
+
# end
|
465
|
+
#
|
466
|
+
# def persist!
|
467
|
+
# @logger << __method__
|
468
|
+
# error "Persist error"
|
469
|
+
# end
|
470
|
+
#
|
471
|
+
# def sync!
|
472
|
+
# @logger << __method__
|
473
|
+
# end
|
474
|
+
# end
|
475
|
+
#
|
476
|
+
# result = CreateRecord.new.call
|
477
|
+
# result.successful? # => false
|
478
|
+
#
|
479
|
+
# result.errors # => ["Prepare data error", "Persist error"]
|
480
|
+
# result.logger # => [:prepare_data!, :persist!, :sync!]
|
481
|
+
def error(message)
|
482
|
+
@__result.add_error message
|
483
|
+
false
|
484
|
+
end
|
485
|
+
|
486
|
+
# Logs an error and interrupts the flow.
|
487
|
+
#
|
488
|
+
# When used, the returned result won't be successful.
|
489
|
+
#
|
490
|
+
# @param message [String] the error message
|
491
|
+
#
|
492
|
+
# @since 0.3.5
|
493
|
+
#
|
494
|
+
# @see Hanami::Interactor#error
|
495
|
+
#
|
496
|
+
# @example
|
497
|
+
# require 'hanami/interactor'
|
498
|
+
#
|
499
|
+
# class CreateRecord
|
500
|
+
# include Hanami::Interactor
|
501
|
+
# expose :logger
|
502
|
+
#
|
503
|
+
# def initialize
|
504
|
+
# @logger = []
|
505
|
+
# end
|
506
|
+
#
|
507
|
+
# def call
|
508
|
+
# prepare_data!
|
509
|
+
# persist!
|
510
|
+
# sync!
|
511
|
+
# end
|
512
|
+
#
|
513
|
+
# private
|
514
|
+
# def prepare_data!
|
515
|
+
# @logger << __method__
|
516
|
+
# error "Prepare data error"
|
517
|
+
# end
|
518
|
+
#
|
519
|
+
# def persist!
|
520
|
+
# @logger << __method__
|
521
|
+
# error! "Persist error"
|
522
|
+
# end
|
523
|
+
#
|
524
|
+
# # THIS WILL NEVER BE INVOKED BECAUSE WE USE #error! IN #persist!
|
525
|
+
# def sync!
|
526
|
+
# @logger << __method__
|
527
|
+
# end
|
528
|
+
# end
|
529
|
+
#
|
530
|
+
# result = CreateRecord.new.call
|
531
|
+
# result.successful? # => false
|
532
|
+
#
|
533
|
+
# result.errors # => ["Prepare data error", "Persist error"]
|
534
|
+
# result.logger # => [:prepare_data!, :persist!]
|
535
|
+
def error!(message)
|
536
|
+
error(message)
|
537
|
+
fail!
|
538
|
+
end
|
539
|
+
|
540
|
+
# @since 0.3.5
|
541
|
+
# @api private
|
542
|
+
def _prepare!
|
543
|
+
@__result.prepare!(_exposures)
|
544
|
+
end
|
545
|
+
|
546
|
+
# @since 0.5.0
|
547
|
+
# @api private
|
548
|
+
def _exposures
|
549
|
+
::Hash[].tap do |result|
|
550
|
+
self.class.exposures.each do |name, ivar|
|
551
|
+
result[name] = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : nil
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
# @since 0.5.0
|
558
|
+
# @api private
|
559
|
+
module ClassMethods
|
560
|
+
# @since 0.5.0
|
561
|
+
# @api private
|
562
|
+
def self.extended(interactor)
|
563
|
+
interactor.class_eval do
|
564
|
+
include Utils::ClassAttribute
|
565
|
+
|
566
|
+
class_attribute :exposures
|
567
|
+
self.exposures = {}
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def method_added(method_name)
|
572
|
+
super
|
573
|
+
return unless method_name == :call
|
574
|
+
|
575
|
+
if instance_method(:call).arity.zero?
|
576
|
+
prepend Hanami::Interactor::LegacyInterface
|
577
|
+
else
|
578
|
+
prepend Hanami::Interactor::Interface
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
# Exposes local instance variables into the returning value of <tt>#call</tt>
|
583
|
+
#
|
584
|
+
# @param instance_variable_names [Symbol,Array<Symbol>] one or more instance
|
585
|
+
# variable names
|
586
|
+
#
|
587
|
+
# @since 0.5.0
|
588
|
+
#
|
589
|
+
# @see Hanami::Interactor::Result
|
590
|
+
#
|
591
|
+
# @example Exposes instance variable
|
592
|
+
#
|
593
|
+
# class Signup
|
594
|
+
# include Hanami::Interactor
|
595
|
+
# expose :user
|
596
|
+
#
|
597
|
+
# def initialize(params)
|
598
|
+
# @params = params
|
599
|
+
# @user = User.new(@params[:user])
|
600
|
+
# end
|
601
|
+
#
|
602
|
+
# def call
|
603
|
+
# # ...
|
604
|
+
# end
|
605
|
+
# end
|
606
|
+
#
|
607
|
+
# result = Signup.new(user: { name: "Luca" }).call
|
608
|
+
#
|
609
|
+
# result.user # => #<User:0x007fa85c58ccd8 @name="Luca">
|
610
|
+
# result.params # => NoMethodError
|
611
|
+
def expose(*instance_variable_names)
|
612
|
+
instance_variable_names.each do |name|
|
613
|
+
exposures[name.to_sym] = "@#{name}"
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module Utils
|
5
|
+
# BasicObject
|
6
|
+
#
|
7
|
+
# @since 0.3.5
|
8
|
+
class BasicObject < ::BasicObject
|
9
|
+
# Lookups constants at the top-level namespace, if they are missing in the
|
10
|
+
# current context.
|
11
|
+
#
|
12
|
+
# @param name [Symbol] the constant name
|
13
|
+
#
|
14
|
+
# @return [Object, Module] the constant
|
15
|
+
#
|
16
|
+
# @raise [NameError] if the constant cannot be found
|
17
|
+
#
|
18
|
+
# @since 1.3.4
|
19
|
+
# @api private
|
20
|
+
#
|
21
|
+
# @see https://ruby-doc.org/core/Module.html#method-i-const_missing
|
22
|
+
def self.const_missing(name)
|
23
|
+
::Object.const_get(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the class for debugging purposes.
|
27
|
+
#
|
28
|
+
# @since 0.3.5
|
29
|
+
#
|
30
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-class
|
31
|
+
def class
|
32
|
+
(class << self; self; end).superclass
|
33
|
+
end
|
34
|
+
|
35
|
+
# Bare minimum inspect for debugging purposes.
|
36
|
+
#
|
37
|
+
# @return [String] the inspect string
|
38
|
+
#
|
39
|
+
# @since 0.3.5
|
40
|
+
#
|
41
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-inspect
|
42
|
+
def inspect
|
43
|
+
"#<#{self.class}:#{'0x0000%x' % (__id__ << 1)}#{__inspect}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!macro [attach] instance_of?(class)
|
47
|
+
#
|
48
|
+
# Determines if self is an instance of given class or module
|
49
|
+
#
|
50
|
+
# @param class [Class,Module] the class of module to verify
|
51
|
+
#
|
52
|
+
# @return [TrueClass,FalseClass] the result of the check
|
53
|
+
#
|
54
|
+
# @raise [TypeError] if the given argument is not of the expected types
|
55
|
+
#
|
56
|
+
# @since 1.3.2
|
57
|
+
#
|
58
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-instance_of-3F
|
59
|
+
define_method :instance_of?, ::Object.instance_method(:instance_of?)
|
60
|
+
|
61
|
+
# @!macro [attach] is_a?(class)
|
62
|
+
#
|
63
|
+
# Determines if self is of the type of the object class or module
|
64
|
+
#
|
65
|
+
# @param class [Class,Module] the class of module to verify
|
66
|
+
#
|
67
|
+
# @return [TrueClass,FalseClass] the result of the check
|
68
|
+
#
|
69
|
+
# @raise [TypeError] if the given argument is not of the expected types
|
70
|
+
#
|
71
|
+
# @since 1.3.2
|
72
|
+
#
|
73
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-is_a-3F
|
74
|
+
define_method :is_a?, ::Object.instance_method(:is_a?)
|
75
|
+
|
76
|
+
# @!macro [attach] kind_of?(class)
|
77
|
+
#
|
78
|
+
# Determines if self is of the kind of the object class or module
|
79
|
+
#
|
80
|
+
# @param class [Class,Module] the class of module to verify
|
81
|
+
#
|
82
|
+
# @return [TrueClass,FalseClass] the result of the check
|
83
|
+
#
|
84
|
+
# @raise [TypeError] if the given argument is not of the expected types
|
85
|
+
#
|
86
|
+
# @since 1.3.2
|
87
|
+
#
|
88
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-kind_of-3F
|
89
|
+
define_method :kind_of?, ::Object.instance_method(:kind_of?)
|
90
|
+
|
91
|
+
# Alias for __id__
|
92
|
+
#
|
93
|
+
# @return [Fixnum] the object id
|
94
|
+
#
|
95
|
+
# @since 0.9.0
|
96
|
+
#
|
97
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-object_id
|
98
|
+
def object_id
|
99
|
+
__id__
|
100
|
+
end
|
101
|
+
|
102
|
+
# Interface for pp
|
103
|
+
#
|
104
|
+
# @param printer [PP] the Pretty Printable printer
|
105
|
+
# @return [String] the pretty-printable inspection of the object
|
106
|
+
#
|
107
|
+
# @since 0.9.0
|
108
|
+
#
|
109
|
+
# @see https://ruby-doc.org/stdlib/libdoc/pp/rdoc/PP.html
|
110
|
+
def pretty_print(printer)
|
111
|
+
printer.text(inspect)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns true if responds to the given method.
|
115
|
+
#
|
116
|
+
# @return [TrueClass,FalseClass] the result of the check
|
117
|
+
#
|
118
|
+
# @since 0.3.5
|
119
|
+
#
|
120
|
+
# @see http://ruby-doc.org/core-2.2.1/Object.html#method-i-respond_to-3F
|
121
|
+
def respond_to?(method_name, include_all = false)
|
122
|
+
respond_to_missing?(method_name, include_all)
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Must be overridden by descendants
|
128
|
+
#
|
129
|
+
# @since 0.3.5
|
130
|
+
# @api private
|
131
|
+
def respond_to_missing?(_method_name, _include_all)
|
132
|
+
::Kernel.raise ::NotImplementedError
|
133
|
+
end
|
134
|
+
|
135
|
+
# @since 0.3.5
|
136
|
+
# @api private
|
137
|
+
def __inspect
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.push File.expand_path("lib", __dir__)
|
2
|
+
|
3
|
+
require "simple-interactors/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "simple-interactors"
|
7
|
+
spec.version = SimpleInteractors::VERSION
|
8
|
+
spec.authors = ["Gonzalo Rodríguez-Baltanás Díaz"]
|
9
|
+
spec.email = ["siotopo@gmail.com"]
|
10
|
+
spec.description = "Interactors for Ruby on Rails applications"
|
11
|
+
spec.summary = "Interactors for Ruby on Rails applications"
|
12
|
+
spec.homepage = "https://github.com/Nerian/simple-interactors"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md simple-interactors.gemspec`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
spec.required_ruby_version = ">= 3.0"
|
20
|
+
|
21
|
+
spec.add_dependency "hanami-utils"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple-interactors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gonzalo Rodríguez-Baltanás Díaz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hanami-utils
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Interactors for Ruby on Rails applications
|
70
|
+
email:
|
71
|
+
- siotopo@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- lib/hanami/interactor.rb
|
78
|
+
- lib/hanami/utils/basic_object.rb
|
79
|
+
- lib/simple-interactors.rb
|
80
|
+
- lib/simple-interactors/version.rb
|
81
|
+
- simple-interactors.gemspec
|
82
|
+
homepage: https://github.com/Nerian/simple-interactors
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '3.0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.3.11
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Interactors for Ruby on Rails applications
|
105
|
+
test_files: []
|