mara 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/Rakefile +26 -0
- data/lib/mara.rb +13 -0
- data/lib/mara/attribute_formatter.rb +161 -0
- data/lib/mara/batch.rb +223 -0
- data/lib/mara/client.rb +43 -0
- data/lib/mara/configure.rb +100 -0
- data/lib/mara/dynamo_helpers.rb +34 -0
- data/lib/mara/error.rb +8 -0
- data/lib/mara/instrument.rb +16 -0
- data/lib/mara/model.rb +13 -0
- data/lib/mara/model/attributes.rb +166 -0
- data/lib/mara/model/base.rb +225 -0
- data/lib/mara/model/dsl.rb +208 -0
- data/lib/mara/model/persistence.rb +120 -0
- data/lib/mara/model/query.rb +97 -0
- data/lib/mara/null_value.rb +13 -0
- data/lib/mara/persistence.rb +204 -0
- data/lib/mara/primary_key.rb +117 -0
- data/lib/mara/query.rb +90 -0
- data/lib/mara/table.rb +141 -0
- data/lib/mara/version.rb +5 -0
- metadata +195 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Mara
|
5
|
+
##
|
6
|
+
# Wraps a primary key.
|
7
|
+
#
|
8
|
+
# @author Maddie Schipper
|
9
|
+
# @since 1.0.0
|
10
|
+
class PrimaryKey
|
11
|
+
class << self
|
12
|
+
##
|
13
|
+
# Create a primary key from a model.
|
14
|
+
#
|
15
|
+
# @param model [Mara::PrimaryKey, Mara::Model::Base] The object to
|
16
|
+
# stringify.
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
def generate(model)
|
20
|
+
case model
|
21
|
+
when Mara::PrimaryKey
|
22
|
+
model.to_s
|
23
|
+
when Mara::Model::Base
|
24
|
+
new(model: model).to_s
|
25
|
+
else
|
26
|
+
raise ArgumentError, "The value passed into generate isn't expected <#{model}>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Parse a primary key string.
|
32
|
+
#
|
33
|
+
# @param key_str [String] The primary key string to return.
|
34
|
+
#
|
35
|
+
# @return [Mara::PrimaryKey]
|
36
|
+
def parse(key_str)
|
37
|
+
parts = JSON.parse(decode(key_str))
|
38
|
+
new(
|
39
|
+
class_name: parts[0],
|
40
|
+
partition_key: parts[1],
|
41
|
+
sort_key: parts[2]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def decode(str)
|
48
|
+
str = str.tr('-_', '+/')
|
49
|
+
str = str.ljust((str.length + 3) & ~3, '=')
|
50
|
+
Base64.strict_decode64(str)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# The classname of the model that the primary key represents.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
attr_reader :class_name
|
59
|
+
|
60
|
+
##
|
61
|
+
# The partion key
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
attr_reader :partition_key
|
65
|
+
|
66
|
+
##
|
67
|
+
# The sort key
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
attr_reader :sort_key
|
71
|
+
|
72
|
+
##
|
73
|
+
# Create a new primary key.
|
74
|
+
#
|
75
|
+
# @note If +:model+ is not present the other three options are required.
|
76
|
+
#
|
77
|
+
# @param opts [Hash] The options param
|
78
|
+
# @option opts [ Mara::Model::Base] :model The model this key will represent.
|
79
|
+
# @option opts [String] :class_name The class name of the model.
|
80
|
+
# @option opts [String] :partition_key The partition key value.
|
81
|
+
# @option opts [String] :sort_key The sort key value.
|
82
|
+
def initialize(opts)
|
83
|
+
if (model = opts.delete(:model)).present?
|
84
|
+
@class_name = model.class.name
|
85
|
+
@partition_key = if model.class.partition_key.present?
|
86
|
+
model.partition_key
|
87
|
+
end
|
88
|
+
@sort_key = if model.class.sort_key.present?
|
89
|
+
model.sort_key
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@class_name = opts.fetch(:class_name).camelize
|
93
|
+
@partition_key = opts.fetch(:partition_key)
|
94
|
+
@sort_key = opts.fetch(:sort_key, nil).presence
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Convert the primary key into a URL safe representation.
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
def to_s
|
103
|
+
payload = JSON.dump([
|
104
|
+
(class_name.presence || '').underscore,
|
105
|
+
partition_key.presence || '',
|
106
|
+
sort_key.presence || ''
|
107
|
+
])
|
108
|
+
encode(payload)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def encode(bin)
|
114
|
+
Base64.strict_encode64(bin).tr('+/', '-_').tr('=', '')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/mara/query.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
require_relative 'configure'
|
3
|
+
require_relative 'instrument'
|
4
|
+
require_relative 'attribute_formatter'
|
5
|
+
require_relative 'dynamo_helpers'
|
6
|
+
|
7
|
+
module Mara
|
8
|
+
##
|
9
|
+
# @private
|
10
|
+
#
|
11
|
+
# Perform calls to DynamoDB for fetching
|
12
|
+
#
|
13
|
+
# @author Maddie Schipper
|
14
|
+
# @since 1.0.0
|
15
|
+
class Query
|
16
|
+
include DynamoHelpers
|
17
|
+
|
18
|
+
##
|
19
|
+
# The response from DynamoDB
|
20
|
+
#
|
21
|
+
# @!attribute [r] items
|
22
|
+
# The items returned by DynamoDB
|
23
|
+
#
|
24
|
+
# @return [Array<Hash>]
|
25
|
+
#
|
26
|
+
# @!attribute [r] consumed_capacity
|
27
|
+
# The capacity DyanmoDB used to perform the request.
|
28
|
+
#
|
29
|
+
# @return [Float]
|
30
|
+
Result = Struct.new(:items, :consumed_capacity)
|
31
|
+
|
32
|
+
class << self
|
33
|
+
##
|
34
|
+
# Perform a single item get request by the primary key.
|
35
|
+
#
|
36
|
+
# @param query_params [Hash] The query items.
|
37
|
+
#
|
38
|
+
# @return [ Mara::Query::Result]
|
39
|
+
def get_item(query_params)
|
40
|
+
client, table_name = config_params(query_params)
|
41
|
+
primary_key = query_params.fetch(:key)
|
42
|
+
projection_expression = query_params.fetch(:projection_expression, nil).presence
|
43
|
+
|
44
|
+
params = {
|
45
|
+
key: primary_key,
|
46
|
+
table_name: table_name,
|
47
|
+
return_consumed_capacity: 'TOTAL',
|
48
|
+
projection_expression: projection_expression
|
49
|
+
}
|
50
|
+
|
51
|
+
result = Mara.instrument('get_item', params) do
|
52
|
+
client.get_item(params)
|
53
|
+
end
|
54
|
+
|
55
|
+
return nil if result.item.nil?
|
56
|
+
|
57
|
+
item = format_item(result.item)
|
58
|
+
cc = calculate_consumed_capacity(result.consumed_capacity, table_name)
|
59
|
+
|
60
|
+
Result.new(
|
61
|
+
[item],
|
62
|
+
cc
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def format_item(item)
|
69
|
+
item.map do |key, value|
|
70
|
+
[key, Mara::AttributeFormatter.flatten(value)]
|
71
|
+
end.to_h
|
72
|
+
end
|
73
|
+
|
74
|
+
def wrap_items(result)
|
75
|
+
if result.responses
|
76
|
+
result.responses
|
77
|
+
else
|
78
|
+
[result.item]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def config_params(params)
|
83
|
+
[
|
84
|
+
params.fetch(:client, Mara::Client.shared),
|
85
|
+
params.fetch(:table_name, Mara.config.dynamodb.table_name)
|
86
|
+
]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/mara/table.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
require_relative 'configure'
|
3
|
+
|
4
|
+
module Mara
|
5
|
+
##
|
6
|
+
# Manage Dev/Test tables.
|
7
|
+
#
|
8
|
+
# While this _can_ be used to create real tables, we don't recommend it.
|
9
|
+
#
|
10
|
+
# @author Maddie Schipper
|
11
|
+
# @since 1.0.0
|
12
|
+
class Table
|
13
|
+
class << self
|
14
|
+
##
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# Default supported environments
|
18
|
+
SUPPORTED_ENVS = %w[development test].freeze
|
19
|
+
|
20
|
+
##
|
21
|
+
# Create a new table if it doesn't exist.
|
22
|
+
#
|
23
|
+
# @note If the table_params do not include the table name, The default
|
24
|
+
# table name from the config will be used.
|
25
|
+
#
|
26
|
+
# @param table_params [Hash] DynamoDB create table params hash.
|
27
|
+
#
|
28
|
+
# @return [true, false]
|
29
|
+
def prepare!(table_params)
|
30
|
+
prepare_table!(table_params, SUPPORTED_ENVS, true)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Teardown the table if it exists.
|
35
|
+
#
|
36
|
+
# @note If the table_params do not include the table name, The default
|
37
|
+
# table name from the config will be used.
|
38
|
+
#
|
39
|
+
# @param table_params [Hash] DynamoDB table name params.
|
40
|
+
#
|
41
|
+
# @return [true, false]
|
42
|
+
def teardown!(table_params = {})
|
43
|
+
teardown_table!(table_params, SUPPORTED_ENVS, true)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Check if a table exists.
|
48
|
+
#
|
49
|
+
# @param table_name [String] the name of the table to check if it exists.
|
50
|
+
#
|
51
|
+
# @return [true, false]
|
52
|
+
def table_exists?(table_name)
|
53
|
+
Mara::Client.shared.list_tables.table_names.include?(table_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# @private
|
58
|
+
#
|
59
|
+
# Prepare the table with extra options.
|
60
|
+
#
|
61
|
+
# @param table_params [Hash] DynamoDB create table params hash.
|
62
|
+
# @param envs [Array<String>] The environments that are allowed to check
|
63
|
+
# if this action is allowed.
|
64
|
+
# @param wait [true, false] Should this function wait for the table to
|
65
|
+
# become fully available before returning.
|
66
|
+
#
|
67
|
+
# @return [true, false]
|
68
|
+
def prepare_table!(table_params, envs, wait)
|
69
|
+
env = Mara.config.env
|
70
|
+
unless Array(envs).include?(env)
|
71
|
+
raise ArgumentError, "Can't prepare table outside of #{envs.join('/')}"
|
72
|
+
end
|
73
|
+
|
74
|
+
table_name = table_params.fetch(:table_name, Mara.config.dynamodb.table_name)
|
75
|
+
|
76
|
+
if table_exists?(table_name)
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
|
80
|
+
table_params = normalize_table_params(table_params, table_name)
|
81
|
+
|
82
|
+
log(" Mara create_table(\"#{table_name}\")")
|
83
|
+
|
84
|
+
Mara::Client.shared.create_table(table_params)
|
85
|
+
Mara::Client.shared.wait_until(:table_exists, table_name: table_name) if wait
|
86
|
+
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# @private
|
92
|
+
#
|
93
|
+
# Teardown the table with extra options.
|
94
|
+
#
|
95
|
+
# @param table_params [Hash] DynamoDB create table params hash.
|
96
|
+
# @param envs [Array<String>] The environments that are allowed to check
|
97
|
+
# if this action is allowed.
|
98
|
+
# @param wait [true, false] Should this function wait for the table to
|
99
|
+
# become fully available before returning.
|
100
|
+
#
|
101
|
+
# @return [true, false]
|
102
|
+
def teardown_table!(table_params, envs, wait)
|
103
|
+
env = Mara.config.env
|
104
|
+
unless envs.include?(env)
|
105
|
+
raise ArgumentError, "Can't prepare table outside of #{envs.join('/')}"
|
106
|
+
end
|
107
|
+
|
108
|
+
table_name = table_params.fetch(:table_name, Mara.config.dynamodb.table_name)
|
109
|
+
|
110
|
+
unless Mara::Client.shared.list_tables.table_names.include?(table_name)
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
|
114
|
+
log(" Mara destroy_table(\"#{table_name}\")")
|
115
|
+
|
116
|
+
Mara::Client.shared.delete_table(table_name: table_name)
|
117
|
+
Mara::Client.shared.wait_until(:table_not_exists, table_name: table_name) if wait
|
118
|
+
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def log(msg)
|
125
|
+
STDERR.puts msg
|
126
|
+
end
|
127
|
+
|
128
|
+
def normalize_table_params(table_params, table_name)
|
129
|
+
provisioned_throughput = table_params.fetch(:provisioned_throughput,
|
130
|
+
read_capacity_units: 10,
|
131
|
+
write_capacity_units: 10)
|
132
|
+
|
133
|
+
table_params.delete(:billing_mode)
|
134
|
+
table_params[:table_name] = table_name
|
135
|
+
table_params[:provisioned_throughput] = provisioned_throughput
|
136
|
+
|
137
|
+
table_params
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/mara/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mara
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Maddie Schipper
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 5.0.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: aws-sdk-dynamodb
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.8.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '2'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.8.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '2'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: bundler
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.17'
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.17'
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '3'
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: pry
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0.10'
|
80
|
+
type: :development
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - "~>"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0.10'
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: rake
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '10.0'
|
94
|
+
type: :development
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '10.0'
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: rspec
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '3.8'
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '3.8'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: simplecov
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: yard
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
description: DynamoDB Client Wrapper
|
144
|
+
email:
|
145
|
+
- me@maddiesch.com
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- lib/mara.rb
|
153
|
+
- lib/mara/attribute_formatter.rb
|
154
|
+
- lib/mara/batch.rb
|
155
|
+
- lib/mara/client.rb
|
156
|
+
- lib/mara/configure.rb
|
157
|
+
- lib/mara/dynamo_helpers.rb
|
158
|
+
- lib/mara/error.rb
|
159
|
+
- lib/mara/instrument.rb
|
160
|
+
- lib/mara/model.rb
|
161
|
+
- lib/mara/model/attributes.rb
|
162
|
+
- lib/mara/model/base.rb
|
163
|
+
- lib/mara/model/dsl.rb
|
164
|
+
- lib/mara/model/persistence.rb
|
165
|
+
- lib/mara/model/query.rb
|
166
|
+
- lib/mara/null_value.rb
|
167
|
+
- lib/mara/persistence.rb
|
168
|
+
- lib/mara/primary_key.rb
|
169
|
+
- lib/mara/query.rb
|
170
|
+
- lib/mara/table.rb
|
171
|
+
- lib/mara/version.rb
|
172
|
+
homepage: https://github.com/maddiesch/mara
|
173
|
+
licenses:
|
174
|
+
- MIT
|
175
|
+
metadata: {}
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubygems_version: 3.0.2
|
192
|
+
signing_key:
|
193
|
+
specification_version: 4
|
194
|
+
summary: DynamoDB Client Wrapper
|
195
|
+
test_files: []
|