Class: Hanami::Repository

Inherits:
ROM::Repository::Root
  • Object
show all
Defined in:
gems/gems/hanami-model-1.3.0/lib/hanami/repository.rb

Overview

Mediates between the entities and the persistence layer, by offering an API to query and execute commands on a database.

By default, a repository is named after an entity, by appending the Repository suffix to the entity class name.

A repository is storage independent. All the queries and commands are delegated to the current adapter.

This architecture has several advantages:

  • Applications depend on an abstract API, instead of low level details (Dependency Inversion principle)

  • Applications depend on a stable API, that doesn't change if the storage changes

  • Developers can postpone storage decisions

  • Isolates the persistence logic at a low level

Hanami::Model is shipped with one adapter:

  • SqlAdapter

All the queries and commands are private. This decision forces developers to define intention revealing API, instead of leaking storage API details outside of a repository.

Examples:

require 'hanami/model'

class Article < Hanami::Entity
end

# valid
class ArticleRepository < Hanami::Repository
end

# not valid for Article
class PostRepository < Hanami::Repository
end
require 'hanami/model'

# This is bad for several reasons:
#
#  * The caller has an intimate knowledge of the internal mechanisms
#      of the Repository.
#
#  * The caller works on several levels of abstraction.
#
#  * It doesn't express a clear intent, it's just a chain of methods.
#
#  * The caller can't be easily tested in isolation.
#
#  * If we change the storage, we are forced to change the code of the
#    caller(s).

ArticleRepository.new.where(author_id: 23).order(:published_at).limit(8)

# This is a huge improvement:
#
#  * The caller doesn't know how the repository fetches the entities.
#
#  * The caller works on a single level of abstraction.
#    It doesn't even know about records, only works with entities.
#
#  * It expresses a clear intent.
#
#  * The caller can be easily tested in isolation.
#    It's just a matter of stubbing this method.
#
#  * If we change the storage, the callers aren't affected.

ArticleRepository.new.most_recent_by_author(author)

class ArticleRepository < Hanami::Repository
  def most_recent_by_author(author, limit = 8)
    articles.
      where(author_id: author.id).
        order(:published_at).
        limit(limit)
  end
end

See Also:

Since:

  • 0.1.0

Defined Under Namespace

Modules: Commands

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeHanami::Repository

Initialize a new instance

Since:

  • 0.7.0

def initialize
  super(self.class.container)
end

Class Method Details

.mapping(&blk) ⇒ Object

Declare mapping between database columns and entity's attributes

NOTE: This should be used only when there is a name mismatch (eg. in legacy databases).

Examples:

class BookRepository < Hanami::Repository
  self.relation = :t_operator

  mapping do
    attribute :id,   from: :operator_id
    attribute :name, from: :s_name
  end
end

Since:

  • 0.7.0

def self.mapping(&blk)
  @mapping = blk
end

.schema(&blk) ⇒ Object

Declare database schema

NOTE: This should be used only when Hanami can't find a corresponding Ruby type for your column.

Examples:

# In this example `name` is a PostgreSQL Enum type that we want to treat like a string.

class ColorRepository < Hanami::Repository
  schema do
    attribute :id,         Hanami::Model::Sql::Types::Int
    attribute :name,       Hanami::Model::Sql::Types::String
    attribute :created_at, Hanami::Model::Sql::Types::DateTime
    attribute :updated_at, Hanami::Model::Sql::Types::DateTime
  end
end

Since:

  • 1.0.0

def self.schema(&blk)
  @schema = blk
end

Instance Method Details

#allArray<Hanami::Entity>

Return all the records for the relation

Examples:

UserRepository.new.all

Returns:

Since:

  • 0.7.0

def all
  root.as(:entity).to_a
end

#clearObject

Deletes all the records from the relation

Examples:

UserRepository.new.clear

Since:

  • 0.7.0

def clear
  root.delete
end

#command(*args, **opts, &block) ⇒ ROM::Command

Define a new ROM::Command while preserving the defaults used by Hanami itself.

It allows the user to define a new command to, for example, create many records at the same time and still get entities back.

The first argument is the command and relation it will operate on.

Examples:

# In this example, calling the create_many method with and array of data,
# would result in the creation of records and return an Array of Task entities.

class TaskRepository < Hanami::Repository
  def create_many(data)
    command(create: :tasks, result: :many).call(data)
  end
end

Returns:

  • (ROM::Command)

    the created command

Since:

  • 1.2.0

def command(*args, **opts, &block)
  opts[:use] = COMMAND_PLUGINS | Array(opts[:use])
  opts[:mapper] = opts.fetch(:mapper, Model::MappedRelation.mapper_name)

  super(*args, **opts, &block)
end

#find(id) ⇒ Hanami::Entity, NilClass

Find by primary key

Examples:

repository = UserRepository.new
user       = repository.create(name: 'Luca')

user       = repository.find(user.id)

Returns:

Raises:

Since:

  • 0.7.0

def find(id)
  root.by_pk(id).as(:entity).one
rescue => e
  raise Hanami::Model::Error.for(e)
end

#firstHanami::Entity, NilClass

Returns the first record for the relation

Examples:

UserRepository.new.first

Returns:

Since:

  • 0.7.0

def first
  root.as(:entity).limit(1).one
end

#lastHanami::Entity, NilClass

Returns the last record for the relation

Examples:

UserRepository.new.last

Returns:

Since:

  • 0.7.0

def last
  root.as(:entity).limit(1).reverse.one
end