Inheriting From Built-In Ruby Types
The string is a stark data structure and everywhere it is passed there is much duplication of process. It is a perfect vehicle for hiding information.
-- Alan Perlis
I was teaching a class on refactoring and wanted a real-world example to demonstrate finding a class to extract. My students were Rails developers, so I immediately knew I wanted to show an example of an ActiveRecord model.
After skinny controllers and fat models became a Rails best practice ActiveRecord models have tended to grow without bounds. It's easy to push code down into them, but developers seem to have some kind of mental block causing them to think they can't create classes that aren't more ActiveRecord models.
Though I've seen this often enough I couldn't use any client code. I scratched
my head a moment and thought of
Discourse, a new forum project. I
went directly to the User model because it's so commonly a god
object. (Just to be clear, I'm not
trying to pick on Discourse. This is a fairly minor improvement for a very
common problem.)
I scrolled down past the usual mass of associations, validations, and hooks to the method definitions. Here's the first eight:
def self.username_length
def self.suggest_username(name)
def self.create_for_email(email, options=())
def self.username_available?(username)
def self.username_valid?(username)
def enqueue_welcome_message(message_type)
def self.suggest_name(email)
def change_username(new_username)
From this quick glance there's a Username class waiting to be extracted.
It's in the name of several methods, it's the sole argument to many methods,
and most of the methods are class methods, implying they don't maintain any
state (class-level variables being rare in Ruby).
It's discouraging to think about extracting a Username. It's stored as a
varchar in the database and ActiveRecord will choke on validations if it
can't treat it as a string.
The solution is straightforward: Username should inherit from String.
Username is-a String, and keeping it in the built-in type is why this
code sprawls.
I extracted the below class, changing as little as possible, mostly just
removing 'username' from the start of method names and using self where
appropriate. I left behind the unrelated create_for_email,
enqueue_welcome_message, suggest_name, and the tempting change_username,
which was about editing a User.
class Username < String
def username_length
3..15
end
def suggest
name = self.dup
if name =~ /([^@]+)@([^\.]+)/
name = Regexp.last_match[1]
# Special case, if it's me @ something, take the something.
name = Regexp.last_match[2] if name == 'me'
end
name.gsub!(/^[^A-Za-z0-9]+/, "")
name.gsub!(/[^A-Za-z0-9_]+$/, "")
name.gsub!(/[^A-Za-z0-9_]+/, "_")
# Pad the length with 1s
missing_chars = username_length.begin - name.length
name << ('1' * missing_chars) if missing_chars > 0
# Trim extra length
name = name[0..username_length.end-1]
i = 1
attempt = name
while !attempt.available?
suffix = i.to_s
max_length = username_length.end - 1 - suffix.length
attempt = "#{name[0..max_length]}#{suffix}"
i+=1
end
attempt
end
def available?
!User.where(username_lower: lower).exists?
end
# export a business name for this operation
alias :lower :downcase
end
And it's used as:
u = Username.new 'eric_blair'
u.available?
new = u.suggest
User.new username: Username.new('Samuel Clemens').suggest
The User class got a lot simpler now that it doesn't know all the business
rules about usernames. I left the validation in User because it's the thing
being persisted to the database, though if it wasn't for the Active Record
pattern I'd want to move that over as well.
Now Username is a simple
immutable value
object that's easier to reason about and
test. It adheres nicely to the
SOLID
principles, and it's an uncommon nice example of inheritance working well in Ruby.
One caveat is that User objects Rails instantiantes will return Strings on
calls to User#username. We'll need to write a getter to instantiate and return a
Username object, though Rails before 3.2 included
composed_of
for this:
class User < ActiveRecord::Base
def username
Username.new(read_attribute(:username))
end
# or, pre Rails 3.2:
composed_of :username, converter: :new
end
(And to the curious: no, I haven't contributed a patch back to Discourse. They have a Contributor License Agreement to backdoor their ostensible open source licensing.)
Single Table Inheritance + Hstore = Lovely Combination
Single table inheritance is an awesome concept with an unfortunate drawback; the fear of an unnecessarily wide and sparse table. Sure you started out with one or even zero columns that aren't shared across subclasses, but there's nothing stopping a future need from requiring a column 80% of the table doesn't care about.
An Example
Let's take Martin Fowler's description of Single Table Inheritance as a canonical example.

In this simple example above we've got a single table Players, which contains information for three specific subclasses. They all share a common attribute of name, but that's where the beauty ends. A Cricket Bowler does not need a column for club and Footballers do not require a batting or bowling average. Wouldn't it be nice if these columns didn't exist for subclasses that don't require them? Wouldn't it be nice to not fear adding additional sportsball player types with any mixture of random attributes specific to their sportsballery?
Hstore to the Rescue
Thanks to PostgreSQL and HStore, this is not only possible but incredibly easy and elegant.
I'm going to gloss over the basics of getting hstore running. Plenty of blogs about that. If you have specific questions feel free to leave me a comment below.
Let's take the same example above and model it in code using ActiveRecord STI. To create the table and models, this is all we need.
# db/migration/<timestamp>_create_players_table.rb
class CreatePlayersTable < ActiveRecord::Migration
def up
create_table :players do |t|
t.string :name
t.string :type
t.hstore :data
end
end
def down
drop_table :players
end
end
# app/models/player.rb
class Player < ActiveRecord::Base
serialize :data, ActiveRecord::Coders::Hstore
end
# app/models/players/footballer.rb
class Footballer < Player
end
# app/models/players/cricketer.rb
class Cricketer < Player
end
# app/models/players/bowler.rb
class Bowler < Cricketer
end
Wow piece of cake. Let's do something with it.
Bowler.create(name: "Sweet Lou",
data: {'batting_average' => 59.23, 'bowling_average' => 18.75})
bowler = Bowler.find(bowler.id)
bowler.data['batting_average'] # => "59.23"
bowler.data['bowling_average'] # => "18.75"
ap bowler
#<Bowler:0x007f8db23b62c0> {
:id => 1,
:type => "Bowler",
:name => "Sweet Lou",
:data => {
"batting_average" => "59.23",
"bowling_average" => "18.75"
}
}
Using Awesome Print we see that bowling and batting average are stored in the data column. Bowlers know nothing about the club attribute that is (curiously) specific to footballers.
Improving the Awesome
At this point we're functional. However, this is Ruby and we can do better with regard to the hash style syntax of accessing the data. I had started writing something similar before I found this gist by Josh Goodall in issue 57 of the activerecord-postgres-hstore gem
# config/initializers/hstore_acessor.rb
module HstoreAccessor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def hstore_accessor(hstore_attribute, *keys)
Array(keys).flatten.each do |key|
define_method("#{key}=") do |value|
send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(key.to_s => value))
send("#{hstore_attribute}_will_change!")
end
define_method(key) do
send(hstore_attribute) && send(hstore_attribute)[key.to_s]
end
end
end
end
end
ActiveRecord::Base.send(:include, HstoreAccessor)
Now after declaring what attributes are inside of our hash we can talk to attributes in a much cleaner way.
# app/models/players/cricketer.rb
class Cricketer < Player
hstore_accessor :data, :batting_average
end
# app/models/players/bowler.rb
class Bowler < Cricketer
hstore_accessor :data, :bowling_average
end
bowler = Bowler.create(batting_average: 59.23, bowling_average: 18.75)
bowler.batting_average # => "59.23"
bowler.bowling_average # => "18.75"
Cricketer.new.bowling_average # => NoMethodError
Notice calling bowling_average on Cricketer will raise an error because that attribute is specific to bowlers.
Validations
The real sweet part is we can now use the majority of validations on our models.
# app/models/players/cricketer.rb
class Cricketer < Player
hstore_accessor :data, :batting_average
validates_presence_of :batting_average
end
Cricketer.new.valid? # => false
You've Got Documents in Your Columns
Well it's not the full power of documents, but among its other uses hstore is a wonderful complement to STI. It does away with the fear of ending up with a sparse table. In the example above we could add any number of attributes to a subclass of player and it does not affect other classes which do not need them. Combine that with the indexing PostgreSQL allows and you've got quite a powerful dynamic schema without the tradeoffs of full blown NoSQL.
Time provided I intend to improve upon hstore_accessor to accept type declarations and do the necessary casting. I'm also curious to see what features Rails 4 gives you out of the box. I hear good things and will not deny myself the power of PostgreSQL specific features. You shouldn't either.
Thanks for reading.

