Module: Epiphy::Repository

Defined in:
lib/epiphy/repository.rb,
lib/epiphy/repository/error.rb,
lib/epiphy/repository/cursor.rb,
lib/epiphy/repository/helper.rb,
lib/epiphy/repository/configuration.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

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.

Examples:


# 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
# PostRepository is repository for Article
mapper = Epiphy::Model::Mapper.new do
  collection :articles do
    entity Article
    repository PostRepository
  end
end
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

See Also:

Since:

  • 0.1.0

Defined Under Namespace

Modules: ClassMethods, Helper Classes: Configuration, Cursor, MissingAdapterError, NotConfigureError

Class Method Summary collapse

Class Method Details

.auto_configObject

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.

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



175
176
177
178
179
# 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| ... } ⇒ Object

Yields:

  • (@config)

Raises:

  • (ArgumentError)

Since:

  • 0.1.0



155
156
157
158
159
# File 'lib/epiphy/repository.rb', line 155

def configure
  raise(ArgumentError, 'Missing config block') unless block_given?
  @config ||= Configuration.new
  yield(@config)
end

.get_configObject

Since:

  • 0.1.0



161
162
163
164
165
166
# File 'lib/epiphy/repository.rb', line 161

def get_config
  if @config.nil?
    auto_config
  end
  @config
end

.included(base) ⇒ Object

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

Examples:

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'

Raises:

See Also:

  • selfself#collection

Since:

  • 0.1.0



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# 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