mara 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []