Created
March 19, 2023 06:47
-
-
Save kkuchta/68ad9cc07639aa7e15aa08011f03382a to your computer and use it in GitHub Desktop.
A definition of a .again method in ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A wrapper class that defines ".again", but passes through all other methods to | |
# the wrapped class. | |
class AgainWrapper < BasicObject | |
def initialize(wrapped_object, again_method) | |
@again_method = again_method | |
@wrapped_object = wrapped_object | |
end | |
def again | |
@wrapped_object.send(@again_method) | |
end | |
def method_missing(method_name, *args, **kwargs, &block) | |
@wrapped_object.send(method_name, *args, **kwargs, &block) | |
end | |
def respond_to_missing?(method_name, include_all) | |
@wrapped_object.respond_to?(method_name, include_all) | |
end | |
end | |
# An example custom class to to test this all out on | |
class Example | |
attr_accessor :foo | |
def initialize(foo) | |
@foo = foo | |
end | |
def increment | |
@foo += 1 | |
return self | |
end | |
end | |
# An example method on a core class | |
class String | |
def indent | |
return " " + self | |
end | |
end | |
# Go through every currently-defined class (by recursing through subclasses of | |
# Object). | |
def walk_subclasses(klass, &block) | |
block[klass] | |
klass.subclasses.each do | |
walk_subclasses(_1, &block) | |
end | |
end | |
walk_subclasses(Object) do |klass| | |
# Don't override methods on these classes to avoid stack overflows | |
next if AgainWrapper == klass || klass <= IO | |
# For each method on this class, wrap that method so that it calls the | |
# original method, but wraps the return value in AgainWrapper. | |
klass.instance_methods(false).each do |method_name| | |
# Skip a few methods that were problematic to override | |
if method_name == :method_missing || method_name == :respond_to_missing? || method_name == :again || method_name == :respond_to? || method_name == :exception || method_name == :to_s | |
next | |
end | |
alias_name = "__aliased__#{method_name}" | |
old_method = klass.alias_method(alias_name, method_name) | |
klass.define_method(method_name) do |*args, **kwargs, &block| | |
normal_return_value = self.send(alias_name, *args, **kwargs, &block) | |
# Don't wrap the results of methods returning true or false - you'll end | |
# up potentially returning a truthy value (an AgainWrapper instance) | |
# instead of a falsey one (false). | |
if normal_return_value == false || normal_return_value == true | |
normal_return_value | |
else | |
AgainWrapper.new(normal_return_value, method_name) | |
end | |
end | |
end | |
end | |
example = Example.new(1) | |
example.increment.again.again | |
puts example.foo # 4 | |
result = "somestring".indent.again.again.again | |
puts result # " somestring" | |
mixed_objects_result = 123.to_s.indent.again.again.again | |
puts mixed_objects_result # " 123" | |
core_method_result = 123.succ.again.again | |
puts core_method_result # 126 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment