Associations: Belongs To

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


$ bundle exec hanami generate model author
      create  lib/bookshelf/entities/author.rb
      create  lib/bookshelf/repositories/author_repository.rb
      create  db/migrations/20171024081558_create_authors.rb
      create  spec/bookshelf/entities/author_spec.rb
      create  spec/bookshelf/repositories/author_repository_spec.rb

$ bundle exec hanami generate model book
      create  lib/bookshelf/entities/book.rb
      create  lib/bookshelf/repositories/book_repository.rb
      create  db/migrations/20171024081617_create_books.rb
      create  spec/bookshelf/entities/book_spec.rb
      create  spec/bookshelf/repositories/book_repository_spec.rb

Edit the migrations:

# db/migrations/20171024081558_create_authors.rb
Hanami::Model.migration do
  change do
    create_table :authors do
      primary_key :id

      column :name,       String,   null: false
      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false
# db/migrations/20171024081617_create_books.rb
Hanami::Model.migration do
  change do
    create_table :books do
      primary_key :id

      foreign_key :author_id, :authors, on_delete: :cascade, null: false

      column :title,      String,   null: false
      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false

Now we can prepare the database:

$ bundle exec hanami db prepare

Basic usage

Let’s edit BookRepository with the following code:

# lib/bookshelf/repositories/book_repository.rb
class BookRepository < Hanami::Repository
  associations do
    belongs_to :author

  def find_with_author(id)
    aggregate(:author).where(id: id).map_to(Book).one

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:

repository =

book = repository.create(author_id: 1, title: "Hanami")
  # => #<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?

book = repository.find(
  # => #<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}>
  # => nil

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

book = repository.find_with_author(
  # => #<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}>}>
  # => => #<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 has the collection of associated author.


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.

  # => nil