Edit the migrations:

  1. # db/migrations/20171024083639_create_users.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :users do
  5. primary_key :id
  6. column :name, String, null: false
  7. column :created_at, DateTime, null: false
  8. column :updated_at, DateTime, null: false
  9. end
  10. end
  11. end
  1. # db/migrations/20171024085712_create_stories.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :stories do
  5. primary_key :id
  6. foreign_key :user_id, :users, null: false, on_delete: :cascade
  7. column :text, String, null: false
  8. column :created_at, DateTime, null: false
  9. column :updated_at, DateTime, null: false
  10. end
  11. end
  12. end
  1. # db/migrations/20171024085858_create_comments.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :comments do
  5. primary_key :id
  6. foreign_key :user_id, :users, null: false, on_delete: :cascade
  7. foreign_key :story_id, :stories, null: false, on_delete: :cascade
  8. column :text, String, null: false
  9. column :updated_at, DateTime, null: false
  10. end
  11. end
  12. end

Now we can prepare the database:

  1. $ bundle exec hanami db prepare
  1. # lib/bookshelf/repositories/story_repository.rb
  2. class StoryRepository < Hanami::Repository
  3. associations do
  4. belongs_to :user
  5. has_many :comments
  6. has_many :users, through: :comments
  7. end
  8. def find_with_comments(id)
  9. aggregate(:user, comments: :user).where(id: id).map_to(Story).one
  10. end
  11. def find_with_commenters(id)
  12. aggregate(:users).where(id: id).map_to(Story).one
  13. end
  14. end
  1. # lib/bookshelf/repositories/comment_repository.rb
  2. class CommentRepository < Hanami::Repository
  3. associations do
  4. belongs_to :story
  5. belongs_to :user
  6. end
  7. end

We have defined only for the operations that we need for our model domain. In this way, we avoid to bloat StoryRepository with dozen of unneeded methods.

Let’s create a couple of users, a story, then a comment:

  1. users = UserRepository.new
  2. author = users.create(name: "Luca")
  3. # => #<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}>
  4. commenter = users.create(name: "Maria G")
  5. # => #<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}>
  1. stories = StoryRepository.new
  2. story = stories.create(user_id: author.id, text: "Hello, folks")
  3. # => #<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}>
  1. story = stories.find(story.id)
  2. story.comments
  3. # => nil

Because we haven’t explicitly loaded the associated records story.comments is nil. We can use the method that we have defined on before (#find_with_comments):

  1. story = stories.find_with_comments(story.id)
  2. # => #<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}>}>]}>
  3. story.comments
  4. # => [#<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}>}>]
  5. story.comments.map(&:user)
  6. # => [#<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}>]

This time story.comments has the associated records.

  1. story = stories.find_with_commenters(story.id)
  2. # => #<Story:0x00007f8e28b79d88 @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, :users=>[#<User:0x00007f8e28b78b40 @attributes={:id=>2, :name=>"Maria G", :created_at=>2017-10-24 09:07:16 UTC, :updated_at=>2017-10-24 09:07:16 UTC}>]}>
  3. story.users
  4. # => [#<User:0x00007f8e28b78b40 @attributes={:id=>2, :name=>"Maria G", :created_at=>2017-10-24 09:07:16 UTC, :updated_at=>2017-10-24 09:07:16 UTC}>]

In the examples above story.users was the way to go, because of the Hanami conventions, but that isn’t a great name for an association. We can alias users with something more meaningful like commenters:

  1. # lib/bookshelf/repositories/story_repository.rb
  2. class StoryRepository < Hanami::Repository
  3. associations do
  4. belongs_to :user
  5. has_many :comments
  6. has_many :users, through: :comments, as: :commenters
  7. end
  8. def find_with_comments(id)
  9. aggregate(:user, comments: :commenter).where(id: id).map_to(Story).one
  10. end
  11. end
  1. story = stories.find_with_comments(2)
  2. # => #<Story:0x00007fe289f2f800 @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:0x00007fe289f2e810 @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: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}>}>]}>
  3. story.comments
  4. # => [#<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}>}>]
  5. # => [#<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}>]