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.

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
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required