Flattening and mapping nested data structures

#1

I have the following relations:

class HomesRelation < ROM::Relation[:sql]
  gateway :default

  schema(:homes, infer: true) do
    associations do
      has_many :attributes, through: :home_attributes
    end
  end
end
class HomeAttributesRelation < ROM::Relation[:sql]
  gateway :default

  schema(:home_attributes, infer: true) do
    associations do
      belongs_to :home
      belongs_to :attribute
    end
  end
end
class AttributesRelation < ROM::Relation[:sql]
  gateway :default

  schema(:attributes, infer: true)
end

Using my HomeRepository, I would like to query for all homes with their attributes like:

class HomeRepository < ROM::Repository::Root
  root :homes

  def all_with_attributes
    homes.combine(:attributes).to_a
  end
end

I am able to successfully retrieve the data, but am attempting to flatten the results using a Mapper and return Home objects. I would like these Home objects to have the following attributes:

  1. all of the attributes coming from the HomesRelation schema
  2. each associated attribute nested in the results of homes.combine(:attributes).to_a

Given the following raw data returned from the relation:
[{:id=>1, :address=>"Fake Address", attributes: [{:attr1=>"attr value"}, {:attr2=>"attr value"}]}]

I’d like to map this into something like:
#<Home id=1 address="Fake Address" attr1="attr value", attr2="attr value">

Anyone have any thoughts on how to do this/best practices? Thanks!

#2

Hey Alex!

This is possible by using custom mappers. You can do something like this:

require 'rom/transformer'

class HomeMapper < ROM::Transformer
  register_as :flat_attrs
  relation :homes

  def call(data)
    data.map { |tuple|
      home = tuple.dup
      attrs = home.delete(:attributes)

      home.update(attrs.map.with_index { |a, i| [:"attr#{i}", attr[:attr1]] }.to_h)
    }
  end
end

your_rom_config.register_mapper(HomeMapper)

# then you'll be able to do:

homes.combine(:attributes).map_with(:flat_attrs).to_a

The only trick is that if you want to use auto-structs, you will also have to provide a customized schema. I’m not sure how exactly your schemas look like so it’s hard to say how to do it. I suspect attr1 etc. is not the actual column names.

If you could provide an executable script that sets up tables and relations, I could tweak it to make it work like you want.

Cheers!

#3

Thanks @solnic! And apologies for taking so long to respond. I am going to take some time to dive deeper into this and will send over an executable script if I get stuck.

This covers retrieving a Home and that home’s associated attributes.

Any thoughts on best practices to create a new Home where the incoming params from the client are flattened attributes?

The incoming params would be something like:
{:address=>"Home1", attr1: "attr value1", attr2: "attr value2"}

I would like to create 3 new entries in the db:

  1. “Home1” stored in the address column on the homes table
  2. The PK for the home and the PK for attr1 stored in the home_attributes table as references
  3. The PK for the home and the PK for attr2 stored in the home_attributes table as references

I know I will have to find the PK for each incoming attribute; however, I am curious if ROM provides a transformation layer that I can use to change data going into the DB into a format that the DB expects.

Would I use a changeset?

Happy to clarify any of the above if need be. Thanks again for your help!