dynamo-record 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/.gitignore +11 -0
- data/.rspec +1 -0
- data/.rubocop.yml +81 -0
- data/.travis.yml +5 -0
- data/Dockerfile.test +23 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +163 -0
- data/README.md +110 -0
- data/build.sh +28 -0
- data/docker-compose.override.yml +6 -0
- data/docker-compose.yml +17 -0
- data/dynamo-record.gemspec +35 -0
- data/lib/dynamo-record.rb +4 -0
- data/lib/dynamo-record/marshalers.rb +44 -0
- data/lib/dynamo-record/model.rb +122 -0
- data/lib/dynamo-record/record.rb +7 -0
- data/lib/dynamo-record/record/railtie.rb +13 -0
- data/lib/dynamo-record/record/version.rb +5 -0
- data/lib/dynamo-record/table_migration.rb +36 -0
- data/lib/dynamo-record/task_helpers/cleanup.rb +16 -0
- data/lib/dynamo-record/task_helpers/drop_all_tables.rb +17 -0
- data/lib/dynamo-record/task_helpers/drop_table.rb +11 -0
- data/lib/dynamo-record/task_helpers/list_tables.rb +13 -0
- data/lib/dynamo-record/task_helpers/migration_runner.rb +53 -0
- data/lib/dynamo-record/task_helpers/scale.rb +86 -0
- data/lib/model_existence_validator.rb +7 -0
- data/lib/tasks/dynamo.rake +38 -0
- metadata +207 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 77cdbe44e4be22d2294565970b108d66c48ffb87
|
4
|
+
data.tar.gz: 70653d3d5cddc536eeefb74d47ea8522665f4b3b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b7fe3b6bd23079f734531545ee1ae42079d5053b99b058d9cc1c96a0ba78804de27b8f3fca6ed7609d299d7c20bf0bfd21c38b34823099b8da6cf254197c2537
|
7
|
+
data.tar.gz: ec96773a915b361673ee1af591ea13a84b8cb5724cc5fc76168412d75cb43bd590d7b721462c2a682a677237459bc0449ee0c5e8f660806aa33097e3157c3731
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
Rails:
|
2
|
+
Enabled: true
|
3
|
+
|
4
|
+
Rails/HttpPositionalArguments:
|
5
|
+
# Renable once we are on Rails v5
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
AllCops:
|
9
|
+
TargetRubyVersion: 2.3
|
10
|
+
|
11
|
+
Metrics/ClassLength:
|
12
|
+
Max: 200 # Default: 100
|
13
|
+
|
14
|
+
Metrics/LineLength:
|
15
|
+
Max: 120 # Default: 80
|
16
|
+
|
17
|
+
Metrics/MethodLength:
|
18
|
+
Max: 20 # Default: 10
|
19
|
+
|
20
|
+
Metrics/BlockLength:
|
21
|
+
Max: 30
|
22
|
+
Exclude:
|
23
|
+
- 'spec/**/*.rb'
|
24
|
+
|
25
|
+
Style/AlignParameters:
|
26
|
+
# Alignment of parameters in multi-line method calls.
|
27
|
+
#
|
28
|
+
# The `with_fixed_indentation` style aligns the following lines with one
|
29
|
+
# level of indentation relative to the start of the line with the method call.
|
30
|
+
#
|
31
|
+
# method_call(a,
|
32
|
+
# b)
|
33
|
+
EnforcedStyle: with_fixed_indentation
|
34
|
+
|
35
|
+
Style/ClassAndModuleChildren:
|
36
|
+
# Checks the style of children definitions at classes and modules.
|
37
|
+
#
|
38
|
+
# Basically there are two different styles:
|
39
|
+
#
|
40
|
+
# `nested` - have each child on a separate line
|
41
|
+
# class Foo
|
42
|
+
# class Bar
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# `compact` - combine definitions as much as possible
|
47
|
+
# class Foo::Bar
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# The compact style is only forced, for classes / modules with one child.
|
51
|
+
EnforcedStyle: nested
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Style/Documentation:
|
55
|
+
# This cop checks for missing top-level documentation of classes and modules.
|
56
|
+
# Classes with no body and namespace modules are exempt from the check.
|
57
|
+
# Namespace modules are modules that have nothing in their bodies except
|
58
|
+
# classes or other modules.
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
Lint/EndAlignment:
|
62
|
+
AlignWith: variable
|
63
|
+
|
64
|
+
Style/CaseIndentation:
|
65
|
+
IndentWhenRelativeTo: end
|
66
|
+
|
67
|
+
Style/FrozenStringLiteralComment:
|
68
|
+
# `when_needed` will add the frozen string literal comment to files
|
69
|
+
# only when the `TargetRubyVersion` is set to 2.3+.
|
70
|
+
# `always` will always add the frozen string literal comment to a file
|
71
|
+
# regardless of the Ruby version or if `freeze` or `<<` are called on a
|
72
|
+
# string literal. If you run code against multiple versions of Ruby, it is
|
73
|
+
# possible that this will create errors in Ruby 2.3.0+.
|
74
|
+
#
|
75
|
+
# See: https://wyeworks.com/blog/2015/12/1/immutable-strings-in-ruby-2-dot-3
|
76
|
+
EnforcedStyle: when_needed
|
77
|
+
Enabled: false
|
78
|
+
|
79
|
+
Style/NumericPredicate:
|
80
|
+
Exclude:
|
81
|
+
- 'spec/**/*'
|
data/.travis.yml
ADDED
data/Dockerfile.test
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
FROM instructure/ruby:2.3
|
2
|
+
|
3
|
+
ENV APP_HOME "/usr/src/app/"
|
4
|
+
|
5
|
+
USER root
|
6
|
+
|
7
|
+
COPY dynamo-record.gemspec Gemfile Gemfile.lock $APP_HOME
|
8
|
+
RUN mkdir -p $APP_HOME/lib/dynamo-record/record
|
9
|
+
COPY lib/dynamo-record/record/version.rb $APP_HOME/lib/dynamo-record/record
|
10
|
+
RUN chown -R docker:docker $APP_HOME
|
11
|
+
|
12
|
+
USER docker
|
13
|
+
RUN gem install bundler
|
14
|
+
RUN bundle install --quiet --jobs 8
|
15
|
+
USER root
|
16
|
+
|
17
|
+
COPY . $APP_HOME
|
18
|
+
RUN mkdir -p $APP_HOME/coverage && \
|
19
|
+
mkdir -p $APP_HOME/spec/internal/log && \
|
20
|
+
chown -R docker:docker $APP_HOME
|
21
|
+
USER docker
|
22
|
+
|
23
|
+
CMD ["bundle", "exec", "rspec"]
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dynamo-record (0.1.0)
|
5
|
+
aws-record (~> 1.1)
|
6
|
+
rails (~> 4.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
actionmailer (4.2.8)
|
12
|
+
actionpack (= 4.2.8)
|
13
|
+
actionview (= 4.2.8)
|
14
|
+
activejob (= 4.2.8)
|
15
|
+
mail (~> 2.5, >= 2.5.4)
|
16
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
+
actionpack (4.2.8)
|
18
|
+
actionview (= 4.2.8)
|
19
|
+
activesupport (= 4.2.8)
|
20
|
+
rack (~> 1.6)
|
21
|
+
rack-test (~> 0.6.2)
|
22
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
24
|
+
actionview (4.2.8)
|
25
|
+
activesupport (= 4.2.8)
|
26
|
+
builder (~> 3.1)
|
27
|
+
erubis (~> 2.7.0)
|
28
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
30
|
+
activejob (4.2.8)
|
31
|
+
activesupport (= 4.2.8)
|
32
|
+
globalid (>= 0.3.0)
|
33
|
+
activemodel (4.2.8)
|
34
|
+
activesupport (= 4.2.8)
|
35
|
+
builder (~> 3.1)
|
36
|
+
activerecord (4.2.8)
|
37
|
+
activemodel (= 4.2.8)
|
38
|
+
activesupport (= 4.2.8)
|
39
|
+
arel (~> 6.0)
|
40
|
+
activesupport (4.2.8)
|
41
|
+
i18n (~> 0.7)
|
42
|
+
minitest (~> 5.1)
|
43
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
44
|
+
tzinfo (~> 1.1)
|
45
|
+
addressable (2.5.1)
|
46
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
47
|
+
arel (6.0.4)
|
48
|
+
aws-record (1.1.0)
|
49
|
+
aws-sdk-resources (~> 2.0)
|
50
|
+
aws-sdk-core (2.9.11)
|
51
|
+
aws-sigv4 (~> 1.0)
|
52
|
+
jmespath (~> 1.0)
|
53
|
+
aws-sdk-resources (2.9.11)
|
54
|
+
aws-sdk-core (= 2.9.11)
|
55
|
+
aws-sigv4 (1.0.0)
|
56
|
+
builder (3.2.3)
|
57
|
+
byebug (9.0.6)
|
58
|
+
combustion (0.6.0)
|
59
|
+
activesupport (>= 3.0.0)
|
60
|
+
railties (>= 3.0.0)
|
61
|
+
thor (>= 0.14.6)
|
62
|
+
concurrent-ruby (1.0.5)
|
63
|
+
crack (0.4.3)
|
64
|
+
safe_yaml (~> 1.0.0)
|
65
|
+
diff-lcs (1.3)
|
66
|
+
docile (1.1.5)
|
67
|
+
erubis (2.7.0)
|
68
|
+
globalid (0.4.0)
|
69
|
+
activesupport (>= 4.2.0)
|
70
|
+
hashdiff (0.3.2)
|
71
|
+
i18n (0.8.1)
|
72
|
+
jmespath (1.3.1)
|
73
|
+
json (2.1.0)
|
74
|
+
loofah (2.0.3)
|
75
|
+
nokogiri (>= 1.5.9)
|
76
|
+
mail (2.6.4)
|
77
|
+
mime-types (>= 1.16, < 4)
|
78
|
+
mime-types (3.1)
|
79
|
+
mime-types-data (~> 3.2015)
|
80
|
+
mime-types-data (3.2016.0521)
|
81
|
+
mini_portile2 (2.1.0)
|
82
|
+
minitest (5.10.1)
|
83
|
+
nokogiri (1.7.1)
|
84
|
+
mini_portile2 (~> 2.1.0)
|
85
|
+
public_suffix (2.0.5)
|
86
|
+
rack (1.6.5)
|
87
|
+
rack-test (0.6.3)
|
88
|
+
rack (>= 1.0)
|
89
|
+
rails (4.2.8)
|
90
|
+
actionmailer (= 4.2.8)
|
91
|
+
actionpack (= 4.2.8)
|
92
|
+
actionview (= 4.2.8)
|
93
|
+
activejob (= 4.2.8)
|
94
|
+
activemodel (= 4.2.8)
|
95
|
+
activerecord (= 4.2.8)
|
96
|
+
activesupport (= 4.2.8)
|
97
|
+
bundler (>= 1.3.0, < 2.0)
|
98
|
+
railties (= 4.2.8)
|
99
|
+
sprockets-rails
|
100
|
+
rails-deprecated_sanitizer (1.0.3)
|
101
|
+
activesupport (>= 4.2.0.alpha)
|
102
|
+
rails-dom-testing (1.0.8)
|
103
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
104
|
+
nokogiri (~> 1.6)
|
105
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
106
|
+
rails-html-sanitizer (1.0.3)
|
107
|
+
loofah (~> 2.0)
|
108
|
+
railties (4.2.8)
|
109
|
+
actionpack (= 4.2.8)
|
110
|
+
activesupport (= 4.2.8)
|
111
|
+
rake (>= 0.8.7)
|
112
|
+
thor (>= 0.18.1, < 2.0)
|
113
|
+
rake (10.5.0)
|
114
|
+
rspec (3.5.0)
|
115
|
+
rspec-core (~> 3.5.0)
|
116
|
+
rspec-expectations (~> 3.5.0)
|
117
|
+
rspec-mocks (~> 3.5.0)
|
118
|
+
rspec-core (3.5.4)
|
119
|
+
rspec-support (~> 3.5.0)
|
120
|
+
rspec-expectations (3.5.0)
|
121
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
122
|
+
rspec-support (~> 3.5.0)
|
123
|
+
rspec-mocks (3.5.0)
|
124
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
125
|
+
rspec-support (~> 3.5.0)
|
126
|
+
rspec-support (3.5.0)
|
127
|
+
safe_yaml (1.0.4)
|
128
|
+
simplecov (0.14.1)
|
129
|
+
docile (~> 1.1.0)
|
130
|
+
json (>= 1.8, < 3)
|
131
|
+
simplecov-html (~> 0.10.0)
|
132
|
+
simplecov-html (0.10.0)
|
133
|
+
sprockets (3.7.1)
|
134
|
+
concurrent-ruby (~> 1.0)
|
135
|
+
rack (> 1, < 3)
|
136
|
+
sprockets-rails (3.2.0)
|
137
|
+
actionpack (>= 4.0)
|
138
|
+
activesupport (>= 4.0)
|
139
|
+
sprockets (>= 3.0.0)
|
140
|
+
thor (0.19.4)
|
141
|
+
thread_safe (0.3.6)
|
142
|
+
tzinfo (1.2.3)
|
143
|
+
thread_safe (~> 0.1)
|
144
|
+
webmock (2.3.2)
|
145
|
+
addressable (>= 2.3.6)
|
146
|
+
crack (>= 0.3.2)
|
147
|
+
hashdiff
|
148
|
+
|
149
|
+
PLATFORMS
|
150
|
+
ruby
|
151
|
+
|
152
|
+
DEPENDENCIES
|
153
|
+
bundler (~> 1.14)
|
154
|
+
byebug (~> 9.0)
|
155
|
+
combustion (~> 0.6.0)
|
156
|
+
dynamo-record!
|
157
|
+
rake (~> 10.0)
|
158
|
+
rspec (~> 3.0)
|
159
|
+
simplecov (~> 0.12)
|
160
|
+
webmock (~> 2.1)
|
161
|
+
|
162
|
+
BUNDLED WITH
|
163
|
+
1.14.6
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# DynamoRecord
|
2
|
+
|
3
|
+
Provides helpful rake tasks and model extensions on top of [aws-record](https://github.com/aws/aws-sdk-ruby-record).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'dynamo-record'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install dynamo-record
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Models
|
24
|
+
|
25
|
+
In `app/models`, create a class that includes `DynamoRecord::Model` and contains one or more of the following attribute declarations:
|
26
|
+
|
27
|
+
* `integer_attr`
|
28
|
+
* `float_attr`
|
29
|
+
* `map_attr`
|
30
|
+
* `composite_string_attr`
|
31
|
+
* `composite_integer_attr`
|
32
|
+
|
33
|
+
An example file:
|
34
|
+
|
35
|
+
```
|
36
|
+
class Model1
|
37
|
+
include DynamoRecord::Model
|
38
|
+
composite_string_attr(
|
39
|
+
:model_key,
|
40
|
+
hash_key: true,
|
41
|
+
parts: [:model_id, :account_id]
|
42
|
+
)
|
43
|
+
integer_attr :position, range_key: true
|
44
|
+
string_attr :user_id
|
45
|
+
float_attr :score
|
46
|
+
map_attr :map_value
|
47
|
+
composite_string_attr :quiz_key, parts: [:quiz_id]
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
The partition key is labeled with `hash_key: true`. The sort key is labeled with `range_key: true`.
|
52
|
+
|
53
|
+
A secondary index can be defined as follows:
|
54
|
+
|
55
|
+
```
|
56
|
+
global_secondary_index(
|
57
|
+
:secondary_idx,
|
58
|
+
hash_key: :user_id,
|
59
|
+
range_key: :score,
|
60
|
+
projection: {
|
61
|
+
projection_type: 'INCLUDE',
|
62
|
+
non_key_attributes: [
|
63
|
+
:map_value
|
64
|
+
]
|
65
|
+
}
|
66
|
+
)
|
67
|
+
```
|
68
|
+
|
69
|
+
The full documentation for `projection_type` and `non_key_attributes` can be found [here](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-projectionobject.html).
|
70
|
+
|
71
|
+
### Migrations
|
72
|
+
|
73
|
+
Dynamo migration files are stored in `db/dynamo_migrate`. The name of the file follows the style of standard Rails migration files like `YYYYMMDDHHMMSS_create_model_1.rb`. A migration file that creates a Dynamo table looks like:
|
74
|
+
|
75
|
+
```
|
76
|
+
module DynamoMigrate
|
77
|
+
class CreateModel1 < DynamoRecord::TableMigration
|
78
|
+
def self.up
|
79
|
+
migrate(Model1) do |migration|
|
80
|
+
migration.create!(
|
81
|
+
provisioned_throughput: {
|
82
|
+
read_capacity_units: 1,
|
83
|
+
write_capacity_units: 1
|
84
|
+
},
|
85
|
+
global_secondary_index_throughput: {
|
86
|
+
secondary_idx: {
|
87
|
+
read_capacity_units: 1,
|
88
|
+
write_capacity_units: 1
|
89
|
+
}
|
90
|
+
}
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Note that the throughput of the table can be configured separately from the throughput of the secondary index.
|
99
|
+
|
100
|
+
A migration file that creates a Dynamo stream looks like:
|
101
|
+
|
102
|
+
```
|
103
|
+
module DynamoMigrate
|
104
|
+
class AddModel1Stream < DynamoRecord::TableMigration
|
105
|
+
def self.update
|
106
|
+
add_stream(Model1)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
data/build.sh
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
export COMPOSE_PROJECT_NAME=dynamorecord
|
4
|
+
export COMPOSE_FILE=docker-compose.yml
|
5
|
+
|
6
|
+
function cleanup() {
|
7
|
+
exit_code=$?
|
8
|
+
set +e
|
9
|
+
docker-compose stop test
|
10
|
+
docker-compose rm -fa test
|
11
|
+
docker rmi -f $(docker images -qf "dangling=true") &>/dev/null
|
12
|
+
exit $exit_code
|
13
|
+
}
|
14
|
+
trap cleanup INT TERM EXIT
|
15
|
+
|
16
|
+
set -e
|
17
|
+
|
18
|
+
docker-compose build --pull
|
19
|
+
docker-compose run --name test test
|
20
|
+
docker cp test:/usr/src/app/coverage .
|
21
|
+
|
22
|
+
#echo "Reporting test coverage"
|
23
|
+
#if [[ $? -eq 0 && -a 'coverage/.last_run.json' ]]; then
|
24
|
+
# ruby_coverage=$(cat coverage/api/.last_run.json | grep 'covered_percent' | egrep -o '[0-9]+(\.[0-9]+)?')
|
25
|
+
# echo "Sending to gergich"
|
26
|
+
# gergich message ":ruby: <http://jenkins.instructure.com/job/dynamo-record/${BUILD_ID}/|${ruby_coverage}%>"
|
27
|
+
# gergich publish
|
28
|
+
#fi
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
version: '2'
|
2
|
+
|
3
|
+
services:
|
4
|
+
test:
|
5
|
+
build:
|
6
|
+
context: .
|
7
|
+
dockerfile: Dockerfile.test
|
8
|
+
environment:
|
9
|
+
AWS_ACCESS_KEY_ID: x
|
10
|
+
AWS_SECRET_ACCESS_KEY: x
|
11
|
+
AWS_REGION: us-west-2
|
12
|
+
DYNAMO_ENDPOINT: http://dynamo:8000
|
13
|
+
links:
|
14
|
+
- dynamo
|
15
|
+
|
16
|
+
dynamo:
|
17
|
+
image: instructure/dynamodb
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dynamo-record/record/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'dynamo-record'
|
8
|
+
spec.version = DynamoRecord::Record::VERSION
|
9
|
+
spec.license = 'MIT'
|
10
|
+
spec.homepage = 'https://instructure.com'
|
11
|
+
spec.authors = ['Davis McClellan', 'Ryan Taylor', 'Bryan Petty', 'Michael Brewer-Davis', 'Marc Phillips', 'Augusto Callejas']
|
12
|
+
spec.email = ['dmcclellan@instructure.com', 'rtaylor@instructure.com', 'bpetty@instructure.com',
|
13
|
+
'mbd@instructure.com', 'mphillips@instructure.com', 'acallejas@instructure.com']
|
14
|
+
|
15
|
+
spec.summary = 'Extensions for working with dynamo via aws-record'
|
16
|
+
spec.description = 'A set of extensions simplifying database operations in aws-record'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'aws-record', '~> 1.1'
|
26
|
+
spec.add_dependency 'rails', '~> 4.2'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
29
|
+
spec.add_development_dependency 'byebug', '~> 9.0'
|
30
|
+
spec.add_development_dependency 'combustion', '~> 0.6.0'
|
31
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
33
|
+
spec.add_development_dependency 'simplecov', '~> 0.12'
|
34
|
+
spec.add_development_dependency 'webmock', '~> 2.1'
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module Marshalers
|
3
|
+
COMPOSITE_DELIMETER = '|'.freeze
|
4
|
+
|
5
|
+
def self.included(sub_class)
|
6
|
+
sub_class.extend(ClassMethods)
|
7
|
+
super(sub_class)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def composite_integer_attr(name, opts = {})
|
12
|
+
composite_attr(name, opts)
|
13
|
+
define_readers(name, opts[:parts], :to_i) if opts.key? :parts
|
14
|
+
end
|
15
|
+
|
16
|
+
def composite_string_attr(name, opts = {})
|
17
|
+
composite_attr(name, opts)
|
18
|
+
define_readers(name, opts[:parts], :to_s) if opts.key? :parts
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def composite_attr(name, opts = {})
|
24
|
+
opts[:dynamodb_type] = 'S'
|
25
|
+
|
26
|
+
# It is very unfortunate that Aws::Record used `attr`
|
27
|
+
# rubocop:disable Style/Attr
|
28
|
+
attr(name, Aws::Record::Marshalers::StringMarshaler.new(opts), opts)
|
29
|
+
# rubocop:enable Style/Attr
|
30
|
+
end
|
31
|
+
|
32
|
+
def define_readers(name, parts, cast_function, allowed_repeats = [:account_uuid])
|
33
|
+
parts.each_with_index do |part, i|
|
34
|
+
raise "#{part} already defined" unless parts.find_index(part) == i
|
35
|
+
next if method_defined?(part)
|
36
|
+
define_method(part) do
|
37
|
+
# @data is used internally by Aws::Record to store all of the attributes
|
38
|
+
@data.get_attribute(name).split(COMPOSITE_DELIMETER)[i].send(cast_function)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'aws-record'
|
2
|
+
|
3
|
+
module DynamoRecord::Model
|
4
|
+
COMPOSITE_DELIMITER = '|'.freeze
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.include(Aws::Record)
|
8
|
+
klass.include(DynamoRecord::Marshalers)
|
9
|
+
|
10
|
+
klass.extend ClassMethods
|
11
|
+
klass.send :prepend, InstanceMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def table_name
|
16
|
+
[Rails.configuration.dynamo['prefix'], name.tableize].join('-').tr('/', '.')
|
17
|
+
end
|
18
|
+
|
19
|
+
def scan
|
20
|
+
raise 'no scanning in production' if Rails.env.production?
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(opts)
|
25
|
+
super(opts).tap do |record|
|
26
|
+
unless record
|
27
|
+
name = self.name.demodulize
|
28
|
+
conditions = opts.map { |k, v| "#{k}=#{v}" }.join(', ')
|
29
|
+
error = "Couldn't find #{name} with #{conditions}"
|
30
|
+
raise Aws::Record::Errors::NotFound, error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_all_by_hash_key(hash_key_value, opts = {})
|
36
|
+
find_all_by_index_and_hash_key(hash_key, hash_key_value, opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_all_by_gsi_hash_key(gsi_name, hash_key_value, opts = {})
|
40
|
+
hash_key_name = global_secondary_indexes[gsi_name.to_sym][:hash_key]
|
41
|
+
find_all_by_index_and_hash_key(hash_key_name, hash_key_value, opts, gsi_name.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_all_by_gsi_hash_and_range_keys(gsi_name, hash_key_value, range_key_value)
|
45
|
+
gsi_config = global_secondary_indexes[gsi_name.to_sym]
|
46
|
+
find_all_by_index_hash_and_range_keys(
|
47
|
+
hash_config: { name: gsi_config[:hash_key], value: hash_key_value },
|
48
|
+
range_config: { name: gsi_config[:range_key], value: range_key_value },
|
49
|
+
index_name: gsi_name.to_s
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_all_by_index_and_hash_key(hash_key_name, hash_key_value, opts = {}, index_name = nil)
|
54
|
+
query_options = {
|
55
|
+
select: 'ALL_ATTRIBUTES',
|
56
|
+
key_condition_expression: "#{hash_key_name} = :hash_key_value",
|
57
|
+
expression_attribute_values: {
|
58
|
+
':hash_key_value': hash_key_value
|
59
|
+
},
|
60
|
+
scan_index_forward: true
|
61
|
+
}
|
62
|
+
query_options[:index_name] = index_name if index_name
|
63
|
+
query_options.merge!(opts)
|
64
|
+
query(query_options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_all_by_index_hash_and_range_keys(hash_config:, range_config:, index_name: nil,
|
68
|
+
scan_index_forward: true, limit: nil)
|
69
|
+
query_options = {
|
70
|
+
select: 'ALL_ATTRIBUTES',
|
71
|
+
key_condition_expression: "#{hash_config[:name]} = :hkv AND #{range_config[:name]} = :rkv",
|
72
|
+
expression_attribute_values: {
|
73
|
+
':hkv': hash_config[:value],
|
74
|
+
':rkv': range_config[:value]
|
75
|
+
},
|
76
|
+
scan_index_forward: scan_index_forward,
|
77
|
+
limit: limit
|
78
|
+
}
|
79
|
+
query_options[:index_name] = index_name if index_name
|
80
|
+
query(query_options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def composite_key(*args)
|
84
|
+
args.join(COMPOSITE_DELIMITER)
|
85
|
+
end
|
86
|
+
|
87
|
+
def split_composite(string)
|
88
|
+
string.split(COMPOSITE_DELIMITER)
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_all_by_model(model, opts = {})
|
92
|
+
find_all_by_hash_key(model.dynamo_key, opts)
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_all_by_model_paginated(model, opts = {})
|
96
|
+
find_all_by_model(model, pagination_opts(opts))
|
97
|
+
end
|
98
|
+
|
99
|
+
def pagination_opts(opts = {})
|
100
|
+
{ limit: opts[:limit], exclusive_start_key: opts[:exclusive_start_key] }
|
101
|
+
end
|
102
|
+
|
103
|
+
# TODO: Create a batch save method.
|
104
|
+
end
|
105
|
+
|
106
|
+
module InstanceMethods
|
107
|
+
def read_attribute_for_serialization(attribute)
|
108
|
+
send(attribute)
|
109
|
+
end
|
110
|
+
|
111
|
+
def attribute_hash
|
112
|
+
attrs = self.class.attributes.attributes
|
113
|
+
attr_keys = attrs.keys
|
114
|
+
hash = {}
|
115
|
+
|
116
|
+
attr_keys.each do |key|
|
117
|
+
hash[key] = attrs[key].type_cast(send(key))
|
118
|
+
end
|
119
|
+
hash
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
class TableMigration
|
3
|
+
def self.migrate(model)
|
4
|
+
migration = Aws::Record::TableMigration.new(model)
|
5
|
+
begin
|
6
|
+
migration.client.describe_table(table_name: model.table_name)
|
7
|
+
return :exists
|
8
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
|
9
|
+
yield migration
|
10
|
+
migration.wait_until_available
|
11
|
+
return :migrated
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.migrate_updates(model)
|
16
|
+
migration = Aws::Record::TableMigration.new(model)
|
17
|
+
yield migration
|
18
|
+
migration.wait_until_available
|
19
|
+
:updated
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.add_stream(model)
|
23
|
+
migrate_updates(model) do |migration|
|
24
|
+
migration.update!(
|
25
|
+
stream_specification: {
|
26
|
+
stream_enabled: true,
|
27
|
+
stream_view_type: 'NEW_IMAGE'
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
rescue Aws::DynamoDB::Errors::ValidationException => e
|
32
|
+
return e.message if e.message == 'Table already has an enabled stream'
|
33
|
+
raise e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module TaskHelpers
|
3
|
+
class Cleanup
|
4
|
+
def self.run
|
5
|
+
raise 'Task not available on production' if Rails.env.production?
|
6
|
+
Dir[Rails.root.join('app/models/*.rb').to_s].each do |filename|
|
7
|
+
klass = File.basename(filename, '.rb').camelize.constantize
|
8
|
+
if klass.included_modules.include? DynamoRecord::Model
|
9
|
+
puts "Deleting all items in table: #{klass}"
|
10
|
+
klass.scan.each(&:delete!)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module TaskHelpers
|
3
|
+
class DropAllTables
|
4
|
+
def self.run(override = false)
|
5
|
+
raise 'Task not available on production' if Rails.env.production?
|
6
|
+
env = Rails.env
|
7
|
+
dynamodb = Aws::DynamoDB::Client.new
|
8
|
+
tables = dynamodb.list_tables
|
9
|
+
tables.table_names.map do |t|
|
10
|
+
next unless t.include?(env) || override
|
11
|
+
dt = dynamodb.delete_table(table_name: t)
|
12
|
+
dt ? "Deleted: #{t}" : "Delete failed: #{t}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module TaskHelpers
|
3
|
+
class ListTables
|
4
|
+
def self.run
|
5
|
+
dynamodb = Aws::DynamoDB::Client.new
|
6
|
+
tables = dynamodb.list_tables
|
7
|
+
tables.table_names.select do |tn|
|
8
|
+
tn.starts_with?(Rails.configuration.dynamo['prefix'])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module TaskHelpers
|
3
|
+
class MigrationRunner
|
4
|
+
def self.run(path='db/dynamo_migrate')
|
5
|
+
constants = []
|
6
|
+
filename_regexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
|
7
|
+
|
8
|
+
# Sorts the files located in `db/dynamo_migrate` to ensure order is preserved
|
9
|
+
Dir[Rails.root.join("#{path}/*.rb")].sort.each do |f|
|
10
|
+
raise "Non-numeric prefix: #{f}" if File.basename(f).scan(filename_regexp).first.nil?
|
11
|
+
require f
|
12
|
+
|
13
|
+
# finds the constant that was added on the require statement above
|
14
|
+
migration_sym = (DynamoMigrate.constants - constants).first
|
15
|
+
migration = DynamoMigrate.const_get(migration_sym)
|
16
|
+
constants.push migration_sym
|
17
|
+
|
18
|
+
# starts the migration
|
19
|
+
yield "Migrating: #{migration}"
|
20
|
+
|
21
|
+
begin
|
22
|
+
status = up(migration)
|
23
|
+
yield status if status
|
24
|
+
|
25
|
+
status = update(migration)
|
26
|
+
yield status if status
|
27
|
+
rescue => e
|
28
|
+
yield "Migration failed: #{e}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.up(migration)
|
34
|
+
return unless migration.respond_to? :up
|
35
|
+
case migration.up
|
36
|
+
when :exists
|
37
|
+
'Table already exists'
|
38
|
+
when :migrated
|
39
|
+
'Migration successful'
|
40
|
+
else
|
41
|
+
raise 'Migration failed'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.update(migration)
|
46
|
+
return unless migration.respond_to? :update
|
47
|
+
status = migration.update
|
48
|
+
return 'Migration successful' if status == :updated
|
49
|
+
status
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module DynamoRecord
|
2
|
+
module TaskHelpers
|
3
|
+
class Scale
|
4
|
+
attr_reader :model, :attribute_selector, :new_throughput
|
5
|
+
attr_reader :migration, :existing_throughput, :model_name
|
6
|
+
|
7
|
+
def initialize(model_name, attribute_selector, new_throughput)
|
8
|
+
@model_name = model_name
|
9
|
+
@attribute_selector = attribute_selector
|
10
|
+
@new_throughput = new_throughput
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
return description if [model_name, attribute_selector, new_throughput].any?(&:nil?)
|
15
|
+
|
16
|
+
@model = model_name.constantize
|
17
|
+
@migration = Aws::Record::TableMigration.new(model)
|
18
|
+
@existing_throughput = model.provisioned_throughput
|
19
|
+
|
20
|
+
update_throughput
|
21
|
+
success_message
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def success_message
|
27
|
+
"Successfully updated #{model.table_name} throughput to #{update_instructions[:provisioned_throughput]}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_throughput
|
31
|
+
raise_attribute_error if !read? && !write?
|
32
|
+
|
33
|
+
migration.update!(update_instructions)
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_instructions
|
37
|
+
{
|
38
|
+
provisioned_throughput: {
|
39
|
+
write_capacity_units: (write? && new_throughput) || existing_write,
|
40
|
+
read_capacity_units: (read? && new_throughput) || existing_read
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def existing_write
|
46
|
+
existing_throughput[:write_capacity_units]
|
47
|
+
end
|
48
|
+
|
49
|
+
def existing_read
|
50
|
+
existing_throughput[:read_capacity_units]
|
51
|
+
end
|
52
|
+
|
53
|
+
def both?
|
54
|
+
attribute_selector.to_sym == :both
|
55
|
+
end
|
56
|
+
|
57
|
+
def read?
|
58
|
+
both? || attribute_selector.to_sym == :read
|
59
|
+
end
|
60
|
+
|
61
|
+
def write?
|
62
|
+
both? || attribute_selector.to_sym == :write
|
63
|
+
end
|
64
|
+
|
65
|
+
def raise_attribute_error
|
66
|
+
raise ArgumentError, 'You didn\'t provide an appropriate attribute selection. We accept [:both, :read, :write]'
|
67
|
+
end
|
68
|
+
|
69
|
+
def description
|
70
|
+
<<~DESCRIPTION
|
71
|
+
--------------------------------------------------------------------------------
|
72
|
+
Here's some usage information:
|
73
|
+
Scale a dynamo table. Requires three inputs.
|
74
|
+
- ModelName
|
75
|
+
ruby class of the model
|
76
|
+
- attribute
|
77
|
+
valid values include "both", "read" and "write"
|
78
|
+
- new_throughput
|
79
|
+
numerical value for the new read/write capacity units
|
80
|
+
Example: `rake dynamo:scale[MySuperDynamoModel,both,50]`
|
81
|
+
--------------------------------------------------------------------------------
|
82
|
+
DESCRIPTION
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
class ModelExistenceValidator < ActiveModel::EachValidator
|
3
|
+
def validate_each(record, attribute, value)
|
4
|
+
return if options[:model].exists? value
|
5
|
+
record.errors[attribute] << (options[:message] || "#{attribute}:#{value} is not a valid #{options[:model]}")
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
namespace :dynamo do
|
2
|
+
desc 'Run dynamo migrations'
|
3
|
+
task migrate: :environment do
|
4
|
+
DynamoRecord::TaskHelpers::MigrationRunner.run { |msg| puts msg }
|
5
|
+
end
|
6
|
+
|
7
|
+
desc 'Drops all dynamo tables and re-runs migrations'
|
8
|
+
task :reset do
|
9
|
+
Rake::Task['dynamo:drop_all'].invoke
|
10
|
+
Rake::Task['dynamo:migrate'].invoke
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Drop all dynamo tables'
|
14
|
+
task :drop_all, [:override] => :environment do |_t, args|
|
15
|
+
puts DynamoRecord::TaskHelpers::DropAllTables.run args[:override]
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Drop a specified dynamo table'
|
19
|
+
task :drop, [:table_name] => :environment do |_t, args|
|
20
|
+
puts DynamoRecord::TaskHelpers::DropTable.run args[:table_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'List all tables with dynamo prefix'
|
24
|
+
task list_tables: :environment do
|
25
|
+
puts DynamoRecord::TaskHelpers::ListTables.run
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Delete all records in all DyanmoDB tables.'
|
29
|
+
task cleanup: :environment do
|
30
|
+
DynamoRecord::TaskHelpers::Cleanup.run
|
31
|
+
puts 'Finished deleting all records in all DynamoDB tables.'
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Scale a dynamo table read/write capacity to the new capacity value provided'
|
35
|
+
task :scale, [:model_name, :attribute, :new_throughput] => :environment do |_t, args|
|
36
|
+
puts DynamoRecord::TaskHelpers::Scale.new(args[:model_name], args[:attribute], args[:new_throughput].to_i).run
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamo-record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Davis McClellan
|
8
|
+
- Ryan Taylor
|
9
|
+
- Bryan Petty
|
10
|
+
- Michael Brewer-Davis
|
11
|
+
- Marc Phillips
|
12
|
+
- Augusto Callejas
|
13
|
+
autorequire:
|
14
|
+
bindir: exe
|
15
|
+
cert_chain: []
|
16
|
+
date: 2017-05-02 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: aws-record
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - "~>"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '1.1'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - "~>"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '1.1'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rails
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - "~>"
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '4.2'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - "~>"
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '4.2'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.14'
|
53
|
+
type: :development
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.14'
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: byebug
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '9.0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '9.0'
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: combustion
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 0.6.0
|
81
|
+
type: :development
|
82
|
+
prerelease: false
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 0.6.0
|
88
|
+
- !ruby/object:Gem::Dependency
|
89
|
+
name: rake
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '10.0'
|
95
|
+
type: :development
|
96
|
+
prerelease: false
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '10.0'
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rspec
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '3.0'
|
109
|
+
type: :development
|
110
|
+
prerelease: false
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '3.0'
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
name: simplecov
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0.12'
|
123
|
+
type: :development
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0.12'
|
130
|
+
- !ruby/object:Gem::Dependency
|
131
|
+
name: webmock
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - "~>"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '2.1'
|
137
|
+
type: :development
|
138
|
+
prerelease: false
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - "~>"
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '2.1'
|
144
|
+
description: A set of extensions simplifying database operations in aws-record
|
145
|
+
email:
|
146
|
+
- dmcclellan@instructure.com
|
147
|
+
- rtaylor@instructure.com
|
148
|
+
- bpetty@instructure.com
|
149
|
+
- mbd@instructure.com
|
150
|
+
- mphillips@instructure.com
|
151
|
+
- acallejas@instructure.com
|
152
|
+
executables: []
|
153
|
+
extensions: []
|
154
|
+
extra_rdoc_files: []
|
155
|
+
files:
|
156
|
+
- ".gitignore"
|
157
|
+
- ".rspec"
|
158
|
+
- ".rubocop.yml"
|
159
|
+
- ".travis.yml"
|
160
|
+
- Dockerfile.test
|
161
|
+
- Gemfile
|
162
|
+
- Gemfile.lock
|
163
|
+
- README.md
|
164
|
+
- build.sh
|
165
|
+
- docker-compose.override.yml
|
166
|
+
- docker-compose.yml
|
167
|
+
- dynamo-record.gemspec
|
168
|
+
- lib/dynamo-record.rb
|
169
|
+
- lib/dynamo-record/marshalers.rb
|
170
|
+
- lib/dynamo-record/model.rb
|
171
|
+
- lib/dynamo-record/record.rb
|
172
|
+
- lib/dynamo-record/record/railtie.rb
|
173
|
+
- lib/dynamo-record/record/version.rb
|
174
|
+
- lib/dynamo-record/table_migration.rb
|
175
|
+
- lib/dynamo-record/task_helpers/cleanup.rb
|
176
|
+
- lib/dynamo-record/task_helpers/drop_all_tables.rb
|
177
|
+
- lib/dynamo-record/task_helpers/drop_table.rb
|
178
|
+
- lib/dynamo-record/task_helpers/list_tables.rb
|
179
|
+
- lib/dynamo-record/task_helpers/migration_runner.rb
|
180
|
+
- lib/dynamo-record/task_helpers/scale.rb
|
181
|
+
- lib/model_existence_validator.rb
|
182
|
+
- lib/tasks/dynamo.rake
|
183
|
+
homepage: https://instructure.com
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata: {}
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
requirements: []
|
202
|
+
rubyforge_project:
|
203
|
+
rubygems_version: 2.5.1
|
204
|
+
signing_key:
|
205
|
+
specification_version: 4
|
206
|
+
summary: Extensions for working with dynamo via aws-record
|
207
|
+
test_files: []
|