Just mappers? Upgrading from old 'rom-mapper' use

I’m reaching out for some advice because I’m not too familiar with ROM and am attempting to upgrade from an older use of the standalone rom-mapper gem to do some data mapping, which I suspect was inspired by this old post by Piotr Solnica.

Basically we have some classes that inherit from ROM::Mapper and the application is calling build/call on these in different ways. The application is using the latest available version of the rom-mapper gem where it can function standalone (0.5.1), but any version above that requires integration with rom (because it’s looking for rom/constants at a minimum).

I’d like to migrate this from using rom-mapper to using rom, but the upgrade guides don’t address this particular use case so I’m wondering how to proceed. Is it as simple as adding rom and continuing to use the ROM::Mapper inheritances because that class still exists? Or should I be trying to use ROM::Transformer or ROM::Relation instead, and what level of context is required to update the application to adhere to modern expectations of ROM?

Thanks for your help!

1 Like

Wow, yeah that is quite old. Based on whats happening in that blog post, it sounds like you just need data transformations and not persistence?

These tools have evolved considerably since the writing of that.

Rather than using ActiveModel::Validations (seeing @solnic recommend an AR lib is amusing), a far better tool today would be dry-schema / dry-validation.

You could probably rewrite the transformations using dry-transformer (formerly transproc).

I would be happy to go into more detail here if you give me a concrete use-case to show you.

1 Like

Yeah, it’s an old use case of this for sure. It appears to be a bunch of transformers/mappers, some more basic classes with calls to attribute and other written like this:

class Foo  < ::ROM::Mapper
  step do
    attribute("x", from: "other_x")
    attribute("y") do |y|
      # [...]
    end
  end

  step do
    # [...]
  end
end

Later, two or more classes will be chained together and fed to something like this:

mapper_chain.reduce(data) do |processed, mapper|
  mapper.build.call(processed)
end

There’s some additional work that happens where transformed data is further altered and .call is used again. So it could be that dry-transformer is right tool here. I was hoping to split the difference by first bringing the dependencies as up-to-date as possible before committing all this code to a rewrite.

attribute("x", from: "other_x")

This looks like copy_keys

class Foo < Dry::Transformer::Pipe
  define! do
    copy_keys other_x: :x
  end
end
mapper_chain.reduce(data) do |processed, mapper|
  mapper.build.call(processed)
end

this would end up looking more like

mapper_chain.reduce(:>>).call(data)

assuming mapper_chain is a list of transformer instances.

The modern convention is to use dry-container to instantiate them instead of calling build

1 Like

Thanks for the tip! I’m definitely going to have to dig into the documentation a bit and see if I can translate all of it to more modern conventions.

1 Like

@brcarp Did you end up with a happy solution? Can you give any hints as to what worked? I expect I’ll be needing to implement low level transformations as well.

It’s in our backlog, but hasn’t been addressed yet. So no solutions to share at this point, but I can update later if there are any developments.