airspace 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -0
- data/.travis.yml +22 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +104 -0
- data/Guardfile +16 -0
- data/LICENSE +7 -0
- data/README.md +220 -0
- data/airspace.gemspec +33 -0
- data/bin/console +15 -0
- data/lib/airspace.rb +10 -0
- data/lib/airspace/airspace.rb +50 -0
- data/lib/airspace/chunker.rb +47 -0
- data/lib/airspace/dataset.rb +79 -0
- data/lib/airspace/has_metadata.rb +24 -0
- data/lib/airspace/info_keys.rb +17 -0
- data/lib/airspace/key.rb +35 -0
- data/lib/airspace/metadata.rb +44 -0
- data/lib/airspace/reader.rb +92 -0
- data/lib/airspace/serializer.rb +42 -0
- data/lib/airspace/store.rb +115 -0
- data/lib/airspace/version.rb +12 -0
- data/spec/airspace/airspace_spec.rb +177 -0
- data/spec/airspace/chunker_spec.rb +111 -0
- data/spec/airspace/store_spec.rb +21 -0
- data/spec/spec_helper.rb +33 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 68fd92350f1c6274af7a30a46918f1b9037ba9d1b0e63b1a88c284a4715f432d
|
4
|
+
data.tar.gz: 8b89696d66ec946c6ee807f86cacd4cdf370057b74f9a06b6cdae1bfbfa2448b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7603141412de37754499c6b148ba58c3c80ce4434d9b58f7d8d4a6e4d36de07827810cff6317281f4ab90af180d3464bf377202c4755ec62c4e330855f702fa3
|
7
|
+
data.tar.gz: d0c7cb11e532003bf867fddddc36759bdadd0fd583430b4a1cdbb12e2c2da510c4263fceb824546b1ed33e37cb6a2f9bb74c626616e9f1864c479f09c4279ace
|
data/.editorconfig
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.0
|
data/.travis.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
env:
|
2
|
+
global:
|
3
|
+
- CC_TEST_REPORTER_ID=8fdaf7c8198b46ebdb0fc75cc04a85a150af827c5f216e12ab1dc189750a8bff
|
4
|
+
services:
|
5
|
+
- redis-server
|
6
|
+
language: ruby
|
7
|
+
rvm:
|
8
|
+
# Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
|
9
|
+
- 2.3.8
|
10
|
+
- 2.4.5
|
11
|
+
- 2.5.3
|
12
|
+
- 2.6.0
|
13
|
+
cache: bundler
|
14
|
+
before_script:
|
15
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
16
|
+
- chmod +x ./cc-test-reporter
|
17
|
+
- ./cc-test-reporter before-build
|
18
|
+
script:
|
19
|
+
- bundle exec rubocop
|
20
|
+
- bundle exec rspec
|
21
|
+
after_script:
|
22
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
airspace (1.0.0)
|
5
|
+
redis (>= 3.3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ansi (1.5.0)
|
11
|
+
ast (2.4.0)
|
12
|
+
coderay (1.1.2)
|
13
|
+
database_cleaner (1.7.0)
|
14
|
+
diff-lcs (1.3)
|
15
|
+
docile (1.3.1)
|
16
|
+
ffi (1.10.0)
|
17
|
+
formatador (0.2.5)
|
18
|
+
guard (2.15.0)
|
19
|
+
formatador (>= 0.2.4)
|
20
|
+
listen (>= 2.7, < 4.0)
|
21
|
+
lumberjack (>= 1.0.12, < 2.0)
|
22
|
+
nenv (~> 0.1)
|
23
|
+
notiffany (~> 0.0)
|
24
|
+
pry (>= 0.9.12)
|
25
|
+
shellany (~> 0.0)
|
26
|
+
thor (>= 0.18.1)
|
27
|
+
guard-compat (1.2.1)
|
28
|
+
guard-rspec (4.7.3)
|
29
|
+
guard (~> 2.1)
|
30
|
+
guard-compat (~> 1.1)
|
31
|
+
rspec (>= 2.99.0, < 4.0)
|
32
|
+
hirb (0.7.3)
|
33
|
+
jaro_winkler (1.5.2)
|
34
|
+
json (2.1.0)
|
35
|
+
listen (3.1.5)
|
36
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
37
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
38
|
+
ruby_dep (~> 1.2)
|
39
|
+
lumberjack (1.0.13)
|
40
|
+
method_source (0.9.2)
|
41
|
+
nenv (0.3.0)
|
42
|
+
notiffany (0.1.1)
|
43
|
+
nenv (~> 0.1)
|
44
|
+
shellany (~> 0.0)
|
45
|
+
parallel (1.13.0)
|
46
|
+
parser (2.6.0.0)
|
47
|
+
ast (~> 2.4.0)
|
48
|
+
powerpack (0.1.2)
|
49
|
+
pry (0.12.2)
|
50
|
+
coderay (~> 1.1.0)
|
51
|
+
method_source (~> 0.9.0)
|
52
|
+
rainbow (3.0.0)
|
53
|
+
rb-fsevent (0.10.3)
|
54
|
+
rb-inotify (0.10.0)
|
55
|
+
ffi (~> 1.0)
|
56
|
+
redis (4.1.0)
|
57
|
+
rspec (3.8.0)
|
58
|
+
rspec-core (~> 3.8.0)
|
59
|
+
rspec-expectations (~> 3.8.0)
|
60
|
+
rspec-mocks (~> 3.8.0)
|
61
|
+
rspec-core (3.8.0)
|
62
|
+
rspec-support (~> 3.8.0)
|
63
|
+
rspec-expectations (3.8.2)
|
64
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
65
|
+
rspec-support (~> 3.8.0)
|
66
|
+
rspec-mocks (3.8.0)
|
67
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
68
|
+
rspec-support (~> 3.8.0)
|
69
|
+
rspec-support (3.8.0)
|
70
|
+
rubocop (0.63.1)
|
71
|
+
jaro_winkler (~> 1.5.1)
|
72
|
+
parallel (~> 1.10)
|
73
|
+
parser (>= 2.5, != 2.5.1.1)
|
74
|
+
powerpack (~> 0.1)
|
75
|
+
rainbow (>= 2.2.2, < 4.0)
|
76
|
+
ruby-progressbar (~> 1.7)
|
77
|
+
unicode-display_width (~> 1.4.0)
|
78
|
+
ruby-progressbar (1.10.0)
|
79
|
+
ruby_dep (1.5.0)
|
80
|
+
shellany (0.0.1)
|
81
|
+
simplecov (0.16.1)
|
82
|
+
docile (~> 1.1)
|
83
|
+
json (>= 1.8, < 3)
|
84
|
+
simplecov-html (~> 0.10.0)
|
85
|
+
simplecov-console (0.4.2)
|
86
|
+
ansi
|
87
|
+
hirb
|
88
|
+
simplecov
|
89
|
+
simplecov-html (0.10.2)
|
90
|
+
thor (0.20.3)
|
91
|
+
unicode-display_width (1.4.1)
|
92
|
+
|
93
|
+
PLATFORMS
|
94
|
+
ruby
|
95
|
+
|
96
|
+
DEPENDENCIES
|
97
|
+
airspace!
|
98
|
+
database_cleaner (~> 1.7)
|
99
|
+
guard-rspec (~> 4.7)
|
100
|
+
pry (~> 0.12)
|
101
|
+
rspec (~> 3.8)
|
102
|
+
rubocop (~> 0.63.1)
|
103
|
+
simplecov (~> 0.16.1)
|
104
|
+
simplecov-console (~> 0.4.2)
|
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
4
|
+
require 'guard/rspec/dsl'
|
5
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2019 Blue Marble Payroll, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# Airspace
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/airspace.svg)](https://badge.fury.io/rb/airspace) [![Build Status](https://travis-ci.org/bluemarblepayroll/airspace.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/airspace) [![Maintainability](https://api.codeclimate.com/v1/badges/d25bfce65b230ee956df/maintainability)](https://codeclimate.com/github/bluemarblepayroll/airspace/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/d25bfce65b230ee956df/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/airspace/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
|
4
|
+
|
5
|
+
The general use case for this library is as follows:
|
6
|
+
|
7
|
+
I have a materialized/immutable dataset that I wish to temporarily store. With this temporary storage, I would like to support some level of server-side paging, but I do not need further querying/sorting.
|
8
|
+
|
9
|
+
A dataset, in this context, is:
|
10
|
+
1. A collection of generic data (i.e. a Ruby hash or some other object)
|
11
|
+
2. A two-dimensional array of pages and rows.
|
12
|
+
|
13
|
+
What this means is: if you can compute a final form of your dataset and it is immutable then you can use Airspace to temporarily store it in Redis.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
To install through Rubygems:
|
18
|
+
|
19
|
+
````
|
20
|
+
gem install install airspace
|
21
|
+
````
|
22
|
+
|
23
|
+
You can also add this to your Gemfile:
|
24
|
+
|
25
|
+
````
|
26
|
+
bundle add airspace
|
27
|
+
````
|
28
|
+
|
29
|
+
## Examples
|
30
|
+
|
31
|
+
### A Basic Example
|
32
|
+
|
33
|
+
Say we have the following dataset:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
dataset = {
|
37
|
+
movie_name: 'Avengers',
|
38
|
+
rating: 'PG-13',
|
39
|
+
release_date: Date.new(2012, 5, 5),
|
40
|
+
actors: [
|
41
|
+
{ id: 1, name: 'Iron Man' },
|
42
|
+
{ id: 2, name: 'Hulk' },
|
43
|
+
{ id: 3, name: 'Thor' },
|
44
|
+
{ id: 4, name: 'Spiderman' },
|
45
|
+
{ id: 5, name: 'Captain America' }
|
46
|
+
]
|
47
|
+
}
|
48
|
+
```
|
49
|
+
|
50
|
+
We could split this data up and also paginate the actors:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
data = dataset.slice(:movie_name, :rating, :release_date)
|
54
|
+
pages = dataset[:actors].each_slice(2).to_a
|
55
|
+
```
|
56
|
+
|
57
|
+
We can split out the movie 'data' from the actors 'pages' and store it:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
id = ::Airspace.set(Redis.new, data: data pages: pages)
|
61
|
+
```
|
62
|
+
|
63
|
+
The #set call will return the unique identifier for the stored dataset, which we can now use to retrieve it back:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
reader = ::Airspace.get(Redis.new, id)
|
67
|
+
```
|
68
|
+
|
69
|
+
The #get call will return an ::Airspace::Reader object that contains the data, metadata (page_count, etc...), and methods to retrieve all or one of the pages. We could access back the data as:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
reader.data
|
73
|
+
```
|
74
|
+
|
75
|
+
or get all the pages:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
reader.pages
|
79
|
+
```
|
80
|
+
|
81
|
+
or just the last page:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
reader.page(reader.page_count)
|
85
|
+
```
|
86
|
+
|
87
|
+
The value here is that the Reader will only load the initial set of data (movie in our case), while the paged data will only load it all if necessary (or one at a time as needed.)
|
88
|
+
|
89
|
+
If you are done with the dataset you can delete it:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
success = ::Airspace.del(Redis.new, id)
|
93
|
+
```
|
94
|
+
|
95
|
+
### Customization / Options
|
96
|
+
|
97
|
+
There are a few options you can leverage:
|
98
|
+
|
99
|
+
* Explicitly Specified ID: If you do not specify an id then one will be assigned for you (SecureRandom.uuid)
|
100
|
+
* Key Prefix: Used for key namespace isolation. For example: [id: 123, prefix: movies] => key: movies:123
|
101
|
+
* Custom Serializer: You can pass in your own serializer in case the default methods do not suffice.
|
102
|
+
* Automatic Expiration (TTL): By default no expiration is set on keys. This allows you to set one.
|
103
|
+
* Pages Per Chunk: By default 5 pages will be stored per chunk. If you think this is not the right balance for your needs you can explicitly pass this in.
|
104
|
+
|
105
|
+
The most complex option is the custom serializer. Airspace comes with a default serializer (::Airspace::Serializer) that defaults to the standard Ruby JSON library. Here is an example of a custom serializer that will store the data and rows as arrays (instead of hashes):
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
class CustomSerializer < ::Airspace::Serializer
|
109
|
+
def serialize_data(obj)
|
110
|
+
obj = obj.map { |k, v| [k.to_sym, v] }.to_h
|
111
|
+
|
112
|
+
json_serialize(
|
113
|
+
[
|
114
|
+
obj[:movie_name],
|
115
|
+
obj[:release_date].to_s,
|
116
|
+
obj[:rating]
|
117
|
+
]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def deserialize_data(json)
|
122
|
+
array = json_deserialize(json)
|
123
|
+
|
124
|
+
{
|
125
|
+
movie_name: array[0],
|
126
|
+
release_date: Date.parse(array[1]),
|
127
|
+
rating: array[2]
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
def serialize_row(obj)
|
132
|
+
obj = obj.map { |k, v| [k.to_sym, v] }.to_h
|
133
|
+
|
134
|
+
json_serialize([obj[:id], obj[:name]])
|
135
|
+
end
|
136
|
+
|
137
|
+
def deserialize_row(json)
|
138
|
+
array = json_deserialize(json)
|
139
|
+
|
140
|
+
{
|
141
|
+
id: array[0],
|
142
|
+
name: array[1]
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
Here is what utilizing all the options would looks like:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
set_options = {
|
152
|
+
expires_in_seconds: 60 * 60 * 12, # 12 hours
|
153
|
+
prefix: 'movies',
|
154
|
+
serializer: CustomSerializer.new,
|
155
|
+
pages_per_chunk: 100
|
156
|
+
}
|
157
|
+
id = ::Airspace.set(Redis.new, data: data pages: pages, options: options)
|
158
|
+
|
159
|
+
# Cannot use expires_in_seconds or pages_per_chunk
|
160
|
+
# as they are values stored alongside of the dataset.
|
161
|
+
get_and_del_options = {
|
162
|
+
prefix: 'movies',
|
163
|
+
serializer: CustomSerializer.new
|
164
|
+
}
|
165
|
+
reader = ::Airspace.get(Redis.new, id, options: get_options)
|
166
|
+
|
167
|
+
success = ::Airspace.del(Redis.new, id, options: get_and_del_options)
|
168
|
+
```
|
169
|
+
|
170
|
+
## Contributing
|
171
|
+
|
172
|
+
### Development Environment Configuration
|
173
|
+
|
174
|
+
Basic steps to take to get this repository compiling:
|
175
|
+
|
176
|
+
1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check airspace.gemspec for versions supported)
|
177
|
+
2. Install bundler (gem install bundler)
|
178
|
+
3. Clone the repository (git clone git@github.com:bluemarblepayroll/airspace.git)
|
179
|
+
4. Navigate to the root folder (cd airspace)
|
180
|
+
5. Install dependencies (bundle)
|
181
|
+
|
182
|
+
### Running Tests
|
183
|
+
|
184
|
+
To execute the test suite run:
|
185
|
+
|
186
|
+
````
|
187
|
+
bundle exec rspec spec --format documentation
|
188
|
+
````
|
189
|
+
|
190
|
+
Alternatively, you can have Guard watch for changes:
|
191
|
+
|
192
|
+
````
|
193
|
+
bundle exec guard
|
194
|
+
````
|
195
|
+
|
196
|
+
Also, do not forget to run Rubocop:
|
197
|
+
|
198
|
+
````
|
199
|
+
bundle exec rubocop
|
200
|
+
````
|
201
|
+
|
202
|
+
### Publishing
|
203
|
+
|
204
|
+
Note: ensure you have proper authorization before trying to publish new versions.
|
205
|
+
|
206
|
+
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
|
207
|
+
|
208
|
+
1. Merge Pull Request into master
|
209
|
+
2. Update the [version number](https://semver.org/) in lib/airspace/version.rb
|
210
|
+
3. Bundle
|
211
|
+
4. Update CHANGELOG.md
|
212
|
+
5. Commit & Push master to remote and ensure CI builds master successfully
|
213
|
+
6. Build the project locally: `gem build airspace`
|
214
|
+
7. Publish package to NPM: `gem push airspace-X.gem` where X is the version to push
|
215
|
+
8. Tag master with new version: `git tag <version>`
|
216
|
+
9. Push tags remotely: `git push origin --tags`
|
217
|
+
|
218
|
+
## License
|
219
|
+
|
220
|
+
This project is MIT Licensed.
|
data/airspace.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require './lib/airspace/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'airspace'
|
7
|
+
s.version = Airspace::VERSION
|
8
|
+
s.summary = 'Redis Dataset Store'
|
9
|
+
|
10
|
+
s.description = <<-DESCRIPTION
|
11
|
+
This library provides a very simple interface for storing/fetching/paging datasets in Redis.
|
12
|
+
DESCRIPTION
|
13
|
+
|
14
|
+
s.authors = ['Matthew Ruggio']
|
15
|
+
s.email = ['mruggio@bluemarblepayroll.com']
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
s.homepage = 'https://github.com/bluemarblepayroll/airspace'
|
20
|
+
s.license = 'MIT'
|
21
|
+
|
22
|
+
s.required_ruby_version = '>= 2.3.8'
|
23
|
+
|
24
|
+
s.add_dependency('redis', '>=3.3.0')
|
25
|
+
|
26
|
+
s.add_development_dependency('database_cleaner', '~>1.7')
|
27
|
+
s.add_development_dependency('guard-rspec', '~>4.7')
|
28
|
+
s.add_development_dependency('pry', '~>0.12')
|
29
|
+
s.add_development_dependency('rspec', '~> 3.8')
|
30
|
+
s.add_development_dependency('rubocop', '~>0.63.1')
|
31
|
+
s.add_development_dependency('simplecov', '~>0.16.1')
|
32
|
+
s.add_development_dependency('simplecov-console', '~>0.4.2')
|
33
|
+
end
|