Also known as many-to-one, is an association between a one of the entities (Book) associated to one parent entity (Author).

Setup

  1. $ bundle exec hanami generate model author
  2. create lib/bookshelf/entities/author.rb
  3. create lib/bookshelf/repositories/author_repository.rb
  4. create db/migrations/20171024081558_create_authors.rb
  5. create spec/bookshelf/entities/author_spec.rb
  6. create spec/bookshelf/repositories/author_repository_spec.rb
  7. $ bundle exec hanami generate model book
  8. create lib/bookshelf/entities/book.rb
  9. create lib/bookshelf/repositories/book_repository.rb
  10. create db/migrations/20171024081617_create_books.rb
  11. create spec/bookshelf/entities/book_spec.rb
  12. create spec/bookshelf/repositories/book_repository_spec.rb

Edit the migrations:

  1. # db/migrations/20171024081558_create_authors.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :authors 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/20171024081617_create_books.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :books do
  5. primary_key :id
  6. foreign_key :author_id, :authors, on_delete: :cascade, null: false
  7. column :title, String, null: false
  8. column :created_at, DateTime, 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

Basic usage

Let’s edit BookRepository with the following code:

  1. # lib/bookshelf/repositories/book_repository.rb
  2. class BookRepository < Hanami::Repository
  3. associations do
  4. belongs_to :author
  5. end
  6. def find_with_author(id)
  7. aggregate(:author).where(id: id).map_to(Book).one
  8. end
  9. end

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

Let’s create a book:

  1. repository = BookRepository.new
  2. book = repository.create(author_id: 1, title: "Hanami")
  3. # => #<Book:0x00007f89ac270118 @attributes={:id=>1, :author_id=>1, :created_at=>2017-10-24 08:25:41 UTC, :updated_at=>2017-10-24 08:25:41 UTC}>

What happens if we load the author with BookRepository#find?

  1. book = repository.find(book.id)
  2. # => #<Book:0x00007f89ac25ba10 @attributes={:id=>1, :author_id=>1, :created_at=>2017-10-24 08:25:41 UTC, :updated_at=>2017-10-24 08:25:41 UTC}>
  3. book.author
  4. # => nil

Because we haven’t explicitly loaded the associated records, book.author is nil. We can use the method that we have defined on before (#find_with_author):

  1. book = repository.find_with_author(book.id)
  2. # => #<Book:0x00007fb3f88896a0 @attributes={:id=>1, :author_id=>1, :created_at=>2017-10-24 08:25:41 UTC, :updated_at=>2017-10-24 08:25:41 UTC, :author=>#<Author:0x00007fb3f8888980 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 08:25:15 UTC, :updated_at=>2017-10-24 08:25:15 UTC}>}>
  3. book.author
  4. # => => #<Author:0x00007fb3f8888980 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 08:25:15 UTC, :updated_at=>2017-10-24 08:25:15 UTC}>

This time book.author has the collection of associated author.

Remove

What if we need to unassociate a book from its author?

Because we declared a foreign key with the migration, we cannot set author_id to NULL or to reference to an unexisting author. This is a mechanism against orphaned records: a book is forced to reference a valid author.

The only way to remove a book from an author is to delete the book record.

  1. repository.delete(book.id)
  2. repository.find(book.id)
  3. # => nil