Sharing code with Ruby and Rails

Ruby and Rails provide many ways to share code between objects. If you don’t work in Ruby or a Rails application often, it can be hard to remember what each one does. Despite this, each of these tools serves a different purpose in a Ruby system’s architecture; understanding when to choose one over another can make your code more readable and maintainable.

Kernel#include, SimpleDelegator, def_delegate, PORO inheritance, STI, Module#append, class injection… which one?
–Anonymous PollEvian

In this article, we will discuss Ruby class inheritance, Rails’ single-table inheritance, several ways to use Ruby modules, and delegation in Ruby and Rails.

Class inheritance

Let us look at some classic Ruby inheritance styles.

Plain old ruby inheritance

In Ruby, class inheritance is denoted by < in the class definition:

class Foo < Bar
  def foo_specific_method
    # ...
  end
end

Ruby’s class inheritance behaves like inheritance in many other programming languages: a subclass has all the methods and instance variables of its superclass plus any additional methods it defines in its body. It can also override superclass methods by defining a method of the same name, and call into the superclass method using the super keyword.

One aspect of Ruby’s inheritance that may differ from other languages you’ve used is that a subclass also inherits its superclass’s namespace, allowing it to reference constants and nested classes or modules without qualification. It can even “override” these constants within its own namespace without affecting the superclass:


class Bar
  BAR_CONSTANT = "Bar constant"
  OVERRIDE_CONSTANT = "Bar's override constant"
end

class Foo < Bar
  OVERRIDE_CONSTANT = "Foo's override constant"
end

Foo::BAR_CONSTANT      #=> "Bar constant"
Foo::OVERRIDE_CONSTANT #=> "Foo's override constant"
Bar::OVERRIDE_CONSTANT #=> "Bar's override constant"

Inheritance isn’t evil, but sometimes we are.
-Sandi Metz

Despite the fact that inheritance allows classes to reuse their superclass’s code, don’t. Class inheritance brings all the behavior of the superclass to its children, which can make future changes difficult to impossible. Instead, use inheritance for specialization; that is, when the subclass really IS-A subtype of a superclass in a behavioral sense, not just a conceptual one. Sandi Metz gives 3 conditions for when inheritance is appropriate:

  1. The inheritance hierarchy is shallow and narrow. If you find yourself with 5 or more subclasses of a single base class, you may want to find a different approach to the problem.
  2. The subclasses are at the leaf nodes of the system’s object graph. If a class references other classes by name, it probably shouldn’t be a subclass.
  3. The subclasses use all the behavior of the superclass. Any code that expects a SQLDatabase should be able to work with a MySQLDatabase without checking its class, or the return types of any of its methods. If a subclass can’t satisfy any of the contracts of its superclass, it shouldn’t be a subclass. Conversely, if all subclasses implement a method, the superclass probably should, too (at least to raise a NotImplementedError)

If you can’t fulfill all of these conditions, you should solve your problem with composition, not inheritance. For further information on the proper use of inheritance and composition, I recommend Metz’s talks All the Small Things and Nothing is Something. If you’re still feeling a craving, check out OO Inheritance – Not Always Evil – Refactoring to Open-Closed with Inheritance by Philip Schwarz.

Single-table inheritance

Rails can use Ruby’s class inheritance system to represent a class hierarchy in a single table. This is known as Single-Table Inheritance (STI), and ActiveRecord reserves the column name type for this purpose. Whenever an ActiveRecord model is connected to a table with a column named type, it will interpret the values in that column as the names of subclasses. When it makes a query from the base class, it will instantiate each record as the subclass named by the type, and queries from the subclasses will be conditioned on the value of the type.

class Company < ActiveRecord::Base
end
class Firm < Company
end
Company.find_by(id: 123)
#=> #
Firm.where(id: 123).to_sql
#=> "SELECT * FROM companies WHERE id = 123 AND type = 'Firm' LIMIT 1;"

However, single-table inheritance still relies on Ruby class inheritance. Subclasses still inherit all the code of their superclasses, so all guidelines from the previous section still apply. In addition, sharing a table means that any database field that is needed for one subclass is available to all subclasses. If there are many (any) columns that are not used by all subclasses, STI is probably the wrong choice.

Mixins

In addition to class inheritance, which limits each class to only one superclass, Ruby supports mixins, known in Ruby as “modules”. Like classes, modules are collections of methods, but unlike classes, modules cannot be instantiated into objects. Instead, modules can be mixed into classes or other modules using the include class method, giving objects of a class access to module methods as though they had been defined on the class itself.

While Ruby modules can be used in a few different ways, their primary purpose is to share code between classes that may behave similarly but are otherwise unrelated. The best example of this is Ruby’s Enumerable module, which provides that class with access to a host of other useful methods, including map, reduce, all?, any?, count, find, and filter. Any class that implements an each method can gain access to them simply by mixing in Enumerable.

At Poll Everywhere, we use modules to share code between related classes, like Ruby’s Comparable module or a Commentable module for ActiveRecord objects that can be commented upon, as well as to organize related code in a large class. In the latter case, Poll Everywhere usually namespaces the module under its including class, such as with User::Password or Account::Billing.

Mixing into a class or module with include

The simplest and most common way to mix a module into a class is with include. This gives instances of the class access to any method defined in the module as though it were their own. This is the primary method of mixing in functionality and should be used any time you want to share code between related classes without introducing an inheritance hierarchy, or when you want to organize some related code from a large class.

module Fooish
  def foo
    "foo"
  end
end
class Bar
  include Fooish
end
Bar.new.foo #=> "foo"

If you’re interested, Ruby implements this by placing the module just above the class in the object’s inheritance hierarchy. So if a module has a method with the same name as the class, the class will win. To override a method defined on the class from a module, use prepend instead of include.

Mixing into an object with extend

While include method is great for adding methods to instances of a class, sometimes you want to add methods to the class itself. In Ruby, this is done with extend. After extending a class with a module, all of the methods defined in the module can be called from the class context. This is often used to create class-level domain-specific languages (DSLs), as with ActiveRecord’s association interface.

module Fooish
  def foo
    "foo from Fooish"
  end

  def has_many(*args)
    # ...
  end
end
class Bar
  extend Fooish

  # Available from Fooish. Did you know that Rails's association
  # DSLs are actually just class method calls?
  has_many :bazzes 
end
Bar.foo     #=> "foo from Fooish"
Bar.new.foo #=> NoMethodError (undefined method `foo' for #)

In fact, this behavior is not special to classes; it is defined for all objects, and works on classes simply because they are themselves objects. You can actually extend any Ruby object with a module at runtime without affecting other objects of its class.

module Fooish
  def foo
    "foo"
  end
end
class MyClass
end
obj = MyClass.new
obj.foo #=> NoMethodError
obj.extend(Fooish)
obj.foo #=> "foo"
MyClass.new.foo #=> NoMethodError

Module#included

In addition to include, Ruby provides the included hook method to give programmers more control over what happens when a module is included. When class Foo includes module Bar, Ruby will call Bar.included(Foo). Note that included is a method of the Bar module itself, not an instance method defined inside it.

module Bar
  def self.included(klass)
    puts "Bar included by #{klass}"
  end
end
class Foo
  include Bar
end
#=> prints "Bar included by Foo"

Use included when you need to define additional class level behavior, like model associations or validations or serializer attributes, that is relevant to the module. For example, Poll Everywhere has a User::AccountRelationship mixin that uses included to define validations on the account role attributes. It can also be used to add class methods at the same time as instance methods:

module Bar
  # Won't be added to including classes
  def self.bar
    "Bar.bar"
  end

  module ClassMethods
    def bar
      "Bar::ClassMethods.bar"
    end
  end

  def self.included(klass)
    klass.extend ClassMethods
  end
end
class Foo
  include Bar
end
Foo.bar #=> "Bar::ClassMethods.bar"

Delegation

Despite the convenience of code sharing through class inheritance or mix-ins, it is often not a good way to build maintainable software. Maintainable, reusable software is better built through composition, in which an object holds on to one or more other objects and calls their methods as appropriate. Sometimes, though, a class wants to expose a method of these internal objects as its own method (often to avoid violating the Law of Demeter). While we could redefine each method needed on the composite class, it’s annoying to do and prone to rot. Ruby and Rails provide several helpers that automate this process, known as delegation, thereby allowing a composite class to reuse the code of its component parts

Ruby class-level delegation: forwardable

Ruby’s standard library includes the module Forwardable, which provides a DSL for delegating method calls to other objects that an instance may be holding onto. This DSL consists primarily of the method def_delegator, which takes three symbols that identify, respectively, the receiver (the instance variable or method that the call is delegated to), the method to delegate, and an optional alias name for the method on the delegating class. From Ruby’s documentation:

class MyQueue
  extend Forwardable
  attr_reader :queue
  def initialize
    @queue = []
  end

  def_delegator :@queue, :push, :mypush
end
e = MyQueue.new
q.mypush 42
q.queue    #=> [42]
q.push 23  #=> NoMethodError

Forwardable also provides def_delegators for delegating several methods to the same receiver without any aliasing.

  def_delegators :@records, :size, :<<, :map
# is equivalent to
def_delegator :@records, :size
def_delegator :@records, :<<
def_delegator :@records, :map

SimpleDelegator

One common delegation pattern involves a wrapper, known as a decorator, around an existing object to extend its functionality for a particular context. For this purpose, Ruby provides SimpleDelegator, a class which is instantiated with a single argument, and will delegate any method that it does not define to that argument.

class GeniusBillionairePlayboyPhilanthropist
  def speak
    "I am Iron Man"
  end
end
class IronManArmor < SimpleDelegator
  def fly
    "Yeah, I can fly"
  end
end
tony = GeniusBillionairePlayboyPhilanthropist.new
tony.speak #=> "I am Iron Man"
tony.fly #=> NoMethodError
iron_man = IronManArmor.new(tony)
iron_man.fly #=> "Yeah, I can fly"
iron_man.speak #=> "I am Iron Man"

Decorators built with SimpleDelegator are powerful because they support arbitrary nesting. An object can be wrapped by as many decorators as desired, and the resulting Matryoshka doll will respond to any method that any of its layers implements.

class InfinityGauntlet < SimpleDelegator
  def snap
    dust(percentage: 0.5)
  end
end
endgame = InfinityGauntlet.new(iron_man)
endgame.speak #=> "I am Iron Man"
endgame.snap #=> ...

However, be wary of nesting too deeply, as outer layers can shadow inner layers, leading to difficult issues to debug. If you find yourself nesting decorators, consider instead implementing a larger composite object to use one decorator at a time.

Rails delegation sugar

While Forwardable is useful for delegation in vanilla Ruby, Rails adds its own delegation DSL to all modules and classes. The two most useful methods are delegate and delegate_missing_to. Like def_delegators, delegate accepts a list of symbols identifying the methods to delegate, but rather than taking the first argument as the receiver, it takes an options hash and looks at the key :to.

class MyRecords
  def initialize(records_array)
    @records = records_array
  end
  attr_reader :records
  delegate :size, :<<, :map, :filter, to: :records
end
my_records = MyRecords.new([])
my_records.size #=> 0
my_records << "record" my_records.map { |r| r.upcase} #=> ["RECORD"]
my_records << "cd"
my_records.filter { |r| r.length < 3 } #=> ["cd"]

This options hash can also include :prefix, which will alias the methods with a prefix, and :allow_nil, which will cause the delegated methods to return nil instead of failing if the receiver is nil.

class MyRecords
  def initialize(records_array)
    @records = records_array
  end
  attr_reader :records
  delegate :size, :<<, to: :records, allow_nil: true
  delegate :map, to: :records, prefix: true
  delegate :filter, to: :records, prefix: :my
end
my_records = MyRecords.new
my_records.size #=> nil
my_records = MyRecords.new([])
my_records.size #=> 0
my_records << "record" my_records.records_map { |r| r.upcase} #=> ["RECORD"]
my_records << "cd"
my_records.my_filter { |r| r.length < 3 } #=> ["cd"]

Use the delegate method to replace violations of the Law of Demeter, such as account.owner.payment_method, with direct or aliased methods, like account.payment_method or account.owner_payment_method. However, beware delegations of methods that are themselves delegated, since this will encode your application structure into one class, making it harder to change in the future.

Rails’ other delegation method, delegate_missing_to, takes a single symbol as its argument, which identifies a catchall receiver for any method the object itself does not implement. It’s not likely that you’ll need this, but if you want an object that is almost a decorator, but needs a little extra special behavior, delegate_missing_to is the perfect tool. Say you’re writing a Rails presenter about the relationship between an Item, but you also need to know a little bit about the User who wants to buy the item.

class ItemPresenter
  def initialize(item, user:)
    @item = item
    @user = user
  end
  attr_reader :item, :user
  delegate :discount_code, to: :user

  def price
    user.price_for(item)
  end

  delegate_missing_to :item
end
user = User.first
item = Item.first
presenter = ItemPresenter.new(item, user: user)
presenter.discount_code #=> calls user.discount_code
presenter.price #=> calls user.price_for(item)
presenter.upc #=> calls item.upc

Conclusion

Ruby and Rails each provide powerful tools for sharing code between objects, each of which serves specific purposes. Inheritance is perfect for creating a few specialized variants of existing classes. Mix-ins make it easy to share functionality across similar but unrelated classes (duck types), and to organize related code in large classes. Delegation helps you build decorators and to avoid violating the Law of Demeter. Each is a sharp knife, but when you know how and when to use them, there is no better tool.