Class: Hanami::Repository

Inherits:
ROM::Repository::Root
  • Object
show all
Defined in:
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 leak storage API details outside of a repository.

Examples:

require 'hanami/model'

class Article
  include 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 stub 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

Constant Summary collapse

MAPPER_NAME =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Mapper name.

With ROM mapping there is a link between the entity class and a generic reference for it. Example: BookRepository references Book as :entity.

See Also:

Since:

  • 0.7.0

:entity
COMMAND_PLUGINS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Plugins for database commands

See Also:

Since:

  • 0.7.0

[:schema, :mapping, :timestamps].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeHanami::Repository

Initialize a new instance

Since:

  • 0.7.0


359
360
361
# File 'lib/hanami/repository.rb', line 359

def initialize
  super(self.class.container)
end

Class Method Details

.associations(&blk) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Declare associations for the repository

NOTE: This is an experimental feature

Examples:

class BookRepository < Hanami::Repository
  associations do
    has_many :books
  end
end

Since:

  • 0.7.0


224
225
226
# File 'lib/hanami/repository.rb', line 224

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

.configurationObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Configuration

Since:

  • 0.7.0


135
136
137
# File 'lib/hanami/repository.rb', line 135

def self.configuration
  Hanami::Model.configuration
end

.containerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Container

Since:

  • 0.7.0


143
144
145
# File 'lib/hanami/repository.rb', line 143

def self.container
  Hanami::Model.container
end

.define_associationsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

It defines associations, by adding relations to the repository

See Also:

Since:

  • 0.7.0


207
208
209
# File 'lib/hanami/repository.rb', line 207

def self.define_associations
  Model::Associations::Dsl.new(self, &@associations) unless @associations.nil?
end

.define_mappingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Defines the ampping between a database table and an entity.

It's also responsible to associate table columns to entity attributes.

rubocop:disable Metrics/MethodLength rubocop:disable Metrics/AbcSize

Since:

  • 0.7.0


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/hanami/repository.rb', line 182

def self.define_mapping
  self.entity = Utils::Class.load!(entity_name)
  e = entity
  m = @mapping

  blk = lambda do |_|
    model       e
    register_as MAPPER_NAME
    instance_exec(&m) unless m.nil?
  end

  root = self.root
  configuration.mappers { define(root, &blk) }
  configuration.define_mappings(root, &blk)
  configuration.register_entity(relation, entity_name.underscore, e)
end

.define_relationObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define a database relation, which describes how data is fetched from the database.

It auto-infers the underlying database table.

Since:

  • 0.7.0


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/hanami/repository.rb', line 154

def self.define_relation # rubocop:disable Metrics/MethodLength
  a = @associations

  configuration.relation(relation) do
    schema(infer: true) do
      associations(&a) unless a.nil?
    end

    # rubocop:disable Lint/NestedMethodDefinition
    def by_primary_key(id)
      where(primary_key => id)
    end
    # rubocop:enable Lint/NestedMethodDefinition
  end

  relations(relation)
  root(relation)
end

.inherited(klass) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 0.7.0


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/hanami/repository.rb', line 259

def self.inherited(klass) # rubocop:disable Metrics/MethodLength
  klass.class_eval do
    include Utils::ClassAttribute

    class_attribute :entity

    class_attribute :entity_name
    self.entity_name = Model::EntityName.new(name)

    class_attribute :relation
    self.relation = Model::RelationName.new(name)

    commands :create, update: :by_primary_key, delete: :by_primary_key, mapper: MAPPER_NAME, use: COMMAND_PLUGINS
    prepend Commands
  end

  Hanami::Model.repositories << klass
end

.load!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Define relations, mapping and associations

Since:

  • 0.7.0


251
252
253
254
255
# File 'lib/hanami/repository.rb', line 251

def self.load!
  define_relation
  define_mapping
  define_associations
end

.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


243
244
245
# File 'lib/hanami/repository.rb', line 243

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

Instance Method Details

#allArray<Hanami::Entity>

Return all the records for the relation

Examples:

UserRepository.new.all

Returns:

Since:

  • 0.7.0


386
387
388
# File 'lib/hanami/repository.rb', line 386

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

#assoc(target, subject = nil) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns an association

NOTE: This is an experimental feature

Since:

  • 0.7.0


432
433
434
# File 'lib/hanami/repository.rb', line 432

def assoc(target, subject = nil)
  Hanami::Model::Association.build(self, target, subject)
end

#clearObject

Deletes all the records from the relation

Examples:

UserRepository.new.clear

Since:

  • 0.7.0


420
421
422
# File 'lib/hanami/repository.rb', line 420

def clear
  root.delete
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:

Since:

  • 0.7.0


374
375
376
# File 'lib/hanami/repository.rb', line 374

def find(id)
  root.by_primary_key(id).as(:entity).one
end

#firstHanami::Entity, NilClass

Returns the first record for the relation

Examples:

UserRepository.new.first

Returns:

Since:

  • 0.7.0


398
399
400
# File 'lib/hanami/repository.rb', line 398

def first
  root.as(:entity).first
end

#lastHanami::Entity, NilClass

Returns the last record for the relation

Examples:

UserRepository.new.last

Returns:

Since:

  • 0.7.0


410
411
412
# File 'lib/hanami/repository.rb', line 410

def last
  root.order(Model::Sql.desc(root.primary_key)).as(:entity).first
end