Repository::Base
This Gem supplies a class, Repository::Base
, which can be used as a base class
for Repositories in an application that uses the
Data Mapper pattern.
As described by Fowler, Data Mapper "moves data between objects and a database
while keeping them independent of each other and the mapper itself".
This was originally developed within the structure of an R&D test-bed application,
new_poc
. That app has a fairly wide-ranging
history; after browsing its README and
commit history, you may find Pull
Requests #153 and
#200 informative as to the
historical basis of this code.
Contents
- Installation
- Usage
- IMPORTANT LEGACY NOTICE
- Concepts
- Entities
- Entity Factories
- StoreResult
-
Repository::Base
Instance Methods
- More Details
- Contributing
Installation
Add this line to your application's Gemfile:
gem 'repository-base'
And then execute:
$ bundle
Or install it yourself as:
$ gem install repository-base
Usage
IMPORTANT LEGACY NOTICE
NOTICE! This Gem was created to support a solo, ad-hoc, early learning experience in what is now known as Clean Architecture. It was part of our first attempt to build an alternative to the ActiveRecord/ActiveModel scheme native to Ruby on Rails.
As such, it has been superseded and far outshone by other, team efforts, notably ROM as used with Hanami and Trailblazer. You are strongly advised to examine these and other tools rather than to use this for any new development. The Gem is being republished as an 0.4.0 release purely for internal archaeological purposes.
Concepts
As mentioned at the top of this file, Repository::Base
is intended to serve as
the base class for your app's Repositories. As with most Data Mapper
implementations, it makes use of a database access object, or DAO, which you
specify as one of the two parameters to #initialize
. The other parameter is an
"entity factory", whose .create
class method hands back an "entity" reflecting
the content of an individual record in the underlying DAO.
Entities
What is an "entity"? It's a domain object in your application, with methods expressing the business logic applicable to the object represented by the underlying database record. This is distinct from an ActiveRecord-style model, in that more than one variation of entity may exist in your application for a given DAO. Most applications have Users, for example; some applications may have distinct variation of User entities (ordinary punters, admins, a "guest user" entity representing a user who has not authenticated as a more-privileged user, etc), that all share a common persistence layer (the underlying DAO) and an entity factory that knows how to create the correct type of entity for a given DAO record.
An entity is uniquely identified by a slug rather than a numeric ID. Generally,
a slug encodes textual, relatively SEO-friendly information corresponding to an
individual record (the title of an article, the name of a user, etc) such that
the corresponding record may be uniquely identified by the DAO. This is in
preference to a "traditional" numeric record ID number; Repository::Base
has
no knowledge of nor direct support for numeric record IDs.
Entity Factories
Again, the entity factory, one of the parameters to this class' #initialize
method, is responsible for creating an entity from a DAO instance (record). The
Repository::Base
class knows nothing of the details of how that is done; it
simply calls methods on the DAO and/or entity factory to accomplish the tasks
encapsulated by its own individual methods.
StoreResult
What is a StoreResult
? It is a simple value object which communicates the
result of a call to a method on the underlying DAO as reported by the DAO,
with the following properties:
-
success?
(accessible asresult.success?
or asresult[:success?]
), has the valuetrue
after a successful operation orfalse
after an unsuccessful one; -
entity
(result.entity
orresult[:entity]
) is the instance or enumeration of instances of your domain entity resulting from a successful operation. After an unsuccessful operation, this property will benil
; -
errors
(result.errors
orresult[:errors]
) is an Array-like object which is empty after a successful operation. After an unsuccessful operation, it will contain an Array of Hash instances, with each Hash having a key identifying the field or similar concept for which an individual error is being reported, and a value of the specific error message. For example, to report that a:name
field was empty or blank, you might have a Hash of `{ name: 'is empty or blank' }. Your application domain logic should then understand how to deal with that convention.
Repository::Base
Instance Methods
-
#add
adds a new record to the underlying DAO (data-access object; see above) using the field values specified by the entity passed as the only parameter. Returns aStoreResult
(see above) which indicates the success or failure of the operation. On success, theStoreResult
contains a new entity that represents the state of the record added to the underlying DAO; -
#all
returns a collection of entities matching all records as reported by the underlying DAO, with entities created by the appropriate entity factory; -
#delete
instructs the DAO to delete the record corresponding to the specifiedslug
. Returns aStoreResult
with an entity corresponding to the deleted record on success, or with appropriate error message(s) on failure; -
#find_by_slug
instructs the DAO to retrieve the record whose unique slug value matches the supplied parameter. On success, returns aStoreResult
with an entity corresponding to the selected record; on failure, theStoreResult
has anentity
value ofnil
and appropriate error messages inerrors
; -
update
causes the DAO to attempt to update the record identified by the passed-in slug using the passed-in field values. On success, returns aStoreResult
whoseentity
value mirrors the updated DAO record; on failure, has appropriate error indications in theStoreResult
'serrors
property.
More Details
This Gem now has full documentation available. (No, this isn't 1990s-era rdoc
.)
This Gem was built with MRI Ruby 2.5.0. It is likely to work when rebuilt with any version after 2.0.0; however, as it makes use of keyword arguments for various methods, it is incompatible with Ruby 1.9 or earlier.
Contributing
- Fork it ( https://github.com/jdickey/repository-base/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Ensure that your changes are completely covered by passing specs, and comply with the Ruby Style Guide as enforced by RuboCop. To verify this, run
bundle exec rake
, noting and repairing any lapses in coverage or style violations; - Commit your changes (
git commit -a
). Please do not use a single-line commit message (git commit -am "some message"
). A good commit message notes what was changed and why in sufficient detail that a relative newcomer to the code can understand your reasoning and your code; - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request. Describe at some length the rationale for your new feature; your implementation strategy at a higher level than each individual commit message; anything future maintainers should be aware of; and so on. If this is a modification to existing code, reference the open issue being addressed.
- Don't be discouraged if the PR generates a discussion that leads to further refinement of your PR through additional commits. These should generally be discussed in comments on the PR itself; discussion in the Gitter room (see below) may also be useful;
- If you've comments, questions, or just want to talk through your ideas, don't hesitate to hang out in the project's room on Gitter. Ask away!