From 82170ff5fb9417277dec4e4f82c960e6bccbe65b Mon Sep 17 00:00:00 2001 From: Ufuk Kayserilioglu Date: Tue, 25 Feb 2020 22:52:04 +0200 Subject: [PATCH 1/2] Optional `sorbet-runtime` support for interface validation Methods that have Sorbet signatures are wrapped by `sorbet-runtime` so that they can be type-checked for correct params/return-value at runtime. However, that wrapper does not and cannot relay the whole information about the original method like `arity` or `parameters`. (Ref: https://github.com/sorbet/sorbet/issues/2643) The workaround is to access the original method from the signature if we detect Sorbet is activated and the method in question has a signature. This commit abstracts the method parameter extraction into a separate method in order to carry out that workaround. --- lib/job-iteration/iteration.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/job-iteration/iteration.rb b/lib/job-iteration/iteration.rb index 83862da3..b53fc749 100644 --- a/lib/job-iteration/iteration.rb +++ b/lib/job-iteration/iteration.rb @@ -176,7 +176,7 @@ def assert_implements_methods! end if respond_to?(:build_enumerator, true) - parameters = method(:build_enumerator).parameters + parameters = method_parameters(:build_enumerator) unless valid_cursor_parameter?(parameters) raise ArgumentError, "Iteration job (#{self.class}) #build_enumerator " \ "expects the keyword argument `cursor`" @@ -187,6 +187,17 @@ def assert_implements_methods! end end + def method_parameters(method_name) + method = method(method_name) + + if defined?(T::Private::Methods) + signature = T::Private::Methods.signature_for_method(method) + method = signature.method if signature + end + + method.parameters + end + def iteration_instrumentation_tags { job_class: self.class.name } end From e92333146fc10072dcd0d2cbaf033a2f8ae813de Mon Sep 17 00:00:00 2001 From: Ufuk Kayserilioglu Date: Tue, 25 Feb 2020 22:56:40 +0200 Subject: [PATCH 2/2] Add test case for `sorbet-runtime` support --- Gemfile | 3 +++ test/unit/iteration_test.rb | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Gemfile b/Gemfile index b675eef4..0dd4c42c 100644 --- a/Gemfile +++ b/Gemfile @@ -23,3 +23,6 @@ gem 'mocha' gem 'rubocop', '~> 0.77.0' gem 'yard' gem 'rake' + +# for unit testing optional sorbet support +gem 'sorbet-runtime' diff --git a/test/unit/iteration_test.rb b/test/unit/iteration_test.rb index 3a810c57..7668a3a5 100644 --- a/test/unit/iteration_test.rb +++ b/test/unit/iteration_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "test_helper" +require "sorbet-runtime" class JobIterationTest < IterationUnitTest class JobWithNoMethods < ActiveJob::Base @@ -17,6 +18,20 @@ def each_iteration(*) end end + class JobWithRightMethodsButWithSorbetSignatures < ActiveJob::Base + extend T::Sig + include JobIteration::Iteration + + sig { params(_params: T.untyped, cursor: T.untyped).returns(T::Enumerator[T.untyped]) } + def build_enumerator(_params, cursor:) + enumerator_builder.build_times_enumerator(2, cursor: cursor) + end + + sig { params(product: T.untyped, params: T.untyped).void } + def each_iteration(product, params) + end + end + class JobWithRightMethodsButMissingCursorKeywordArgument < ActiveJob::Base include JobIteration::Iteration def build_enumerator(params, cursor) @@ -53,6 +68,11 @@ def test_jobs_that_define_build_enumerator_and_each_iteration_will_not_raise work_one_job end + def test_jobs_that_define_build_enumerator_and_each_iteration_with_sigs_will_not_raise + push(JobWithRightMethodsButWithSorbetSignatures, 'walrus' => 'best') + work_one_job + end + def test_jobs_that_pass_splat_argument_to_build_enumerator_will_not_raise push(JobWithRightMethodsUsingSplatInTheArguments, {}) work_one_job