Skip to content

Commit

Permalink
Back out the concept of magically assigning positional call arguments
Browse files Browse the repository at this point in the history
There were edge cases that weren't yet covered by the specs that would
fail for combinations of positional and keyword arguments depending on
what's available in the context. For example:

    class MyInteractor
      include Interactor

      def call(a, b: "bears")
        context.output = [a, b]
      end
    end

    MyInteractor.call(b: "beets").output # => [{ b: "beets" }, "bears"]

Plus, this simplifies the interface by giving the developer one choice
rather than multiple competing choices that achieve the same thing.
  • Loading branch information
laserlemon authored and taylorthurlow committed Jul 12, 2020
1 parent 2d9d0ed commit c36f52b
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 164 deletions.
18 changes: 8 additions & 10 deletions lib/interactor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,24 +166,22 @@ def rollback

private

# Internal: Determine what arguments (if any) should be passed to the "call"
# instance method when invoking an Interactor. The "call" instance method may
# accept any combination of positional and keyword arguments. This method
# will extract values from the context in order to populate those arguments
# based on their names.
# Internal: Determine what keyword arguments (if any) should be passed to the
# "call" instance method when invoking an Interactor. The "call" instance
# method may accept any number of keyword arguments. This method will extract
# values from the context in order to populate those arguments based on their
# names.
#
# Returns an Array of arguments to be applied as an argument list.
def arguments_for_call # rubocop:disable Metrics/MethodLength
def arguments_for_call
positional_arguments = []
keyword_arguments = {}

method(:call).parameters.each do |(type, name)|
next unless type == :keyreq || type == :key
next unless context.include?(name)

case type
when :req, :opt then positional_arguments << context[name]
when :keyreq, :key then keyword_arguments[name] = context[name]
end
keyword_arguments[name] = context[name]
end

positional_arguments << keyword_arguments if keyword_arguments.any?
Expand Down
160 changes: 6 additions & 154 deletions spec/interactor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,6 @@
describe "#call" do
let(:interactor) { Class.new.send(:include, described_class) }

context "positional arguments" do
it "accepts required positional arguments" do
interactor.class_eval do
def call(foo)
context.output = foo
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq("baz")
end

it "accepts optional positional arguments" do
interactor.class_eval do
def call(foo = "bar")
context.output = foo
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq("baz")
end

it "assigns absent positional arguments" do
interactor.class_eval do
def call(foo = "bar")
context.output = foo
end
end

result = interactor.call(hello: "world")

expect(result.output).to eq("bar")
end

it "raises an error for missing positional arguments" do
interactor.class_eval do
def call(foo)
context.output = foo
end
end

expect { interactor.call(hello: "world") }.to raise_error(ArgumentError)
end
end

context "keyword arguments" do
it "accepts required keyword arguments" do
interactor.class_eval do
Expand All @@ -60,9 +12,9 @@ def call(foo:)
end
end

result = interactor.call(foo: "baz", hello: "world")
result = interactor.call(foo: "bar", hello: "world")

expect(result.output).to eq("baz")
expect(result.output).to eq("bar")
end

it "accepts optional keyword arguments" do
Expand Down Expand Up @@ -98,115 +50,15 @@ def call(foo:)

expect { interactor.call(hello: "world") }.to raise_error(ArgumentError)
end
end

context "combination arguments" do
it "accepts required positional with required keyword arguments" do
interactor.class_eval do
def call(foo, hello:)
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq(["baz", "world"])
end

it "accepts required positional with optional keyword arguments" do
interactor.class_eval do
def call(foo, hello: "there")
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq(["baz", "world"])
end

it "accepts required positional and assigns absent keyword arguments" do
interactor.class_eval do
def call(foo, hello: "there")
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz")

expect(result.output).to eq(["baz", "there"])
end

it "accepts optional positional with required keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello:)
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq(["baz", "world"])
end

it "accepts optional positional with optional keyword arguments" do
it "raises an error for call definitions with non-keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello: "there")
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz", hello: "world")

expect(result.output).to eq(["baz", "world"])
end

it "accepts optional positional and assigns absent keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello: "there")
context.output = [foo, hello]
end
end

result = interactor.call(foo: "baz")

expect(result.output).to eq(["baz", "there"])
end

it "assigns absent positional and accepts required keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello:)
context.output = [foo, hello]
end
end

result = interactor.call(hello: "world")

expect(result.output).to eq(["bar", "world"])
end

it "assigns absent positional and accepts optional keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello: "there")
context.output = [foo, hello]
end
end

result = interactor.call(hello: "world")

expect(result.output).to eq(["bar", "world"])
end

it "assigns absent positional and absent keyword arguments" do
interactor.class_eval do
def call(foo = "bar", hello: "there")
context.output = [foo, hello]
def call(foo)
context.output = foo
end
end

result = interactor.call

expect(result.output).to eq(["bar", "there"])
expect { interactor.call(foo: "bar") }.to raise_error(ArgumentError)
end
end
end
Expand Down

0 comments on commit c36f52b

Please sign in to comment.