Mathish

the two halves of my tasty brain

The Un-Ruby

A follow up to Properties of Code: Functional Complexity is coming. It’s much less “mathy” than its predecessor but serves as a jumping off point for the next in the series. However, there is something that has been nagging at me after watching some of the talks at this year’s RailsConf. It’s a ubiquitous and seemingly trivial thing, but it bothers the hell out of me: ActiveSupport::Concern.

For as long as I’ve been using Rails, a common idiom for plugins that extend the functionality of ActiveRecord::Base and other Rails classes follows:

module MyRailsExtension
  def self.included base
    base.extend ClassMethods
    base.send :include, InstanceMethods
  end
  
  module ClassMethods
    def my_dsl_extension
      # ...
    end
  end
  
  module InstanceMethods
    # ...
  end
end

class MyModel < ActiveRecord::Base
  include MyRailsExtension
  
  # ...
end

I came to Ruby by way of Rails, and many of my early plugins used this pattern. As time went on, I began doing more straight Ruby coding and learned to embrace both extend and include. They send clear signals as to how the mixed in modules will behave. Using the self.included Railsism obscures that for the sake of adding instance and singleton methods with a single line. This bothers me a bit.

This pattern is so common that ActiveSupport::Concern was added to Rails to simplify the process:

module MyRailsExtension
  extend ActiveSupport::Concern
  
  included do
    # custom stuff
  end
  
  module ClassMethods
    def my_dsl_extension
      # ...
    end
  end
  
  module InstanceMethods
    # ...
  end
end

class MyModel
  include MyRailsExtension

  # ...
end

This will automatically extend ClassMethods and include InstanceMethods and evaluate whatever code is put into the included do ... end block. This module has been in the Rails code base for about 2 years now, and it bothers me. I get that

class MyModel
  include MyRailsExtension
end

is more terse than

class MyOtherModel
  extend  MyRailsExtension::ClassMethods
  include MyRailsExtension::InstanceMethods
end

but the latter gives us details that especially helpful when we need to track down what an extension is really doing.

Given the frequency with which this idiom is used in the Rails world, I am probably voicing a minority opinion — as time goes on I find myself embracing more Rubyisms than Railsisms — but this is my self-serving blog. I’ll piss and moan on the topics I want to piss and moan about.