Box brackets in Ruby class declarations

posted by Ayush Newatia
27 December, 2024



If you’ve ever used Rails, you’ll almost certainly have created a database migration and seen the below syntax in the class declaration:

class CreateUserTable < ActiveRecord::Migration[7.1]
  def change
    # ...
  end
end

I’d never understood how the [7.1] in the superclass definition worked, and never really questioned it to be honest. I thought it was a fancy Ruby language feature that I didn’t need to bother myself with at the moment.

While looking through the code for async-rest, I came across the same syntax.

I’m attempting to build a client for Paddle to use in Scattergun using async-rest, so I needed to understand what that syntax was actually doing.

It’s such a typically elegant mechanism. The box brackets aren’t a language feature, but a class level method which returns a class.

class SomeClass
  def self.[]
    return SomeOtherClass
  end
end

Going back to the Active Record example:

class CreateUserTable < ActiveRecord::Migration[7.1]
  def change
    # ...
  end
end

The above is functionally equivalent to:

class CreateUserTable < ActiveRecord::Migration::Compatibility::V7_1
  def change
    # ...
  end
end

Run ActiveRecord::Migration[7.1] in a Rails console and see that it returns a class. It’s just like calling a method!

Here’s the method definition: https://github.com/rails/rails/blob/af9ecc7a35dcec5c744353d9494d92709f6e1381/activerecord/lib/active_record/migration.rb#L630.

In the case of async-rest, it’s a really elegant way to set the representation format (JSON, form etc.) for a resource. The method definition shows how it injects the wrapper class into a new class definition and then returns that.

That enables us to write code like:

module Paddle
  class PaymentMethod < Async::REST::Representation[Async::REST::Wrapper::JSON]
    # ...
  end
end

It’s makes things so clear to a reader!