Repository build on top of sequel to store simple data records. Errol encourages a separation of concerns in the domain model of a system where data is persisted. It uses these extra components to include a default solution for paginating its records.
They key components are the Repository that is responsible for storing and retrieving Records in response to various Inquiries. Records are not the same as the bloated god objects of active record but should remain simple data stores. The responsibility of a record is representing a database row with domain friendly types, this means serialising data. Where the behaviour that is no longer represented on your model now belongs is dependant on the system. It is often worth having an object that wraps the data record to define extra behaviour as well as methods to save. Errol ships with an entity that can be used as a starting point for these objects.
Add this line to your application's Gemfile:
gem 'errol'
And then execute:
$ bundle
Or install it yourself as:
$ gem install errol
Usage is best described by each of the components
The inquiry object is simply a store of data that was sent with each inquiry plus an optional set of defaults. The repository assumes that a page and page_size is available when using the ability to paginate
class Posts
class Inquiry < Errol::Inquiry
default :order, :id
default :page, 1
default :page_size, 12
default :published, true
end
end
inquiry = Inquiry.new :page => 2
inquiry.page
# => 2
inquiry.published?
# => true
inquiry.other
# => raise DefaultValueUndefined
These are simply sequel models. there is no wrapper and you define them as sequel models
class Post
class Record < Sequel::Model(:posts)
end
end
The repository contains all the records, instances of a repository contain a subset dependant on the inquiry, by default the inquiry will be used to paginate.
class Posts < Errol::Repository
class << self
# The repository needs to know what records it is storing
def record_class
Post::Record
end
# The repository needs to know how to build a requirments hash that may be empty into a inquiry containing defaults
def inquiry(requirements)
Posts::Inquiry.new(requirements)
end
end
# This the odd part, the dataset method is called when returning data. This a custom method that allows arbitrarily complex manipulation of data using settings from the inquiry
def dataset
partial = raw_dataset
partial = partial.order(inquiry.order)
partial = partial.where(:published) if inquiry.published
partial
end
end
page = Posts.new
# First page of published posts ordered by id
page.first_page?
# => true
page.page_size
# => 12
page.each { |post| puts post }
# Outputs each of the posts on the page
# class methods also use an inquiry to filter data
Posts.each { |post| puts post }
# Outputs each published post in the dataset ordered by id
Posts.each(:published => false, :order => :created_at) { |post| puts post }
# Outputs each post in the database ordered by creation date
#if you want a filtered dataset which is not paginated pass false as the last argument
set = Posts.new({:order => :created_at}, false)
Entities are an optional wrapper around the record objects. My personal criteria as to if can add something to my record is can this object still be represented by an open struct? This allows me to test my entities with Ostructs and see the data thats is pulled or pushed to them. Use a entity for anything else. Say we want a post intro which is the first 20 words of a post. I often name my entities after the domain object and name space the record below them, this is because I never interact with the data record.
# Without Errol::Entity
class Post
def initialize(record)
@record = record
end
attr_reader :record
def intro
body.split[' '][0, 3].join(' ') + '...'
end
def body
record.body
end
def title
record.title
end
def save
Posts.save record
end
end
# With Errol::Entity
class Customer < Errorl::Entity
repository = Customers
entry_accessor :title, :body
def intro
body.split[' '][0, 3].join(' ') + '...'
end
end
If you are working with entities you might not want to have to keep wrapping records. The repository allows you to define a method for sending out and accepting entities.
class Posts < Errol::Repository
class << self
def dispatch(record)
Post.new(record)
end
def receive(entity)
entity.record
end
end
end
::entry_reader entity.entry_reader(*entries) => self
defines reader access to entries on the record which is short hand for calling entity.record.entry
::entry_writer entity.entry_writer(*entries) => self
defines writer access to entries on the record which is short hand for calling entity.record.entry = value
::boolean_query entity.boolean_query(*entries) => self
defines query(appends '?' on method name) to entries on the record which is short hand for calling !!entity.record.entry
::entry_accessor entity.entry_accessor(*entries) => self
short hand for entry_reader and entry_writer for entries
::boolean_accessor entity.boolean_accessor(*entries) => self
short hand for boolean_query and entry_writer for entries
#set entity.set(**attributes) => self
sends each item in the hash to a method matching the key with argument of the value
#save entity.save => self
Submits itself to the declared repository for saving
#destroy entity.destroy => self
Submits itself to the declared repository for removal
#refresh entity.refresh => self
Submits itself to the declared repository to be refreshed
- Fork it ( https://github.com/[my-github-username]/errol/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
- separate repository save method to insert and replace
- method missing bang methods call normal method then save
- raise error if entity initialised with nil?
- Protect record access method. Problem is access is require by repository to run sql filtering