Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Optional Context Contract / Verification capability #205

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.6', '2.7', '3.0']
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake
- name: Code Climate Coverage Action
uses: paambaati/[email protected]
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,48 @@ interactor needs to do its work.

When an interactor does its single purpose, it affects its given context.

#### Declaring required keys in the context

If you want to optionally declare what data an interactor requires to
function correctly, you can add the `ContextValidation` module to
your interactor and declare any required items.

```ruby
include ContextValidation

needs_context :user, :new_password
```

If any of the items from the `needs_context` declaration are missing, an
error is raised.

```ruby
class UpdateUser
include Interactor
include ContextValidation

needs_context :user, :new_password
end
```

```ruby
result = UpdateUser.call(user: user, new_password: 'newpasswordstring')
result.success? #=> true
```

```ruby
result = UpdateUser.call(user: user)
#<RuntimeError: Missing context: new_password in #<UpdateUser>>
```

Passing `nil` or `''` for the value of a required context will not raise
an error, to allow for setting items to those values.

```ruby
result = UpdateUser.call(user: user, new_password: nil)
result.success? #=> true
```

#### Adding to the Context

As an interactor runs it can add information to the context.
Expand Down
1 change: 1 addition & 0 deletions lib/interactor.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "interactor/context"
require "interactor/context_validation"
require "interactor/error"
require "interactor/hooks"
require "interactor/organizer"
Expand Down
30 changes: 30 additions & 0 deletions lib/interactor/context_validation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Interactor
module ContextValidation
def self.included(base)
base.extend(self)
end

# Override Interactor before hooks to ensure
# that the needs_context before hook is
# executed last. This will allow us to
# set required context keys in an interactor
# before hook without raising a needs_context
# error.
def before(*hooks, &block)
before_hooks.unshift block if block
hooks.each { |h| before_hooks.unshift h }
end

def needs_context(*args)
before_hooks.push -> {
missing_context = args - context.to_h.keys.map(&:to_sym)
missing_keys = missing_context.reduce([]) do |reduced, key|
reduced << key
reduced
end

raise "Missing context: #{missing_keys.join(', ')} in #{self}" if missing_keys.any?
}
end
end
end
52 changes: 52 additions & 0 deletions spec/interactor/context_validation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Interactor
describe ContextValidation do
describe "when context keys are missing" do
subject(:interactor) do
module Test
class SomeInteractor
include Interactor
include ContextValidation

needs_context :a, :b

def call
end
end
end

Test::SomeInteractor
end

it "raises an error" do
expect { interactor.call({}) }.to raise_error(/Missing context: a, b/)
end
end

context "when missing context keys are set in a before hook" do
subject(:interactor) do
module Test
class InteractorWithContextInBeforeHook
include Interactor
include ContextValidation

needs_context :a, :b

before do
context.a = 'a'
context.b = 'b'
end

def call
end
end
end

Test::InteractorWithContextInBeforeHook
end

it "does not raise an error" do
expect { interactor.call({}) }.not_to raise_error
end
end
end
end