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
  7. column :title, String, null: false
  8. column :created_at, DateTime, null: false
  9. column :updated_at, DateTime, null: false
  10. end
  11. end

Now we can prepare the database:

  1. $ bundle exec hanami db prepare

Let’s edit AuthorRepository with the following code:

Let’s create an author with a collection of books with a single database operation:

  1. repository = AuthorRepository.new
  2. author = repository.create_with_books(name: "Alexandre Dumas", books: [{title: "The Count of Montecristo"}])
  3. # => #<Author:0x007f811c415420 @attributes={:id=>1, :name=>"Alexandre Dumas", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC, :books=>[#<Book:0x007f811c40fe08 @attributes={:id=>1, :author_id=>1, :title=>"The Count of Montecristo", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC}>]}>
  4. author.id
  5. # => 1
  6. author.name
  7. # => "Alexandre Dumas"
  8. author.books
  9. # => [#<Book:0x007f811c40fe08 @attributes={:id=>1, :author_id=>1, :title=>"The Count of Montecristo", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC}>]

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

  1. author = repository.find(author.id)
  2. # => #<Author:0x007f811b6237e0 @attributes={:id=>1, :name=>"Alexandre Dumas", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC}>
  3. author.books
  4. # => nil

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

  1. author = repository.find_with_books(author.id)
  2. # => #<Author:0x007f811bbeb6f0 @attributes={:id=>1, :name=>"Alexandre Dumas", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC, :books=>[#<Book:0x007f811bbea430 @attributes={:id=>1, :author_id=>1, :title=>"The Count of Montecristo", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC}>]}>
  3. # => [#<Book:0x007f811bbea430 @attributes={:id=>1, :author_id=>1, :title=>"The Count of Montecristo", :created_at=>2016-11-15 09:19:38 UTC, :updated_at=>2016-11-15 09:19:38 UTC}>]

What if we need to add or remove books from an author? We need to define new methods to do so.

Let’s add a book:

  1. book = repository.add_book(author, title: "The Three Musketeers")

And remove it:

  1. repository.remove_book(author, book.id)
  1. # lib/bookshelf/repositories/author_repository.rb
  2. class AuthorRepository < Hanami::Repository
  3. # ...
  4. def books_count(author)
  5. assoc(:books, author).count
  6. end
  7. def on_sales_books_count(author)
  8. assoc(:books, author).where(on_sale: true).count
  9. end
  10. def book_exists?(author, id)
  11. book_for(author, id).exists?
  12. end
  13. private
  14. def book_for(author, id)
  15. assoc(:books, author).where(id: id)
  16. end

You can also run operations on top of these scopes: