and .inherited()

In Ruby, the typical way to define a class is using the class keyword:

The class keyword, however, is effectively just syntax sugar for the Class constructor:

Using is occasionally preferable, e.g. when you want an anonymous class which isn’t assigned to a constant:

I like to use this technique for certain metaprogramming tasks, and when testing/spec-ing modules:

There are a few subtle semantic differences between the class keyword and Because the Class constructor uses a block to define the contents of the class, it can reference the surrounding lexical scope:

I’d known about this difference for a long time. The other day I came across another difference between these two methods of class definition which was new to me, and can lead to potentially surprising behavior. It concerns the order of execution and the .inherited() callback.

When a parent class defines a class method called inherited:

And then that class is subclassed:

The order of execution is 1) run the A.inherited callback; then 2) execute the class definition body. You can see this if you run the above two blocks; the output will be:

If, however, we try to inherit from A dynamically, using the Class constructor:

The output is reversed:

I’m not sure if either of these behaviors is right or wrong, per se; but it can catch you by surprise if you are expecting dynamic class definition to have the same order of operations as the keyword form. In particular, it can lead to some confusing side effects when using certain libraries that extend the behavior of Ruby’s classes. For instance, I discovered this difference while working on some code that used the class-inheritable attributes feature of ActiveSupport:

As it turns out, class_inheritable_accessor and its friends work by defining Object.inherited to initialize some variables that ActiveSupport uses for bookkeeping. In the dynamic form of the class definition, the class body would assign values to the inheritable attributes – and then Object.inherited would be called back, re-initializing the variables and wiping out my assigned values. The solution was to separate the class creation and definition into two steps:

So there you go, another, lesser-known semantic distinction between “static” and “dynamic” class definition in Ruby.