NopeCode

Ruby inherited method bug

This post is about the bug I found when I was writing tests for quiet_assets. I won’t show you all those tests, just a small piece:

Class.new(Rails::Application) do
  routes.append { ... }
end

All of them were passed on my laptop, but Travis-CI showed me the odd message for Ruby 1.8: undefined local variable or method 'routes' for #<Class:0xb6b9a92c>. It says that there’s no such method routes inside dynamically generated class, but it works for Ruby 1.9. What’s wrong with it? Let’s take a look at Rails core. In our example we define dynamic class whose parent is Rails::Application that inherited from class Rails::Engine that inherited from Rails::Railtie. You can find routes definition at line 488 of Rails::Engine. I consider only 3-2-stable branch in my post. It’s defined as an instance method. How can it be possible to use it on the class level? If you take a look at the chain of self.inherited callbacks in all those classes you’ll see that Rails::Railtie has module inclusion:

def inherited(base)
  ...
  base.send(:include, Railtie::Configurable)
end

Railtie::Configurable has method_missing which does exactly our case - proxying our calls to instance. You see that all logic rely on self.inhereted callback. Let’s check it:

class Parent
  def self.inherited(base)
    puts 'Inside inherited'
  end
end

class Child < Parent
  puts 'We are inside class definition'
end

app = Class.new(Parent) do
  puts 'We are inside class definition'
end

If you run this code you’ll see that for Class.new we’ll get this:

We are inside class definition
Inside inherited

Ruby 1.8 cannot find routes, even method_missing just because self.inherited chain couldn’t be invoked inside our block, it would be invoked after class definition. Be careful!

Яндекс.Метрика