mara 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ module Mara
2
+ ##
3
+ # Configure Mara
4
+ #
5
+ # @param env [String] The default environment that is being configured.
6
+ #
7
+ # If this block is called a second time, the previous env version will
8
+ # be overridden
9
+ #
10
+ # @yield [config] The configuration object to set values on.
11
+ #
12
+ # @yieldparam config [ Mara::Configure] The configuration object.
13
+ # @yieldreturn [void]
14
+ #
15
+ # @return [void]
16
+ def self.configure(env)
17
+ instance = config
18
+ instance.send(:_set_env, env)
19
+ yield(instance)
20
+ end
21
+
22
+ ##
23
+ # @private
24
+ #
25
+ # The current config
26
+ def self.config
27
+ @config ||= Configure.new
28
+ @config
29
+ end
30
+
31
+ ##
32
+ # The configuration for Mara
33
+ #
34
+ # @author Maddie Schipper
35
+ # @since 1.0.0
36
+ class Configure
37
+ ##
38
+ # DynamoDB specific config values.
39
+ #
40
+ # @!attribute [rw] table_name
41
+ # The name of the DynamoDB table to use.
42
+ #
43
+ # @note If this is not set, pretty much nothing will work.
44
+ #
45
+ # @return [String, nil]
46
+ # @!attribute [rw] endpoint
47
+ # The DynamoDB endpoint to use. If `nil` this will fallback to the
48
+ # AWS default endpoint.
49
+ #
50
+ # @return [String, nil]
51
+ DynamoConfig = Struct.new(:table_name, :endpoint)
52
+
53
+ ##
54
+ # Aws specific config values.
55
+ #
56
+ # @!attribute [rw] region
57
+ # The region name to use for the Client.
58
+ #
59
+ # @note By default this is `us-east-1`
60
+ #
61
+ # @return [String]
62
+ AwsConfig = Struct.new(:region)
63
+
64
+ ##
65
+ # The current environment that Mara is configured for.
66
+ #
67
+ # @return [String]
68
+ attr_reader :env
69
+
70
+ ##
71
+ # The Aws config
72
+ #
73
+ # @return [ Mara::Configure::AwsConfig]
74
+ attr_reader :aws
75
+
76
+ ##
77
+ # The DynamoDB config
78
+ #
79
+ # @return [ Mara::Configure::DynamoConfig]
80
+ attr_reader :dynamodb
81
+
82
+ ##
83
+ # @private
84
+ #
85
+ # Create a new instance.
86
+ #
87
+ # @note This should never be called directly by the client.
88
+ def initialize
89
+ @env = 'production'
90
+ @dynamodb = DynamoConfig.new(nil, nil)
91
+ @aws = AwsConfig.new('us-east-1')
92
+ end
93
+
94
+ private
95
+
96
+ def _set_env(env)
97
+ @env = env.to_s
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,34 @@
1
+ module Mara
2
+ ##
3
+ # @private
4
+ #
5
+ # Helper methods for formatting data as it comes back from DynamoDB
6
+ module DynamoHelpers
7
+ def self.included(klass)
8
+ klass.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ ##
13
+ # Calculate all the consumed capacity in an array of capacity objects.
14
+ def calculate_consumed_capacity(consumed_capacity, table_name)
15
+ consumed = consumed_capacity.is_a?(Array) ? consumed_capacity : [consumed_capacity]
16
+
17
+ if table_name
18
+ consumed.select! { |cap| cap.table_name == table_name }
19
+ end
20
+
21
+ consumed.map! { |cap| _sum_capacity(cap) }
22
+ consumed.sum
23
+ end
24
+
25
+ ##
26
+ # @private
27
+ #
28
+ # Count the number of capcity unites in a capacity object.
29
+ def _sum_capacity(cap)
30
+ cap.capacity_units.to_f
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/mara/error.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Mara
2
+ ##
3
+ # The base error for any Mara specific errors.
4
+ #
5
+ # @author Maddie Schipper
6
+ # @since 1.0.0
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+
3
+ module Mara
4
+ ##
5
+ # @private
6
+ #
7
+ # Convenience method for {ActiveSupport::Notifications} that also namespaces
8
+ # the key.
9
+ #
10
+ # @param name [string] The name of the action being instrumented.
11
+ #
12
+ # @return [Object] The return value of the block
13
+ def self.instrument(name, *args, &block)
14
+ ActiveSupport::Notifications.instrument("mara.#{name}", *args, &block)
15
+ end
16
+ end
data/lib/mara/model.rb ADDED
@@ -0,0 +1,13 @@
1
+ require_relative 'model/base'
2
+
3
+ module Mara
4
+ ##
5
+ # A Mara Model.
6
+ #
7
+ # @see Mara::Model::Base
8
+ #
9
+ # @author Maddie Schipper
10
+ # @since 1.0.0
11
+ module Model
12
+ end
13
+ end
@@ -0,0 +1,166 @@
1
+ require_relative '../error'
2
+
3
+ module Mara
4
+ module Model
5
+ ##
6
+ # Errors raised when trying to set a key.
7
+ #
8
+ # @author Maddie Schipper
9
+ # @since 1.0.0
10
+ class AttributeError < Mara::Error; end
11
+
12
+ ##
13
+ # The container class for attributes.
14
+ #
15
+ # This should not be created directly. Instead use the Model convenience
16
+ # methods to have this setup automatically.
17
+ #
18
+ # @author Maddie Schipper
19
+ # @since 1.0.0
20
+ class Attributes
21
+ ##
22
+ # @private
23
+ #
24
+ # Create a new instance of attributes.
25
+ #
26
+ # @param default [Hash] The default attribute values to set.
27
+ def initialize(default)
28
+ @storage = {}
29
+ default.each { |k, v| set(k, v) }
30
+ end
31
+
32
+ ##
33
+ # @private
34
+ #
35
+ # Enumerate each key, value pair.
36
+ def each
37
+ return @storage.enum_for(:each) unless block_given?
38
+
39
+ @storage.each do |*args|
40
+ yield(*args)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Set a key, value pair on the attributes.
46
+ #
47
+ # @param key [#to_s] The key for the attribute you're trying to set.
48
+ #
49
+ # This key will be normalized before being stored as an attribute.
50
+ #
51
+ # @param value [Any, nil] The value to set for the key.
52
+ #
53
+ # @param pre_formatted [true, false] If the key is already normalized.
54
+ #
55
+ # @note If the value is +nil+, the key will be deleted.
56
+ #
57
+ # @raise [AttributeError] The normalized value of the key can't be blank.
58
+ #
59
+ # @return [void]
60
+ def set(key, value, pre_formatted: false)
61
+ nkey = normalize_key(key, pre_formatted)
62
+
63
+ raise AttributeError, "Can't set an attribute without a key" if nkey.blank?
64
+
65
+ if value.nil?
66
+ @storage.delete(nkey)
67
+ else
68
+ @storage[nkey] = value
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Get a value an attribute value.
74
+ #
75
+ # @param key [#to_s] The key for the attribute you're getting.
76
+ #
77
+ # @param pre_formatted [true, false] If the key is already normalized.
78
+ #
79
+ # @raise [AttributeError] The normalized value of the key can't be blank.
80
+ #
81
+ # @return [Any, nil]
82
+ def get(key, pre_formatted: false)
83
+ nkey = normalize_key(key, pre_formatted)
84
+
85
+ raise AttributeError, "Can't get an attribute without a key" if nkey.blank?
86
+
87
+ @storage[nkey]
88
+ end
89
+
90
+ ##
91
+ # Get an attribute value but it that value is nil, return the default
92
+ # passed as the second argument.
93
+ #
94
+ # @example Get a default value.
95
+ # attrs.set('foo', nil)
96
+ # attrs.fetch('foo', 'bar')
97
+ # # => 'bar'
98
+ #
99
+ # @param key [#to_s] The key for the attribute you're getting.
100
+ #
101
+ # @param default [Any, nil] The default value to return if the value
102
+ # for the key is nil.
103
+ #
104
+ # @param pre_formatted [true, false] If the key is already normalized.
105
+ #
106
+ # @return [Any, nil]
107
+ def fetch(key, default = nil, pre_formatted: false)
108
+ value = get(key, pre_formatted: pre_formatted)
109
+ return default if value.nil?
110
+
111
+ value
112
+ end
113
+
114
+ ##
115
+ # Check if a key exists.
116
+ #
117
+ # @param key [#to_s] The key you want to check to see if it exists.
118
+ #
119
+ # @param pre_formatted [true, false] If the key is already normalized.
120
+ #
121
+ # @return [true, false]
122
+ def key?(key, pre_formatted: false)
123
+ nkey = normalize_key(key, pre_formatted)
124
+ @storage.key?(nkey)
125
+ end
126
+
127
+ ##
128
+ # @private
129
+ #
130
+ # Dump the storage backend into a hash.
131
+ #
132
+ # @return [Hash<String, Any>]
133
+ def to_h
134
+ @storage.dup
135
+ end
136
+
137
+ ##
138
+ # @private
139
+ #
140
+ # Attribute Magic
141
+ def method_missing(name, *args, &_block)
142
+ if name.to_s.end_with?('=')
143
+ set(name.to_s.gsub(/=$/, ''), *args)
144
+ else
145
+ get(name, *args)
146
+ end
147
+ end
148
+
149
+ ##
150
+ # @private
151
+ #
152
+ # Attribute Magic
153
+ def respond_to_missing?(_name, _include_private = false)
154
+ true
155
+ end
156
+
157
+ private
158
+
159
+ def normalize_key(key, pre_formatted)
160
+ return key if pre_formatted == true
161
+
162
+ key.to_s.camelize
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,225 @@
1
+ require 'active_model'
2
+
3
+ require_relative '../error'
4
+ require_relative '../primary_key'
5
+
6
+ require_relative 'dsl'
7
+ require_relative 'query'
8
+ require_relative 'persistence'
9
+ require_relative 'attributes'
10
+
11
+ module Mara
12
+ module Model
13
+ class PrimaryKeyError < Mara::Error; end
14
+
15
+ ##
16
+ # The base class for a Mara Model
17
+ #
18
+ # @example A basic Person class.
19
+ # class Person < Mara::Model::Base
20
+ # # Set the Partition Key & Sort Key names.
21
+ # primary_key 'PrimaryKey', 'RangeKey'
22
+ # end
23
+ #
24
+ # @example Set dynamic attribute values.
25
+ # person = Person.build
26
+ # person[:first_name] = 'Maddie'
27
+ # person.last_name = 'Schipper'
28
+ #
29
+ # @author Maddie Schipper
30
+ # @since 1.0.0
31
+ class Base
32
+ # @!parse extend Mara::Model::Dsl::ClassMethods
33
+ # @!parse extend Mara::Model::Query::ClassMethods
34
+ # @!parse extend Mara::Model::Persistence::ClassMethods
35
+
36
+ include ActiveModel::Validations
37
+ include Mara::Model::Dsl
38
+ include Mara::Model::Query
39
+ include Mara::Model::Persistence
40
+
41
+ ##
42
+ # @private
43
+ #
44
+ # The attributes container.
45
+ #
46
+ # @return [ Mara::Model::Attributes]
47
+ attr_reader :attributes
48
+
49
+ class << self
50
+ ##
51
+ # Create a new instance of the model.
52
+ #
53
+ # @example Building a new model.
54
+ # person = Person.build(
55
+ # partition_key: SecureRandom.uuid,
56
+ # first_name: 'Maddie',
57
+ # last_name: 'Schipper'
58
+ # )
59
+ #
60
+ # @param attributes [Hash] The default attributes that can be assigned.
61
+ #
62
+ # If a +partition_key+ is specified it will be used to set the model's
63
+ # +partion_key+
64
+ #
65
+ # If a +sort_key+ is specified it will be used to set the model's
66
+ # +sort_key+
67
+ #
68
+ # @return [ Mara::Model::Base]
69
+ def build(attributes = {})
70
+ partition_key = attributes.delete(:partition_key)
71
+ sort_key = attributes.delete(:sort_key)
72
+
73
+ attrs = Mara::Model::Attributes.new(attributes)
74
+
75
+ new(
76
+ partition_key: partition_key,
77
+ sort_key: sort_key,
78
+ attributes: attrs,
79
+ persisted: false
80
+ )
81
+ end
82
+
83
+ ##
84
+ # @private
85
+ def construct(record_hash)
86
+ partition_key = record_hash.delete(self.partition_key)
87
+ sort_key = record_hash.delete(self.sort_key)
88
+
89
+ attrs = Mara::Model::Attributes.new(record_hash)
90
+
91
+ new(
92
+ partition_key: partition_key,
93
+ sort_key: sort_key,
94
+ attributes: attrs,
95
+ persisted: true
96
+ )
97
+ end
98
+ end
99
+
100
+ ##
101
+ # @private
102
+ #
103
+ # Create a new instance of the model.
104
+ #
105
+ # @param partition_key [Any] The partition_key for the model.
106
+ # @param sort_key [Any] The sort key for the model.
107
+ # @param attributes [ Mara::Model::Attributes] The already existing
108
+ # attributes for the model.
109
+ def initialize(partition_key:, sort_key:, attributes:, persisted:)
110
+ if self.class.partition_key.blank?
111
+ raise Mara::Model::PrimaryKeyError,
112
+ "Can't create instance of #{self.class.name} without a `partition_key` set."
113
+ end
114
+
115
+ unless attributes.is_a?( Mara::Model::Attributes)
116
+ raise ArgumentError, 'attributes is not Mara::Model::Attributes'
117
+ end
118
+
119
+ @persisted = persisted == true
120
+ @attributes = attributes
121
+
122
+ self.partition_key = partition_key
123
+ self.sort_key = sort_key if sort_key
124
+ end
125
+
126
+ ##
127
+ # The partition_key key value for the object.
128
+ #
129
+ # @return [Any, nil]
130
+ attr_accessor :partition_key
131
+
132
+ ##
133
+ # Set an attribute key value pair.
134
+ #
135
+ # @param key [#to_s] The key for the attribute.
136
+ # @param value [Any, nil] The value of the attribute.
137
+ #
138
+ # @return [void]
139
+ def []=(key, value)
140
+ attributes.set(key, value)
141
+ end
142
+
143
+ ##
144
+ # Get an attribute's current value.
145
+ #
146
+ # @param key [#to_s] The key for the attribute.
147
+ #
148
+ # @return [Any, nil]
149
+ def [](key)
150
+ attributes.get(key)
151
+ end
152
+
153
+ def model_primary_key
154
+ Mara::PrimaryKey.new(model: self)
155
+ end
156
+
157
+ def model_identifier
158
+ Mara::PrimaryKey.generate(model_primary_key)
159
+ end
160
+
161
+ ##
162
+ # Fetch the current sort key value.
163
+ #
164
+ # @return [Any, nil]
165
+ def sort_key
166
+ if self.class.sort_key.blank?
167
+ raise Mara::Model::PrimaryKeyError,
168
+ "Model #{self.class.name} does not specify a sort_key."
169
+ end
170
+
171
+ @sort_key
172
+ end
173
+
174
+ ##
175
+ # Checks if the model should have a sort key and returns the value if
176
+ # it does.
177
+ #
178
+ # @return [Any, nil]
179
+ def conditional_sort_key
180
+ return nil if self.class.sort_key.blank?
181
+
182
+ sort_key
183
+ end
184
+
185
+ ##
186
+ # Set a sort key value.
187
+ #
188
+ # @param sort_key [String] The sort key value.
189
+ #
190
+ # @return [void]
191
+ def sort_key=(sort_key)
192
+ if self.class.sort_key.blank?
193
+ raise Mara::Model::PrimaryKeyError,
194
+ "Model #{self.class.name} does not specify a sort_key."
195
+ end
196
+
197
+ @sort_key = sort_key
198
+ end
199
+
200
+ ##
201
+ # @private
202
+ #
203
+ # Attribute Magic
204
+ def method_missing(name, *args, &block)
205
+ if attributes.respond_to?(name)
206
+ attributes.send(name, *args, &block)
207
+ else
208
+ super
209
+ end
210
+ end
211
+
212
+ ##
213
+ # @private
214
+ #
215
+ # Attribute Magic
216
+ def respond_to_missing?(name, include_private = false)
217
+ if attributes.respond_to?(name)
218
+ true
219
+ else
220
+ super
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end