Skip to content

Commit

Permalink
Allow developers to define #call with arguments for convenience
Browse files Browse the repository at this point in the history
If the "call" instance method accepts arguments, those arguments will be
automatically assigned from the provided context, matching on name. This
works for both positional and keyword arguments. If an argument is
specified but no matching value is provided in the context, an
ArgumentError is raised.
  • Loading branch information
laserlemon committed Mar 31, 2017
1 parent 31efe66 commit 3607df5
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 1 deletion.
30 changes: 29 additions & 1 deletion lib/interactor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def run
# Raises Interactor::Failure if the context is failed.
def run!
with_hooks do
call
call(*arguments_for_call)
context.called!(self)
end
rescue
Expand All @@ -163,4 +163,32 @@ def call
# Returns nothing.
def rollback
end

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.
#
# Returns an Array of arguments to be applied as an argument list.
def arguments_for_call
positional_arguments, keyword_arguments = [], {}
available_context_keys = context.to_h.keys

method(:call).parameters.each do |(type, name)|
next unless available_context_keys.include?(name)

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

positional_arguments << keyword_arguments if keyword_arguments.any?
positional_arguments
end
end
210 changes: 210 additions & 0 deletions spec/interactor_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,213 @@
describe Interactor do
include_examples :lint

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
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 keyword 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 keyword 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 keyword 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 "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
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]
end
end

result = interactor.call

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

0 comments on commit 3607df5

Please sign in to comment.