interactify 0.3.0.pre.alpha.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.ruby-version +1 -0
- data/Appraisals +23 -0
- data/CHANGELOG.md +14 -0
- data/README.md +33 -44
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/no_railties_no_sidekiq.gemfile +18 -0
- data/gemfiles/no_railties_no_sidekiq.gemfile.lock +127 -0
- data/gemfiles/railties_6.gemfile +14 -0
- data/gemfiles/railties_6.gemfile.lock +253 -0
- data/gemfiles/railties_6_no_sidekiq.gemfile +19 -0
- data/gemfiles/railties_6_no_sidekiq.gemfile.lock +159 -0
- data/gemfiles/railties_6_sidekiq.gemfile +20 -0
- data/gemfiles/railties_6_sidekiq.gemfile.lock +168 -0
- data/gemfiles/railties_7_no_sidekiq.gemfile +19 -0
- data/gemfiles/railties_7_no_sidekiq.gemfile.lock +158 -0
- data/gemfiles/railties_7_sidekiq.gemfile +20 -0
- data/gemfiles/railties_7_sidekiq.gemfile.lock +167 -0
- data/lib/interactify/async/job_klass.rb +63 -0
- data/lib/interactify/async/job_maker.rb +58 -0
- data/lib/interactify/async/jobable.rb +96 -0
- data/lib/interactify/async/null_job.rb +23 -0
- data/lib/interactify/configuration.rb +15 -0
- data/lib/interactify/contracts/call_wrapper.rb +19 -0
- data/lib/interactify/contracts/failure.rb +8 -0
- data/lib/interactify/contracts/helpers.rb +81 -0
- data/lib/interactify/contracts/mismatching_promise_error.rb +19 -0
- data/lib/interactify/contracts/promising.rb +36 -0
- data/lib/interactify/contracts/setup.rb +39 -0
- data/lib/interactify/dsl/each_chain.rb +90 -0
- data/lib/interactify/dsl/if_interactor.rb +81 -0
- data/lib/interactify/dsl/if_klass.rb +82 -0
- data/lib/interactify/dsl/organizer.rb +32 -0
- data/lib/interactify/dsl/unique_klass_name.rb +23 -0
- data/lib/interactify/dsl/wrapper.rb +74 -0
- data/lib/interactify/dsl.rb +12 -6
- data/lib/interactify/rspec_matchers/matchers.rb +68 -0
- data/lib/interactify/version.rb +1 -1
- data/lib/interactify/{interactor_wiring → wiring}/callable_representation.rb +2 -2
- data/lib/interactify/{interactor_wiring → wiring}/constants.rb +4 -4
- data/lib/interactify/{interactor_wiring → wiring}/error_context.rb +1 -1
- data/lib/interactify/{interactor_wiring → wiring}/files.rb +1 -1
- data/lib/interactify/{interactor_wiring.rb → wiring.rb} +5 -5
- data/lib/interactify.rb +58 -38
- metadata +48 -71
- data/lib/interactify/async_job_klass.rb +0 -61
- data/lib/interactify/call_wrapper.rb +0 -17
- data/lib/interactify/contract_failure.rb +0 -6
- data/lib/interactify/contract_helpers.rb +0 -71
- data/lib/interactify/each_chain.rb +0 -88
- data/lib/interactify/if_interactor.rb +0 -70
- data/lib/interactify/interactor_wrapper.rb +0 -72
- data/lib/interactify/job_maker.rb +0 -56
- data/lib/interactify/jobable.rb +0 -92
- data/lib/interactify/mismatching_promise_error.rb +0 -17
- data/lib/interactify/organizer.rb +0 -30
- data/lib/interactify/promising.rb +0 -34
- data/lib/interactify/rspec/matchers.rb +0 -67
- data/lib/interactify/unique_klass_name.rb +0 -21
data/lib/interactify/jobable.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "interactify/job_maker"
|
4
|
-
|
5
|
-
module Interactify
|
6
|
-
module Jobable
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
# e.g. if Klass < Base
|
10
|
-
# and Base has a Base::Job class
|
11
|
-
#
|
12
|
-
# then let's make sure to define Klass::Job separately
|
13
|
-
included do |base|
|
14
|
-
def base.inherited(klass)
|
15
|
-
super_klass = klass.superclass
|
16
|
-
super_job = super_klass::Job # really spiffing
|
17
|
-
|
18
|
-
opts = super_job::JOBABLE_OPTS
|
19
|
-
jobable_method_name = super_job::JOBABLE_METHOD_NAME
|
20
|
-
|
21
|
-
to_call = defined?(super_klass::Async) ? :interactor_job : :job_calling
|
22
|
-
|
23
|
-
klass.send(to_call, opts:, method_name: jobable_method_name)
|
24
|
-
super(klass)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class_methods do
|
29
|
-
# create a Job class and an Async class
|
30
|
-
# see job_calling for details on the Job class
|
31
|
-
#
|
32
|
-
# the Async class is a wrapper around the Job class
|
33
|
-
# that allows it to be used in an interactor chain
|
34
|
-
#
|
35
|
-
# E.g.
|
36
|
-
#
|
37
|
-
# class ExampleInteractor
|
38
|
-
# include Interactify
|
39
|
-
# expect :foo
|
40
|
-
#
|
41
|
-
# include Jobable
|
42
|
-
# interactor_job
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# doing the following will immediately enqueue a job
|
46
|
-
# that calls the interactor ExampleInteractor with (foo: 'bar')
|
47
|
-
# ExampleInteractor::Async.call(foo: 'bar')
|
48
|
-
#
|
49
|
-
# it will also ensure to pluck only the expects from the context
|
50
|
-
# so that you can have other non primitive values in the context
|
51
|
-
# but the job will only have the expects passed to it
|
52
|
-
#
|
53
|
-
# obviously you will need to be aware that later interactors
|
54
|
-
# in an interactor chain cannot depend on the result of the async
|
55
|
-
# interactor
|
56
|
-
def interactor_job(method_name: :call!, opts: {}, klass_suffix: "")
|
57
|
-
job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
|
58
|
-
# with WhateverInteractor::Job you can perform the interactor as a job
|
59
|
-
# from sidekiq
|
60
|
-
# e.g. WhateverInteractor::Job.perform_async(...)
|
61
|
-
const_set("Job#{klass_suffix}", job_maker.job_klass)
|
62
|
-
|
63
|
-
# with WhateverInteractor::Async you can call WhateverInteractor::Job
|
64
|
-
# in an organizer oro on its oen using normal interactor call call! semantics
|
65
|
-
# e.g. WhateverInteractor::Async.call(...)
|
66
|
-
# WhateverInteractor::Async.call!(...)
|
67
|
-
const_set("Async#{klass_suffix}", job_maker.async_job_klass)
|
68
|
-
end
|
69
|
-
|
70
|
-
# if this was defined in ExampleClass this creates the following class
|
71
|
-
# ExampleClass::Job
|
72
|
-
# this class ia added as a convenience so you can easily turn a
|
73
|
-
# class method into a job
|
74
|
-
#
|
75
|
-
# Example:
|
76
|
-
#
|
77
|
-
# class ExampleClass
|
78
|
-
# include Jobable
|
79
|
-
# job_calling method_name: :some_method
|
80
|
-
# end
|
81
|
-
#
|
82
|
-
# # the following class is created that you can use to enqueue a job
|
83
|
-
# in the sidekiq yaml file
|
84
|
-
# ExampleClass::Job.some_method
|
85
|
-
def job_calling(method_name:, opts: {}, klass_suffix: "")
|
86
|
-
job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
|
87
|
-
|
88
|
-
const_set("Job#{klass_suffix}", job_maker.job_klass)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "interactify/contract_failure"
|
4
|
-
|
5
|
-
module Interactify
|
6
|
-
class MismatchingPromiseError < ContractFailure
|
7
|
-
def initialize(interactor, promising, promised_keys)
|
8
|
-
super <<~MESSAGE.chomp
|
9
|
-
#{interactor} does not promise:
|
10
|
-
#{promising.inspect}
|
11
|
-
|
12
|
-
Actual promises are:
|
13
|
-
#{promised_keys.inspect}
|
14
|
-
MESSAGE
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "interactify/interactor_wrapper"
|
4
|
-
|
5
|
-
module Interactify
|
6
|
-
module Organizer
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
class_methods do
|
10
|
-
def organize(*interactors)
|
11
|
-
wrapped = InteractorWrapper.wrap_many(self, interactors)
|
12
|
-
|
13
|
-
super(*wrapped)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def call
|
18
|
-
self.class.organized.each do |interactor|
|
19
|
-
instance = interactor.new(context)
|
20
|
-
|
21
|
-
instance.instance_variable_set(
|
22
|
-
:@_interactor_called_by_non_bang_method,
|
23
|
-
@_interactor_called_by_non_bang_method
|
24
|
-
)
|
25
|
-
|
26
|
-
instance.tap(&:run!)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "interactify/mismatching_promise_error"
|
4
|
-
|
5
|
-
module Interactify
|
6
|
-
class Promising
|
7
|
-
attr_reader :interactor, :promising
|
8
|
-
|
9
|
-
def self.validate(interactor, *promising)
|
10
|
-
new(interactor, *promising).validate
|
11
|
-
|
12
|
-
interactor
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(interactor, *promising)
|
16
|
-
@interactor = interactor
|
17
|
-
@promising = format_keys promising
|
18
|
-
end
|
19
|
-
|
20
|
-
def validate
|
21
|
-
return if promising == promised_keys
|
22
|
-
|
23
|
-
raise MismatchingPromiseError.new(interactor, promising, promised_keys)
|
24
|
-
end
|
25
|
-
|
26
|
-
def promised_keys
|
27
|
-
format_keys interactor.promised_keys
|
28
|
-
end
|
29
|
-
|
30
|
-
def format_keys(keys)
|
31
|
-
Array(keys).compact.map(&:to_sym).sort
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "interactify/interactor_wiring"
|
4
|
-
|
5
|
-
# Custom matcher that implements expect_inputs
|
6
|
-
# e.g.
|
7
|
-
# expect(described_class).to expect_inputs(:connection, :order)
|
8
|
-
|
9
|
-
RSpec::Matchers.define :expect_inputs do |*expected_inputs|
|
10
|
-
match do |actual|
|
11
|
-
next false unless actual.respond_to?(:expected_keys)
|
12
|
-
|
13
|
-
actual_inputs = Array(actual.expected_keys)
|
14
|
-
@missing_inputs = expected_inputs - actual_inputs
|
15
|
-
@extra_inputs = actual_inputs - expected_inputs
|
16
|
-
|
17
|
-
@missing_inputs.empty? && @extra_inputs.empty?
|
18
|
-
end
|
19
|
-
|
20
|
-
failure_message do |actual|
|
21
|
-
message = "expected #{actual} to expect inputs #{expected_inputs.inspect}"
|
22
|
-
message += "\n\tmissing inputs: #{@missing_inputs}" if @missing_inputs
|
23
|
-
message += "\n\textra inputs: #{@extra_inputs}" if @extra_inputs
|
24
|
-
message
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Custom matcher that implements promise_outputs
|
29
|
-
# e.g. expect(described_class).to promise_outputs(:request_logger)
|
30
|
-
RSpec::Matchers.define :promise_outputs do |*expected_outputs|
|
31
|
-
match do |actual|
|
32
|
-
next false unless actual.respond_to?(:promised_keys)
|
33
|
-
|
34
|
-
actual_outputs = Array(actual.promised_keys)
|
35
|
-
@missing_outputs = expected_outputs - actual_outputs
|
36
|
-
@extra_outputs = actual_outputs - expected_outputs
|
37
|
-
|
38
|
-
@missing_outputs.empty? && @extra_outputs.empty?
|
39
|
-
end
|
40
|
-
|
41
|
-
failure_message do |actual|
|
42
|
-
message = "expected #{actual} to promise outputs #{expected_outputs.inspect}"
|
43
|
-
message += "\n\tmissing outputs: #{@missing_outputs}" if @missing_outputs
|
44
|
-
message += "\n\textra outputs: #{@extra_outputs}" if @extra_outputs
|
45
|
-
message
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Custom matcher that implements organize_interactors
|
50
|
-
#
|
51
|
-
# e.g. expect(described_class).to organize_interactors(SeparateIntoPackages, SendPackagesToSeko)
|
52
|
-
RSpec::Matchers.define :organize_interactors do |*expected_interactors|
|
53
|
-
match do |actual|
|
54
|
-
actual_interactors = actual.organized
|
55
|
-
@missing_interactors = expected_interactors - actual_interactors
|
56
|
-
@extra_interactors = actual_interactors - expected_interactors
|
57
|
-
|
58
|
-
@missing_interactors.empty? && @extra_interactors.empty?
|
59
|
-
end
|
60
|
-
|
61
|
-
failure_message do |actual|
|
62
|
-
message = "expected #{actual} to organize interactors #{expected_interactors.inspect}"
|
63
|
-
message += "\n\tmissing interactors: #{@missing_interactors}" if @missing_interactors
|
64
|
-
message += "\n\textra interactors: #{@extra_interactors}" if @extra_interactors
|
65
|
-
message
|
66
|
-
end
|
67
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Interactify
|
4
|
-
module UniqueKlassName
|
5
|
-
def self.for(namespace, prefix)
|
6
|
-
id = generate_unique_id
|
7
|
-
klass_name = :"#{prefix}#{id}"
|
8
|
-
|
9
|
-
while namespace.const_defined?(klass_name)
|
10
|
-
id = generate_unique_id
|
11
|
-
klass_name = :"#{prefix}#{id}"
|
12
|
-
end
|
13
|
-
|
14
|
-
klass_name.to_sym
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.generate_unique_id
|
18
|
-
rand(10_000)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|