errol 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c43849e71e1ed66ec52d37a22663f6b4d44332e9
4
+ data.tar.gz: 41200a851e4a32ba4e67a52f7887a4550ed94348
5
+ SHA512:
6
+ metadata.gz: 9ce82562e4c6f32be9824a1547630942750e818a3de7010ed4af9bc9e1cb9e96f1345d64570e047d3d85b239c102075b5b5d1ea85a1e802d9f6789f541937ad7
7
+ data.tar.gz: 051da105e5b3623b20fd0902cc6ae68a35eee31dda201c4265f6f54241052a8acc7f5974b10654f0c23e7ba6bcf3795d38317824f0f0c6c83bf9145aaf75820a
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in errol.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Peter Saxton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,220 @@
1
+ # Errol
2
+
3
+ **Repository build on top of [sequel](http://sequel.jeremyevans.net/) 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.
4
+
5
+ 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.
6
+
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'errol'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install errol
24
+
25
+ ## Usage
26
+
27
+ Usage is best described by each of the components
28
+
29
+ ### Inquiry
30
+ 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
31
+
32
+ ```rb
33
+ class Posts
34
+ class Inquiry < Errol::Inquiry
35
+ default :order, :id
36
+ default :page, 1
37
+ default :page_size, 12
38
+ default :published, true
39
+ end
40
+ end
41
+
42
+ inquiry = Inquiry.new :page => 2
43
+ inquiry.page
44
+ # => 2
45
+ inquiry.published?
46
+ # => true
47
+ inquiry.other
48
+ # => raise DefaultValueUndefined
49
+ ```
50
+
51
+ ### Records
52
+ These are simply sequel models. there is no wrapper and you define them as sequel models
53
+
54
+ ```rb
55
+ class Post
56
+ class Record < Sequel::Model(:posts)
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### Repository
62
+ 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.
63
+
64
+ ```rb
65
+ class Posts < Errol::Repository
66
+
67
+ class << self
68
+ # The repository needs to know what records it is storing
69
+ def record_class
70
+ Post::Record
71
+ end
72
+
73
+ # The repository needs to know how to build a requirments hash that may be empty into a inquiry containing defaults
74
+ def inquiry(requirements)
75
+ Posts::Inquiry.new(requirements)
76
+ end
77
+ end
78
+
79
+ # 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
80
+ def dataset
81
+ partial = raw_dataset
82
+ partial = partial.order(inquiry.order)
83
+ partial = partial.where(:published) if inquiry.published
84
+ partial
85
+ end
86
+ end
87
+
88
+ page = Posts.new
89
+ # First page of published posts ordered by id
90
+
91
+ page.first_page?
92
+ # => true
93
+
94
+ page.page_size
95
+ # => 12
96
+
97
+ page.each { |post| puts post }
98
+ # Outputs each of the posts on the page
99
+
100
+ # class methods also use an inquiry to filter data
101
+
102
+ Posts.each { |post| puts post }
103
+ # Outputs each published post in the dataset ordered by id
104
+
105
+ Posts.each(:published => false, :order => :created_at) { |post| puts post }
106
+ # Outputs each post in the database ordered by creation date
107
+
108
+ #if you want a filtered dataset which is not paginated pass false as the last argument
109
+ set = Posts.new({:order => :created_at}, false)
110
+ ```
111
+
112
+ ### Entity
113
+ 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.*
114
+
115
+ ```rb
116
+ # Without Errol::Entity
117
+ class Post
118
+ def initialize(record)
119
+ @record = record
120
+ end
121
+
122
+ attr_reader :record
123
+
124
+ def intro
125
+ body.split[' '][0, 3].join(' ') + '...'
126
+ end
127
+
128
+ def body
129
+ record.body
130
+ end
131
+
132
+ def title
133
+ record.title
134
+ end
135
+
136
+ def save
137
+ Posts.save record
138
+ end
139
+ end
140
+
141
+ # With Errol::Entity
142
+ class Customer < Errorl::Entity
143
+ repository = Customers
144
+ entry_accessor :title, :body
145
+
146
+ def intro
147
+ body.split[' '][0, 3].join(' ') + '...'
148
+ end
149
+ end
150
+ ```
151
+
152
+ 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.
153
+
154
+ ```rb
155
+ class Posts < Errol::Repository
156
+ class << self
157
+ def dispatch(record)
158
+ Post.new(record)
159
+ end
160
+
161
+ def receive(entity)
162
+ entity.record
163
+ end
164
+ end
165
+ end
166
+ ```
167
+
168
+ ## Documentation
169
+
170
+ ### Entity
171
+
172
+ **::entry_reader** `entity.entry_reader(*entries) => self`
173
+
174
+ defines reader access to entries on the record which is short hand for calling `entity.record.entry`
175
+
176
+ **::entry_writer** `entity.entry_writer(*entries) => self`
177
+
178
+ defines writer access to entries on the record which is short hand for calling `entity.record.entry = value`
179
+
180
+ **::boolean_query** `entity.boolean_query(*entries) => self`
181
+
182
+ defines query(appends '?' on method name) to entries on the record which is short hand for calling `!!entity.record.entry`
183
+
184
+ **::entry_accessor** `entity.entry_accessor(*entries) => self`
185
+
186
+ short hand for entry_reader and entry_writer for entries
187
+
188
+ **::boolean_accessor** `entity.boolean_accessor(*entries) => self`
189
+
190
+ short hand for boolean_query and entry_writer for entries
191
+
192
+ **#set** `entity.set(**attributes) => self`
193
+
194
+ sends each item in the hash to a method matching the key with argument of the value
195
+
196
+ **#save** `entity.save => self`
197
+
198
+ Submits itself to the declared repository for saving
199
+
200
+ **#destroy** `entity.destroy => self`
201
+
202
+ Submits itself to the declared repository for removal
203
+
204
+ **#refresh** `entity.refresh => self`
205
+
206
+ Submits itself to the declared repository to be refreshed
207
+
208
+ ## Contributing
209
+
210
+ 1. Fork it ( https://github.com/[my-github-username]/errol/fork )
211
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
212
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
213
+ 4. Push to the branch (`git push origin my-new-feature`)
214
+ 5. Create a new Pull Request
215
+
216
+ ## Upcoming
217
+ 1. separate repository save method to insert and replace
218
+ 2. method missing bang methods call normal method then save
219
+ 3. raise error if entity initialised with nil?
220
+ 4. Protect record access method. Problem is access is require by repository to run sql filtering
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ test_tasks = Dir['test/*/'].map { |d| File.basename(d) }
5
+
6
+ test_tasks.each do |folder|
7
+ Rake::TestTask.new("test:#{folder}") do |test|
8
+ test.pattern = "test/#{folder}/**/*_test.rb"
9
+ test.verbose = true
10
+ end
11
+ end
12
+
13
+ desc "Run application test suite"
14
+ Rake::TestTask.new("test") do |test|
15
+ test.pattern = "test/**/*_test.rb"
16
+ test.verbose = true
17
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'errol/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "errol"
8
+ spec.version = Errol::VERSION
9
+ spec.authors = ["Peter Saxton"]
10
+ spec.email = ["peterhsaxton@gmail.com"]
11
+ spec.summary = %q{Repository to store and deliver encapsulated records.}
12
+ spec.description = %q{Based of the Sequel Library to deliver a repository interface to persisted data. Handles pagination by default}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "sequel", "~> 4.19"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.4"
25
+ spec.add_development_dependency "minitest-reporters", "~> 1.0"
26
+ end
@@ -0,0 +1,8 @@
1
+ require "errol/version"
2
+ require "errol/repository"
3
+ require "errol/inquiry"
4
+ require "errol/entity"
5
+
6
+ module Errol
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,109 @@
1
+ module Errol
2
+ class Entity
3
+ RepositoryUndefined = Class.new(StandardError)
4
+ NilRecord = Class.new(StandardError)
5
+ def self.repository=(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def self.repository
10
+ @repository || (raise RepositoryUndefined, "No repository set for #{self.name}")
11
+ end
12
+
13
+ def self.entry_reader(*entries)
14
+ entries.each do |entry|
15
+ define_method entry do
16
+ record.public_send entry
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.entry_writer(*entries)
22
+ entries.each do |entry|
23
+ define_method "#{entry}=" do |value|
24
+ record.public_send "#{entry}=", value
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.boolean_query(*entries)
30
+ entries.each do |entry|
31
+ define_method "#{entry}?" do
32
+ !!(record.public_send entry)
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.entry_accessor(*entries)
38
+ entry_reader *entries
39
+ entry_writer *entries
40
+ end
41
+
42
+ def self.boolean_accessor(*entries)
43
+ boolean_query *entries
44
+ entry_writer *entries
45
+ end
46
+
47
+ def initialize(record)
48
+ # raise NilRecord, "Tried to initialise #{self.class.name} with nil record" if record.nil?
49
+ @record = record
50
+ end
51
+
52
+ attr_reader :record
53
+ # protected :record
54
+ entry_reader :id
55
+
56
+ def set(**attributes)
57
+ attributes.each do |attribute, value|
58
+ self.public_send "#{attribute}=", value
59
+ end
60
+ self
61
+ end
62
+
63
+ def set!(*args)
64
+ set(*args)
65
+ save
66
+ end
67
+
68
+ def repository
69
+ self.class.repository
70
+ end
71
+
72
+ def save
73
+ repository.save self
74
+ self
75
+ end
76
+
77
+ def destroy
78
+ repository.remove self
79
+ self
80
+ end
81
+
82
+ def refresh
83
+ repository.refresh self
84
+ self
85
+ end
86
+
87
+ def ==(other)
88
+ other.class == self.class && other.record == record
89
+ end
90
+ alias_method :eql?, :==
91
+ # def method_missing(method_name, *args, &block)
92
+ # if method_name.to_s =~ /^(.+)!$/
93
+ # self.public_send($1, *args, &block)
94
+ # repository.save self
95
+ # else
96
+ # super
97
+ # end
98
+ # end
99
+
100
+ # def respond_to?(method_name)
101
+ # if method_name.to_s =~ /^(.+)!$/
102
+ # self.respond_to? $1
103
+ # else
104
+ # super
105
+ # end
106
+ # end
107
+
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+ module Errol
2
+ class Inquiry
3
+ DefaultValueUndefined = Class.new(StandardError)
4
+
5
+ def initialize(options={})
6
+ options.each do |key, value|
7
+ defaults[key.to_sym] = value
8
+ end
9
+ end
10
+
11
+ def defaults
12
+ @defaults ||= self.class.defaults.clone
13
+ end
14
+
15
+ def self.default(property, value)
16
+ defaults[property.to_sym] = value
17
+ end
18
+
19
+ def self.defaults
20
+ @defaults ||= {}
21
+ end
22
+
23
+ def method_missing(method_name, *args, &block)
24
+ if method_name.to_s =~ /^(.+)(?:\?)$/
25
+ !!self.public_send($1, *args, &block)
26
+ else
27
+ defaults.fetch(method_name) do |requirement|
28
+ raise DefaultValueUndefined.new "Inquiry requirement for \"#{requirement}\" has not been set"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end