interactify 0.3.0.pre.RC1 → 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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/Appraisals +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/README.md +10 -4
  6. data/gemfiles/no_railties_no_sidekiq.gemfile +3 -1
  7. data/gemfiles/no_railties_no_sidekiq.gemfile.lock +1 -1
  8. data/gemfiles/railties_6_no_sidekiq.gemfile +3 -1
  9. data/gemfiles/railties_6_no_sidekiq.gemfile.lock +2 -1
  10. data/gemfiles/railties_6_sidekiq.gemfile +3 -1
  11. data/gemfiles/railties_6_sidekiq.gemfile.lock +2 -1
  12. data/gemfiles/railties_7_no_sidekiq.gemfile +3 -1
  13. data/gemfiles/railties_7_no_sidekiq.gemfile.lock +2 -1
  14. data/gemfiles/railties_7_sidekiq.gemfile +3 -1
  15. data/gemfiles/railties_7_sidekiq.gemfile.lock +2 -1
  16. data/lib/interactify/async/job_klass.rb +63 -0
  17. data/lib/interactify/async/job_maker.rb +58 -0
  18. data/lib/interactify/async/jobable.rb +96 -0
  19. data/lib/interactify/async/null_job.rb +23 -0
  20. data/lib/interactify/configuration.rb +15 -0
  21. data/lib/interactify/contracts/call_wrapper.rb +19 -0
  22. data/lib/interactify/contracts/failure.rb +8 -0
  23. data/lib/interactify/contracts/helpers.rb +81 -0
  24. data/lib/interactify/contracts/mismatching_promise_error.rb +19 -0
  25. data/lib/interactify/contracts/promising.rb +36 -0
  26. data/lib/interactify/contracts/setup.rb +39 -0
  27. data/lib/interactify/dsl/each_chain.rb +90 -0
  28. data/lib/interactify/dsl/if_interactor.rb +81 -0
  29. data/lib/interactify/dsl/if_klass.rb +82 -0
  30. data/lib/interactify/dsl/organizer.rb +32 -0
  31. data/lib/interactify/dsl/unique_klass_name.rb +23 -0
  32. data/lib/interactify/dsl/wrapper.rb +74 -0
  33. data/lib/interactify/dsl.rb +12 -6
  34. data/lib/interactify/rspec_matchers/matchers.rb +68 -0
  35. data/lib/interactify/version.rb +1 -1
  36. data/lib/interactify/{interactor_wiring → wiring}/callable_representation.rb +2 -2
  37. data/lib/interactify/{interactor_wiring → wiring}/constants.rb +1 -1
  38. data/lib/interactify/{interactor_wiring → wiring}/error_context.rb +1 -1
  39. data/lib/interactify/{interactor_wiring → wiring}/files.rb +1 -1
  40. data/lib/interactify/{interactor_wiring.rb → wiring.rb} +4 -4
  41. data/lib/interactify.rb +13 -50
  42. metadata +31 -56
  43. data/lib/interactify/async_job_klass.rb +0 -61
  44. data/lib/interactify/call_wrapper.rb +0 -17
  45. data/lib/interactify/contract_failure.rb +0 -6
  46. data/lib/interactify/contract_helpers.rb +0 -71
  47. data/lib/interactify/each_chain.rb +0 -88
  48. data/lib/interactify/if_interactor.rb +0 -70
  49. data/lib/interactify/interactor_wrapper.rb +0 -72
  50. data/lib/interactify/job_maker.rb +0 -56
  51. data/lib/interactify/jobable.rb +0 -94
  52. data/lib/interactify/mismatching_promise_error.rb +0 -17
  53. data/lib/interactify/null_job.rb +0 -11
  54. data/lib/interactify/organizer.rb +0 -30
  55. data/lib/interactify/promising.rb +0 -34
  56. data/lib/interactify/rspec/matchers.rb +0 -67
  57. data/lib/interactify/unique_klass_name.rb +0 -21
@@ -1,94 +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
- next if Interactify.sidekiq_missing?
15
-
16
- def base.inherited(klass)
17
- super_klass = klass.superclass
18
- super_job = super_klass::Job # really spiffing
19
-
20
- opts = super_job::JOBABLE_OPTS
21
- jobable_method_name = super_job::JOBABLE_METHOD_NAME
22
-
23
- to_call = defined?(super_klass::Async) ? :interactor_job : :job_calling
24
-
25
- klass.send(to_call, opts:, method_name: jobable_method_name)
26
- super(klass)
27
- end
28
- end
29
-
30
- class_methods do
31
- # create a Job class and an Async class
32
- # see job_calling for details on the Job class
33
- #
34
- # the Async class is a wrapper around the Job class
35
- # that allows it to be used in an interactor chain
36
- #
37
- # E.g.
38
- #
39
- # class ExampleInteractor
40
- # include Interactify
41
- # expect :foo
42
- #
43
- # include Jobable
44
- # interactor_job
45
- # end
46
- #
47
- # doing the following will immediately enqueue a job
48
- # that calls the interactor ExampleInteractor with (foo: 'bar')
49
- # ExampleInteractor::Async.call(foo: 'bar')
50
- #
51
- # it will also ensure to pluck only the expects from the context
52
- # so that you can have other non primitive values in the context
53
- # but the job will only have the expects passed to it
54
- #
55
- # obviously you will need to be aware that later interactors
56
- # in an interactor chain cannot depend on the result of the async
57
- # interactor
58
- def interactor_job(method_name: :call!, opts: {}, klass_suffix: "")
59
- job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
60
- # with WhateverInteractor::Job you can perform the interactor as a job
61
- # from sidekiq
62
- # e.g. WhateverInteractor::Job.perform_async(...)
63
- const_set("Job#{klass_suffix}", job_maker.job_klass)
64
-
65
- # with WhateverInteractor::Async you can call WhateverInteractor::Job
66
- # in an organizer oro on its oen using normal interactor call call! semantics
67
- # e.g. WhateverInteractor::Async.call(...)
68
- # WhateverInteractor::Async.call!(...)
69
- const_set("Async#{klass_suffix}", job_maker.async_job_klass)
70
- end
71
-
72
- # if this was defined in ExampleClass this creates the following class
73
- # ExampleClass::Job
74
- # this class ia added as a convenience so you can easily turn a
75
- # class method into a job
76
- #
77
- # Example:
78
- #
79
- # class ExampleClass
80
- # include Jobable
81
- # job_calling method_name: :some_method
82
- # end
83
- #
84
- # # the following class is created that you can use to enqueue a job
85
- # in the sidekiq yaml file
86
- # ExampleClass::Job.some_method
87
- def job_calling(method_name:, opts: {}, klass_suffix: "")
88
- job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
89
-
90
- const_set("Job#{klass_suffix}", job_maker.job_klass)
91
- end
92
- end
93
- end
94
- 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,11 +0,0 @@
1
- module Interactify
2
- class NullJob
3
- def method_missing(...)
4
- self
5
- end
6
-
7
- def self.method_missing(...)
8
- self
9
- end
10
- end
11
- 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