rrrmatey 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +16 -0
- data/README.md +249 -0
- data/RELEASE_NOTES.md +26 -0
- data/Rakefile +39 -0
- data/lib/rrrmatey.rb +8 -0
- data/lib/rrrmatey/crud_controller.rb +97 -0
- data/lib/rrrmatey/discrete_result.rb +31 -0
- data/lib/rrrmatey/errors.rb +11 -0
- data/lib/rrrmatey/retryable.rb +28 -0
- data/lib/rrrmatey/string_model/connection_methods.rb +47 -0
- data/lib/rrrmatey/string_model/consumer_adapter_methods.rb +9 -0
- data/lib/rrrmatey/string_model/delete_methods.rb +12 -0
- data/lib/rrrmatey/string_model/field_definition_methods.rb +101 -0
- data/lib/rrrmatey/string_model/find_methods.rb +85 -0
- data/lib/rrrmatey/string_model/index_methods.rb +90 -0
- data/lib/rrrmatey/string_model/namespaced_key_methods.rb +21 -0
- data/lib/rrrmatey/string_model/string_model.rb +92 -0
- data/lib/rrrmatey/type_coercion.rb +61 -0
- data/lib/rrrmatey/version.rb +3 -0
- data/spec/rrrmatey/crud_controller/crud_controller_spec.rb +258 -0
- data/spec/rrrmatey/crud_controller/model_methods_spec.rb +26 -0
- data/spec/rrrmatey/discrete_result_spec.rb +104 -0
- data/spec/rrrmatey/retryable_spec.rb +95 -0
- data/spec/rrrmatey/string_model/connection_methods_spec.rb +64 -0
- data/spec/rrrmatey/string_model/consumer_adapter_methods_spec.rb +43 -0
- data/spec/rrrmatey/string_model/delete_methods_spec.rb +36 -0
- data/spec/rrrmatey/string_model/field_definition_methods_spec.rb +51 -0
- data/spec/rrrmatey/string_model/find_methods_spec.rb +147 -0
- data/spec/rrrmatey/string_model/index_methods_spec.rb +231 -0
- data/spec/rrrmatey/string_model/namespaced_key_methods_spec.rb +34 -0
- data/spec/rrrmatey/string_model/string_model_spec.rb +208 -0
- data/spec/spec_helper.rb +16 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b26fa620ebb2d28f3d3ae5dbd19274f076e5c810
|
4
|
+
data.tar.gz: 6a10068e527b7cb91e516fc0b6e6929aa649e857
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ebdc1b2f0341a663d3c579f808e5b83a7866c25922b8df1b427a19e3d8c065aa02bc9f9c146be69be7229e691cc1e7d570c44b3e9ddf783f27c46680162cb7d
|
7
|
+
data.tar.gz: e57d5187f5cb638f2db3ab7810d495a7e4ac72ff6cb9f8766f450540fa43cda5509ed5b2b5f631127bc89d4819c1d73f4eb692b68323878fda4ac90aac4e0e5b
|
data/LICENSE.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Copyright 2015-2016 James Gorlick and Basho Technologies, Inc.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
15
|
+
All of the files in this project are under the project-wide license
|
16
|
+
unless they are otherwise marked.
|
data/README.md
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
# RRRMatey
|
2
|
+
|
3
|
+
`rrrmatey` is an ODM (Object-Document-Mapper) framework for use with Basho Data
|
4
|
+
Platform (BDP) Cache Proxy and Riak KV.
|
5
|
+
|
6
|
+
CRUD operations are encapsulated at the controller- and model-level for relatively
|
7
|
+
smooth mixin for use in a `rails` or leaner `ruby` application framework.
|
8
|
+
|
9
|
+
BDP Cache Proxy provides the majority of the underlying interface to the data
|
10
|
+
layer. Redis acts as the cache layer while Riak provides availability with
|
11
|
+
partition tolerance. Riak Search is also used to provide the Index operation (the
|
12
|
+
silent I in CRUD).
|
13
|
+
|
14
|
+
## Dependencies
|
15
|
+
|
16
|
+
* Ruby
|
17
|
+
* 1.9.3, 2.0, 2.1, and 2.2 are supported. JRuby in 1.9 and 2.0 modes are also
|
18
|
+
supported.
|
19
|
+
* BDP Cache Proxy
|
20
|
+
* Depends on write-around and read-through caching, so BDP EE 1.1.0+ .
|
21
|
+
* Riak KV
|
22
|
+
* Depends on basic KV and Riak Search, so Riak KV v.2.0.0+ . Ensure Riak Search
|
23
|
+
is enabled.
|
24
|
+
|
25
|
+
### Development Dependencies
|
26
|
+
Development dependencies are handled with bundler. Install bundler
|
27
|
+
(`gem install bundler`), if not present.
|
28
|
+
|
29
|
+
```shell
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
### Tests
|
34
|
+
RSpec is generally run without coverage analysis.
|
35
|
+
|
36
|
+
```shell
|
37
|
+
rake spec
|
38
|
+
```
|
39
|
+
|
40
|
+
To enable coverage analysis, run with the 'CI' environment variable set.
|
41
|
+
|
42
|
+
```shell
|
43
|
+
CI=1 rake spec
|
44
|
+
```
|
45
|
+
|
46
|
+
## Use Cases
|
47
|
+
|
48
|
+
### Configuring Connections
|
49
|
+
|
50
|
+
Since `rrrmatey` uses BDP Cache Proxy which speaks a subset of the Redis protocol,
|
51
|
+
a `redis` client should be configured with one or more host entries corresponding
|
52
|
+
to the BDP Cache Proxies.
|
53
|
+
|
54
|
+
Similarly, the `riak` client is used for Riak Search.
|
55
|
+
|
56
|
+
In both cases, the use of `connection_pool` is recommended.
|
57
|
+
|
58
|
+
The following example captures the essential elements of configuration. However,
|
59
|
+
for a Rails application, the connection details should be in environment-specific
|
60
|
+
files to avoid leaking production credentials.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
require 'rrrmatey'
|
64
|
+
require 'connection_pool'
|
65
|
+
|
66
|
+
RRRMatey::StringModel.cache_proxy = RRRMatey::Retryable.new(
|
67
|
+
ConnectionPool.new(:size => 5, :timeout => 5) {
|
68
|
+
Redis.new(:host => '127.0.0.1', :port => 22122)
|
69
|
+
}
|
70
|
+
)
|
71
|
+
|
72
|
+
RRRMatey::StringModel.riak = RRRMatey::Retryable.new(
|
73
|
+
ConnectionPool.new(:size => 5, :timeout => 5) {
|
74
|
+
Riak::Client.new(:nodes => [
|
75
|
+
{:host => '127.0.0.1', :pb_port => 10017},
|
76
|
+
{:host => '127.0.0.1', :pb_port => 10027},
|
77
|
+
{:host => '127.0.0.1', :pb_port => 10037},
|
78
|
+
{:host => '127.0.0.1', :pb_port => 10047},
|
79
|
+
{:host => '127.0.0.1', :pb_port => 10057},
|
80
|
+
])
|
81
|
+
}
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
### Developing Models with Relations
|
86
|
+
|
87
|
+
To provide a shipworthy example, the models for Pirate and Vessel including a
|
88
|
+
relation for aboard follows:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class Pirate
|
92
|
+
include ::RRRMatey::StringModel
|
93
|
+
field :name, :type => :string
|
94
|
+
field :created, :type => :date, :default => Date.new(1970, 1, 1)
|
95
|
+
field :aboard, :type => :string
|
96
|
+
|
97
|
+
def valid?
|
98
|
+
valid_name? &&
|
99
|
+
valid_created?
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def valid_name?
|
105
|
+
!name.blank?
|
106
|
+
end
|
107
|
+
|
108
|
+
def valid_created?
|
109
|
+
!created.nil? && created < Date.today - 14 * 365.25
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Vessel
|
114
|
+
include ::RRRMatey::StringModel
|
115
|
+
field :name, :type => :string
|
116
|
+
|
117
|
+
def valid?
|
118
|
+
valid_name?
|
119
|
+
end
|
120
|
+
|
121
|
+
def boardings(offset, limit)
|
122
|
+
Pirate.list_by(offset, limit, :aboard_s => vessel_id)
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def valid_name?
|
128
|
+
!name.blank?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Specialiing the Model Connection
|
134
|
+
The module-level `riak` and `cache_proxy` connections may be overriden on the
|
135
|
+
Model-level.
|
136
|
+
|
137
|
+
#### Opinionated, but Open
|
138
|
+
The StringModel is opinionted, providing reasonable defaults for the following:
|
139
|
+
* item_name
|
140
|
+
* default: the class name in snake case format (underscored)
|
141
|
+
* purpose: the element wrapper for json or xml content
|
142
|
+
* index_name
|
143
|
+
* default: the item_name
|
144
|
+
* purpose: the Riak Search index name
|
145
|
+
* note: the index is created automatically
|
146
|
+
|
147
|
+
Several Rails "built-in" methods such as blank? and underscore are used if
|
148
|
+
present or otherwise are monkey-patched in Rails fashion. This is mostly okey,
|
149
|
+
but Rails inflections, ie Person => People, are not re-implemented within
|
150
|
+
RRRMatey. For use in a Rails application, existing inflections will be used.
|
151
|
+
To be 100% covered, if a Model is known to be an abnormal case, requiring
|
152
|
+
inflection, specialize the item_name on the Model.
|
153
|
+
|
154
|
+
### Developing an API Controller
|
155
|
+
|
156
|
+
Using the Pirate and Vessel models and mixing in the CrudController requires
|
157
|
+
only definition of non-CRUD methods, ie "A Pirate boards a Vessel":
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class PiratesController < ApplicationController
|
161
|
+
include RRRMatey::CrudController
|
162
|
+
|
163
|
+
def board
|
164
|
+
vessel_id = params['vessel_id']
|
165
|
+
return respond_bad_request if vessel_id.blank?
|
166
|
+
pirate_id = params['pirate_id']
|
167
|
+
return respond_bad_request if pirate_id.blank?
|
168
|
+
|
169
|
+
v = Vessel.get(vessel_id)
|
170
|
+
return respond_not_found if v.nil?
|
171
|
+
|
172
|
+
p = Pirate.get(pirate_id)
|
173
|
+
return respond_not_found if p.nil?
|
174
|
+
p.aboard = v.id
|
175
|
+
p.save
|
176
|
+
respond_ok(p)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class VesselsController < ApplicationController
|
181
|
+
include RRRMatey::CrudController
|
182
|
+
|
183
|
+
def boardings
|
184
|
+
vessel_id = params['vessel_id']
|
185
|
+
return respond_bad_request if vessel_id.blank?
|
186
|
+
offset = params['offset'] || 0
|
187
|
+
limit = params['limit'] || 20
|
188
|
+
vessel = Vessel.new(vessel_id)
|
189
|
+
pirates_aboard = vessel.boardings(offset, limit)
|
190
|
+
respond_ok(pirates_aboard)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
#### Strong Parameters
|
196
|
+
By mixing in the CrudController functionality, existing Rails strong_parameters
|
197
|
+
best practice is still available within a Rails context and should be used.
|
198
|
+
|
199
|
+
## How to Contribute
|
200
|
+
|
201
|
+
* Fork the project on Github. If you have already forked, use `git pull --rebase`
|
202
|
+
to reapply your changes on top of the mainline. Example:
|
203
|
+
|
204
|
+
```shell
|
205
|
+
git checkout master
|
206
|
+
git pull --rebase basho master
|
207
|
+
```
|
208
|
+
|
209
|
+
* Create a topic branch. If you've already created a topic branch, rebase it on
|
210
|
+
top of changes from the mainline "master" branch. Examples:
|
211
|
+
* New branch:
|
212
|
+
|
213
|
+
```shell
|
214
|
+
git checkout -b topic
|
215
|
+
```
|
216
|
+
|
217
|
+
* Existing branch:
|
218
|
+
|
219
|
+
```shell
|
220
|
+
git rebase master
|
221
|
+
```
|
222
|
+
|
223
|
+
* Write an RSpec example or set of examples that demonstrate the necessity and
|
224
|
+
validity of your changes. **Patches without specs will most often be ignored.
|
225
|
+
Just do it, you'll thank me later. Documenation patches need no specs, of course.
|
226
|
+
|
227
|
+
* Make your feature addition or bug fix. Make your specs and stories pass (green).
|
228
|
+
|
229
|
+
* Run the suite using multiruby or rvm or rbenv to ensure cross-version
|
230
|
+
compatibility.
|
231
|
+
|
232
|
+
* Cleanup any trailing whitespace in your code and generally follow the coding
|
233
|
+
style of existing code.
|
234
|
+
|
235
|
+
* Send a pull request to the upstream repositoty.
|
236
|
+
|
237
|
+
## License & Copyright
|
238
|
+
Copyright ©2015-2016 James Gorlick and Basho Technologies, Inc.
|
239
|
+
|
240
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
241
|
+
this file except in compliance with the License. You may obtain a copy of the
|
242
|
+
License at
|
243
|
+
|
244
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
245
|
+
|
246
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
247
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
248
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
249
|
+
specific language governing permissions and limitations under the License.
|
data/RELEASE_NOTES.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# RRRMatey Release Notes
|
2
|
+
|
3
|
+
## 0.1.0 Release - 2016-01-04
|
4
|
+
|
5
|
+
Version 0.1.0 is released as a reference for how to use the BDP Cache Proxy
|
6
|
+
and Riak KV to implement an API.
|
7
|
+
|
8
|
+
### New Features:
|
9
|
+
* StringModel mixin integrating BDP Cache Proxy and Riak Search to support all
|
10
|
+
CRUD operations.
|
11
|
+
* CrudController mixin supporting all CRUD operations, requiring only non-CRUD
|
12
|
+
operations to be added.
|
13
|
+
|
14
|
+
### Significant Known Issues
|
15
|
+
|
16
|
+
Several Rails "built-in" methods such as blank? and underscore are used if
|
17
|
+
present or otherwise are monkey-patched in Rails fashion. This is mostly okey,
|
18
|
+
but Rails inflections, ie Person => People, are not re-implemented within
|
19
|
+
RRRMatey. For use in a Rails application, existing inflections will be used.
|
20
|
+
To be 100% covered, if a Model is known to be an abnormal case, requiring
|
21
|
+
inflection, specialize the item_name on the Model.
|
22
|
+
|
23
|
+
The Update method on CrudController does not operate in a PATCHy way, merging the
|
24
|
+
existing values from the underlying data store with the values provided via the PUT
|
25
|
+
method. Implementing PATCH seemed to conflate understanding of the use of BDP
|
26
|
+
Cache Proxy and Riak Search.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
8
|
+
require 'rrrmatey/version'
|
9
|
+
|
10
|
+
task :gem => :build
|
11
|
+
task :build do
|
12
|
+
system 'gem build rrrmatey.gemspec'
|
13
|
+
end
|
14
|
+
|
15
|
+
task :install => :build do
|
16
|
+
system "sudo gem install rrrmatey-#{RRRMatey::VERSION}.gem"
|
17
|
+
end
|
18
|
+
|
19
|
+
task :uninstall do
|
20
|
+
system 'sudo gem uninstall rrrmatey'
|
21
|
+
end
|
22
|
+
|
23
|
+
task :release => :build do
|
24
|
+
system "git tag -a v#{RRRMatey::VERSION} -m 'Tagging #{RRRMatey::VERSION}'"
|
25
|
+
system 'git push --tags'
|
26
|
+
system "gem push rrrmatey-#{RRRMatey::VERSION}.gem"
|
27
|
+
system "rm rrrmatey-#{RRRMatey::VERSION}.gem"
|
28
|
+
end
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new('spec') do |spec|
|
31
|
+
spec.pattern = "spec/**/*_spec.rb"
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new('spec:progress') do |spec|
|
35
|
+
spec.rspec_opts = %w(--format progress)
|
36
|
+
spec.pattern = "spec/**/*_spec.rb"
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
data/lib/rrrmatey.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'riak'
|
3
|
+
require_relative 'rrrmatey/type_coercion.rb'
|
4
|
+
require_relative 'rrrmatey/errors.rb'
|
5
|
+
require_relative 'rrrmatey/retryable.rb'
|
6
|
+
require_relative 'rrrmatey/discrete_result.rb'
|
7
|
+
require_relative 'rrrmatey/string_model/string_model.rb'
|
8
|
+
require_relative 'rrrmatey/crud_controller.rb'
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module RRRMatey
|
2
|
+
module CrudController
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ModelMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ModelMethods
|
8
|
+
def model(*model_klass_n_stuff)
|
9
|
+
if model_klass_n_stuff.blank?
|
10
|
+
model_klass
|
11
|
+
else
|
12
|
+
@model_klass = model_klass_n_stuff[0]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def model_klass
|
17
|
+
@model_klass ||= opinionated_model
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def opinionated_model
|
23
|
+
# remove sController from Controller klass name
|
24
|
+
self.name[0..-12].constantize
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def index
|
29
|
+
offset = params[:offset] || 0
|
30
|
+
limit = params[:limit] || 20
|
31
|
+
items = self.class.model_klass.list(offset, limit)
|
32
|
+
respond_ok(items)
|
33
|
+
end
|
34
|
+
|
35
|
+
def show
|
36
|
+
id = params[:id]
|
37
|
+
if id.blank?
|
38
|
+
return respond_bad_request
|
39
|
+
end
|
40
|
+
item = self.class.model_klass.get(id)
|
41
|
+
if item.nil?
|
42
|
+
return respond_not_found
|
43
|
+
end
|
44
|
+
respond_ok(item)
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroy
|
48
|
+
id = params[:id]
|
49
|
+
if id.blank?
|
50
|
+
return respond_bad_request
|
51
|
+
end
|
52
|
+
self.class.model_klass.delete(id)
|
53
|
+
respond_ok(nil)
|
54
|
+
end
|
55
|
+
|
56
|
+
def update
|
57
|
+
# TODO: get and merge with params if a patch-y update is desired
|
58
|
+
item = self.class.model_klass.from_params(params)
|
59
|
+
begin
|
60
|
+
item.save
|
61
|
+
rescue RRRMatey::InvalidModelError
|
62
|
+
return respond_bad_request
|
63
|
+
rescue StandardError => e
|
64
|
+
return respond_internal_server_error(e)
|
65
|
+
end
|
66
|
+
respond_ok(nil)
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_bad_request
|
70
|
+
respond_to do |format|
|
71
|
+
format.json { render :json => nil, :status => 400 }
|
72
|
+
format.xml { render :xml => nil, :status => 400 }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def respond_not_found
|
77
|
+
respond_to do |format|
|
78
|
+
format.json { render :json => 'Not Found', :status => 404 }
|
79
|
+
format.xml { render :xml => 'Not Found', :status => 404 }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def respond_internal_server_error(e)
|
84
|
+
respond_to do |format|
|
85
|
+
format.json { render :json => { :mesage => e.message }, :status => 500 }
|
86
|
+
format.xml { render :xml => { :message => e.message }, :status => 500 }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def respond_ok(item)
|
91
|
+
respond_to do |format|
|
92
|
+
format.json { render :json => item }
|
93
|
+
format.xml { render :xml => item }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|