Dear friends, please watch President Zelenskyy's speech. 🇺🇦 Help our brave mates in Ukraine with a donation.

ROM - Mapping is everything

Episode #30PRO

I've told you about the importance of mapping things to each other several times already.

Linking unknown concepts to something we know and can work with, not only speeds up the learning process but is a source of creativity.

In the digital world, where no idea is original, creative people link existing ideas to form completely new concepts.

But how could we make use of this thought in development?

Well, if you have data, where it is problematic to work with, an easy way to map your incoming data structure to something we could easily work with, is a tremendous time saver, and a window to more efficiently bring innovations to your projects.

Mapping data in ROM

In ROM, If you use relations, the result is returned in a form of plain hashes by default.

authors = container['persistence.rom'].relations[:authors]
authors.to_a
=> [{:id=>1, :first_name=>"Seb", :last_name=>"Wilgosz", :nickname=>"swilgosz"}]

It is not very convenient to work with such objects, however simple they are. If you've watched my previous episode, you already know, that Repositories simplify this, by automatically mapping your data to struct objects, with all attributes accessible as getters which is easier to play with.

repo = container['repositories.authors']
repo.all.first
=> #<ROM::Struct::Author id=1 first_name="Seb", last_name="Wilgosz", nickname="swilgosz">
repo.all.first.first_name
=> "Seb"

However, this is just a default behavior, that you can disable, or tweak further.

In this episode, I'll dig a bit into the automatic mapping of your objects into different data structures, and hopefully give you some ideas about when they may be useful.

We'll cover the examples for:

  • default mapping
  • custom entities
  • and custom mappers, writing a small Event Store adapter.

Let's start then.

Automatic structs in relations

If you want to use relations directly in your gem or app, you can still enable auto-struct, by setting the auto_struct option to true.

module Sandbox
  module Persistence
    module Relations
      class Authors < ROM::Relation[:sql]
        schema(:authors, infer: true)

        # maps the data to ROM::Struct::Author object
        auto_struct :true
      end
    end
  end
end

Now when I'll read my author from the database, it'll be automatically instantiated into an object, of which I can easily access all the attributes!

authors.first
# => #<ROM::Struct::Author id=1 first_name="Seb" last_name="Wilgosz" nickname="swilgosz">
authors.first.first_name
=> "Seb"

In this case, the first name is automatically read from the corresponding column, as I use an SQL data store.

It's cool, however, what if I'd like to add custom methods to my struct classes?

Custom Entities

For this, we can set our custom struct objects which we will have full control over.

In my relation, I can set the struct_namespace to Entities

Then in my application, I'm going to create a base entity object, that inherits from ROM::Struct.

# frozen_string_literal: true

module Sandbox
  class Entity < ROM::Struct
  end
end

This is not obligatory, but I find it a good practice to rely on objects I control instead of objects controlled by a library, and it's easier to update the code later.

Having that I can now add my author's entity.

# app/entities/author.rb

module Sandbox
  module Entities
    class Author < Entity
      def full_name
        first_name << " " << last_name
      end
    end
  end
end

Inside I can define a method named:full_name, and with this, I get my author objects instantiated automatically to the Author entity. Immediately I can access my newly defined full_name method from the console.

authors.first
# => #<ROM::Struct::Author id=1 first_name="Seb" last_name="Wilgosz" nickname="swilgosz">
authors.first.full_name
=> "Seb Wilgosz"

Repositories make it easier.

In my application, I'll rarely call relations directly though, if ever. Usually, I'll work with repositories instead, and those do the struct mapping by default.

Let me move the config to the base repository of my application.

Because repositories do the auto_struct by default, I can remove this line, and just specify the namespace for my Entities.

# app/repository.rb

module Sandbox
  class Repository < ROM::Repository::Root
    include Deps[container: 'persistence.rom']

    struct_namespace Entities

    # ...
  end
end

Now I can have the mapping configuration set in a single place while keeping the behavior untouched.

authors_repo.authors.first
# => #<Sandbox::Entities::Author::Author id=1 first_name="Seb" last_name="Wilgosz" nickname="swilgosz">
=> "Seb Wilgosz"

That’s it, for the very basic usage of the automating mapping functionality. If you are interested only in that, you may finish the episode here.

However, in some scenarios, you’ll need a custom mapping ability. One example would be to read a stream of events from the event store.

Let me show you how to do this.

Subscribe to Hanami Mastery PRO

This is only a preview of the episode. Subscribe to Hanami Mastery PRO to see the full video, and access several other premium resources. Thanks for watching, and see you in the next one!

Thanks

That's all for today, you're awesome, and see you in the next episode!

Do you know great Ruby gems?

Add your suggestion to our discussion panel!

I'll gladly cover them in the future episodes! Thank you!