Runtime extends with Ruby
A Ruby design pattern for extending objects with new behaviour at run-time, using modules and singleton classes — illustrated with an RPG character.
Ruby is a dynamic language that supports many ways to organise logic. We can use class inheritance and/or compose our classes by including selected modules (mixins).

We can define or un-define methods on the fly. We can even use methods that
aren’t really defined (using method_missing). Another powerful feature is the
ability to extend an object with new methods at run-time, by including modules in
the class or singleton class (if you want to extend only one instance).
To present this design pattern, let’s assume that we want to create an application which will entertain our users — an RPG game, where the plot takes place in a fantasy world.
For simplicity we will model a character class. The player can pick one of six different races: dwarf, elf, gnome, hobbit, human or ogre. And they must choose their character’s occupation from: priest, programmer, smith, thief, warrior or wizard.
Our base character class is going to have a public method greeting, where the
output will depend on a character’s race and occupation. To illustrate this,
let’s start with a test:
# spec/character_spec.rb
require 'spec_helper'
require 'character'
describe Character do
describe '#greeting' do
it 'works for ogre warrior' do
ogre = Character.new(:race => 'ogre', :occupation => 'warrior')
ogre.greeting.should == 'Grumph! I will kill you!'
end
it 'works for elven wizard' do
elf = Character.new(:race => 'elf', :occupation => 'wizard')
elf.greeting.should == 'Heil! Did you see my staff?'
end
it 'works for human programmer' do
human = Character.new(:race => 'human', :occupation => 'programmer')
human.greeting.should == 'Good Day. Do you know Ruby?'
end
end
end
Character#greeting is composed of two parts. The first depends on how a given
race performs a greeting. For example an ogre will say Grumph!. The second part
is influenced by a character’s occupation, e.g. a programmer will ask about Ruby.
By combining the above, an “ogre programmer” will greet you with:
Grumph! Do you know Ruby?.
With our tests in a failing state, let’s think about some possible implementations.
The easiest way to make our tests pass is to create just one class — Character —
composed of multiple if (or case) statements, each modifying the output of
the greeting. However, it is likely that other attributes could be introduced in
the future, resulting in complicated logic that would be difficult to maintain.
Another approach might separate the logic into classes, each inheriting from the
Character class. This solution is nicely supported by Rails through Single Table
Inheritance (STI). Going with this approach is good for one layer of separation.
But we want to separate logic by both race and occupation. This would lead to
two layers and 36 classes like: OgreProgrammer, OgrePriest, GnomeThief,
HobbitWizard, etc. And this number will grow when even more layers are added.
We could end up with thousands of classes like FemaleYoungWoodenElfArcher!
The solution I would like to present uses run-time extends with Ruby. We create a module for each race and occupation and some logic to glue things together.
Let’s start with the Character class:
# lib/character.rb
class Character
include Character::Race
include Character::Occupation
def greeting
"#{race_greeting} #{occupation_greeting}"
end
def race_greeting
raise 'Not implemented'
end
def occupation_greeting
raise 'Not implemented'
end
end
The Character class implements greeting, which depends on two other methods:
race_greeting and occupation_greeting. Those two methods are expected to be
implemented in the included modules. They are also defined in the Character class
itself, but they raise an error to indicate that they should be defined
elsewhere.
Let’s continue with the implementation of the modules included in the Character class — Race and Occupation:
# lib/character/race.rb
class Character
module Race
def initialize(options = {})
@race = options[:race]
include_race
super if defined? super
end
def race_module
ActiveSupport::Inflector.constantize("Character::Race::#{@race.capitalize}")
end
private
def include_race
singleton_class = class << self; self; end
singleton_class.send(:include, race_module)
end
end
end
# lib/character/occupation.rb
class Character
module Occupation
def initialize(options = {})
@occupation = options[:occupation]
include_occupation
super if defined? super
end
def occupation_module
ActiveSupport::Inflector.constantize("Character::Occupation::#{@occupation.capitalize}")
end
private
def include_occupation
singleton_class = class << self; self; end
singleton_class.send(:include, occupation_module)
end
end
end
Those two modules look similar and can be refactored, but we’ll examine that later. For now take a look at the Occupation module.
The initialize method sets an instance variable @occupation and then calls
include_occupation, which includes the chosen occupation into the object’s
singleton class (this means the module is available only for this object, not for
all Character objects). The occupation_module method returns the module to be
included (using ActiveSupport’s constantize).
Finally, the super call in initialize calls initialize in any other
module/class through inheritance. This is important, because it calls not only
the initialize of the Character class, but also the initialize defined in all
modules that had been included before the described one. It ensures that both the
initialize defined in the Race module and in the Occupation module are called.
The Race module works in the same way.
The last thing we need to implement are the modules for each race and occupation. Since they are quite similar, I’m only going to list a couple here:
# lib/character/race/ogre.rb
class Character
module Race
module Ogre
def race_greeting
'Grumph!'
end
end
end
end
# lib/character/occupation/programmer.rb
class Character
module Occupation
module Programmer
def occupation_greeting
'Do you know Ruby?'
end
end
end
end
Implementing all required modules and requiring ActiveSupport in the Character class makes our tests pass.
# lib/character.rb
require 'rubygems'
require 'active_support'
Changing existing objects at runtime
So far we’ve implemented a structure that allows us to set a character’s race and
occupation at object creation, using the new method. However, this doesn’t
fulfil our need — we need to be able to change an existing character’s occupation
and race (this is some kind of magic) at run-time. This can be easily achieved by
improving our modules. Let’s write some tests first:
# spec/character_spec.rb
describe Character do
context 'attribute readers' do
it 'are being set during initialization' do
ogre = Character.new(:race => 'ogre', :occupation => 'warrior')
ogre.race.should == 'ogre'
ogre.occupation.should == 'warrior'
end
it 'are changeable' do
character = Character.new(:race => 'ogre', :occupation => 'warrior')
character.race = 'elf'
character.occupation = 'smith'
character.race.should == 'elf'
character.occupation.should == 'smith'
end
end
end
To make this pass we need to add two methods to the Race (and Occupation) modules:
# lib/character.rb
class Character
module Race
def self.included(base)
base.send(:attr_reader, :race)
end
def race=(value)
@race = value
include_race
end
# Rest of code removed for clarity.
end
end
The first method is called when the module is included in another module or class
(held by the variable base — the Character class in our case). It sets an
attribute reader for the race variable on the Character class.
The second method is an attribute writer, which assigns the value to an object’s instance variable, and then includes the appropriate race module.
Playing with it even more
To make things complicated, let’s implement the gnome’s speaking dialect. Gnomes
are very smart and they have a lot to say, so their dialect should speak faster.
Gnomes will omit the pauses between words and use special accents to substitute
that. They will say, for example: HowAreYouDoing? instead of How are you doing?.
To depict our needs let’s start with a modification of the character’s tests:
# spec/character_spec.rb
describe Character do
describe '#greeting' do
it 'works for gnome programmer' do
gnome = Character.new(:race => 'gnome', :occupation => 'programmer')
gnome.greeting.should == 'GutenTag.DoYouKnowRuby?'
end
end
end
To implement that we need to modify the Character class again:
# lib/character.rb
class Character
include Character::Race
include Character::Occupation
def greeting
if race_module.methods.include?(:race_modifier)
race_module.race_modifier(clean_greeting)
else
clean_greeting
end
end
def clean_greeting
"#{race_greeting} #{occupation_greeting}"
end
# Rest of code omitted for clarity
end
So now, if race_modifier has been implemented in race_module then it will be
used, otherwise an unmodified clean_greeting is returned. Finally we implement
race_modifier in the Gnome module:
# lib/character/race/gnome.rb
class Character
module Race
module Gnome
def race_greeting
'Guten Tag.'
end
def self.race_modifier(value)
value.split(' ').map(&:capitalize).join
end
end
end
end
This simple example of speech modification for gnomes illustrates how easily and cleanly logic can be extended using this run-time extends design pattern.
Some refactoring
So far we have a lot of code duplication. The Race and Occupation modules look almost identical. We can address this by creating a Common module to be included in the Race and Occupation modules:
# lib/character/common.rb
module Common
def self.included(base)
base_name = base.name.split('::').last.downcase
base.send(:attr_reader, base_name.to_sym)
base.class_eval <<-EOS
def initialize(options = {})
@#{base_name} = options[:#{base_name}]
include_#{base_name}
super if defined? super
end
def #{base_name}=(value)
@#{base_name} = value
include_#{base_name}
end
def #{base_name}_module
ActiveSupport::Inflector.constantize("Character::#{base_name.capitalize}::\#{@#{base_name}.capitalize}")
end
private
def include_#{base_name}
singleton_class = class << self; self; end
singleton_class.send(:include, #{base_name}_module)
end
EOS
end
end
This code uses the included hook to read the name of the including module and
then sets a reader for the attribute (with that name) and defines its methods
(using class_eval).
Wrapping up
The complete code from this blog post can be found here: github.com/dawid-sklodowski/runtime-extends.