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.
@@ -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
@@ -0,0 +1,5 @@
1
+ module Mara
2
+ ##
3
+ # The current version of Mara
4
+ VERSION = '0.1.0'.freeze
5
+ end
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: []