dynamo-record 1.4.0 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dynamo/record/version.rb +1 -1
- data/lib/tasks/dynamo.rake +1 -1
- data/{Gemfile → spec/gemfiles/rails-5.2.gemfile} +3 -1
- data/spec/gemfiles/rails-6.0.gemfile +7 -0
- data/spec/gemfiles/rails-6.1.gemfile +7 -0
- data/spec/internal/app/models/model1.rb +9 -0
- data/spec/internal/app/models/model2.rb +9 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/db/dynamo_migrate/20170402163638_create_model_1.rb +13 -0
- data/spec/internal/db/dynamo_migrate/20170402163639_create_model_2.rb +9 -0
- data/spec/internal/db/invalid_dynamo_migrate/add_model_1_stream.rb +9 -0
- data/spec/lib/dynamo/record/batch_get_spec.rb +85 -0
- data/spec/lib/dynamo/record/batch_write_spec.rb +90 -0
- data/spec/lib/dynamo/record/model_existence_vaildator_spec.rb +43 -0
- data/spec/lib/dynamo/record/model_spec.rb +120 -0
- data/spec/lib/dynamo/record/table_migration_spec.rb +68 -0
- data/spec/lib/dynamo/record/task_helpers/cleanup_spec.rb +28 -0
- data/spec/lib/dynamo/record/task_helpers/drop_all_tables_spec.rb +21 -0
- data/spec/lib/dynamo/record/task_helpers/drop_table_spec.rb +27 -0
- data/spec/lib/dynamo/record/task_helpers/list_tables_spec.rb +12 -0
- data/spec/lib/dynamo/record/task_helpers/migration_runner_spec.rb +54 -0
- data/spec/lib/dynamo/record/task_helpers/scale_spec.rb +64 -0
- data/spec/lib/dynamo/record/version_spec.rb +7 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/shared_contexts/with_dummy_model.rb +28 -0
- metadata +52 -30
- data/.dockerignore +0 -17
- data/.gitignore +0 -16
- data/.rspec +0 -2
- data/.rubocop.yml +0 -61
- data/.travis.yml +0 -28
- data/Dockerfile +0 -21
- data/Jenkinsfile +0 -45
- data/LICENSE.txt +0 -21
- data/docker-compose.override.example.yml +0 -19
- data/docker-compose.yml +0 -20
- data/dynamo-record.gemspec +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 193614a73977889190f759bb367f89d72ea36965a093488dc2203be83f28e37e
|
4
|
+
data.tar.gz: cc792d8f2d367b0fd14b7af1578d6f0a3f249b589fd8e7e880d9f40e9715b383
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d2e98eeafd27be2c4e00e861515cd03c222de15a8eff4196377113d7791fa311b873b389037c8834cd039ab3df3590ff1e6bbdcf9c99ba09f8f567c8040acc4
|
7
|
+
data.tar.gz: 8250e400b5c6a63dd0d33daedf9f5d1bba2ba8f5fdba1326543075dbc2c90a91bf4c7530a1edea2b4112ee2c8f56bba8c33e115efdc6d94177c11833a784245b
|
data/lib/tasks/dynamo.rake
CHANGED
@@ -14,7 +14,7 @@ namespace :dynamo do
|
|
14
14
|
|
15
15
|
desc 'Drop all dynamo tables'
|
16
16
|
task :drop_all, [:override] => :environment do |_t, args|
|
17
|
-
puts Dynamo::Record::TaskHelpers::DropAllTables.run args[:override]
|
17
|
+
puts Dynamo::Record::TaskHelpers::DropAllTables.run override: args[:override]
|
18
18
|
end
|
19
19
|
|
20
20
|
desc 'Drop a specified dynamo table'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DynamoMigrate
|
4
|
+
class CreateModel1 < Dynamo::Record::TableMigration
|
5
|
+
def self.table_config
|
6
|
+
Aws::Record::TableConfig.define do |config|
|
7
|
+
config.model_class Model1
|
8
|
+
config.read_capacity_units 1
|
9
|
+
config.write_capacity_units 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::BatchGet do
|
4
|
+
include_context 'with dummy model'
|
5
|
+
|
6
|
+
table_name = DynamoDummyModel.table_name
|
7
|
+
|
8
|
+
let(:records) { (1..5).map { |id| DynamoDummyModel.new(id: id, name: (64 + id).chr) } }
|
9
|
+
let(:keys) { (1..5).map { |id| { id: id } } }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
DynamoDummyModel.batch_save!(records)
|
13
|
+
expect(DynamoDummyModel.scan.count).to eq 5
|
14
|
+
end
|
15
|
+
|
16
|
+
# Overrides sleep everywhere to speed up specs.
|
17
|
+
before { allow_any_instance_of(Object).to receive(:sleep) }
|
18
|
+
|
19
|
+
it 'gets all at once' do
|
20
|
+
result = DynamoDummyModel.batch_get(keys)
|
21
|
+
expect(result.map(&:name).sort).to eq %w[A B C D E]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'handles empty array' do
|
25
|
+
result = DynamoDummyModel.batch_get([])
|
26
|
+
expect(result.count).to eq 0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'gets all items if item queue is larger than possible in one batch get' do
|
30
|
+
stub_const('Dynamo::Record::BatchGet::BATCH_SIZE', 3)
|
31
|
+
result = DynamoDummyModel.batch_get(keys)
|
32
|
+
expect(result.map(&:name).sort).to eq %w[A B C D E]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises when attempts are unsuccessful' do
|
36
|
+
allow(DynamoDummyModel).to receive(:max_retries).and_return(1)
|
37
|
+
dynamodb_client_stub = double
|
38
|
+
allow(DynamoDummyModel).to receive(:dynamodb_client).and_return(dynamodb_client_stub)
|
39
|
+
expect(dynamodb_client_stub).to receive(:batch_get_item) do |opts|
|
40
|
+
items = opts[:request_items][table_name][:keys]
|
41
|
+
Aws::DynamoDB::Types::BatchGetItemOutput.new(
|
42
|
+
responses: { table_name => [] },
|
43
|
+
unprocessed_keys: { table_name => Aws::DynamoDB::Types::KeysAndAttributes.new(keys: items) }
|
44
|
+
)
|
45
|
+
end
|
46
|
+
expect do
|
47
|
+
DynamoDummyModel.batch_get(keys)
|
48
|
+
end.to raise_error(Dynamo::Record::BatchGet::NumberOfRetriesExceeded)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'reprocesses unprocessed items' do
|
52
|
+
dynamodb_client_stub = double
|
53
|
+
original_batch_get_item = DynamoDummyModel.dynamodb_client.method(:batch_get_item)
|
54
|
+
allow(DynamoDummyModel).to receive(:dynamodb_client).and_return(dynamodb_client_stub)
|
55
|
+
expect(dynamodb_client_stub).to receive(:batch_get_item) do |opts|
|
56
|
+
# Pick two successful items, return the remaining keys
|
57
|
+
items = opts[:request_items][table_name][:keys]
|
58
|
+
success = items.sample(2)
|
59
|
+
response = original_batch_get_item.call(opts)
|
60
|
+
pr, = response.responses[table_name].partition { |r| success.map { |i| i['id'] }.include? r['id'] }
|
61
|
+
unpr = items - success
|
62
|
+
Aws::DynamoDB::Types::BatchGetItemOutput.new(
|
63
|
+
responses: { table_name => pr },
|
64
|
+
unprocessed_keys: { table_name => Aws::DynamoDB::Types::KeysAndAttributes.new(keys: unpr) }
|
65
|
+
)
|
66
|
+
end.exactly(3).times
|
67
|
+
result = DynamoDummyModel.batch_get(keys)
|
68
|
+
expect(result.map(&:name).sort).to eq %w[A B C D E]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'retries if provisioned throughput is exceeded' do
|
72
|
+
stub_const('Dynamo::Record::BatchGet::BATCH_SIZE', 3)
|
73
|
+
call_count = 0
|
74
|
+
original_batch_get_item = DynamoDummyModel.dynamodb_client.method(:batch_get_item)
|
75
|
+
expect(DynamoDummyModel.dynamodb_client).to receive(:batch_get_item) do |opts|
|
76
|
+
call_count += 1
|
77
|
+
raise Aws::DynamoDB::Errors::ProvisionedThroughputExceededException.new(nil, nil) if call_count == 1
|
78
|
+
|
79
|
+
original_batch_get_item.call(opts)
|
80
|
+
end.exactly(3).times
|
81
|
+
|
82
|
+
result = DynamoDummyModel.batch_get(keys)
|
83
|
+
expect(result.map(&:name).sort).to eq %w[A B C D E]
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::BatchWrite do
|
4
|
+
include_context 'with dummy model'
|
5
|
+
|
6
|
+
shared_examples_for 'a batch write' do |method|
|
7
|
+
# Overrides sleep everywhere to speed up specs.
|
8
|
+
before { allow_any_instance_of(Object).to receive(:sleep) }
|
9
|
+
|
10
|
+
it 'writes all at once' do
|
11
|
+
DynamoDummyModel.send(method, records)
|
12
|
+
expect(DynamoDummyModel.scan.count).to eq expected_count
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'handles empty array' do
|
16
|
+
DynamoDummyModel.send(method, [])
|
17
|
+
expect(DynamoDummyModel.scan.count).to eq num_records
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'writes all items if item queue is larger than possible in one batch write' do
|
21
|
+
stub_const('Dynamo::Record::BatchWrite::BATCH_SIZE', 3)
|
22
|
+
DynamoDummyModel.send(method, records)
|
23
|
+
expect(DynamoDummyModel.scan.count).to eq expected_count
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'writes all items if payload size would be exceeded in a single write' do
|
27
|
+
stub_const('Dynamo::Record::BatchWrite::MAX_PAYLOAD_SIZE', 50)
|
28
|
+
records = (1..5).map { |id| DynamoDummyModel.new(id: id) }
|
29
|
+
DynamoDummyModel.send(method, records)
|
30
|
+
expect(DynamoDummyModel.scan.count).to eq expected_count
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'reprocesses unprocessed items' do
|
34
|
+
records = [DynamoDummyModel.new(id: 1)]
|
35
|
+
dynamodb_client_stub = double
|
36
|
+
expect(DynamoDummyModel).to receive(:max_retries).and_return(1).exactly(2).times
|
37
|
+
expect(DynamoDummyModel).to receive(:dynamodb_client).and_return(dynamodb_client_stub).exactly(2).times
|
38
|
+
allow(dynamodb_client_stub).to receive(:batch_write_item) do |opts|
|
39
|
+
items = opts[:request_items][DynamoDummyModel.table_name].map { |ri| Aws::DynamoDB::Types::WriteRequest.new ri }
|
40
|
+
double(unprocessed_items: { DynamoDummyModel.table_name => items })
|
41
|
+
end
|
42
|
+
expect do
|
43
|
+
DynamoDummyModel.send(method, records)
|
44
|
+
end.to raise_error(Dynamo::Record::BatchWrite::NumberOfRetriesExceeded)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'fails if a record is too large' do
|
48
|
+
stub_const('Dynamo::Record::BatchRequest::MAX_RECORD_SIZE', 5)
|
49
|
+
expect do
|
50
|
+
DynamoDummyModel.send(method, records)
|
51
|
+
end.to raise_error(Dynamo::Record::RecordTooLargeError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'retries if provisioned throughput is exceeded' do
|
55
|
+
stub_const('Dynamo::Record::BatchWrite::BATCH_SIZE', 3)
|
56
|
+
call_count = 0
|
57
|
+
original_batch_write_item = DynamoDummyModel.dynamodb_client.method(:batch_write_item)
|
58
|
+
expect(DynamoDummyModel.dynamodb_client).to receive(:batch_write_item) do |opts|
|
59
|
+
call_count += 1
|
60
|
+
raise Aws::DynamoDB::Errors::ProvisionedThroughputExceededException.new(nil, nil) if call_count == 1
|
61
|
+
|
62
|
+
original_batch_write_item.call(opts)
|
63
|
+
end.exactly(3).times
|
64
|
+
|
65
|
+
DynamoDummyModel.send(method, records)
|
66
|
+
expect(DynamoDummyModel.scan.count).to eq expected_count
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '.batch_save!' do
|
71
|
+
let(:records) { (1..5).map { |id| DynamoDummyModel.new(id: id) } }
|
72
|
+
let(:expected_count) { 5 }
|
73
|
+
let(:num_records) { 0 }
|
74
|
+
|
75
|
+
it_behaves_like 'a batch write', :batch_save!
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '.batch_delete!' do
|
79
|
+
let(:records) { (1..5).map { |id| DynamoDummyModel.new(id: id) } }
|
80
|
+
let(:expected_count) { 0 }
|
81
|
+
let(:num_records) { 5 }
|
82
|
+
|
83
|
+
before(:each) do
|
84
|
+
DynamoDummyModel.batch_save!(records)
|
85
|
+
expect(DynamoDummyModel.scan.count).to eq 5
|
86
|
+
end
|
87
|
+
|
88
|
+
it_behaves_like 'a batch write', :batch_delete!
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::ModelExistenceValidator do
|
4
|
+
class TestModel
|
5
|
+
def self.exists?(id)
|
6
|
+
id == 1
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#validate_each' do
|
11
|
+
before(:each) do
|
12
|
+
validator.validate_each(record, :test_model_id, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:record) do
|
16
|
+
Class.new do
|
17
|
+
attr_accessor :errors
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@errors = { test_model_id: [] }
|
21
|
+
end
|
22
|
+
end.new
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:validator) { described_class.new(attributes: [:test_model_id], model: TestModel) }
|
26
|
+
|
27
|
+
context 'valid record' do
|
28
|
+
let(:value) { 1 }
|
29
|
+
|
30
|
+
it 'has no errors' do
|
31
|
+
expect(record.errors[:test_model_id]).to be_empty
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'invalid record' do
|
36
|
+
let(:value) { 2 }
|
37
|
+
|
38
|
+
it 'has errors' do
|
39
|
+
expect(record.errors[:test_model_id]).not_to be_empty
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::Model do
|
4
|
+
class TestDynamoModel
|
5
|
+
include Dynamo::Record::Model
|
6
|
+
|
7
|
+
integer_attr :test_id_1, hash_key: true
|
8
|
+
integer_attr :test_id_2, range_key: true
|
9
|
+
integer_attr :test_id_3
|
10
|
+
composite_integer_attr(
|
11
|
+
:test_id_4,
|
12
|
+
parts: %i[test_id_4_1 test_id_4_2]
|
13
|
+
)
|
14
|
+
|
15
|
+
global_secondary_index(
|
16
|
+
:gsi_1,
|
17
|
+
hash_key: :test_id_3,
|
18
|
+
range_key: :test_id_2,
|
19
|
+
projection: {
|
20
|
+
projection_type: 'ALL'
|
21
|
+
}
|
22
|
+
)
|
23
|
+
|
24
|
+
local_secondary_index(
|
25
|
+
:lsi_1,
|
26
|
+
range_key: :test_id_3,
|
27
|
+
projection: {
|
28
|
+
projection_type: 'ALL'
|
29
|
+
}
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '.table_name' do
|
34
|
+
it 'should have correct table name' do
|
35
|
+
expect(TestDynamoModel.table_name).to eq 'test-test_dynamo_models'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'composite keys' do
|
40
|
+
before do
|
41
|
+
@d_1 = 'data_1'
|
42
|
+
@d_2 = 'data_2'
|
43
|
+
@key = TestDynamoModel.composite_key(@d_1, @d_2)
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.split_composite' do
|
47
|
+
it 'returns the input from composite_key' do
|
48
|
+
r_1, r_2 = TestDynamoModel.split_composite(@key)
|
49
|
+
expect(r_1).to eq @d_1
|
50
|
+
expect(r_2).to eq @d_2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '.composite_key' do
|
55
|
+
it 'returns the composite key' do
|
56
|
+
expect(@key).to eq "#{@d_1}|#{@d_2}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.find' do
|
62
|
+
it 'raises Aws::Record::Errors::NotFound if no record matches' do
|
63
|
+
resp = Aws::DynamoDB::Types::GetItemOutput.new
|
64
|
+
expect(TestDynamoModel.dynamodb_client).to receive(:get_item).once.and_return(resp)
|
65
|
+
expect { TestDynamoModel.find(test_id_1: 'omg', test_id_2: 'pls') }.to raise_error(Aws::Record::Errors::NotFound)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'find queries' do
|
70
|
+
before do
|
71
|
+
expect(TestDynamoModel).to receive(:query).once
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.find_all_by_hash_key' do
|
75
|
+
it 'queries by hash key' do
|
76
|
+
TestDynamoModel.find_all_by_hash_key(1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '.find_all_by_gsi_hash_key' do
|
81
|
+
it 'queries the gsi' do
|
82
|
+
TestDynamoModel.find_all_by_gsi_hash_key('gsi_1', 1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '.find_all_by_gsi_hash_and_range_keys' do
|
87
|
+
it 'queries the gsi' do
|
88
|
+
TestDynamoModel.find_all_by_gsi_hash_and_range_keys('gsi_1', 1, 5)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '.find_all_by_lsi_hash_key' do
|
93
|
+
it 'queries the lsi' do
|
94
|
+
TestDynamoModel.find_all_by_lsi_hash_key('lsi_1', 1)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.find_all_by_lsi_hash_and_range_keys' do
|
99
|
+
it 'queries the lsi' do
|
100
|
+
TestDynamoModel.find_all_by_lsi_hash_and_range_keys('lsi_1', 1, 3)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '.find_all_by_index_hash_and_range_keys' do
|
105
|
+
it 'can fetch item ranges' do
|
106
|
+
hash = { name: 'test_id_1', value: 2 }
|
107
|
+
range = { expression: 'test_id_2 > :rkv', value: 10 }
|
108
|
+
TestDynamoModel.find_all_by_index_hash_and_range_keys(hash_config: hash, range_config: range)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#read_attribute_for_serialization' do
|
114
|
+
it 'generates an attribute hash' do
|
115
|
+
key = TestDynamoModel.composite_key(4, 5)
|
116
|
+
m = TestDynamoModel.new(test_id_1: 1, test_id_2: 2, test_id_3: 3, test_id_4: key)
|
117
|
+
expect(m.attribute_hash).to eq(test_id_1: 1, test_id_2: 2, test_id_3: 3, test_id_4: key)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TableMigration do
|
4
|
+
class TestDynamoTableMigration
|
5
|
+
include Aws::Record
|
6
|
+
|
7
|
+
integer_attr :test_id_1, hash_key: true
|
8
|
+
integer_attr :test_id_2, range_key: true
|
9
|
+
end
|
10
|
+
|
11
|
+
class CreateTestDynamoTableMigration < Dynamo::Record::TableMigration
|
12
|
+
def self.up
|
13
|
+
migrate(TestDynamoTableMigration) do |migration|
|
14
|
+
migration.create!(
|
15
|
+
provisioned_throughput: {
|
16
|
+
read_capacity_units: 1,
|
17
|
+
write_capacity_units: 1
|
18
|
+
}
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:client) { double(describe_table: true) }
|
25
|
+
|
26
|
+
describe '.migrate' do
|
27
|
+
it 'returns `:migrated` if successful' do
|
28
|
+
expect_any_instance_of(Aws::Record::TableMigration).to receive(:client).and_return(client)
|
29
|
+
expect(client).to receive(:describe_table).and_raise(
|
30
|
+
Aws::DynamoDB::Errors::ResourceNotFoundException.new(nil, '')
|
31
|
+
)
|
32
|
+
|
33
|
+
expect_any_instance_of(Aws::Record::TableMigration).to receive(:create!).once
|
34
|
+
expect_any_instance_of(Aws::Record::TableMigration).to receive(:wait_until_available).once
|
35
|
+
|
36
|
+
expect(CreateTestDynamoTableMigration.up).to eq :migrated
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns `:exists` if table already exists' do
|
40
|
+
expect_any_instance_of(Aws::Record::TableMigration).to receive(:client).and_return(client)
|
41
|
+
|
42
|
+
expect(CreateTestDynamoTableMigration.up).to eq :exists
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.add_stream' do
|
47
|
+
it 'raises an error if ValidationException raised' do
|
48
|
+
allow_any_instance_of(Aws::Record::TableMigration).to(
|
49
|
+
receive(:update!).and_raise(Aws::DynamoDB::Errors::ValidationException.new(nil, nil))
|
50
|
+
)
|
51
|
+
expect { CreateTestDynamoTableMigration.add_stream(TestDynamoTableMigration) }.to(
|
52
|
+
raise_error Aws::DynamoDB::Errors::ValidationException
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not raise an excetion if stream already enabled' do
|
57
|
+
message = 'Table already has an enabled stream'
|
58
|
+
expect_any_instance_of(Aws::DynamoDB::Errors::ValidationException).to(
|
59
|
+
receive(:message).at_least(:once).and_return(message)
|
60
|
+
)
|
61
|
+
allow_any_instance_of(Aws::Record::TableMigration).to(
|
62
|
+
receive(:update!).and_raise(Aws::DynamoDB::Errors::ValidationException.new(nil, ''))
|
63
|
+
)
|
64
|
+
expect { CreateTestDynamoTableMigration.add_stream(TestDynamoTableMigration) }.to_not raise_error
|
65
|
+
expect(CreateTestDynamoTableMigration.add_stream(TestDynamoTableMigration)).to eq message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::Cleanup do
|
4
|
+
describe '.run' do
|
5
|
+
it 'raises error if rails is in production' do
|
6
|
+
allow(Rails).to receive(:env).and_return(
|
7
|
+
ActiveSupport::StringInquirer.new('production')
|
8
|
+
)
|
9
|
+
expect do
|
10
|
+
Dynamo::Record::TaskHelpers::Cleanup.run
|
11
|
+
end.to raise_error('Task not available on production')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'deletes records in all DynamoDB tables' do
|
15
|
+
Dir[Rails.root.join('app/models/*.rb').to_s].each do |filename|
|
16
|
+
klass = File.basename(filename, '.rb').camelize.constantize
|
17
|
+
next unless klass.included_modules.include? Dynamo::Record::Model
|
18
|
+
|
19
|
+
object = double.as_null_object
|
20
|
+
list = [object]
|
21
|
+
expect(klass).to receive(:scan).and_return(list)
|
22
|
+
expect(object).to receive(:delete!)
|
23
|
+
end
|
24
|
+
|
25
|
+
described_class.run
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::DropAllTables do
|
4
|
+
describe '.run' do
|
5
|
+
it 'raises error if rails is in production' do
|
6
|
+
allow(Rails).to receive(:env).and_return(
|
7
|
+
ActiveSupport::StringInquirer.new('production')
|
8
|
+
)
|
9
|
+
expect do
|
10
|
+
Dynamo::Record::TaskHelpers::DropAllTables.run
|
11
|
+
end.to raise_error('Task not available on production')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'drops all dynamo tables' do
|
15
|
+
expect_any_instance_of(Aws::DynamoDB::Client).to receive(:list_tables)
|
16
|
+
.and_return double(table_names: %w[test_t1 test_t2 test_t3])
|
17
|
+
expect_any_instance_of(Aws::DynamoDB::Client).to receive(:delete_table).at_least(3).times
|
18
|
+
Dynamo::Record::TaskHelpers::DropAllTables.run
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::DropTable do
|
4
|
+
include_context 'with dummy model'
|
5
|
+
|
6
|
+
class BadDummyModel; end # rubocop:disable Lint/EmptyClass
|
7
|
+
|
8
|
+
describe '#run' do
|
9
|
+
it 'raises error if specified class does not include Aws::Record' do
|
10
|
+
expect do
|
11
|
+
Dynamo::Record::TaskHelpers::DropTable.run('BadDummyModel')
|
12
|
+
end.to raise_error('Cannot do operations on a non-existent table')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'drops dynamo table' do
|
16
|
+
dynamodb = Aws::DynamoDB::Client.new
|
17
|
+
table = dynamodb.describe_table(table_name: DynamoDummyModel.table_name)
|
18
|
+
expect(table).not_to be_empty
|
19
|
+
|
20
|
+
Dynamo::Record::TaskHelpers::DropTable.run DynamoDummyModel.table_name
|
21
|
+
|
22
|
+
expect do
|
23
|
+
dynamodb.describe_table(table_name: DynamoDummyModel.table_name)
|
24
|
+
end.to raise_error(Aws::DynamoDB::Errors::ResourceNotFoundException)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::ListTables do
|
4
|
+
describe '#run' do
|
5
|
+
include_context 'with dummy model'
|
6
|
+
it 'lists all dynamo tables' do
|
7
|
+
list = Dynamo::Record::TaskHelpers::ListTables.run
|
8
|
+
|
9
|
+
expect(list).to include DynamoDummyModel.table_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::MigrationRunner do
|
4
|
+
describe '.run' do
|
5
|
+
it 'runs migration for each dynamo table' do
|
6
|
+
allow_any_instance_of(Aws::Record::TableMigration).to(
|
7
|
+
receive(:client).and_raise(
|
8
|
+
Aws::DynamoDB::Errors::ResourceNotFoundException.new(nil, '')
|
9
|
+
)
|
10
|
+
)
|
11
|
+
|
12
|
+
allow_any_instance_of(Aws::Record::TableMigration).to receive(:create!)
|
13
|
+
allow_any_instance_of(Aws::Record::TableConfig).to receive(:migrate!)
|
14
|
+
allow_any_instance_of(Aws::Record::TableMigration).to receive(:update!)
|
15
|
+
allow_any_instance_of(Aws::Record::TableMigration).to receive(:wait_until_available)
|
16
|
+
messages = []
|
17
|
+
|
18
|
+
Dynamo::Record::TaskHelpers::MigrationRunner.run do |thing|
|
19
|
+
messages << thing
|
20
|
+
end
|
21
|
+
|
22
|
+
switch = true
|
23
|
+
messages.each do |message|
|
24
|
+
expect(message).to include(switch ? 'Migrating:' : 'Migration successful')
|
25
|
+
switch = !switch
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'displays failed migrations' do
|
30
|
+
allow_any_instance_of(Aws::Record::TableConfig).to(
|
31
|
+
receive(:migrate!).and_raise(
|
32
|
+
StandardError.new('')
|
33
|
+
)
|
34
|
+
)
|
35
|
+
messages = []
|
36
|
+
|
37
|
+
Dynamo::Record::TaskHelpers::MigrationRunner.run do |thing|
|
38
|
+
messages << thing
|
39
|
+
end
|
40
|
+
|
41
|
+
switch = true
|
42
|
+
messages.each do |message|
|
43
|
+
expect(message).to include(switch ? 'Migrating:' : 'Migration failed')
|
44
|
+
switch = !switch
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises on non-numeric prefixes' do
|
49
|
+
expect do
|
50
|
+
Dynamo::Record::TaskHelpers::MigrationRunner.run('db/invalid_dynamo_migrate')
|
51
|
+
end.to raise_error(/Non-numeric prefix/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Dynamo::Record::TaskHelpers::Scale do
|
4
|
+
include_context 'with dummy model'
|
5
|
+
|
6
|
+
describe '.run' do
|
7
|
+
it 'changes both throughputs on the table' do
|
8
|
+
migration = Aws::Record::TableMigration.new(DynamoDummyModel)
|
9
|
+
current_settings = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table
|
10
|
+
|
11
|
+
# configure unique throughput settings so ordering doesn't matter
|
12
|
+
new_throughput = current_settings.provisioned_throughput.read_capacity_units == 50 ? 49 : 50
|
13
|
+
|
14
|
+
# Run the scaling
|
15
|
+
scale = Dynamo::Record::TaskHelpers::Scale.new 'DynamoDummyModel', 'both', new_throughput
|
16
|
+
scale.run
|
17
|
+
throughput = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table.provisioned_throughput
|
18
|
+
expect(throughput.read_capacity_units).to eq(new_throughput)
|
19
|
+
expect(throughput.write_capacity_units).to eq(new_throughput)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'changes the write throughput on the table' do
|
23
|
+
migration = Aws::Record::TableMigration.new(DynamoDummyModel)
|
24
|
+
current_settings = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table
|
25
|
+
|
26
|
+
# configure unique throughput settings so ordering doesn't matter
|
27
|
+
new_write_throughput = current_settings.provisioned_throughput.write_capacity_units == 20 ? 19 : 20
|
28
|
+
|
29
|
+
# Run the scaling
|
30
|
+
scale = Dynamo::Record::TaskHelpers::Scale.new 'DynamoDummyModel', 'write', new_write_throughput
|
31
|
+
scale.run
|
32
|
+
|
33
|
+
throughput = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table.provisioned_throughput
|
34
|
+
expect(throughput.write_capacity_units).to eq(new_write_throughput)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'changes the read throughput on the table' do
|
38
|
+
migration = Aws::Record::TableMigration.new(DynamoDummyModel)
|
39
|
+
current_settings = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table
|
40
|
+
|
41
|
+
# configure unique throughput settings so ordering doesn't matter
|
42
|
+
new_read_throughput = current_settings.provisioned_throughput.read_capacity_units == 20 ? 19 : 20
|
43
|
+
|
44
|
+
# Run the scaling
|
45
|
+
scale = Dynamo::Record::TaskHelpers::Scale.new 'DynamoDummyModel', 'read', new_read_throughput
|
46
|
+
scale.run
|
47
|
+
|
48
|
+
throughput = migration.client.describe_table(table_name: DynamoDummyModel.table_name).table.provisioned_throughput
|
49
|
+
expect(throughput.read_capacity_units).to eq(new_read_throughput)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'prints description if any param is blank' do
|
53
|
+
message = Dynamo::Record::TaskHelpers::Scale.new('DynamoDummyModel', 'read', nil).run
|
54
|
+
expect(message).to include('Here\'s some usage information:')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises exception if attribute selection is invalid' do
|
58
|
+
scale = Dynamo::Record::TaskHelpers::Scale.new('DynamoDummyModel', 'invalid_selection', 20)
|
59
|
+
expect { scale.run }.to raise_error(
|
60
|
+
/provide an appropriate attribute selection/
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Limit coverage reporting to one build:
|
4
|
+
if /^2\.6/ =~ RUBY_VERSION && /6\.0/ =~ ENV['BUNDLE_GEMFILE']
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter 'lib/dynamo/record/version.rb'
|
9
|
+
add_filter 'spec'
|
10
|
+
track_files 'lib/**/*.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
SimpleCov.minimum_coverage(94)
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'bundler'
|
17
|
+
Bundler.require :default, :development
|
18
|
+
|
19
|
+
Combustion.initialize! do
|
20
|
+
config.dynamo = { 'prefix' => 'test' }
|
21
|
+
end
|
22
|
+
|
23
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
# Enable flags like --only-failures and --next-failure
|
27
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
28
|
+
|
29
|
+
config.expect_with :rspec do |c|
|
30
|
+
c.syntax = :expect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Aws.config.update(
|
35
|
+
dynamodb: { endpoint: ENV['DYNAMO_ENDPOINT'] }
|
36
|
+
)
|
37
|
+
|
38
|
+
WebMock.disable_net_connect!(allow: ['dynamo:8000'])
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_context 'with dummy model' do
|
4
|
+
class DynamoDummyModel
|
5
|
+
include Aws::Record
|
6
|
+
include Dynamo::Record::Model
|
7
|
+
|
8
|
+
integer_attr :id, hash_key: true
|
9
|
+
string_attr :name
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
migration = Aws::Record::TableMigration.new(DynamoDummyModel)
|
14
|
+
migration.create!(
|
15
|
+
provisioned_throughput: {
|
16
|
+
read_capacity_units: 1,
|
17
|
+
write_capacity_units: 1
|
18
|
+
}
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
dynamodb = Aws::DynamoDB::Client.new
|
24
|
+
dynamodb.delete_table table_name: DynamoDummyModel.table_name
|
25
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
|
26
|
+
# some tests will drop this table
|
27
|
+
end
|
28
|
+
end
|
metadata
CHANGED
@@ -1,20 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamo-record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
- Ryan Taylor
|
9
|
-
- Bryan Petty
|
10
|
-
- Michael Brewer-Davis
|
11
|
-
- Marc Phillips
|
12
|
-
- Augusto Callejas
|
13
|
-
- Frank Murphy
|
7
|
+
- Alex Slaughter
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
date:
|
11
|
+
date: 2023-08-22 00:00:00.000000000 Z
|
18
12
|
dependencies:
|
19
13
|
- !ruby/object:Gem::Dependency
|
20
14
|
name: activemodel
|
@@ -232,30 +226,12 @@ dependencies:
|
|
232
226
|
version: '1.3'
|
233
227
|
description:
|
234
228
|
email:
|
235
|
-
-
|
236
|
-
- rtaylor@instructure.com
|
237
|
-
- bpetty@instructure.com
|
238
|
-
- mbd@instructure.com
|
239
|
-
- mphillips@instructure.com
|
240
|
-
- acallejas@instructure.com
|
241
|
-
- fmurphy@instructure.com
|
229
|
+
- aslaughter@instructure.com
|
242
230
|
executables: []
|
243
231
|
extensions: []
|
244
232
|
extra_rdoc_files: []
|
245
233
|
files:
|
246
|
-
- ".dockerignore"
|
247
|
-
- ".gitignore"
|
248
|
-
- ".rspec"
|
249
|
-
- ".rubocop.yml"
|
250
|
-
- ".travis.yml"
|
251
|
-
- Dockerfile
|
252
|
-
- Gemfile
|
253
|
-
- Jenkinsfile
|
254
|
-
- LICENSE.txt
|
255
234
|
- README.md
|
256
|
-
- docker-compose.override.example.yml
|
257
|
-
- docker-compose.yml
|
258
|
-
- dynamo-record.gemspec
|
259
235
|
- lib/dynamo/record.rb
|
260
236
|
- lib/dynamo/record/batch_get.rb
|
261
237
|
- lib/dynamo/record/batch_request.rb
|
@@ -273,6 +249,29 @@ files:
|
|
273
249
|
- lib/dynamo/record/task_helpers/scale.rb
|
274
250
|
- lib/dynamo/record/version.rb
|
275
251
|
- lib/tasks/dynamo.rake
|
252
|
+
- spec/gemfiles/rails-5.2.gemfile
|
253
|
+
- spec/gemfiles/rails-6.0.gemfile
|
254
|
+
- spec/gemfiles/rails-6.1.gemfile
|
255
|
+
- spec/internal/app/models/model1.rb
|
256
|
+
- spec/internal/app/models/model2.rb
|
257
|
+
- spec/internal/config/database.yml
|
258
|
+
- spec/internal/db/dynamo_migrate/20170402163638_create_model_1.rb
|
259
|
+
- spec/internal/db/dynamo_migrate/20170402163639_create_model_2.rb
|
260
|
+
- spec/internal/db/invalid_dynamo_migrate/add_model_1_stream.rb
|
261
|
+
- spec/lib/dynamo/record/batch_get_spec.rb
|
262
|
+
- spec/lib/dynamo/record/batch_write_spec.rb
|
263
|
+
- spec/lib/dynamo/record/model_existence_vaildator_spec.rb
|
264
|
+
- spec/lib/dynamo/record/model_spec.rb
|
265
|
+
- spec/lib/dynamo/record/table_migration_spec.rb
|
266
|
+
- spec/lib/dynamo/record/task_helpers/cleanup_spec.rb
|
267
|
+
- spec/lib/dynamo/record/task_helpers/drop_all_tables_spec.rb
|
268
|
+
- spec/lib/dynamo/record/task_helpers/drop_table_spec.rb
|
269
|
+
- spec/lib/dynamo/record/task_helpers/list_tables_spec.rb
|
270
|
+
- spec/lib/dynamo/record/task_helpers/migration_runner_spec.rb
|
271
|
+
- spec/lib/dynamo/record/task_helpers/scale_spec.rb
|
272
|
+
- spec/lib/dynamo/record/version_spec.rb
|
273
|
+
- spec/spec_helper.rb
|
274
|
+
- spec/support/shared_contexts/with_dummy_model.rb
|
276
275
|
homepage: https://github.com/instructure/dynamo-record
|
277
276
|
licenses:
|
278
277
|
- MIT
|
@@ -292,8 +291,31 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
292
291
|
- !ruby/object:Gem::Version
|
293
292
|
version: '0'
|
294
293
|
requirements: []
|
295
|
-
rubygems_version: 3.
|
294
|
+
rubygems_version: 3.4.10
|
296
295
|
signing_key:
|
297
296
|
specification_version: 4
|
298
297
|
summary: Extensions to Aws::Record for working with DynamoDB.
|
299
|
-
test_files:
|
298
|
+
test_files:
|
299
|
+
- spec/gemfiles/rails-5.2.gemfile
|
300
|
+
- spec/gemfiles/rails-6.0.gemfile
|
301
|
+
- spec/gemfiles/rails-6.1.gemfile
|
302
|
+
- spec/internal/app/models/model1.rb
|
303
|
+
- spec/internal/app/models/model2.rb
|
304
|
+
- spec/internal/config/database.yml
|
305
|
+
- spec/internal/db/dynamo_migrate/20170402163638_create_model_1.rb
|
306
|
+
- spec/internal/db/dynamo_migrate/20170402163639_create_model_2.rb
|
307
|
+
- spec/internal/db/invalid_dynamo_migrate/add_model_1_stream.rb
|
308
|
+
- spec/lib/dynamo/record/batch_get_spec.rb
|
309
|
+
- spec/lib/dynamo/record/batch_write_spec.rb
|
310
|
+
- spec/lib/dynamo/record/model_existence_vaildator_spec.rb
|
311
|
+
- spec/lib/dynamo/record/model_spec.rb
|
312
|
+
- spec/lib/dynamo/record/table_migration_spec.rb
|
313
|
+
- spec/lib/dynamo/record/task_helpers/cleanup_spec.rb
|
314
|
+
- spec/lib/dynamo/record/task_helpers/drop_all_tables_spec.rb
|
315
|
+
- spec/lib/dynamo/record/task_helpers/drop_table_spec.rb
|
316
|
+
- spec/lib/dynamo/record/task_helpers/list_tables_spec.rb
|
317
|
+
- spec/lib/dynamo/record/task_helpers/migration_runner_spec.rb
|
318
|
+
- spec/lib/dynamo/record/task_helpers/scale_spec.rb
|
319
|
+
- spec/lib/dynamo/record/version_spec.rb
|
320
|
+
- spec/spec_helper.rb
|
321
|
+
- spec/support/shared_contexts/with_dummy_model.rb
|
data/.dockerignore
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
/.gitignore
|
2
|
-
/.rspec_status
|
3
|
-
/.ruby-version
|
4
|
-
/coverage/
|
5
|
-
/doc/
|
6
|
-
/Dockerfile
|
7
|
-
/docker-compose.yml
|
8
|
-
/docker-compose.override.example.yml
|
9
|
-
/docker-compose.override.yml
|
10
|
-
/dynamo-record-*.gem
|
11
|
-
/Gemfile.lock
|
12
|
-
/log/
|
13
|
-
/pkg/
|
14
|
-
/spec/gemfiles/.bundle/
|
15
|
-
/spec/gemfiles/*.gemfile.lock
|
16
|
-
/switchman-inst-jobs-*.gem
|
17
|
-
/tmp/
|
data/.gitignore
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
/.bundle/
|
2
|
-
/.rspec_status
|
3
|
-
/.ruby-version
|
4
|
-
/.yardoc
|
5
|
-
/_yardoc/
|
6
|
-
/coverage/
|
7
|
-
/docker-compose.override.yml
|
8
|
-
/dynamo-record-*.gem
|
9
|
-
/Gemfile.lock
|
10
|
-
/pkg/
|
11
|
-
/spec/gemfiles/.bundle/
|
12
|
-
/spec/dummy/log/
|
13
|
-
/spec/dummy/tmp/
|
14
|
-
/spec/gemfiles/*.gemfile.lock
|
15
|
-
/spec/reports/
|
16
|
-
/tmp/
|
data/.rspec
DELETED
data/.rubocop.yml
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require:
|
2
|
-
- rubocop-rails
|
3
|
-
|
4
|
-
AllCops:
|
5
|
-
NewCops: disable
|
6
|
-
SuggestExtensions: false
|
7
|
-
TargetRailsVersion: 4.2
|
8
|
-
TargetRubyVersion: 2.6
|
9
|
-
|
10
|
-
Metrics/AbcSize:
|
11
|
-
Max: 20 # Default: 15
|
12
|
-
|
13
|
-
Metrics/ClassLength:
|
14
|
-
Max: 200 # Default: 100
|
15
|
-
|
16
|
-
Metrics/MethodLength:
|
17
|
-
Max: 20 # Default: 10
|
18
|
-
|
19
|
-
Metrics/BlockLength:
|
20
|
-
Max: 30
|
21
|
-
Exclude:
|
22
|
-
- dynamo-record.gemspec
|
23
|
-
- spec/**/*.rb
|
24
|
-
|
25
|
-
Layout/EndAlignment:
|
26
|
-
EnforcedStyleAlignWith: variable
|
27
|
-
|
28
|
-
Layout/LineLength:
|
29
|
-
Max: 120 # Default: 80
|
30
|
-
|
31
|
-
Layout/ParameterAlignment:
|
32
|
-
# Alignment of parameters in multi-line method calls.
|
33
|
-
#
|
34
|
-
# The `with_fixed_indentation` style aligns the following lines with one
|
35
|
-
# level of indentation relative to the start of the line with the method call.
|
36
|
-
#
|
37
|
-
# method_call(a,
|
38
|
-
# b)
|
39
|
-
EnforcedStyle: with_fixed_indentation
|
40
|
-
|
41
|
-
Lint/ConstantDefinitionInBlock:
|
42
|
-
Exclude:
|
43
|
-
- spec/**/*.rb
|
44
|
-
|
45
|
-
Naming/FileName:
|
46
|
-
Exclude:
|
47
|
-
- spec/gemfiles/*
|
48
|
-
|
49
|
-
Naming/VariableNumber:
|
50
|
-
EnforcedStyle: snake_case
|
51
|
-
|
52
|
-
Style/Documentation:
|
53
|
-
# This cop checks for missing top-level documentation of classes and modules.
|
54
|
-
# Classes with no body and namespace modules are exempt from the check.
|
55
|
-
# Namespace modules are modules that have nothing in their bodies except
|
56
|
-
# classes or other modules.
|
57
|
-
Enabled: false
|
58
|
-
|
59
|
-
Style/NumericPredicate:
|
60
|
-
Exclude:
|
61
|
-
- spec/**/*.rb
|
data/.travis.yml
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
dist: trusty
|
2
|
-
sudo: false
|
3
|
-
language: ruby
|
4
|
-
cache: bundler
|
5
|
-
|
6
|
-
rvm:
|
7
|
-
- 2.6
|
8
|
-
- 2.7
|
9
|
-
- 3.0
|
10
|
-
|
11
|
-
gemfile:
|
12
|
-
- spec/gemfiles/rails-5.2.gemfile
|
13
|
-
- spec/gemfiles/rails-6.0.gemfile
|
14
|
-
- spec/gemfiles/rails-6.1.gemfile
|
15
|
-
|
16
|
-
# Rails 5.2 doesn't support Ruby 3.0, so don't try
|
17
|
-
matrix:
|
18
|
-
exclude:
|
19
|
-
- rvm: 3.0
|
20
|
-
gemfile: spec/gemfiles/rails-5.2.gemfile
|
21
|
-
|
22
|
-
before_install: gem update bundler
|
23
|
-
bundler_args: --jobs 3
|
24
|
-
install: bundle install --jobs 3
|
25
|
-
|
26
|
-
script:
|
27
|
-
- bash -c "if [ '$TRAVIS_RUBY_VERSION' = '3.0' ] && [[ '$BUNDLE_GEMFILE' == *'rails-6.1'* ]]; then bundle exec rubocop --fail-level autocorrect; fi"
|
28
|
-
- bundle exec rspec
|
data/Dockerfile
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
FROM instructure/rvm
|
2
|
-
|
3
|
-
WORKDIR /app
|
4
|
-
USER root
|
5
|
-
RUN chown -R docker:docker /app
|
6
|
-
USER docker
|
7
|
-
|
8
|
-
COPY --chown=docker:docker dynamo-record.gemspec Gemfile /app/
|
9
|
-
COPY --chown=docker:docker lib/dynamo/record/version.rb /app/lib/dynamo/record/version.rb
|
10
|
-
|
11
|
-
RUN mkdir -p coverage \
|
12
|
-
spec/gemfiles/.bundle \
|
13
|
-
spec/internal/log
|
14
|
-
|
15
|
-
RUN bash -lc "rvm 2.6,2.7,3.0 do gem install --no-document bundler -v '~> 2.2'"
|
16
|
-
RUN bash -lc "cd /app && rvm-exec 2.6 bundle install --jobs 5"
|
17
|
-
RUN bash -lc "cd /app && rvm-exec 2.7 bundle install --jobs 5"
|
18
|
-
RUN bash -lc "cd /app && rvm-exec 3.0 bundle install --jobs 5"
|
19
|
-
COPY --chown=docker:docker . /app
|
20
|
-
|
21
|
-
CMD /bin/bash -lc "rvm-exec 3.0 bundle exec wwtd"
|
data/Jenkinsfile
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
#!/usr/bin/env groovy
|
2
|
-
|
3
|
-
pipeline {
|
4
|
-
agent {
|
5
|
-
label 'docker'
|
6
|
-
}
|
7
|
-
options {
|
8
|
-
ansiColor("xterm")
|
9
|
-
buildDiscarder(logRotator(numToKeepStr: '50'))
|
10
|
-
timeout(time: 20, unit: 'MINUTES')
|
11
|
-
}
|
12
|
-
stages {
|
13
|
-
stage('Build') {
|
14
|
-
steps {
|
15
|
-
sh 'docker-compose pull dynamo'
|
16
|
-
sh 'docker-compose up -d dynamo'
|
17
|
-
sh 'docker-compose build --pull app'
|
18
|
-
}
|
19
|
-
}
|
20
|
-
stage('Test') {
|
21
|
-
steps {
|
22
|
-
sh '''
|
23
|
-
docker-compose run --rm app /bin/bash -l -c \
|
24
|
-
"rvm-exec 3.0 bundle exec rubocop --fail-level autocorrect"
|
25
|
-
docker-compose run --name coverage app
|
26
|
-
'''
|
27
|
-
}
|
28
|
-
post {
|
29
|
-
always {
|
30
|
-
sh 'docker cp coverage:/app/coverage .'
|
31
|
-
sh 'docker-compose down --rmi=all --volumes --remove-orphans'
|
32
|
-
|
33
|
-
publishHTML target: [
|
34
|
-
allowMissing: false,
|
35
|
-
alwaysLinkToLastBuild: false,
|
36
|
-
keepAll: true,
|
37
|
-
reportDir: "coverage",
|
38
|
-
reportFiles: 'index.html',
|
39
|
-
reportName: 'Coverage Report'
|
40
|
-
]
|
41
|
-
}
|
42
|
-
}
|
43
|
-
}
|
44
|
-
}
|
45
|
-
}
|
data/LICENSE.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2017 Instructure, Inc.
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|
@@ -1,19 +0,0 @@
|
|
1
|
-
version: '2'
|
2
|
-
|
3
|
-
services:
|
4
|
-
app:
|
5
|
-
volumes:
|
6
|
-
- .:/app
|
7
|
-
- gems:/home/docker/.rvm/gems
|
8
|
-
# Disable the rest of these volumes if the container can safely write to
|
9
|
-
# your host filesystem mount named above. You might want to use the rest
|
10
|
-
# of these unless you're using dinghy on OSX (usually needed for linux).
|
11
|
-
- coverage:/app/coverage
|
12
|
-
- bundle-config:/app/spec/gemfiles/.bundle
|
13
|
-
- internal-log:/app/spec/internal/log
|
14
|
-
|
15
|
-
volumes:
|
16
|
-
coverage: {}
|
17
|
-
bundle-config: {}
|
18
|
-
gems: {}
|
19
|
-
internal-log: {}
|
data/docker-compose.yml
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
version: '2'
|
2
|
-
|
3
|
-
services:
|
4
|
-
app:
|
5
|
-
build: .
|
6
|
-
environment:
|
7
|
-
AWS_ACCESS_KEY_ID: x
|
8
|
-
AWS_SECRET_ACCESS_KEY: x
|
9
|
-
AWS_REGION: us-west-2
|
10
|
-
DYNAMO_ENDPOINT: http://dynamo:8000
|
11
|
-
RAILS_ENV: test
|
12
|
-
logging:
|
13
|
-
options:
|
14
|
-
max-file: '1'
|
15
|
-
max-size: 5m
|
16
|
-
links:
|
17
|
-
- dynamo
|
18
|
-
|
19
|
-
dynamo:
|
20
|
-
image: instructure/dynamodb
|
data/dynamo-record.gemspec
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
lib = File.expand_path('lib', __dir__)
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require 'dynamo/record/version'
|
6
|
-
|
7
|
-
Gem::Specification.new do |s|
|
8
|
-
s.name = 'dynamo-record'
|
9
|
-
s.version = Dynamo::Record::VERSION
|
10
|
-
s.summary = 'Extensions to Aws::Record for working with DynamoDB.'
|
11
|
-
s.homepage = 'https://github.com/instructure/dynamo-record'
|
12
|
-
s.license = 'MIT'
|
13
|
-
|
14
|
-
s.authors = [
|
15
|
-
'Davis McClellan',
|
16
|
-
'Ryan Taylor',
|
17
|
-
'Bryan Petty',
|
18
|
-
'Michael Brewer-Davis',
|
19
|
-
'Marc Phillips',
|
20
|
-
'Augusto Callejas',
|
21
|
-
'Frank Murphy'
|
22
|
-
]
|
23
|
-
s.email = [
|
24
|
-
'dmcclellan@instructure.com',
|
25
|
-
'rtaylor@instructure.com',
|
26
|
-
'bpetty@instructure.com',
|
27
|
-
'mbd@instructure.com',
|
28
|
-
'mphillips@instructure.com',
|
29
|
-
'acallejas@instructure.com',
|
30
|
-
'fmurphy@instructure.com'
|
31
|
-
]
|
32
|
-
|
33
|
-
s.files = `git ls-files -z`.split("\x0").reject do |f|
|
34
|
-
f.match(%r{^(test|spec|features)/})
|
35
|
-
end
|
36
|
-
s.require_paths = ['lib']
|
37
|
-
|
38
|
-
s.required_ruby_version = '>= 2.6'
|
39
|
-
|
40
|
-
s.add_dependency 'activemodel', '>= 5.2', '< 6.2'
|
41
|
-
s.add_dependency 'aws-record', '~> 2.0'
|
42
|
-
s.add_dependency 'railties', '>= 5.2', '< 6.2'
|
43
|
-
|
44
|
-
s.add_development_dependency 'activesupport', '>= 5.2', '< 6.2'
|
45
|
-
s.add_development_dependency 'bundler', '~> 2.2'
|
46
|
-
s.add_development_dependency 'byebug', '~> 11.0'
|
47
|
-
s.add_development_dependency 'combustion', '~> 1.3'
|
48
|
-
s.add_development_dependency 'rake', '~> 13.0'
|
49
|
-
s.add_development_dependency 'rspec', '~> 3.6'
|
50
|
-
s.add_development_dependency 'rubocop', '~> 1.8.1'
|
51
|
-
s.add_development_dependency 'rubocop-rails', '~> 2.9.1'
|
52
|
-
s.add_development_dependency 'simplecov', '~> 0.16'
|
53
|
-
s.add_development_dependency 'webmock', '~> 3.3'
|
54
|
-
s.add_development_dependency 'wwtd', '~> 1.3'
|
55
|
-
end
|