hanami / model

Ruby persistence framework with entities and repositories

Home Page:http://hanamirb.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Two problems with one-to-many associations in Hanami 1.1.0.beta1

erubin opened this issue · comments

I have a has_many/belongs_to association:

class EventRepository < Hanami::Repository
  associations do
    has_many :actions
    ...
  end

class ActionRepository < Hanami::Repository
  associations do
    ...
    belongs_to :event
  end

When I call this EventRepository method:

  def add_action(event, data)
    assoc(:actions, event).add(data)
  end

I get this error:

  KeyError:
    key not found: :id
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/associations/has_many.rb:209:in `fetch'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/associations/has_many.rb:209:in `_build_scope'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/associations/has_many.rb:55:in `initialize'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/association.rb:20:in `new'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/association.rb:20:in `build'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/repository.rb:472:in `assoc'
  # ./lib/backend/repositories/event_repository.rb:21:in `add_action'
  ...

I look into has_many.rb and found that the id was present in subject, but it gets removed when to_hash is called in initialize. I got around this by adding this to_hash method to my entity:

class Event < Hanami::Entity
  attr_reader :id
...
  def to_hash
    if id
      super.merge(id: id)
    else
      super
    end
  end

When I call this EventRepository method:

  def create_with_actions(data)
    assoc(:actions).create(data)
  end

I get this this error:

  Hanami::Model::Error:
  ...
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/associations/has_many.rb:73:in `rescue in create'
  # /home/eric/.gem/ruby/2.3.4/gems/hanami-model-1.1.0.beta1/lib/hanami/model/associations/has_many.rb:63:in `create'
  # ./lib/backend/repositories/event_repository.rb:16:in `create_with_actions'
  ...
  # ------------------
  # --- Caused by: ---
  # NoMethodError:
    undefined method `to_hash' for #<ROM::Repository::CommandProxy:0x005625d3e176d0>
    Did you mean?  to_s
  #   /home/eric/.gem/ruby/2.3.4/gems/hanami-utils-1.1.0.beta1/lib/hanami/utils/hash.rb:145:in `initialize'

I can't figure out what's going on in this one...

@erubin Hi, I'm sorry about these problems. I tried to reproduce them, but I couldn't. Please check this demo project I created for you: https://github.com/jodosha/hanami-model-447

The only issue that I get is the returning value of #add_action is ROM::Struct::Action, instead of Action entity. /cc @mereghost

Anything else works fine. Would you like to check the unit tests from the demo project and compare with your usage? Thanks.

I'm closing this as I manually verified that this isn't an issue. @erubin If I'm missing something, please reopen this ticket. Thanks!

If you add attributes to the Entity

class Event < Hanami::Entity
  attributes do
    attribute :title, Types::String
    attribute :actions, Types::Array
  end
end

When you call add_action you get this error:

     Failure/Error: assoc(:actions, event).add(data)
     
     KeyError:
       key not found: :id
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/model/associations/has_many.rb:189:in `fetch'
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/model/associations/has_many.rb:189:in `_build_scope'
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/model/associations/has_many.rb:45:in `initialize'
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/model/association.rb:20:in `new'
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/model/association.rb:20:in `build'
     # /var/lib/gems/2.3.0/gems/hanami-model-1.1.0.beta3/lib/hanami/repository.rb:471:in `assoc'
     # ./lib/backend/repositories/event_repository.rb:13:in `add_action'
     # ./spec/backend/repositories/event_repository_spec.rb:27:in `block (3 levels) in <top (required)>'

@erubin Sorry for the late feedback, but I only had now the time to look at it again.

We require an entity to have an :id in its attributes. It's a fundamental prerequisite for it to work.

Having an identity (ID) is the main difference between an entity and a value object, where the identity is determined by its values.

I'm sorry, but we can't support the absence of :id for entities.


If you need to work with objects without an identity, my suggestion is to leave the ID to Event and then to use a value object.

That means:

# lib/backend/entities/event.rb
class Event < Hanami::Entity
end
# lib/backend/value_objects/event_value.rb
require "dry-struct"

# Sorry for the silly name EventValue :D
class EventValue < Dry::Struct
  attribute :name, Hanami::Model::Types::String
  attribute :actions, Hanami::Model::Types::Array
end
# lib/backend/repositories/event_repository.rb
class EventRepository < Hanami::Repository
  associations do
    has_many :actions
  end

  def create_with_actions(data)
    assoc(:actions).create(data)
  end

  def add_action(event, data)
    assoc(:actions, event).add(data)
  end

  def find_event_value(id)
    aggregate(:actions)
      .where(id: id)
      .limit(1)
      .map_to(EventValue)
      .one
  end
end
irb(main):001:0> EventRepository.new.find_event_value(1)
[backend] [INFO] [2017-10-19 15:57:00 +0200] (0.000401s) SELECT "id", "name", "created_at", "updated_at" FROM "events" WHERE ("id" = 1) ORDER BY "events"."id" LIMIT 1
[backend] [INFO] [2017-10-19 15:57:00 +0200] (0.000412s) SELECT "actions"."id", "actions"."event_id", "actions"."name", "actions"."created_at", "actions"."updated_at" FROM "actions" INNER JOIN "events" ON ("events"."id" = "actions"."event_id") WHERE ("actions"."event_id" IN (1)) ORDER BY "actions"."id"
=> #<EventValue name="signup" actions=[{:id=>1, :event_id=>1, :name=>"visit", :created_at=>2017-10-05 15:25:26 UTC, :updated_at=>2017-10-05 15:25:26 UTC}]>