Class methods are methods on a object's singleton class. Everyone knows this (1). I think I sort of knew it also, but recently I was working on a thing and this was brought home to me.
And I had a subclass that attempted to call that
before_action class method:
Then filter_decrufter could define a singleton method that would check the
before_action arguments and flag any options for missing actions:
What I was seeing, though, was that
AnotherFakeController would raise an exception when I loaded it and it attempted to call the parent class method as part of the class definition:
But why? The
before_action method is declared right there in
The problem was that the
before_action method that
ActionController::Base defined was living on
ActionController::Base's singleton class. No need to take my word for it though; you can verify this by defining a class method and checking the singleton methods:
So when I defined a singleton method on
ActionController::Base I was not intercepting the method call like I intended. Instead, I was redefining the existing method. And my new method definition called
super, but since I'd redefined the only method with that name in this class's ancestor chain, there was no superclass method by that name available, and so bam, exception.
As a side note,
singleton_methods looks up the inheritance chain, so it's not quite reliable for saying "this method is defined right here":
Back to the original problem - how to solve it? By defining the method not on the singleton class but instead further up the ancestor chain. And how to do that? By defining the method in a module and
extend'ing that module:
I think the lessons learned are the usual ones. Unexpected exceptions are an opportunity for learning something. Don't confuse Java static methods with Ruby's class methods. Verify expected behavior in irb or in a small program. And read books written by people who have poured a lot of time and energy into the topic that's currently giving you trouble!
(2) You could argue that I should just declare a dependency on actionpack and use it. That probably would be better; I might do that.