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 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
# File lib/epiphy/repository.rb, line 155 def configure raise(ArgumentError, 'Missing config block') unless block_given? @config ||= Configuration.new yield(@config) end
# File lib/epiphy/repository.rb, line 161 def get_config if @config.nil? auto_config end @config end
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