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

Use prepend instead of alias_method_chain like approach #10

Open
wants to merge 2 commits into
base: main
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
47 changes: 20 additions & 27 deletions lib/captain_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def hook(
)
end

def hooks_module
@hooks_module ||= prepend_hooks_module
end

def prepend_hooks_module
const_set(:CaptainHookDecorator, Module.new).tap do |decorator|
prepend decorator
end
end

####
# Hooks logic part
####
Expand All @@ -65,44 +75,27 @@ def hooks
# Decorator pattern logic part
####
def overriden?(method)
overriden_methods.include? method
hooks_module.methods.include? method
end

def method_excluded_by_all_hooks?(method)
get_hooks(:before).all? { |hook| hook.exclude.include?(method) } &&
get_hooks(:around).all? { |hook| hook.exclude.include?(method) } &&
get_hooks(:after).all? { |hook| hook.exclude.include?(method) }
end

def method_added(method_name)
unless !public_method_defined?(method_name) || overriden?(method_name) ||
method_name.to_s.end_with?("__without_hooks")

decorate_method!(method_name)
end
prepend_method!(method_name) unless !public_method_defined?(method_name) || overriden?(method_name)

super
end

def overriden_methods
@overriden_methods ||= Set.new
end

def mark_as_overriden!(method)
overriden_methods << method
end

# Replaces the method with a decorated version of it
def decorate_method!(method_name)
mark_as_overriden!(method_name)

original_method_name = :"#{method_name}__without_hooks"

def prepend_method!(method_name)
# Skip if the method is excluded by all hooks
return if method_excluded_by_all_hooks?(method_name)

alias_method original_method_name, method_name

define_method(method_name) do |*args, **kwargs|
hooks_module.define_method(method_name) do |*args, **kwargs|
hook_args = args
hook_kwargs = kwargs

Expand All @@ -115,17 +108,17 @@ def decorate_method!(method_name)
# Supporting any kind of method, without arguments, with positional
# or with named parameters. Or any combination of them.
result = if args.empty? && kwargs.empty?
send(original_method_name)
super()
elsif args.empty?
send(original_method_name, **kwargs)
super(**kwargs)
elsif kwargs.empty?
if args.length == 1 && args[0].is_a?(Hash)
send(original_method_name, **args[0])
super(**args[0])
else
send(original_method_name, *args)
super(*args)
end
else
send(original_method_name, *args, **kwargs)
super(*args, **kwargs)
end

# Run after hooks
Expand Down
1 change: 1 addition & 0 deletions spec/captain_hook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def prepare(dto:)
subject.foo(dto: "fooing")

expect_any_instance_of(BeforeAllHook).to receive(:call).once

expect(subject.prepare(dto: "foo")).to eq("child foo")
end
end
Expand Down