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 support for inheriting hooks #191

Open
wants to merge 3 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
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,62 @@ end
interactor should have a single purpose, there should be no need to clean up
after any failed interactor.

## Interactor Inheritance

Interactors can inherit from other interactors. Subclasses will inherit hooks declared in ancestors:

```ruby
class ParentInteractor
around do |interactor|
puts "around before ancestor"
interactor.call
puts "around after ancestor"
end

before do
puts "before ancestor"
end

after do
puts "after ancestor"
end
end

class ChildInteractor < ParentInteractor
around do |interactor|
puts "around before child"
interactor.call
puts "around after child"
end

before do
puts "before child"
end

after do
puts "after child"
end

def call
puts "called"
end
end
```

Calling the child interactor will output:

```
around before child
around before parent
before child
before parent
called
after parent
after child
around after parent
around after child
```

## Testing Interactors

When written correctly, an interactor is easy to test because it only *does* one
Expand Down
85 changes: 78 additions & 7 deletions lib/interactor/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module ClassMethods
# Returns nothing.
def around(*hooks, &block)
hooks << block if block
hooks.each { |hook| around_hooks.push(hook) }
hooks.each { |hook| internal_around_hooks.push(hook) }
end

# Public: Declare hooks to run before Interactor invocation. The before
Expand Down Expand Up @@ -87,7 +87,7 @@ def around(*hooks, &block)
# Returns nothing.
def before(*hooks, &block)
hooks << block if block
hooks.each { |hook| before_hooks.push(hook) }
hooks.each { |hook| internal_before_hooks.push(hook) }
end

# Public: Declare hooks to run after Interactor invocation. The after
Expand Down Expand Up @@ -124,11 +124,12 @@ def before(*hooks, &block)
# Returns nothing.
def after(*hooks, &block)
hooks << block if block
hooks.each { |hook| after_hooks.unshift(hook) }
hooks.each { |hook| internal_after_hooks.unshift(hook) }
end

# Internal: An Array of declared hooks to run around Interactor
# invocation. The hooks appear in the order in which they will be run.
# Includes hooks declared in ancestors.
#
# Examples
#
Expand All @@ -143,11 +144,12 @@ def after(*hooks, &block)
#
# Returns an Array of Symbols and Procs.
def around_hooks
@around_hooks ||= []
internal_around_hooks + ancestor_hooks(:around_hooks)
end

# Internal: An Array of declared hooks to run before Interactor
# invocation. The hooks appear in the order in which they will be run.
# Includes hooks declared in ancestors.
#
# Examples
#
Expand All @@ -162,11 +164,12 @@ def around_hooks
#
# Returns an Array of Symbols and Procs.
def before_hooks
@before_hooks ||= []
internal_before_hooks + ancestor_hooks(:before_hooks)
end

# Internal: An Array of declared hooks to run before Interactor
# Internal: An Array of declared hooks to run after Interactor
# invocation. The hooks appear in the order in which they will be run.
# Includes hooks declared in ancestors.
#
# Examples
#
Expand All @@ -181,7 +184,75 @@ def before_hooks
#
# Returns an Array of Symbols and Procs.
def after_hooks
@after_hooks ||= []
ancestor_hooks(:after_hooks) + internal_after_hooks
end

private

# Internal: An Array of declared hooks to run around Interactor
# invocation. The hooks appear in the order in which they will be run.
#
# Examples
#
# class MyInteractor
# include Interactor
#
# around :time_execution, :use_transaction
# end
#
# MyInteractor.internal_around_hooks
# # => [:time_execution, :use_transaction]
#
# Returns an Array of Symbols and Procs.
def internal_around_hooks
@internal_around_hooks ||= []
end

# Internal: An Array of declared hooks to run before Interactor
# invocation. The hooks appear in the order in which they will be run.
#
# Examples
#
# class MyInteractor
# include Interactor
#
# before :set_start_time, :say_hello
# end
#
# MyInteractor.internal_before_hooks
# # => [:set_start_time, :say_hello]
#
# Returns an Array of Symbols and Procs.
def internal_before_hooks
@internal_before_hooks ||= []
end

# Internal: An Array of declared hooks to run after Interactor
# invocation. The hooks appear in the order in which they will be run.
#
# Examples
#
# class MyInteractor
# include Interactor
#
# after :set_finish_time, :say_goodbye
# end
#
# MyInteractor.internal_after_hooks
# # => [:say_goodbye, :set_finish_time]
#
# Returns an Array of Symbols and Procs.
def internal_after_hooks
@internal_after_hooks ||= []
end

# Internal: Fetches hooks declared in the ancestor.
#
# name - A Symbol corresponding to the hook method in the ancestor.
#
# Returns an Array of Symbols and Procs.
def ancestor_hooks(hook)
superclass&.respond_to?(hook) ? superclass.send(hook) : []
end
end

Expand Down
Loading