2
votes

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.

1

1 Answers

1
votes

You are entirely wrong. Assuming all class methods to be idempotent is neither a sufficient nor a necessary condition for a code to not break when loaded multiple times.

Therefore, it naturally follows that there is no style guide to enforce that.