I searched the docs for how to implement relationships among entities (eg, one-to-many, many-to-many, etc), but didn't find any examples.
So I tried a reasonable guess. Here's my attempt at implementing a Person
who can be tagged with Tag
s:
require 'moocho_query'
require 'hanami/model'
require 'hanami/model/adapters/file_system_adapter'
class Person
include Hanami::Entity
attributes :name, :age, :tags
end
class Tag
include Hanami::Entity
attributes :name
end
class PersonRepository
include Hanami::Repository
end
class TagRepository
include Hanami::Repository
end
Hanami::Model.configure do
adapter type: :file_system, uri: './file_db'
mapping do
collection :people do
entity Person
repository PersonRepository
attribute :id, Integer
attribute :name, String
attribute :age, Integer
attribute :tags, type: Array[Tag]
end
collection :tags do
entity Tag
repository TagRepository
attribute :id, Integer
attribute :name, String
end
end
end.load!
me = Person.new(name: 'Jonah', age: 99)
t1 = Tag.new(name: 'programmer')
t2 = Tag.new(name: 'nice')
me.tags = [t1, t2]
PersonRepository.create(me)
This fails on the load!
call, with the following error:
/Users/x/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/hanami-utils-0.7.0/lib/hanami/utils/class.rb:90:in `load_from_pattern!': uninitialized constant (Hanami::Model::Mapping::Coercers::{:type=>[Tag]}|
{:type=>[Tag]}) (NameError)
from /Users/jg/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/hanami-model-0.6.0/lib/hanami/model/mapping/attribute.rb:80:in `coercer'
from /Users/jg/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/hanami-model-0.6.0/lib/hanami/model/mapping/attribute.rb:53:in `load_coercer'
What is the correct way to implement relationships in Hanami?
As of version 0.7.0, there is no way to implement relationships between the entities. This is why there is no how-to in the documentation as well.
Out of curiousity, I had inquired this using a tweet which can be taken as an official word on entity relationships.
As a work around, in Hanami, entities are simply objects that you are persisting to the database which means an entity's persistence details can vary from it's schema.
I would suggest having a tags
method on the Person
object. Inside this method, you can retrieve the person's tags. Something like this:
def self.tags
TagRepository.query do
where(id: [tag-id-1, tag-id-2, ... , tag-id-n])
end.all
end
Though you will need to persist the tag ids associated with the person to the database as an attribute of the person or using a join table.
Do know that this implementation will have the n+1
query problem.
I know it's an old question, but I'll leave this answer in case somebody stumbles here:
Hanami added many-to-many support on version 1.1
http://hanamirb.org/guides/1.1/associations/has-many-through/
% bundle exec hanami generate model user
create lib/bookshelf/entities/user.rb
create lib/bookshelf/repositories/user_repository.rb
create db/migrations/20171024083639_create_users.rb
create spec/bookshelf/entities/user_spec.rb
create spec/bookshelf/repositories/user_repository_spec.rb
% bundle exec hanami generate model story
create lib/bookshelf/entities/story.rb
create lib/bookshelf/repositories/story_repository.rb
create db/migrations/20171024085712_create_stories.rb
create spec/bookshelf/entities/story_spec.rb
create spec/bookshelf/repositories/story_repository_spec.rb
% bundle exec hanami generate model comment
create lib/bookshelf/entities/comment.rb
create lib/bookshelf/repositories/comment_repository.rb
create db/migrations/20171024085858_create_comments.rb
create spec/bookshelf/entities/comment_spec.rb
create spec/bookshelf/repositories/comment_repository_spec.rb
# db/migrations/20171024083639_create_users.rb
Hanami::Model.migration do
change do
create_table :users do
primary_key :id
column :name, String, null: false
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end
# db/migrations/20171024085712_create_stories.rb
Hanami::Model.migration do
change do
create_table :stories do
primary_key :id
foreign_key :user_id, :users, null: false, on_delete: :cascade
column :text, String, null: false
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end
# db/migrations/20171024085858_create_comments.rb
Hanami::Model.migration do
change do
create_table :comments do
primary_key :id
foreign_key :user_id, :users, null: false, on_delete: :cascade
foreign_key :story_id, :stories, null: false, on_delete: :cascade
column :text, String, null: false
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end
# lib/bookshelf/repositories/user_repository.rb
class UserRepository < Hanami::Repository
associations do
has_many :stories
has_many :comments
end
end
# lib/bookshelf/repositories/story_repository.rb
class StoryRepository < Hanami::Repository
associations do
belongs_to :user
has_many :comments
has_many :users, through: :comments
end
def find_with_comments(id)
aggregate(:user, comments: :user).where(id: id).map_to(Story).one
end
def find_with_commenters(id)
aggregate(:users).where(id: id).map_to(Story).one
end
end
# lib/bookshelf/repositories/comment_repository.rb
class CommentRepository < Hanami::Repository
associations do
belongs_to :story
belongs_to :user
end
end
user_repo = UserRepository.new
author = user_repo.create(name: "Luca")
# => #<User:0x00007ffe71bc3b18 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 09:06:57 UTC, :updated_at=>2017-10-24 09:06:57 UTC}>
commenter = user_repo.create(name: "Maria G")
# => #<User:0x00007ffe71bb3010 @attributes={:id=>2, :name=>"Maria G", :created_at=>2017-10-24 09:07:16 UTC, :updated_at=>2017-10-24 09:07:16 UTC}>
story_repo = StoryRepository.new
story_repo.create(user_id: author.id, text: "Hello, folks")
# => #<Story:0x00007ffe71b4ace0 @attributes={:id=>1, :user_id=>1, :text=>"Hello folks", :created_at=>2017-10-24 09:09:59 UTC, :updated_at=>2017-10-24 09:09:59 UTC}>
story = story_repo.find_with_comments(story.id)
# => #<Story:0x00007fd45e327e60 @attributes={:id=>2, :user_id=>1, :text=>"Hello folks", :created_at=>2017-10-24 09:09:59 UTC, :updated_at=>2017-10-24 09:09:59 UTC, :user=>#<User:0x00007fd45e326bc8 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 09:06:57 UTC, :updated_at=>2017-10-24 09:06:57 UTC}>, :comments=>[#<Comment:0x00007fd45e325930 @attributes={:id=>1, :user_id=>2, :story_id=>2, :text=>"Hi and welcome!", :created_at=>2017-10-24 09:12:30 UTC, :updated_at=>2017-10-24 09:12:30 UTC, :user=>#<User:0x00007fd45e324490 @attributes={:id=>2, :name=>"Maria G", :created_at=>2017-10-24 09:07:16 UTC, :updated_at=>2017-10-24 09:07:16 UTC}>}>]}>
story.comments
# => [#<Comment:0x00007fe289f2d618 @attributes={:id=>1, :user_id=>2, :story_id=>2, :text=>"Hi and welcome!", :created_at=>2017-10-24 09:12:30 UTC, :updated_at=>2017-10-24 09:12:30 UTC, :commenter=>#<User:0x00007fe289f2c420 @attributes={:id=>2, :name=>"Maria G", :created_at=>2017-10-24 09:07:16 UTC, :updated_at=>2017-10-24 09:07:16 UTC}>}>]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With