ROM, the Ruby Object Mapper is a set of libraries developed by the same people who also authored the famous Dry-rb gems.
ROM, for example, used as a part of database-persistence and model layers in Hanami framework. It’s backed by Sequel, which
performs lower level database interaction operations, but unlike Sequel and ActiveRecord, ROM is not simply another ORM.
The key concept of ROM is a separation of database-persistence logic from domain model logic and it has its roots in the
concept of domain-driven design for software applications. Being used to MVC frameworks, many of us have seen such an approach
to building an application, where one class performs all the business logic, all the database-related logic, and even form validations.
When applications grow, we tend to refactor these bloated classes, extracting form objects, validation objects and so on.
ROM also makes possible to completely decouple database-related actions from business logic. Of course, ROM is not an only
database-related thing. It also can be used to work with any data source, even some arbitrary structured files. ROM provides adapter abstraction,
that makes possible to write our own adapter for any possible data source.
Example: use ROM to map to already existing legacy database
Getting up and running
Assuming we have a database with the following structure:
One author has many books, each book has many reviews, each review belongs to a user.
We can create mappings for this database using ROM.
First, the connection:
The following is not mandatory, I just assigned all relations to instance variables inside the ruby file, to make work with
the example easier:
And now we can load this file from pry using
And we have @books, @reviews, @authors, @users relations already available.
Now we can query the database for needed data:
Looks pretty neat. Using simple and intuitive DSL, we’ve set up database mapping, associations and query methods, and are
ready to work with data.
ROM uses commands to make changes in database, i.e. to perform create, delete and update operations. Command is an object.
As any object, we can instantiate it for later use.
Commands are simple abstraction provided by ROM and are a foundation for more abstract one - changeset
Changesets also serve for making changes in the database, but being built on top of commands, they provide more advanced features,
such as custom mapping, associating data. I won’t touch these topics in this post, and you can find a brief description of them
in the documentation.
Repository provides a set of abstractions to read and write complex data. It can be viewed as an implementation of repository pattern from
domain-driven design concepts and may sound familiar to those who acquainted with Hanami or Phoenix frameworks. The principles used in
repositories of both are the same, and ROM repositories are basically similar.
Repositories can be used to perform complex data insertions:
ROM can even be used in Rails application, for example, if you plan to refactor your app using domain-driven design principles.
When used inside Rails, it can be either run alongside ActiveRecord, or completely replace it, and offer a possibility to connect to multiple databases.