module Epiphy::Repository

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.

@example

# Configuration and initalize the necessary config. Can be put in Rails
# config file.
connection = Epiphy::Connection.create  
adapter = Epiphy::Adapter::Rethinkdb.new(connection)
Epiphy::Repository.configure do |c|
  c.adapter = adapter
end

require 'epiphy/model'

class Article
  include Epiphy::Entity
end

# valid
class ArticleRepository
  include Epiphy::Repository
end

# not valid for Article
class PostRepository
  include Epiphy::Repository
end

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

@example

# PostRepository is repository for Article
mapper = Epiphy::Model::Mapper.new do
  collection :articles do
    entity Article
    repository PostRepository
  end
end

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

Epiphy::Model is shipped with adapter:

* RethinkDB

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.

@example

require 'epiphy/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 Epiphy::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

@since 0.1.0

@see Epiphy::Entity @see martinfowler.com/eaaCatalog/repository.html @see en.wikipedia.org/wiki/Dependency_inversion_principle

Public Class Methods

auto_config() click to toggle source

Auto configure with default RethinkDB setting. 127.0.0.1, 28015, plain auth key.

With this design, people can just drop in and start using it without worry about setting up and configure. @since 0.3.0 @api private

# File lib/epiphy/repository.rb, line 175
def auto_config
  Epiphy::Repository.configure do |config|
    config.adapter = Epiphy::Adapter::Rethinkdb.new connection, database: 'test'
  end
end
configure() { |config| ... } click to toggle source
# File lib/epiphy/repository.rb, line 155
def configure
  raise(ArgumentError, 'Missing config block') unless block_given?
  @config ||= Configuration.new
  yield(@config)
end
get_config() click to toggle source
# File lib/epiphy/repository.rb, line 161
def get_config
  if @config.nil?
    auto_config
  end
  @config
end
included(base) click to toggle source

Inject the public API into the hosting class.

Also setup the repository. Collection name, Adapter will be set automatically at this step. By changing adapter, you can force the Repository to be read/written from somewhere else.

In a master/slave environment, the adapter can be change depend on the repository.

The name of table to hold this collection in database can be change with self.collection= method

@since 0.1.0 @see self#collection

@example

require 'epiphy/model'

class UserRepository
  include Epiphy::Repository
end

UserRepository.collection #=> User

class MouseRepository
  include Epiphy::Repository

end
MouseRepository.collection = 'Mice'
MouseRepository.collection #=> Mice

class FilmRepository
  include Epiphy::Repository
  collection = 'Movie'
end
FilmRepository.collection = 'Movie'
# File lib/epiphy/repository.rb, line 219
def self.included(base)
  config = Epiphy::Repository.get_config
  
  raise Epiphy::Repository::NotConfigureError if config.nil?
  raise Epiphy::Repository::MissingAdapterError if config.adapter.nil?

  base.class_eval do
    extend ClassMethods
    include Lotus::Utils::ClassAttribute

    class_attribute :collection
    self.adapter=(config.adapter)
    self.collection=(get_name) if self.collection.nil?
  end
end