Mapping nested entities and other transformation examples


#1

Hi all,

Been reading up on ROM and my first project is a ROM adapter for the RailsEventStore project. I am still getting my head around the shift from ActiveRecord to this paradigm, but ROM is what I’ve been looking for. rom-rb.org has been super helpful!

As I learn about adjusting to different ways of querying data, I’m still looking for examples of simple shifts from AR to ROM. I’ll save those for another time.

Question: How do I map nested entities to a specific model, for certain queries in my repository class? As far as I can tell, I can use map_to or map_with for the root entity.

Where can I find more examples of mapping and moving from AR to ROM? I’ve used rom-rb.org, but the mappers section is sparse, unless I missed where to see more examples.

Thanks!


#2

Could you come up with some simple example (in terms of data structures and their source)?


#3

@solnic the use case at the time was simply a many-to-one association and wanting to map the entity as well as the child object, each to their own class.

In retrospect, it sounds like maybe the mapper for the entity would also map the association attribute to the custom class for that association, potentially.


#4

I’d recommend using auto-structs with custom namespaces and then name struct classes after relation names. Remember that you can alias relations at run-time too. See here for more info.

If you want to rename a relation that’s part of the graph you’re loading, you can do this:

users.combine(:tasks).node(:tasks) { |t| t.as(:todos) }

If you have a struct class in your configured namespace that’s called Todo, it’s gonna be used for mapping associated tasks.

If you want to use #map_to, you can do it in a similar way, ie:

users.combine(:tasks).node(:tasks) { |t| t.map_to(Todo) }

#5

@solnic I’ve been using the auto-structs, but the node/as features are interesting as well.

I think the different strategies for mapping and where all you can do it is taking some time to understand. I would assume I can setup default mappers for relations or repositories, but haven’t had much luck doing that. Also, can Transproc DSL only be used in a custom mapper class, or elsewhere?


#6

@solnic I’ve got an example of a commit where I’m replacing a SQL join with ROM combine. There’s the “StreamEntries” which reference an “Event” ID. I want to get a list of Event entities, but they need to be ordered based on “position” in the StreamEntry entity. I’ve been attempting to not intermingle relations, but it’s proven challenging, particularly when combined with mapping.

This commit shows the change from SQL joins to ROM combine: https://github.com/joelvh/rails_event_store/commit/5a9809cdee2f34f12294658faf6a10596ee977f7

I tried using your node mapping suggestion, but we’re mapping to a custom class and something was causing an error where the mapper or combine was trying to access attributes of the mapped class via event[...]. I was able to avoid mapping before combining by using the mapper outside the relation work.

  • How can I call the mapper by it’s registered name when I have an array instead of a relation?
  • Can a nested entity be mapped to a custom class? (I assume it has to be a Hash-like object because you store the combine key on the nested object)
  • I’d also love to not have to loop over the result by calling map to grab the nested “event” object, but imagine that’s probably the most practical way.

The only reason this relation work is inverted is because the ordering of the results matters, but the ordering is not on the entity that I want to return from the relation/repository.

Any suggestions on the approaches taken in the example? (This is the project I’m learning ROM with)

Thanks!

P.S. The tests pass, just want to see how you would do this differently.


#7

This probably means that the order of mapping was not correct, ie you mapped to custom objects prior auto-mapping. Please make sure you tried it with latest rom-core and rom-mapper because this is exactly the issue I fixed recently.

Just call it, mappers work with anything array-like.

Yes, via node(:foo) { |foo| foo.map_to(CustomClass) } like I showed before or by using a custom struct with attribute set to CustomClass (assuming you’re using rom/struct or dry/struct).

If it bothers you, define a mapper that hides it :slight_smile:


#8

Thanks @solnic. Would it be appropriate to post some transformation examples to get feedback on how to best translate them to the ROM way or optimize them if they’re an attempt to use ROM? I have various examples that I can show in plain Ruby that would be helpful to see how you’d approach them, as I haven’t found many examples in the wild.

Thanks!


#9

Yes no problem :slight_smile: