ROM 5.1 released

We're happy to announce the release of the rom suite 5.1.0! This release includes a couple of bug fixes and mostly focuses on improvements in the ROM::Transformer API and adds plugin APIs to ROM::Repository and ROM::Changeset. Let's take a look at these nice additions!

Improved Transformer API

Currently ROM has two different APIs for defining custom mappers. One, called ROM::Mapper, is the old API that goes back to the very early days of the project. In version 4.0 a new API was introduced called ROM::Transformer, with the intention to eventually replace ROM::Mapper as Transformer API is more straight-forward and powerful than the original one.

In 5.1.0 we've made significant improvements that make using transformer-based mappers even better than before. Here's how it looks like in rom 5.1.0 with its new map method:

class JSONSerializer < ROM::Transformer
  map do
    nest(:address, %i[city street zipcode])
    deep_stringify_keys
  end
end

You can easily check out how it works by simply creating an instance and calling it with some data as input:

json_serializer = JSONSerializer.new

json_serializer.(
  [{ name: "Jane", city: "Cracow", street: "Street 1/2", zipcode: "12-345" }]
)
# => [{"name"=>"Jane",
#      "address"=>{"city"=>"Cracow", "street"=>"Street 1/2", "zipcode"=>"12-345"}
#    }]

Another big improvement is support for instance methods as mapping functions. Sometimes you need to access mapper's state in order to perform some mapping, this is where instance methods come in handy:

class JSONSerializer < ROM::Transformer
  map do
    # map zipcode using the corresponding instance method
    map_value(:zipcode, &:normalize_zipcode)
    nest(:address, %i[city street zipcode])
    deep_stringify_keys
  end

  def normalize_zipcode(zipcode)
    # do whatever you need
  end
end

Transformers are typically registered within a rom container, this part has been simplified too and you can now do it in one line, which is consistent with other rom components. If we wanted to register our JSONSerializer to be a mapper used by the users relation, we could simply do this now:

class JSONSerializer < ROM::Transformer
  relation :users, as: :json

  map do
    nest(:address, %i[city street zipcode])
    deep_stringify_keys
  end
end

Then the transformer will be available under :json identifier:

# assuming `users` is our relation
users.map_with(:json).to_a

Plugin API for Repository and Changeset

Starting from rom 5.1.0 you can write plugins for repositories and changesets. This is a huge improvement that you should find useful whether you're integrating rom with your library or just want to DRY-up code in an application. Currently there are no built-in plugins for repositories or changesets, but it's very likely we'll be adding some in the near future.

Here's an example of a repository plugin that sets "default scope" for a repository root relation:

module DefaultScope
  class ScopedRelation < Module
    attr_reader :name, :view

    def initialize(name, view)
      @name = name
      @view = view
      define_relation_reader
    end

    private

    def define_relation_reader
      relation_view = view

      define_method(name) do
        super().public_send(relation_view)
      end
    end
  end

  def self.apply(repo, view:)
    repo.prepend(ScopedRelation.new(repo.root, view))
  end
end

ROM.plugins do
  register :default_scope, DefaultScope, type: :repository
end

Now we can enable our plugin in a root repository:

class PostRepo < ROM::Repository[:posts]
  use :default_scope, view: :published
end

That's it - the repository will always use posts scoped to its published view.

Release information

This is a backward-compatible release, which means upgrading should not break your code. If you have any issues please report them. Please refer to 5.1.0 CHANGELOG for more information.


Announcing rom-http

Today we have another exciting release for you - rom-http 0.8.0. This is the biggest release of this adapter so far, as it's been integrated fully with the core APIs. This means you can now use rom-http to build HTTP client libraries, and leverage powerful auto-mapping capabilities of rom-rb.

This adapter works in the same way as any other rom-rb adapter. You configure a gateway, register relations with schemas and voilĂ  - you can talk to remote HTTP APIs now. Let's see how this looks in actual code.

GitHub API Example

To connect to a remote HTTP API you simply provide the uri and which response/request handlers should be used, in this case we will set :json:

config = ROM::Configuration.new(:http, uri: "https://api.github.com", handlers: :json)

Now we can define a relation class. For the purpose of this example we'll use a relation that will query /orgs end-point:

module GitHub
  module Resources
    class Organizations < ROM::Relations[:http]
      schema(:orgs) do
        attribute :id, Types::Integer
        attribute :name, Types::String
        attribute :created_at, Types::JSON::Time
        attribute :updated_at, Types::JSON::Time
      end

      def by_name(name)
        append_path(name)
      end
    end
  end
end

config.register_relation(GitHub::Resources::Organizations)

rom = ROM.container(config)

We only defined a sub-set of all the attributes, mappers will reject extra keys for us and give us back simpler data structures. Notice the by_name view in this class - it appends organization name to the base path. For example org/rom-rb is the path we want to use to find an organization named rom-rb.

Relation#append_path is just one of the many convenient methods that are available, that will help you in constructing HTTP queries. Refer to API documentation for more information.

Let's see this in action now:

orgs = rom.relations[:orgs]

# Plain hashes by default unless you set `auto_struct true` globally
orgs.by_name('rom-rb').one
# {:id=>4589832, :name=>"rom-rb", :created_at=>2013-06-01 22:03:54 UTC, :updated_at=>2019-04-03 14:36:48 UTC}

# Auto-structs on demand
orgs.with(auto_struct: true).by_name('rom-rb').one
# #<ROM::Struct::Org id=4589832 name="rom-rb" created_at=2013-06-01 22:03:54 UTC updated_at=2019-04-03 14:36:48 UTC>

Sweet. Data automatically converted to the exact format that we wanted to have, with unspecified keys rejected and attribute values coerced to configured types.

Release Information

For more information refer to the CHANGELOG.

Give this adapter a try and tell us what you think!


  • 1 of 9