rrrmatey 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|