Skip to content

Instantly share code, notes, and snippets.

@alisnic
Created June 28, 2017 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alisnic/1ccc6e1357e0365ee4ed8655f2e1d7dd to your computer and use it in GitHub Desktop.
Save alisnic/1ccc6e1357e0365ee4ed8655f2e1d7dd to your computer and use it in GitHub Desktop.
# test.rb
module Guardian
def self.ensure_method_called!(location)
unless caller.any? {|line| line.include?(location)}
raise ArgumentError.new("nice try lol")
end
end
def self.ensure_instance_method_called!(klass, method_name)
file_name, source_lines = instance_method_info(klass, method_name)
is_method_called = caller.any? do |frame|
file, line = frame.split(":")[0..1]
file == file_name && source_lines.include?(line.to_i)
end
raise ArgumentError.new("nice try lol") unless is_method_called
end
def self.instance_method_info(klass, method_name)
methods = klass.instance_methods - Object.methods
# We start by building an array with all the instance methods of the class.
# Each entry would look like this:
#
# {
# name: :other_method,
# file: "/Users/andrei/Play/alisnic.github.com/test.rb",
# start_line: 24
# }
candidates = methods.map do |name|
location = klass.instance_method(name).source_location
{
name: name,
file: location[0],
start_line: location[1]
}
end
# Next, we sort those entries by start line, in reverse order. We will start
# guessing method locations from bottom to up
reversed = candidates.sort_by {|entry| -entry[:start_line] }
# We don't know where is the end of the last method of the class, hence we
# assume it is 99999. Starting from there, each method's last line is the
# line before the previous method start line
with_lines = reversed.reduce([]) do |result, entry|
previous_method = result.last
previous_line = previous_method ? previous_method[:start_line] : 99999
result << entry.merge(lines: entry[:start_line]..previous_line)
end
# Next, we find the method we are interested in
method = with_lines.find {|entry| entry[:name] == method_name }
# And return only the information we need
[method[:file], method[:lines]]
end
end
class Foo
def bar
Guardian.ensure_instance_method_called!(Work, :execute)
"bar"
end
end
class Work
def execute
# calling Foo.new.bar here should work
Foo.new.bar + " from execute"
end
def other_method
# calling Foo.new.bar here should raise error
Foo.new.bar
end
end
puts Work.new.execute
puts Work.new.other_method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment