ROM 0.6.0 Released

We're happy to announce the final release of ROM 0.6.0! This version brings a lot of improvements and simplifies the internal architecture of ROM. There are over 700 commits between 0.5.0 and this release which is a result of fantastic feedback that we've received from early adopters and many contributions from new people.

This release includes updates of the following gems:

The following new adapters have been released too:

Class-based setup

The biggest change in this release is the ability to define your own relation, mapper and command subclasses that ROM will instantiate for you and expose through its registry. The DSL-based setup is still available but using classes turned out to be a better approach in bigger contexts like web-applications.

Here's a simple Relation subclass definition:

class Users < ROM::Relation[:memory]
  def by_name(name)
    restrict(name: name)
  end
end

Lazy-relations with data-pipelining

Previous version of ROM had the concept of Reader which turned out to be confusing. After lots of discussions it was replaced by a new interface for accessing relations. This change makes mapping relations explicit as you need to provide the name of mapper(s) that you want to use when loading a relation.

Lazy relations can be used with any object that implements mapper interface but the simplest way to levarage this feature is to register ROM mappers for quick access:

ROM.setup(:memory)

class Users < ROM::Relation[:memory]
  def by_name(name)
    restrict(name: name)
  end
end

class UserMapper < ROM::Mapper
  relation :users
  register_as :entity

  model User

  attribute :name
end

rom = ROM.finalize.env

user_entites = rom.relation(:users).as(:entity)
user_entities.by_name('Jane')

Thanks to data-pipelining feature you can send a relation through multiple mapping objects. This turned out to be very useful for decorating data:

class UserPresenterMapper < ROM::Mapper
  relation :users
  register_as :presenter

  model UserPresenter
end

user_presenter = rom.relation(:users).as(:entity, :presenter).by_name('Jane').first

On top of that there's also an experimental partial-application feature where you can grab a reference to a relation with partially applied arguments:

users_by_name = rom.relation(:users).as(:entity).by_name

users_by_name['Jane'].first

Improved and simplified command interface

Commands are now composable and you can access them directly and call them. This makes it more obvious what's going on. We are still working on a generalized error/success handling that will be based on a 3rd party dependency since this is really out of ROM's scope.

You can define your commands using class interfaces too:

ROM.setup(:memory)

class CreateUser < ROM::Commands::Create[:memory]
  relation :users
  register_as :create
  result :one
end

rom = ROM.finalize.env

create_user = rom.command(:users).create

rom.command(:users).try { create_user.call(name: 'Jane') }

Command API is growing quickly and more database-specific features will be implemented soon on top of existing functionality.

Improved adapter infrastructure

Adapter infrastructure has been simplified so now an adapter can provide its Repository and Relation subclasses and register itself under specific identifier. It means that a ROM adapter is just a couple of extensions that allow you to use ROM with various data sources. If the adapter can work in full read/write mode it can also provide its own Command subclasses.

Setting up an adapter is now simpler too - ROM doesn't make any assumptions about repository instantiation and you can also instaniate it yourself and pass the instance to ROM.setup. This feature is already used by rom-rails where you can configure your repositories in an initializer if you don't want ROM to infer configuration from database.yml.

Another nice addition is a set of Lint tests that are useful for adapter creators. We even made the effort to make it work with both MiniTest and RSpec. Here are a couple of linters from the new CSV adapter:

require 'rom/lint/spec'

describe ROM::CSV::Dataset do
  let(:data) { [{ id: 1 }, { id: 2 }] }
  let(:dataset) { ROM::CSV::Dataset.new(data) }

  it_behaves_like "a rom enumerable dataset"
end

describe ROM::CSV::Repository do
  let(:repository) { ROM::CSV::Repository }
  let(:uri) { File.expand_path('./spec/fixtures/users.csv') }

  it_behaves_like "a rom repository" do
    let(:identifier) { :csv }
  end
end

ROM's adapter ecosystem is growing fast - we have Redis, Neo4j and InfluxDB in the works already and more are planned.

Form objects for Rails

This release also includes updates to rom-rails - the most notable change is the addition of ROM::Model::Form which allows you to define form objects that work nicely with ROM. It is using Virtus for params handling and ActiveModel::Validations for validations.

Our Rails tutorial was updated too and explains how to use form objects in ROM. Here's an example of a simple form object definition:

class NewUserForm < ROM::Model::Form
  commands users: :create

  input do
    attribute :name, String

    timestamps
  end

  validations do
    validates :name, presence: true, uniqueness: true
  end

  def commit!
    users.try { users.create.call(attributes) }
  end
end

Upgrading from 0.5.0

The setup DSL works the same but you probably want to switch to class-based setup as it's easier to organize your code that way.

Please let us know if you have any trouble with the upgrade on our zulip channel or submit an issue describing problems you're having.

About 1.0.0

This has been amazing couple of months for ROM. The community is growing, we're seeing more and more people contributing to the project and geting great feedback from the real world usage.

Even though initially we planned to release 1.0.0 as soon as ROM proves itself on production, based on feedback from the community and discussing the release process we decided to give it few more months to push the first stable release.

We prefer to polish the interfaces and implement a bunch of important missing features that will make ROM simpler to use. In addition to features it's also important to provide great documentation and user guides, which is one of the priorities of 1.0.0 release.

Thank you!

Thank you to all the early adopters and new contributors. This release would not be possible without your help! It's amazing to see how the project evolves thanks to you.

We have more great news, so stay tuned and watch this space :)