V1.3: Belongs To


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

Setup

$ 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
    end
  end
end
# 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
    end
  end
end

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
  end

  def find_with_author(id)
    aggregate(:author).where(id: id).map_to(Book).one
  end
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:

repository = BookRepository.new

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.id)
  # => #<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}>
book.author
  # => 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):

book = repository.find_with_author(book.id)
  # => #<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}>}>

book.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}>

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.

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