Module: Lotus::Repository

Defined in:
lib/lotus/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.

Repository for an entity can be configured by setting # the ‘#repository` on the mapper.

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

Lotus::Model is shipped with two adapters:

* SqlAdapter
* MemoryAdapter

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 'lotus/model'

class Article
  include Lotus::Entity
end

# valid
class ArticleRepository
  include Lotus::Repository
end

# not valid for Article
class PostRepository
  include Lotus::Repository
end
# PostRepository is repository for Article
mapper = Lotus::Model::Mapper.new do
  collection :articles do
    entity Article
    repository PostRepository
  end
end
require 'lotus/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.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.most_recent_by_author(author)

class ArticleRepository
  include Lotus::Repository

  def self.most_recent_by_author(author, limit = 8)
    query do
      where(author_id: author.id).
        order(:published_at)
    end.limit(limit)
  end
end

See Also:

Since:

  • 0.1.0

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Inject the public API into the hosting class.

Examples:

require 'lotus/model'

class UserRepository
  include Lotus::Repository
end

Since:

  • 0.1.0



132
133
134
135
136
137
138
139
140
# File 'lib/lotus/repository.rb', line 132

def self.included(base)
  base.class_eval do
    extend ClassMethods
    include Lotus::Utils::ClassAttribute

    class_attribute :collection
    self.adapter = Lotus::Model::Adapters::NullAdapter.new
  end
end