#!/usr/bin/env ruby#!/usr/bin/env rubySee all parts · View the source · Follow the blog · Say hi on Twitter
We’ve spent a few weeks now exploring a next-generation web app architecture for Ruby, but we’re yet to cover one very important thing: database persistence.
How we choose to build our app’s persistence support will have a profound impact on its overall design.
We could follow the active record pattern and wind up with a select few models deeply intertwined with every aspect of our application’s functionality.
Or we could build a clean separation of responsibilities and make an app that remains easy to change. This is where rom-rb can help.
rom-rb is a flexible persistence and data mapping toolkit for Ruby. It separates queries and commands and offers powerful, workable abstractions at every point between our app and the data store. This mean there are a few concepts to learn, but the payoff is significant: a persistence layer that can be perfectly tailored to our app’s needs.
Let’s get started, then: follow the code and commentary below for an introduction to rom-rb and a playground for making your own experiments.
Over in the Gemfile, we’re installing the rom-rb gems from their master
branches, since we’ll be looking at some major improvements that are slated
for release later this month.
require "bundler/setup"Require the gems we need.
One thing to note here is that rom-rb is a multi-adapter toolkit. It
supports many kinds of data sources (and even allows you to use them
together!). For today, though we’ll stick to SQL. Apart from the “rom-sql”
adapter, the only other require we need is “rom-repository”, which will
act as our app’s primary persistence interface.
require "rom-sql"
require "rom-repository"In this playground, we’ll be using rom-rb to work with articles in our database.
The first thing we need to define is a Relation. Relations are the interface to a particular collection in our data source, which in SQL terms is either a table or a view.
module RelationsThis relation is for the articles table.
  class Articles < ROM::Relation[:sql]Define a canonical schema for this relation. This will be used when we use commands to make changes to our data. It ensures that only appropriate attributes are written through to the database table.
    schema(:articles) do
      attribute :id, Types::Serial
      attribute :title, Types::String
      attribute :published, Types::Bool
    endDefine some composable, reusable query methods to return filtered results from our database table. We’ll use them in a moment.
    def by_id(id)
      where(id: id)
    end
    def published
      where(published: true)
    end
  end
endNow, let’s define a Repository. Repositories are the primary persistence interfaces in our app. Repositories contribute a couple of important things to a well-designed app:
module RepositoriesThis simple repository uses articles as its main relation.
  class Articles < ROM::Repository[:articles]Define a command to create new articles.
    commands :createDefine methods to return the article objects we want to use within our
app. Each of these can access the relation via articles and use its
query methods.
Unlike the query methods inside the relations, these ones should not be chainable. Their purpose is to return a set of articles for each distinct use case within our app. This means that our repository API (and therefore our persistence API in general) is a perfect reflection of our app’s persistence requirements.
    def [](id)
      articles.by_id(id).one!
    end
    def published
      articles.published.to_a
    end
  end
endrom-rb is built to be non-intrusive. When we initialize it here, all our relations and commands are bundled into a single container that we can inject into our app.
In any kind of framework, this setup will be taken care of for us, but because rom-rb is flexible, explicit setup for our playground is still nice and easy.
Configure rom-rb to use an in-memory SQLite database via its SQL adapter, register our articls relation, then build and finalize the persistence container.
config = ROM::Configuration.new(:sql, "sqlite::memory")
config.register_relation Relations::Articles
container = ROM.container(config)Since this is a standalone playground, run a migration to give us a database table to work with.
container.gateways[:default].tap do |gateway|
  migration = gateway.migration do
    change do
      create_table :articles do
        primary_key :id
        string :title, null: false
        boolean :published, null: false, default: false
      end
    end
  end
  migration.apply gateway.connection, :up
endFirst, get a repo to use.
repo = Repositories::Articles.new(container)Let’s see if we have any published articles
Nope, nothing. We’ve just created this table, after all!
repo.published
# => []
puts "Published articles?"
puts repo.published.inspectIt’s time to create an article then. We can do this via the repo too. rom-rb will give it back to us in a convenient struct for accessing the attributes of the new article.
repo.create(title: "Hello rom-rb")
# => #<ROM::Struct[Article] id=1 title="Hello rom-rb" published=false>
first_article = repo.create(title: "Hello rom-rb")
puts "\nCreate an article:"
puts first_article.inspectputs "\nPublished articles?"
puts repo.published.inspectBut we can find it by ID, because we’ve built our repo’s #[] method to
find an article regardless of published status.
repo[first_article.id]
# => #<ROM::Struct[Article] id=1 title="Hello rom-rb" published=false>
puts "\nFind first article by ID"
puts repo[first_article.id].inspectLet’s create a published article now.
repo.create(title: "Hello world", published: true)
# => #<ROM::Struct[Article] id=2 title="An alien or sutin" published=true>
published_article = repo.create(title: "An alien or sutin", published: true)
puts "\nCreate a published article:"
puts published_article.inspectThis article should be in our list now!
repo.published
# => [#<ROM::Struct[Article] id=2 title="An alien or sutin" published=true>]
puts "\nPublished articles?"
puts repo.published.inspectThis is the end of our first little foray into the world of rom-rb. I hope you’ve enjoyed it! Next week we’ll look at a few more advanced features.
Until then, if you can clone this playground from GitHub and experiment:
git clone https://github.com/icelab/conversational-intro-to-rom-rb
cd conversational-intro-to-rom-rb
bundle
./intro.rb
Feel free to extend the playground, make your own changes and see how things work!
And if you want to learn more, check out the helpful documentation on the rom-rb website, and join the gitter chat if you have any questions!