Blog

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:

1
2
3
  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:

1
2
3
4
  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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  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:

1
2
  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!

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