The data mapper pattern in Rails 4 using the perpetuity gem

This article shows you how to get started with the Data Mapper pattern in Rails 4 using the perpetuity gem.

Data Mapper

In this post I'll describe how to get started using the Data Mapper pattern in Rails 4. But first, I'd like to explain what the Data Mapper pattern is, why you might want to use it, and the history of the Data Mapper + Rails combo.

What Data Mapper is

In Patterns of Enterprise Application Architecture, Martin Fowler describes two different object-relational mapping patterns. One of them is called Active Record, and this is in fact what Rails' ActiveRecord library implements. With the Active Record pattern, there is typically a one-to-one relationship between database tables and model classes, so if I had an article database table, I'd also have an Article class definition. My Article class would be responsible for validating that the title is present and for saving itself to the database. With Active Record, domain concerns and persistence concerns are mixed together.

The main difference with the Data Mapper pattern, in my understanding, is that objects are not only not responsible for persisting themselves, but that they are entirely decoupled from and unaware of anything to do with persistence. In other words, domain concerns and persistence concerns are kept separate.

(If you'd like additional clarification, I think Jamie Gaskins did a good job of describing the difference between the two patterns in his post, Data Mapper vs. Active Record.)

Why you'd want to use Data Mapper

Active Record is an absolute delight to use when you're working with a small- or medium-sized application, but things turn sharply south once your application grows to any size. Your models get huge. Your tests get slow. Your classes lose the tidy cohesion they possessed in their youth. You yearn for some way to combat the tangled knots of complexity.

I've been frustrated for some time with the mess that results when you take everything that has to do with a Post or an Appointment and pile it all together in one class, and I've always been attracted to the idea of neatly separating domain logic, application logic and persistence logic from one another. I've tried doing things like pulling validation out of an ActiveRecord class and putting it elsewhere, but I could never get the idea to fly. Lately I've been playing with Data Mapper and, while I'm new to this ORM pattern, my experience so far has been promising.

A brief history of Data Mapper and Rails

If you search for "rails data mapper," the first result is for a library calledDataMapper. I understand that a lot of people really like DataMapper and I'm sure it does a wonderful job of certain important things, but I personally found this ORM problematic for a couple reasons.

First, despite its name, it appears that DataMapper actually implements the Active Record pattern. Second, it looks like the last commit to the dm-rails gem happened about two years ago, and in fact this version of DataMapper is no longer maintained. There are plans for a Data Mapper 2, AKA Ruby Object Mapper, but it's not clear when this library will be finished and available. (As of this writing, the site's Status page simply reads "coming soon.") You can use this old DataMapper library if you want, but I was only able to easily getting working on Rails 3. If DataMapper works on Rails 4, it must take some extra "jiggling the knob" to get it functioning properly.

I understand that Ruby Object Mapper actually will implement the Data Mapper pattern (unlike the miseleadingly-named DataMapper library), but this of course doesn't do you much good if you want to do Data Mapper now. Luckily, there does seem to be at least one somewhat usable Data Mapper ORM at present: Jamie Gaskins' Perpetuity.

A Data Mapper "Hello, World!"

Perpetuity currently supports (to varying degrees) MongoDB and PostgreSQL. The author seems to prefer MongoDB over a relational DBMS in general, and it looks like MongoDB is supported fully but PostgreSQL support is still coming along.

All my Rails projects use PostgreSQL, so I'm a lot more interested in the PostgreSQL flavor of Perpetuity than the MongoDB one. MongoDB is also a lot better covered in Perpetuity's documentation than PostgreSQL, so I figured I'd complement Perpetuity's documentation rather than duplicate it.

You won't be able to take this example to production since Perpetuity's PostgreSQL adapter doesn't yet fully support retrieval or updating, but hopefully I'm inching Data Mapper forward just a tiny bit with what I'm showing here.

Create the project

First let's create the project, which I'm calling journal. (The -T is to skipTest::Unit.)

rails new journal -T -d postgresql

You'll only need to add two gems to the Gemfile: Perpetuity itself and the Perpetuity PostgreSQL adapter. (I'm using specific commit SHAs here so my code examples will continue to work...in perpetuity. Sorry.) Complete Gemfile available here.

# Gemfile

gem 'perpetuity',          git: 'git://github.com/jgaskins/perpetuity.git',          ref: '82cad54d7226ad17ce25d74c751faf8f2c2c4eb2'
gem 'perpetuity-postgres', git: 'git://github.com/jgaskins/perpetuity-postgres.git', ref: 'c167d338edc05da582ff3856e86f7fb7693df0bb'

Then, of course, do a:

bundle install

Create the database:

createuser -P -s -e journal

rake db:create

In this next step we need to tell Perpetuity that our data source is PostgreSQL as opposed to MongoDB or any other adapter, and that our database is calledjournal_development. The Perpetuity docs don't seem to say where to put this line, so I threw it at the bottom of config/environments/development.rb due to the reference to journal_development.

(As far as the development of the Perpetuity gem goes, this seems to me like a good opportunity to use convention over configuration and just infer that the dev database will be called "#{NAME_OF_APP}_development". Maybe I just volunteered to be the one to add that feature.)

# config/environments/development.rb
Perpetuity.data_source :postgres, 'journal_development'

That takes care of most of the administrative-type work. Now we can add our first model class which, as you'll notice, does not inherit from ActiveRecord::Base, or any parent class at all.

# app/models/article.rb

class Article
  attr_accessor :title, :body
end

I believe what we're doing in this next step is "generating a mapper." Create anapp/mappers directory and put the following mapper code intoapp/mappers/article_mapper.rb.

mkdir app/mappers

# app/mappers/article_mapper.rb

Perpetuity.generate_mapper_for Article do
  attribute :title, type: String
  attribute :body, type: String
end

That's all! You should now be able to pop open a console and save an Articleobject to the database.

rails console

Paste the following code into the console:

article = Article.new
article.title = 'New Article'
article.body = 'This is an article.'

This creates our Plain Old Ruby Object. Our PORO doesn't know how to save itself, so next we'll use Perpetuity to save it.

Perpetuity[Article].insert article

When I ran that for the first time I got this:

> Perpetuity[Article].insert article
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "Article_pkey" for table "Article"
 => "c2e441b9-cab8-4dc9-9a18-fbb365ba47a7"

Interestingly, it created a table. You might be thinking, "Oh, yeah...we never did any migrations." Apparently Perpetuity takes care of creating your tables for you based on the mappers you define. This feels a little weird to me but I'll suspend judgement for now. Let's take a look at that table.

rails dbconsole

The table is in fact there. I prefer snake_case table names over CamelCase, but I won't look this gift horse in the mouth. I'm just glad it worked.

journal_development=# \d
           List of relations
 Schema |  Name   | Type  |   Owner
--------+---------+-------+------------
 public | Article | table | jasonswett
(1 row)

And it does have the right attributes...kind of. It seems like a Ruby String should map to character varying(255) the way it does in ActiveRecord, but again, whatever. I understand that Perpetuity's PostgreSQL adapter is a work in progress.

journal_development=# \d "Article"
               Table "public.Article"
 Column | Type |              Modifiers
--------+------+-------------------------------------
 id     | uuid | not null default uuid_generate_v4()
 title  | text |
 body   | text |
Indexes:
    "Article_pkey" PRIMARY KEY, btree (id)

Finally, let's check out the data:

journal_development=# select * from "Article";
                  id                  |    title    |        body
--------------------------------------+-------------+---------------------
 c2e441b9-cab8-4dc9-9a18-fbb365ba47a7 | New Article | This is an article.
(1 row)

It worked!

I don't know how impressed you are, but to me, this is pretty profound. We have a Plain Old Ruby Object that we were able to get saved to the database with barely any extra work. I'm excited now to start building some meatier functionality with Perpetuity in order to explore the possibilities of the Data Mapper pattern and hopefully help make Rails apps a little more maintainable.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial