Code and Cake

An ORM won't save you from SQL, and that's ok


A common criticism of ORMs is that they’re a leaky abstraction on top of SQL. Consider this controller action to get a list of directors for a video using Ruby’s ORM, ActiveRecord.

def directors
  video = Video.find_by(title: params[:title])

  render(json: video.directors)
end

Despite using an ORM, there’s a lot you have to know here about SQL!

  • Is the title column indexed?
  • Does the title column have a unique constraint on it?
  • What fields are we SELECTing out of the database? Do we need them all?
  • How does this interact with transactions?

I’m sure you could come up with even more. It seems like the ORM critics have a point: you still need to know SQL when using an ORM, so why bother?

The ORM-less solution

Consider a similar action with raw SQL. Here we’ll only use ActiveRecord’s find_by_sql method to have a rough approximation of something like C#’s Dapper.

def directors
  directors = Director.find_by_sql(<<~SQL, params[:title])
    SELECT directors.name FROM directors
    INNER JOIN videos_directors
      ON videos_directors.director_id = directors.id
    INNER JOIN videos
      ON videos.id = videos_directors.video_id
    WHERE videos.title = ?
  SQL

  render(json: directors)
end

Here we have some code that’s harder to read, but that’s a fixable problem! We’ll just make a DirectorRepo class:

class DirectorRepo
  def find_by_video_title(title)
    # The SQL from above!
  end
end

def directors
  repo = DirectorRepo.new
  directors = repo.find_by_video_title(params[:title])

  render(json: directors)
end

And it looks like we’ve done it–we’ve solved the need for an ORM!

The problem

Now let’s step back a moment: imagine you’re a new developer on this project, and you encounter the perfect ORM-free code we just wrote:

def directors
  repo = DirectorRepo.new
  directors = repo.find_by_video_title(params[:title])

  render(json: directors)
end

You’re pretty sure DirectorRepo#find_by_video_title makes a SQL call, what does it do exactly?

  • Is the title column indexed?
  • Does the title column have a unique constraint on it?
  • What fields are we SELECTing out of the database? Do we need them all?
  • How does this interact with transactions?

If these look familiar to you, you’re right. You’ve ended up with the same questions as the ORM example. This makes sense though; you can’t escape learning SQL. But consider that we have some new questions now too:

  • How are directors and video titles linked?
  • How many SQL queries does this method make?
  • How do I filter by a different field?
  • How do I link directors to other movies they’ve directed?

You could look at the source code to find the answer to these, but note that these are questions we have to ask every single time for every single project. There’s no uniform answer to any of these. You could define a pattern and follow it, but it’s still going to be a pattern new developers have to learn when starting your project. Wait, an abstraction on top of SQL that you have to learn along with SQL sounds familiar.

Return of the ORM

Congratulations! You’ve just invented your own ORM, and it’s an ORM that’s missing a bunch of features and that no one knows anything about. You have all the downsides of a traditional ORM–a leaky abstraction you have to learn on top of SQL–but none of the upsides–a well-understood and Google-able interface, or useful out-of-the-box features.

An ORM doesn’t save you from SQL. It saves you from your own ORM.