interactify 0.3.0.pre.alpha.1 → 0.4.0

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.ruby-version +1 -0
  4. data/Appraisals +23 -0
  5. data/CHANGELOG.md +14 -0
  6. data/README.md +33 -44
  7. data/gemfiles/.bundle/config +2 -0
  8. data/gemfiles/no_railties_no_sidekiq.gemfile +18 -0
  9. data/gemfiles/no_railties_no_sidekiq.gemfile.lock +127 -0
  10. data/gemfiles/railties_6.gemfile +14 -0
  11. data/gemfiles/railties_6.gemfile.lock +253 -0
  12. data/gemfiles/railties_6_no_sidekiq.gemfile +19 -0
  13. data/gemfiles/railties_6_no_sidekiq.gemfile.lock +159 -0
  14. data/gemfiles/railties_6_sidekiq.gemfile +20 -0
  15. data/gemfiles/railties_6_sidekiq.gemfile.lock +168 -0
  16. data/gemfiles/railties_7_no_sidekiq.gemfile +19 -0
  17. data/gemfiles/railties_7_no_sidekiq.gemfile.lock +158 -0
  18. data/gemfiles/railties_7_sidekiq.gemfile +20 -0
  19. data/gemfiles/railties_7_sidekiq.gemfile.lock +167 -0
  20. data/lib/interactify/async/job_klass.rb +63 -0
  21. data/lib/interactify/async/job_maker.rb +58 -0
  22. data/lib/interactify/async/jobable.rb +96 -0
  23. data/lib/interactify/async/null_job.rb +23 -0
  24. data/lib/interactify/configuration.rb +15 -0
  25. data/lib/interactify/contracts/call_wrapper.rb +19 -0
  26. data/lib/interactify/contracts/failure.rb +8 -0
  27. data/lib/interactify/contracts/helpers.rb +81 -0
  28. data/lib/interactify/contracts/mismatching_promise_error.rb +19 -0
  29. data/lib/interactify/contracts/promising.rb +36 -0
  30. data/lib/interactify/contracts/setup.rb +39 -0
  31. data/lib/interactify/dsl/each_chain.rb +90 -0
  32. data/lib/interactify/dsl/if_interactor.rb +81 -0
  33. data/lib/interactify/dsl/if_klass.rb +82 -0
  34. data/lib/interactify/dsl/organizer.rb +32 -0
  35. data/lib/interactify/dsl/unique_klass_name.rb +23 -0
  36. data/lib/interactify/dsl/wrapper.rb +74 -0
  37. data/lib/interactify/dsl.rb +12 -6
  38. data/lib/interactify/rspec_matchers/matchers.rb +68 -0
  39. data/lib/interactify/version.rb +1 -1
  40. data/lib/interactify/{interactor_wiring → wiring}/callable_representation.rb +2 -2
  41. data/lib/interactify/{interactor_wiring → wiring}/constants.rb +4 -4
  42. data/lib/interactify/{interactor_wiring → wiring}/error_context.rb +1 -1
  43. data/lib/interactify/{interactor_wiring → wiring}/files.rb +1 -1
  44. data/lib/interactify/{interactor_wiring.rb → wiring.rb} +5 -5
  45. data/lib/interactify.rb +58 -38
  46. metadata +48 -71
  47. data/lib/interactify/async_job_klass.rb +0 -61
  48. data/lib/interactify/call_wrapper.rb +0 -17
  49. data/lib/interactify/contract_failure.rb +0 -6
  50. data/lib/interactify/contract_helpers.rb +0 -71
  51. data/lib/interactify/each_chain.rb +0 -88
  52. data/lib/interactify/if_interactor.rb +0 -70
  53. data/lib/interactify/interactor_wrapper.rb +0 -72
  54. data/lib/interactify/job_maker.rb +0 -56
  55. data/lib/interactify/jobable.rb +0 -92
  56. data/lib/interactify/mismatching_promise_error.rb +0 -17
  57. data/lib/interactify/organizer.rb +0 -30
  58. data/lib/interactify/promising.rb +0 -34
  59. data/lib/interactify/rspec/matchers.rb +0 -67
  60. data/lib/interactify/unique_klass_name.rb +0 -21
@@ -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