How to filter attributes with mapper in a chain?


#1

I created a mapper to filter down the attributes before sending data to an entity mapper. But the filter mapper isn’t working, so the entity mapper trips on the extra attributes. Can anyone suggest what I am doing wrong?

Mapper code:

    class AccountMapper < ROM::Mapper
      register_as :filter_attributes
      relation :accounts
      reject_keys true
      symbolize_keys

      attribute :id
      attribute :name
    end

    class AccountEntityMapper < ROM::Mapper
      register_as :entity
      relation :accounts
      model Domain::Account
    end

Relation code:

    class Relation < ROM::Relation[:sql]
      register_as :accounts
      dataset :accounts

      def by_id(id)
        where(id: id)
      end

      def with_relationships
      	inner_join(:account_relationships, account_id: :id)
      end

      def related_to_user_id(user_id)
      	with_relationships.where(user_id: user_id)
      end
    end

    class RelationshipRelation < ROM::Relation[:sql]
      register_as :account_relationships
      dataset :account_relationships


    end

My repository calls the code thusly:

accounts.as(:filter_attributes, :entity).related_to_user_id(user.id).limit(1).one

Expected: <Domain::Account ...>

Actual:

    Failure/Error: expect(repo.first_for_user(user)).to eq(account_1)
     Transproc::MalformedInputError:
       Failed to call_function map_array with [[{:id=>599, :name=>"one", :account_id=>599, :user_id=>361}]] - Failed to call_function constructor_inject with [{:id=>599, :name=>"one", :account_id=>599, :user_id=>361}] - Unknown attribute(s) [:account_id, :user_id] for ROM::Struct[Account]

When I remove the call to .related_to_user_id(user.id) my test works great (since there are no other DB rows and those extra columns don’t get brought in).


#2

@rep Did you see this issue: https://github.com/rom-rb/rom-mapper/issues/9 ? Try remove symbolize_keys and try again.


#3

Thanks. Interesting link. But I get the same result without symbolize_keys.


#4

Why do you use symbolize_keys with adapter that always returns hashes with symbolized keys? Also why do you use reject_keys when you can define relations with the attributes you want?

And last, but not least, why don’t you just use rom-repository and skip the manual mapping configuration? :smile:

One more thing - it’s symbolize_keys true where did you see examples without an argument? It’s a mistake, w/o an argument it’s a getter, so it doesn’t do anything, just returns current value of @symbolize_keys ivar.


#5

I included the call to symbolize_keys for honesty…I’m just cargo-culting from the Mappers guide (which has a call to that without the argument), the only documentation I can find. How am I supposed to find out what it is and what it does?

I do not know how to define relations with the attributes I want, or that that’s what I am supposed to do. How would I know that?

The “repository” I mentioned is indeed in a rom-repository. How does rom-repository skip “the manual mapping configuration”? Where do I find these usage patterns and concepts? Our system uses domain entity and value objects defined by another gem; if ROM can’t handle that use case gracefully it’s not appropriate for us. (I think it can, but I’m explaining why that line of questioning isn’t germane.)

Sadly our project decided last night to abandon ROM because what limited documentation exists is extremely thin and so often wrong. A real disappointment.


#6

[quote=“rep, post:5, topic:43, full:true”]
I included the call to symbolize_keys for honesty…I’m just cargo-culting from the Mappers guide (which has a call to that without the argument), the only documentation I can find.[/quote]

That’s a mistake, I’ll fix it in the docs. Sorry about that.

Looks like it’s not fully covered by guides except that short example with renaming keys section in mapper guide.

In your examples you show an sql relation so I assumed you’re using rom-sql, this means you can use standard query dsl to cherry-pick which attributes a given relation returns. When using repositories you can define relation views explicitly and it’s described in the repository guide.

In most of the cases you can leverage relation API to define lower-level data structs that relations return, this is really all you need in majority of the cases and it is faster since it’s using native db features. On top of it repositories give you a simple interface to compose relations together that will produce aggregates, something that would require manual mapping configuration if you didn’t use repositories.

For now the only piece of docs about repository is the repository guide I linked above.

Ah, so you’re not using SQL? I’m not sure what your use case is exactly so I can’t provide more information.

Sorry to “hear” that. What are you going to use instead, if I may ask?


#7

The “what it is and what it does” is by far the biggest need. Especially for something as different as ROM, without concrete, working examples of code that does various common things to illustrate how the parts work together, this is not a usable project. I’ve used AR, Madeleine, Hibernate, WebObjects, raw database code in PHP, against FreeTDS, Microsoft Access…this was by far the most inscrutable experience I’ve had trying to use a data store. And I think lack of documentation and example code is the primary reason why.

Good documentation would give me a way to (correctly) understand utterances like your kind reply, which contains the following “buzzwords” that all have special meaning in this project and require contextual knowledge to apply correctly:

  • keys
  • mapper
  • relation
  • repository
  • relation view
  • compose
  • aggregate

We are indeed using SQL. We use an :entity mapper to convert ROM::Structs to Domain::User or whatever. You’ve intimated a few times that there’s something suboptimal about that, even though the project is called “Ruby Object Mapper”, this specific use case is featured in the docs, and you have said that different people will use ROM differently.

We needed something that would Just Work™ while saddling us with the least runtime baggage and design assumptions. It’s obvious to us that ROM “has the goods”, but for whatever reason we haven’t been able to work out what code to type in for even simple cases. After quite some time of this we need to move on. Sadly we are going to AR to implement our Persist component/gem, chosen over Sequel at the stakeholder’s request (and I can’t blame him). We simply need to deliver, and the team’s budget & tolerance for risk has been depleted by the time lost attempting to decipher ROM—so we go with what we know.


#8

I hear you. Thanks for replying. We’ll be working on documentation, it’s now one of the biggest priorities, next to finishing rom-repository. I’m sorry it didn’t work for you. Maybe next time :smile:

This is also one of the reasons why I decided to write a whole book about it. I don’t think ROM docs can explain everything properly, it’s kind of a mind-shift as well, that’s why a book seems like a better fit and should be helpful.