hanami / model

Ruby persistence framework with entities and repositories

Home Page:http://hanamirb.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Entity to_h - nested attribute entity is not converted to hash

iJackUA opened this issue · comments

Is it by design? Because I was expecting it to work a bit different.

Suppose I have two Entities

class User < Hanami::Entity
  attributes do
    attribute :id, Types::Int
    attribute :auth_data, Types::Entity(::UserAuthData)
  end
end
class UserAuthData < Hanami::Entity
  attributes do
    attribute :hi, Types::String
  end
end

I can initialize it that way

user_params = {id: 3, auth_data: {hi: "hello"}}
user = User.new(user_params)

now when I do user.to_h

{:id=>3,
 :auth_data=>#<UserAuthData:0x000055d8e7fcf600 @attributes={:hi=>"hello"}>}

User entity is serialized to hash, but enclosed UserAuthData is still an object, we did not return to the same user_params

The only way I see to do that is
Hanami::Utils::Hash.new(user).to_hash
or
Hanami::Utils::Hash.deep_serialize(user)
these methods return {:id=>3, :auth_data=>{:hi=>"hello"}} as expected

Maybe Entity object by itself need to have a helper to serialize itself to real nested hash?
Actually I would expect from entity_object.to_h to do this by default, but maybe I miss some consideration.

Duplicate of #470.

You noticed you can use Hanami::Utils::Hash.deep_serialize, which is the recommendation from that issue.

But we didn't really answer the question why this doesn't happen by default though. I agree it's surprising that it doesn't work that way. Any input @mereghost? I wonder if a workaround could be adding an optional param, like#to_h(deep: false) might make sense?

I vaguely remember a past issue of this up ahead in the framework. Also that's how ruby's #to_h works. It only operates on the instance leaving nested objects intact.

We could provide a way to make sure that the object is deep serialized into a hash for convenience, but that's already provided by hanami-utils which we depend on.

Thanks @cllns it seems I searched not very good before creating an issue.
There are really valid points (of why current behaviour is "as it is now"):

  • that is how ruby's #to_h works
  • for JSON API we need to use better serializers (what I actually do) than to_h

My potential use case to have recursive to_h serialization was in adopting Entity in Trailblazer's Operation. Input of the typical Operation is Hash of params from Action. Later on, an Entity is retrieved based on params and data from Entity is being manipulated during next steps of the Operation. Idea was to use Entity as DTO through these steps, so each step can modify Entity attribute (and nested also).

But at the last step of the Operation I want to persist this Entity to DB via Repository.
And Repository's update method takes Hash (of attributes) as a data param.

So I was searching a way to get back a Hash representation of "complex" Entity.
And yes Hanami::Utils::Hash.deep_serialize(user) can be used, but it took me 30 minutes to find it out.
Maybe a more straightforward way to this can be introduced?

P/S Actually my another and wider question first time I tried Hanami was "Oh great, I can get Entity as a "select" query result from Repo, but wait... why I can not put ready made Entity object as a data
in "insert/update" command in Repo?" :)

@iJackUA I'm sorry for the veeery late response.

We really need to up our game on the documentation side of things (contributions are always welcome ;)).

About the insert/update part: if you use the Repository#create method you can safely pass an entity. For the Repository#update method we always take the arguments provided as canon (as we don't do dirty tracking), so if you pass in an Entity instance as the second parameter (the first being the id) it will set whatever attributes are set there(even stuff like created_at etc).

Think of it as a poor man's changeset (which is a concept we might introduce in the future). You pass in what you want to update and we do it.