I recently ran into an issue where calling load
twice on a Ruby class caused bugs (here's a real-world example). This is because there were stateful method invocations taking place in the class body, and load
was causing these to be executed twice. A simple example is as follows:
base.rb
:
class Base
def foo
puts "BASE"
end
end
derived.rb
:
require "./base"
class Derived < Base
alias_method :foo_aliased, :foo
def foo
puts "DERIVED!"
end
end
Execution from a REPL:
$ load './derived.rb'
> true
$ Derived.new.foo
> DERIVED!
> nil
$ Derived.new.foo_aliased
> BASE
> nil
$ load './derived.rb'
> true
$ Derived.new.foo
> DERIVED!
> nil
$ Derived.new.foo_aliased
> DERIVED!
> nil
In this example, the second load
causes alias_method
to clobber the original alias. As a result, we've broken any code that depends on having an intact alias to the original method.
Brute-force class reloading is common enough (e.g., you see it a lot in the each_run
clause for RSpec configurations using Spork) that it's not always easy to forbid the use of load
outright. As such, it seems like the only way to prevent bugs is to ensure that class definitions are "idempotent". In other words, you have make sure that methods intended to be called from a class definition should produce the same result no matter how many times you call them. The fact that re-loads don't break more code seems to suggest an unspoken convention.
Is there some style guide for Ruby class design that mandates this? If so, what are the common techniques for enforcing these semantics? For some methods, you could just do some memoization to prevent re-execution of the same method with the same arguments, but other things like alias_method
seem harder to work around.