views:

36

answers:

1

Consider this very simple logging class:

class MockLog
  # ...
end

Let's add logging to all our classes:

class Module
  def has_logging()
    class_eval {
      @log = MockLog.new
      def log
        self.class.instance_variable_get :@log
      end
    }
  end
end

Now, why doesn't this work?

class Foo
  has_logging
end
Foo.new.log.nil?   # => false, as expected

class Bar < Foo
end
Bar.new.log.nil?   # => true?! Why wasn't the `log` method inherited?
+1  A: 

The log method was inherited (if it wasn't, you would get a NoMethodError), but your class level instance variable wasn't, so instance_variable_get returned nil. You should declare a regular class variable like:

class Module
  def has_logging()
    class_eval {
      @@log = MockLog.new
      def log
        @@log
      end
    }
  end
end

class Foo
  has_logging
end

class Bar < Foo
end

Now it will be inherited. See Class and Instance Variables In Ruby

Matthew Flaschen
Hmm.. but if the `log` method was inherited, then why does it return true when we ask if it's nil?
Kevin Bannister
I don't think this is a good idea. Now if you want to have a _different_ log in `Bar`, it can't override the `@@log` in `Foo`; they'll _all_ share the same `@@log` instance. I think the behavior he wants is "subclasses of `Foo` will use `@@log` unless overridden, but if they call `has_logging` themselves, then that subclass gets its own `@@log`".
John Feminella
That is, let's say you had `class Quux < Foo; has_logging; end`. I think the OP wants `Quux.new.log != Foo.new.log` to be true.
Kevin Bannister
The continuing saga: Ruby's class variables friend or foe? (See here for some discussion: http://oreilly.com/ruby/excerpts/ruby-best-practices/worst-practices.html)
Telemachus