Also known as one-to-one, is an association between an entity (User) associated to one child entity (Avatar).

Setup

  1. $ bundle exec hanami generate model user
  2. create lib/bookshelf/entities/user.rb
  3. create lib/bookshelf/repositories/user_repository.rb
  4. create db/migrations/20171024083639_create_users.rb
  5. create spec/bookshelf/entities/user_spec.rb
  6. create spec/bookshelf/repositories/user_repository_spec.rb
  7. $ bundle exec hanami generate model avatar
  8. create lib/bookshelf/entities/avatar.rb
  9. create lib/bookshelf/repositories/avatar_repository.rb
  10. create db/migrations/20171024083725_create_avatars.rb
  11. create spec/bookshelf/entities/avatar_spec.rb
  12. create spec/bookshelf/repositories/avatar_repository_spec.rb

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/20171024083725_create_avatars.rb
  2. Hanami::Model.migration do
  3. change do
  4. create_table :avatars do
  5. primary_key :id
  6. foreign_key :user_id, :users, null: false, on_delete: :cascade
  7. column :url, 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 UserRepository with the following code:

  1. # lib/bookshelf/repositories/user_repository.rb
  2. class UserRepository < Hanami::Repository
  3. associations do
  4. has_one :avatar
  5. end
  6. def create_with_avatar(data)
  7. assoc(:avatar).create(data)
  8. end
  9. def find_with_avatar(id)
  10. aggregate(:avatar).where(id: id).map_to(User).one
  11. end
  12. end

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

Let’s create an user with an avatar single database operation:

  1. repository = UserRepository.new
  2. user = repository.create_with_avatar(name: "Luca", avatar: { url: "https://avatars.test/luca.png" })
  3. # => #<User:0x00007fa166ac8550 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC, :avatar=>#<Avatar:0x00007fa166ac35c8 @attributes={:id=>1, :user_id=>1, :url=>"https://avatars.test/luca.png", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC}>}>
  4. user.id
  5. # => 1
  6. user.name
  7. # => "Luca"
  8. user.avatar
  9. # => #<Avatar:0x00007fa166ac35c8 @attributes={:id=>1, :user_id=>1, :url=>"https://avatars.test/luca.png", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC}>

What happens if we load the user with UserRepository#find?

  1. user = repository.find(user.id)
  2. # => #<User:0x00007fa166aa3a70 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC}>
  3. user.avatar
  4. # => nil

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

  1. user = repository.find_with_avatar(user.id)
  2. # => #<User:0x00007fa166a71048 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC, :avatar=>#<Avatar:0x00007fa166a70328 @attributes={:id=>1, :user_id=>1, :url=>"https://avatars.test/luca.png", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC}>}>
  3. user.avatar
  4. # => #<Avatar:0x00007fa166a70328 @attributes={:id=>1, :user_id=>1, :url=>"https://avatars.test/luca.png", :created_at=>2017-10-24 08:44:27 UTC, :updated_at=>2017-10-24 08:44:27 UTC}>

This time user.avatar has the associated avatar.

Add, Remove, Update, and Replace

You can perform operations to add, remove, update, and replace the avatar:

  1. # lib/bookshelf/repositories/user_repository.rb
  2. class UserRepository < Hanami::Repository
  3. # ...
  4. def add_avatar(user, data)
  5. assoc(:avatar, user).add(data)
  6. end
  7. def remove_avatar(user)
  8. assoc(:avatar, user).delete
  9. end
  10. def update_avatar(user, data)
  11. assoc(:avatar, user).update(data)
  12. end
  13. def replace_avatar(user, data)
  14. assoc(:avatar, user).replace(data)
  15. end
  16. end