#!/usr/bin/env ruby
#!/usr/bin/env ruby
See 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 Relations
This 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
end
Define 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
end
Now, 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 Repositories
This simple repository uses articles as its main relation.
class Articles < ROM::Repository[:articles]
Define a command to create new articles.
commands :create
Define 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
end
rom-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
end
First, 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.inspect
It’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.inspect
puts "\nPublished articles?"
puts repo.published.inspect
But 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].inspect
Let’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.inspect
This 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.inspect
This 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!