Clarifications on Jason Swett's Perpetuity Article
Jason Swett published an article about starting out on a Rails application using the perpetuity
gem, and I'd like to address a few points he brought up in that article. I've been meaning to write this since I read his article, but time seemed to slip away from me.
Jason contacted me back in November about wanting to write his article and wanted to make sure he got everything right. Between the fact that I was somewhat unavailable to answer questions (I was in rural Louisiana with horrifically slow internet, even on my phone) and his really short deadline, which was over 6 weeks before it was finally published, there are a few things in the article that were either slightly inaccurate or outdated and I wanted to correct them.
You won't be able to take this example to production since Perpetuity's PostgreSQL adapter doesn't yet fully support retrieval or updating
This is just a timing issue. When Jason contacted me, Perpetuity::Postgres
did not support retrieval unless your attributes were all strings (this means his Article
example actually would've worked; it was just strings) and did not support updating at all. However, this was something I discussed with Jason over a month before his article was released and the Postgres adapter did completely support both retrieval and updating when he published it.
First let's create the project, which I'm calling journal. (The -T is to skip Test::Unit.)
rails new journal -T -d postgresql
Rails apps using Perpetuity should also include the -O
command to skip ActiveRecord (the O stands for ORM). The -d postgresql
option is unnecessary since pg
is already a dependency of perpetuity-postgres
.
This does change a few things, though. For example, Jason relied on the rake db:create
command provided by ActiveRecord. Just as he mentioned in the article about how Perpetuity::Postgres creates tables automatically, it will also create the database if it doesn't exist.
You also won't get the rails dbconsole
command without a database.yml file, but I'll see about providing something for that, as well.
You'll only need to add two gems to the Gemfile: Perpetuity itself and the Perpetuity PostgreSQL adapter.
His Gemfile looked like this:
gem 'perpetuity', git: 'git://github.com/jgaskins/perpetuity.git', ref: '82cad54d7226ad17ce25d74c751faf8f2c2c4eb2'
gem 'perpetuity-postgres', git: 'git://github.com/jgaskins/perpetuity-postgres.git', ref: 'c167d338edc05da582ff3856e86f7fb7693df0bb'
You only need to declare perpetuity-postgres
in the Gemfile. There are no :git
/:github
requirements since there have been a few versions released. The core perpetuity
gem is a dependency of perpetuity-postgres
, so it does not need to be added to the Gemfile (similarly to how rspec-rails
brings in the rspec
gem) unless you want to specify a specific version or git ref as he did here.
TL;DR: This is all you need to add to your Gemfile:
gem 'perpetuity-postgres'
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.
I'm sure it does feel weird. After working with tools that force you to do this manually, this is very likely to cause a few people to do a double take. Tables are created automatically mostly because running an additional command to do that is unnecessary. It's also because damn near every time I have to add a model in an application that uses ActiveRecord, this is what happens:
- Run tests.
uninitialized constant Foo
rails g model Foo bar baz quux:integer
- Run tests.
table "foos" does not exist
- Swear loudly
rake db:migrate
- Run tests.
table "foos" does not exist
At this point, I may or may not have realized that I forgot to add db:test:prepare
after db:migrate
. If not, that's another 10 minutes or so of lost productivity while I check the database and make sure I'm not insane.
Perpetuity::Postgres
will also add columns automatically when you add attributes to the mapper. This allows your app to have a deploy without downtime if there need to be changes made to the DB schema. When you deploy an ActiveRecord app to Heroku, you have to wait run the migrations after the deploy because your app won't have the updated migrations until then. Depending on the size of your app, this could cause problems because heroku run rake db:migrate
has to load your entire application on an underpowered EC2 instance. In large apps, this leads to 30 seconds or more (some apps take over a minute) with your app already running and that table or column being unavailable. If you're getting constant traffic, this is probably unacceptable.
It feels like a bit of magic, but all we're doing is rescuing an exception (raised by the Postgres driver), using information already provided in the mapper to generate a table and then retrying the code that raised the exception.
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.
I used the unmodified class names as table names because the DB is a detail, so the format of the table name is inconsequential in the majority of cases. I'm sure this borders on violating Least Astonishment, but if you're looking for the articles
table and you find Article
, I'd say that's a pretty easy pill to swallow. Feel free to let me know of any reasons you might disagree.
That's not to say that there isn't a problem here, though. I haven't yet put in a way to customize the table name, so if you're dealing with legacy data, you'd have to adapt the table to Perpetuity, which goes completely against the purpose of a Data Mapper. Martin Fowler even mentions in PoEAA that the Data Mapper pattern is great for when your data and your domain don't necessarily match. Everything that implies will take a fair bit longer to implement, but I would like to be able to customize the table names sooner rather than later.
And it does have the right attributes...kind of. It seems like a Ruby
String
should map tocharacter varying(255)
the way it does in ActiveRecord, but again, whatever. I understand that Perpetuity's PostgreSQL adapter is a work in progress.
This actually isn't a work-in-progress issue. It was a deliberate choice. Strings map to the text
type rather than varchar(n)
because, in Postgres, there is no difference between the two. If you want to limit a string to 255 characters, this should be done in your domain model, at least until I get constraints setup in the Postgres adapter.
Thank you, Jason
All in all, Jason's article was a great initial introduction to using Perpetuity with Rails and, as far as I know, the only one in existence that I didn't write. I'm thrilled that he felt that something I created was worth writing about in such a visible medium and I really do appreciate his help in spreading the word about Perpetuity, Ruby Object Mapper, and the Data Mapper pattern to the Ruby community. It's about time Rubyists had choices in ORMs that weren't Active Record implementations.
I'll be working on some blog posts and screencasts soon that will dive a lot deeper into creating a Rails app with Perpetuity, including some nice idioms that I've been able to tease out of its usage. I'm also working on a documentation website that will have all of this information on it.