diff --git a/lib/interactor/context.rb b/lib/interactor/context.rb index 1402e60..742c6c5 100644 --- a/lib/interactor/context.rb +++ b/lib/interactor/context.rb @@ -52,11 +52,18 @@ class Context < OpenStruct # # => 2170969340 # # Returns the Interactor::Context. + # Raises ArgumentError if the argument is not nil, an Interactor::Context + # or an object responding to each_pair (like a Hash). def self.build(context = {}) if self === context context - else + elsif context.nil? || context.respond_to?(:each_pair) new(context) + else + raise ArgumentError, + "Expected a Hash or an object responding to #each_pair, " \ + "got #{context.class}. Did you mean to pass keyword arguments? " \ + "(e.g. MyInteractor.call(foo: \"bar\"))" end end diff --git a/spec/interactor/context_spec.rb b/spec/interactor/context_spec.rb index d990479..c3fd708 100644 --- a/spec/interactor/context_spec.rb +++ b/spec/interactor/context_spec.rb @@ -27,6 +27,31 @@ module Interactor } end + it "builds an empty context if nil is given" do + context = Context.build(nil) + + expect(context).to be_a(Context) + expect(context.send(:table)).to eq({}) + end + + it "accepts any object responding to #each_pair" do + hash_like = double(:hash_like) + allow(hash_like).to receive(:each_pair) do |&block| + {foo: "bar"}.each_pair(&block) + end + + context = Context.build(hash_like) + + expect(context).to be_a(Context) + expect(context.foo).to eq("bar") + end + + it "raises a helpful error when given a non-hash argument" do + expect { + Context.build("baz") + }.to raise_error(ArgumentError, /String/) + end + it "preserves an already built context" do context1 = Context.build(foo: "bar") context2 = Context.build(context1)