aws-record 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 84fe186e9a5995f6ca277aa4776a37dfaef73928
4
+ data.tar.gz: 267cf8b8556f23108e5cc3e660177af43ebe0c26
5
+ SHA512:
6
+ metadata.gz: e69390465c8fe8f2eb45eaffeeef9d90a8ded9159a440170e3d5a3258e0679a230f4c6cc50930e2b6cd377f4a132a84389607569c1811238cd858f6a5fb9e830
7
+ data.tar.gz: b2ff7b50b0c2a652f21901d592ed6476fe97bd146cc95c89bdb0fccd34a2ffec3fe4233ea1e764fe72ff53bbf9af5d53e58a7cd4ac10e4717f2c45d9fe778778
@@ -0,0 +1,24 @@
1
+ require 'aws-sdk-resources'
2
+
3
+ module Aws
4
+ autoload :Record, 'aws-record/record'
5
+
6
+ module Record
7
+ autoload :Attribute, 'aws-record/record/attribute'
8
+ autoload :Attributes, 'aws-record/record/attributes'
9
+ autoload :Errors, 'aws-record/record/errors'
10
+ autoload :ItemOperations, 'aws-record/record/item_operations'
11
+ autoload :TableMigration, 'aws-record/record/table_migration'
12
+ autoload :VERSION, 'aws-record/record/version'
13
+
14
+ module Attributes
15
+ autoload :StringMarshaler, 'aws-record/record/attributes/string_marshaler'
16
+ autoload :BooleanMarshaler, 'aws-record/record/attributes/boolean_marshaler'
17
+ autoload :IntegerMarshaler, 'aws-record/record/attributes/integer_marshaler'
18
+ autoload :FloatMarshaler, 'aws-record/record/attributes/float_marshaler'
19
+ autoload :DateMarshaler, 'aws-record/record/attributes/date_marshaler'
20
+ autoload :DateTimeMarshaler, 'aws-record/record/attributes/date_time_marshaler'
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ module Aws
2
+ module Record
3
+ def self.included(sub_class)
4
+ sub_class.extend(RecordClassMethods)
5
+ sub_class.include(Attributes)
6
+ sub_class.include(ItemOperations)
7
+ end
8
+
9
+ private
10
+ def dynamodb_client
11
+ self.class.dynamodb_client
12
+ end
13
+
14
+ module RecordClassMethods
15
+ def table_name
16
+ if @table_name
17
+ @table_name
18
+ else
19
+ @table_name = self.name
20
+ end
21
+ end
22
+
23
+ def set_table_name(name)
24
+ @table_name = name
25
+ end
26
+
27
+ def provisioned_throughput
28
+ begin
29
+ resp = dynamodb_client.describe_table(table_name: @table_name)
30
+ throughput = resp.table.provisioned_throughput
31
+ return {
32
+ read_capacity_units: throughput.read_capacity_units,
33
+ write_capacity_units: throughput.write_capacity_units
34
+ }
35
+ rescue DynamoDB::Errors::ResourceNotFoundException
36
+ raise Record::Errors::TableDoesNotExist
37
+ end
38
+ end
39
+
40
+ def table_exists?
41
+ begin
42
+ resp = dynamodb_client.describe_table(table_name: @table_name)
43
+ if resp.table.table_status == "ACTIVE"
44
+ true
45
+ else
46
+ false
47
+ end
48
+ rescue DynamoDB::Errors::ResourceNotFoundException
49
+ false
50
+ end
51
+ end
52
+
53
+ def configure_client(opts = {})
54
+ provided_client = opts.delete(:client)
55
+ opts[:user_agent_suffix] = user_agent(opts.delete(:user_agent_suffix))
56
+ client = provided_client || Aws::DynamoDB::Client.new(opts)
57
+ @dynamodb_client = client
58
+ end
59
+
60
+ def dynamodb_client
61
+ @dynamodb_client ||= configure_client
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,46 @@
1
+ module Aws
2
+ module Record
3
+ class Attribute
4
+
5
+ attr_reader :name, :database_name, :dynamodb_type
6
+
7
+ def initialize(name, options = {})
8
+ @name = name
9
+ @database_name = options[:database_attribute_name] || name.to_s
10
+ @dynamodb_type = options[:dynamodb_type]
11
+ @marshaler = options[:marshaler] || DefaultMarshaler
12
+ @validators = options[:validators] || []
13
+ end
14
+
15
+ def type_cast(raw_value)
16
+ @marshaler.type_cast(raw_value)
17
+ end
18
+
19
+ def serialize(raw_value)
20
+ @marshaler.serialize(raw_value)
21
+ end
22
+
23
+ def valid?(raw_value)
24
+ value = type_cast(raw_value)
25
+ @validators.all? do |validator|
26
+ validator.validate(value)
27
+ end
28
+ end
29
+
30
+ def extract(dynamodb_item)
31
+ dynamodb_item[database_name]
32
+ end
33
+
34
+ end
35
+
36
+ module DefaultMarshaler
37
+ def self.type_cast(raw_value, options = {})
38
+ raw_value
39
+ end
40
+
41
+ def self.serialize(raw_value, options = {})
42
+ raw_value
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,242 @@
1
+ module Aws
2
+ module Record
3
+ module Attributes
4
+
5
+ def self.included(sub_class)
6
+ sub_class.extend(ClassMethods)
7
+ sub_class.instance_variable_set("@keys", {})
8
+ sub_class.instance_variable_set("@attributes", {})
9
+ sub_class.instance_variable_set("@storage_attributes", {})
10
+ end
11
+
12
+ def initialize
13
+ @data = {}
14
+ end
15
+
16
+ # Returns a hash representation of the attribute data.
17
+ #
18
+ # @return [Hash] Map of attribute names to raw values.
19
+ def to_h
20
+ @data.dup
21
+ end
22
+
23
+ module ClassMethods
24
+
25
+ # Define an attribute for your model, providing your own attribute type.
26
+ #
27
+ # @param [Symbol] name Name of this attribute. It should be a name that
28
+ # is safe to use as a method.
29
+ # @param [Marshaler] marshaler The marshaler for this attribute. So long
30
+ # as you provide a marshaler which implements `#type_cast` and
31
+ # `#serialize` that consume raw values as expected, you can bring your
32
+ # own marshaler type. Convenience methods will provide this for you.
33
+ # @param [Hash] options
34
+ # @option options [Array] :validators An array of validator classes that
35
+ # will be run when an attribute is checked for validity.
36
+ # @option options [String] :database_attribute_name Optional attribute
37
+ # used to specify a different name for database persistence than the
38
+ # `name` parameter. Must be unique (you can't have overlap between
39
+ # database attribute names and the names of other attributes).
40
+ # @option options [String] :dynamodb_type Generally used for keys and
41
+ # index attributes, one of "S", "N", "B", "BOOL", "SS", "NS", "BS",
42
+ # "M", "L". Optional if this attribute will never be used for a key or
43
+ # secondary index, but most convenience methods for setting attributes
44
+ # will provide this.
45
+ # @option options [Boolean] :hash_key Set to true if this attribute is
46
+ # the hash key for the table.
47
+ # @option options [Boolean] :range_key Set to true if this attribute is
48
+ # the range key for the table.
49
+ def attr(name, marshaler, opts = {})
50
+ validate_attr_name(name)
51
+
52
+ opts = opts.merge(marshaler: marshaler)
53
+ attribute = Attribute.new(name, opts)
54
+
55
+ storage_name = attribute.database_name
56
+
57
+ check_for_naming_collisions(name, storage_name)
58
+ check_if_reserved(name)
59
+
60
+ @attributes[name] = attribute
61
+ @storage_attributes[storage_name] = name
62
+
63
+ define_attr_methods(name, attribute)
64
+ key_attributes(name, opts)
65
+ end
66
+
67
+ # Define a string-type attribute for your model.
68
+ #
69
+ # @param [Symbol] name Name of this attribute. It should be a name that
70
+ # is safe to use as a method.
71
+ # @param [Hash] options
72
+ # @option options [Boolean] :hash_key Set to true if this attribute is
73
+ # the hash key for the table.
74
+ # @option options [Boolean] :range_key Set to true if this attribute is
75
+ # the range key for the table.
76
+ def string_attr(id, opts = {})
77
+ opts[:dynamodb_type] = "S"
78
+ attr(id, Attributes::StringMarshaler, opts)
79
+ end
80
+
81
+ # Define a boolean-type attribute for your model.
82
+ #
83
+ # @param [Symbol] name Name of this attribute. It should be a name that
84
+ # is safe to use as a method.
85
+ # @param [Hash] options
86
+ # @option options [Boolean] :hash_key Set to true if this attribute is
87
+ # the hash key for the table.
88
+ # @option options [Boolean] :range_key Set to true if this attribute is
89
+ # the range key for the table.
90
+ def boolean_attr(id, opts = {})
91
+ opts[:dynamodb_type] = "BOOL"
92
+ attr(id, Attributes::BooleanMarshaler, opts)
93
+ end
94
+
95
+ # Define a integer-type attribute for your model.
96
+ #
97
+ # @param [Symbol] name Name of this attribute. It should be a name that
98
+ # is safe to use as a method.
99
+ # @param [Hash] options
100
+ # @option options [Boolean] :hash_key Set to true if this attribute is
101
+ # the hash key for the table.
102
+ # @option options [Boolean] :range_key Set to true if this attribute is
103
+ # the range key for the table.
104
+ def integer_attr(id, opts = {})
105
+ opts[:dynamodb_type] = "N"
106
+ attr(id, Attributes::IntegerMarshaler, opts)
107
+ end
108
+
109
+ # Define a float-type attribute for your model.
110
+ #
111
+ # @param [Symbol] name Name of this attribute. It should be a name that
112
+ # is safe to use as a method.
113
+ # @param [Hash] options
114
+ # @option options [Boolean] :hash_key Set to true if this attribute is
115
+ # the hash key for the table.
116
+ # @option options [Boolean] :range_key Set to true if this attribute is
117
+ # the range key for the table.
118
+ def float_attr(id, opts = {})
119
+ opts[:dynamodb_type] = "N"
120
+ attr(id, Attributes::FloatMarshaler, opts)
121
+ end
122
+
123
+ # Define a date-type attribute for your model.
124
+ #
125
+ # @param [Symbol] name Name of this attribute. It should be a name that
126
+ # is safe to use as a method.
127
+ # @param [Hash] options
128
+ # @option options [Boolean] :hash_key Set to true if this attribute is
129
+ # the hash key for the table.
130
+ # @option options [Boolean] :range_key Set to true if this attribute is
131
+ # the range key for the table.
132
+ def date_attr(id, opts = {})
133
+ opts[:dynamodb_type] = "S"
134
+ attr(id, Attributes::DateMarshaler, opts)
135
+ end
136
+
137
+ # Define a datetime-type attribute for your model.
138
+ #
139
+ # @param [Symbol] name Name of this attribute. It should be a name that
140
+ # is safe to use as a method.
141
+ # @param [Hash] options
142
+ # @option options [Boolean] :hash_key Set to true if this attribute is
143
+ # the hash key for the table.
144
+ # @option options [Boolean] :range_key Set to true if this attribute is
145
+ # the range key for the table.
146
+ def datetime_attr(id, opts = {})
147
+ opts[:dynamodb_type] = "S"
148
+ attr(id, Attributes::DateTimeMarshaler, opts)
149
+ end
150
+
151
+ # @return [Hash] hash of symbolized attribute names to attribute objects
152
+ def attributes
153
+ @attributes
154
+ end
155
+
156
+ # @return [Aws::Record::Attribute,nil]
157
+ def hash_key
158
+ @attributes[@keys[:hash]]
159
+ end
160
+
161
+ # @return [Aws::Record::Attribute,nil]
162
+ def range_key
163
+ @attributes[@keys[:range]]
164
+ end
165
+
166
+ # @return [Hash] A mapping of the :hash and :range keys to the attribute
167
+ # name symbols associated with them.
168
+ def keys
169
+ @keys
170
+ end
171
+
172
+ private
173
+ def define_attr_methods(name, attribute)
174
+ define_method(name) do
175
+ raw = @data[name]
176
+ attribute.type_cast(raw)
177
+ end
178
+
179
+ define_method("#{name}=") do |value|
180
+ @data[name] = value
181
+ end
182
+ end
183
+
184
+ def key_attributes(id, opts)
185
+ if opts[:hash_key] == true && opts[:range_key] == true
186
+ raise ArgumentError.new(
187
+ "Cannot have the same attribute be a hash and range key."
188
+ )
189
+ elsif opts[:hash_key] == true
190
+ define_key(id, :hash)
191
+ elsif opts[:range_key] == true
192
+ define_key(id, :range)
193
+ end
194
+ end
195
+
196
+ def define_key(id, type)
197
+ @keys[type] = id
198
+ end
199
+
200
+ def validate_attr_name(name)
201
+ unless name.is_a?(Symbol)
202
+ raise ArgumentError.new("Must use symbolized :name attribute.")
203
+ end
204
+ if @attributes[name]
205
+ raise Errors::NameCollision.new(
206
+ "Cannot overwrite existing attribute #{name}"
207
+ )
208
+ end
209
+ end
210
+
211
+ def check_if_reserved(name)
212
+ if instance_methods.include?(name)
213
+ raise Errors::ReservedName.new(
214
+ "Cannot name an attribute #{name}, that would collide with an"\
215
+ " existing instance method."
216
+ )
217
+ end
218
+ end
219
+
220
+ def check_for_naming_collisions(name, storage_name)
221
+ if @attributes[storage_name]
222
+ raise Errors::NameCollision.new(
223
+ "Custom storage name #{storage_name} already exists as an"\
224
+ " attribute name in #{@attributes}"
225
+ )
226
+ elsif @storage_attributes[name]
227
+ raise Errors::NameCollision.new(
228
+ "Attribute name #{name} already exists as a custom storage"\
229
+ " name in #{@storage_attributes}"
230
+ )
231
+ elsif @storage_attributes[storage_name]
232
+ raise Errors::NameCollision.new(
233
+ "Custom storage name #{storage_name} already in use in"\
234
+ " #{@storage_attributes}"
235
+ )
236
+ end
237
+ end
238
+ end
239
+
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,41 @@
1
+ module Aws
2
+ module Record
3
+ module Attributes
4
+ module BooleanMarshaler
5
+
6
+ class << self
7
+
8
+ def type_cast(raw_value, options = {})
9
+ case raw_value
10
+ when nil
11
+ nil
12
+ when ''
13
+ nil
14
+ when false, 'false', '0', 0
15
+ false
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def serialize(raw_value, options = {})
22
+ boolean = type_cast(raw_value, options)
23
+ case boolean
24
+ when nil
25
+ nil
26
+ when false
27
+ false
28
+ when true
29
+ true
30
+ else
31
+ msg = "expected a boolean value or nil, got #{boolean.class}"
32
+ raise ArgumentError, msg
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ require 'date'
2
+
3
+ module Aws
4
+ module Record
5
+ module Attributes
6
+ module DateMarshaler
7
+
8
+ class << self
9
+
10
+ def type_cast(raw_value, options = {})
11
+ case raw_value
12
+ when nil
13
+ nil
14
+ when ''
15
+ nil
16
+ when Date
17
+ raw_value
18
+ when Integer
19
+ begin
20
+ Date.parse(Time.at(raw_value).to_s) # assumed timestamp
21
+ rescue
22
+ nil
23
+ end
24
+ else
25
+ begin
26
+ Date.parse(raw_value.to_s) # Time, DateTime or String
27
+ rescue
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ def serialize(raw_value, options = {})
34
+ date = type_cast(raw_value)
35
+ if date.nil?
36
+ nil
37
+ elsif date.is_a?(Date)
38
+ date.strftime('%Y-%m-%d')
39
+ else
40
+ raise ArgumentError, "expected a Date value or nil, got #{date.class}"
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ require 'date'
2
+
3
+ module Aws
4
+ module Record
5
+ module Attributes
6
+ module DateTimeMarshaler
7
+
8
+ class << self
9
+
10
+ def type_cast(raw_value, options = {})
11
+ case raw_value
12
+ when nil
13
+ nil
14
+ when ''
15
+ nil
16
+ when DateTime
17
+ raw_value
18
+ when Integer
19
+ begin
20
+ DateTime.parse(Time.at(raw_value).to_s) # timestamp
21
+ rescue
22
+ nil
23
+ end
24
+ else
25
+ begin
26
+ DateTime.parse(raw_value.to_s) # Time, Date or String
27
+ rescue
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ def serialize(raw_value, options = {})
34
+ datetime = type_cast(raw_value)
35
+ if datetime.nil?
36
+ nil
37
+ elsif datetime.is_a?(DateTime)
38
+ datetime.strftime('%Y-%m-%dT%H:%M:%S%Z')
39
+ else
40
+ msg = "expected a DateTime value or nil, got #{datetime.class}"
41
+ raise ArgumentError, msg
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ module Aws
2
+ module Record
3
+ module Attributes
4
+ module FloatMarshaler
5
+
6
+ class << self
7
+
8
+ def type_cast(raw_value, options = {})
9
+ case raw_value
10
+ when nil
11
+ nil
12
+ when ''
13
+ nil
14
+ when Float
15
+ raw_value
16
+ else
17
+ raw_value.respond_to?(:to_f) ?
18
+ raw_value.to_f :
19
+ raw_value.to_s.to_f
20
+ end
21
+ end
22
+
23
+ def serialize(raw_value, options = {})
24
+ float = type_cast(raw_value, options = {})
25
+ if float.nil?
26
+ nil
27
+ elsif float.is_a?(Float)
28
+ float
29
+ else
30
+ msg = "expected a Float value or nil, got #{value.class}"
31
+ raise ArgumentError, msg
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ module Aws
2
+ module Record
3
+ module Attributes
4
+ module IntegerMarshaler
5
+
6
+ class << self
7
+
8
+ def type_cast(raw_value, options = {})
9
+ case raw_value
10
+ when nil
11
+ nil
12
+ when ''
13
+ nil
14
+ when Integer
15
+ raw_value
16
+ else
17
+ raw_value.respond_to?(:to_i) ?
18
+ raw_value.to_i :
19
+ raw_value.to_s.to_i
20
+ end
21
+ end
22
+
23
+ def serialize(raw_value, options = {})
24
+ integer = type_cast(raw_value, options = {})
25
+ if integer.nil?
26
+ nil
27
+ elsif integer.is_a?(Integer)
28
+ integer
29
+ else
30
+ msg = "expected an Integer value or nil, got #{value.class}"
31
+ raise ArgumentError, msg
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ module Aws
2
+ module Record
3
+ module Attributes
4
+ module StringMarshaler
5
+
6
+ class << self
7
+
8
+ def type_cast(raw_value, options = {})
9
+ case raw_value
10
+ when nil
11
+ if options[:nil_as_empty_string]
12
+ ''
13
+ else
14
+ nil
15
+ end
16
+ when String
17
+ if raw_value.empty? && !options[:nil_as_empty_string]
18
+ nil
19
+ else
20
+ raw_value
21
+ end
22
+ else
23
+ raw_value.to_s
24
+ end
25
+ end
26
+
27
+ def serialize(raw_value, options = {})
28
+ value = type_cast(raw_value)
29
+ if value.is_a?(String)
30
+ if value.empty?
31
+ nil
32
+ else
33
+ value
34
+ end
35
+ elsif value.nil?
36
+ nil
37
+ else
38
+ msg = "expected a String value or nil, got #{value.class}"
39
+ raise ArgumentError, msg
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ module Aws
2
+ module Record
3
+ module Errors
4
+
5
+ class KeyMissing < RuntimeError; end
6
+ class NameCollision < RuntimeError; end
7
+ class ReservedName < RuntimeError; end
8
+ class InvalidModel < RuntimeError; end
9
+ class TableDoesNotExist < RuntimeError; end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,107 @@
1
+ module Aws
2
+ module Record
3
+ module ItemOperations
4
+
5
+ def self.included(sub_class)
6
+ sub_class.extend(ItemOperationsClassMethods)
7
+ end
8
+
9
+ def save
10
+ dynamodb_client.put_item(
11
+ table_name: self.class.table_name,
12
+ item: build_item_for_save
13
+ )
14
+ end
15
+
16
+ def delete!
17
+ dynamodb_client.delete_item(
18
+ table_name: self.class.table_name,
19
+ key: key_values
20
+ )
21
+ true
22
+ end
23
+
24
+ private
25
+ def build_item_for_save
26
+ validate_key_values
27
+ attributes = self.class.attributes
28
+ @data.inject({}) do |acc, name_value_pair|
29
+ attr_name, raw_value = name_value_pair
30
+ db_name = attributes[attr_name].database_name
31
+ acc[db_name] = attributes[attr_name].serialize(raw_value)
32
+ acc
33
+ end
34
+ end
35
+
36
+ def key_values
37
+ validate_key_values
38
+ attributes = self.class.attributes
39
+ self.class.keys.inject({}) do |acc, (_, attr_name)|
40
+ db_name = attributes[attr_name].database_name
41
+ acc[db_name] = attributes[attr_name].serialize(@data[attr_name])
42
+ acc
43
+ end
44
+ end
45
+
46
+ def validate_key_values
47
+ missing = missing_key_values
48
+ unless missing.empty?
49
+ raise Errors::KeyMissing.new(
50
+ "Missing required keys: #{missing.join(', ')}"
51
+ )
52
+ end
53
+ end
54
+
55
+ def missing_key_values
56
+ self.class.keys.inject([]) do |acc, key|
57
+ acc << key.last if @data[key.last].nil?
58
+ acc
59
+ end
60
+ end
61
+
62
+ module ItemOperationsClassMethods
63
+ def find(opts)
64
+ key = {}
65
+ @keys.each_value do |attr_sym|
66
+ unless opts[attr_sym]
67
+ raise Errors::KeyMissing.new(
68
+ "Missing required key #{attr_sym} in #{opts}"
69
+ )
70
+ end
71
+ attr_name = attr_sym.to_s
72
+ key[attr_name] = attributes[attr_sym].serialize(opts[attr_sym])
73
+ end
74
+ request_opts = {
75
+ table_name: table_name,
76
+ key: key
77
+ }
78
+ resp = dynamodb_client.get_item(request_opts)
79
+ if resp.item.nil?
80
+ nil
81
+ else
82
+ build_item_from_resp(resp)
83
+ end
84
+ end
85
+
86
+ private
87
+ def build_item_from_resp(resp)
88
+ record = new
89
+ data = record.instance_variable_get("@data")
90
+ attributes.each do |name, attr|
91
+ data[name] = attr.extract(resp.item)
92
+ end
93
+ record
94
+ end
95
+
96
+ def user_agent(custom)
97
+ if custom
98
+ custom
99
+ else
100
+ " aws-record/#{VERSION}"
101
+ end
102
+ end
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,89 @@
1
+ module Aws
2
+ module Record
3
+ class TableMigration
4
+
5
+ attr_accessor :client
6
+
7
+ def initialize(model, client: nil)
8
+ assert_model_valid(model)
9
+ @model = model
10
+ @client = client || Aws::DynamoDB::Client.new
11
+ end
12
+
13
+ def create!(opts)
14
+ create_opts = opts.merge({
15
+ table_name: @model.table_name,
16
+ attribute_definitions: attribute_definitions,
17
+ key_schema: key_schema
18
+ })
19
+ @client.create_table(create_opts)
20
+ end
21
+
22
+ def update!(opts)
23
+ begin
24
+ update_opts = opts.merge({
25
+ table_name: @model.table_name
26
+ })
27
+ @client.update_table(update_opts)
28
+ rescue DynamoDB::Errors::ResourceNotFoundException => e
29
+ raise Errors::TableDoesNotExist.new(e)
30
+ end
31
+ end
32
+
33
+ def delete!
34
+ begin
35
+ @client.delete_table(table_name: @model.table_name)
36
+ rescue DynamoDB::Errors::ResourceNotFoundException => e
37
+ raise Errors::TableDoesNotExist.new(e)
38
+ end
39
+ end
40
+
41
+ def wait_until_available
42
+ @client.wait_until(:table_exists, table_name: @model.table_name)
43
+ end
44
+
45
+ private
46
+ def assert_model_valid(model)
47
+ assert_required_include(model)
48
+ assert_keys(model)
49
+ end
50
+
51
+ def assert_required_include(model)
52
+ unless model.include?(::Aws::Record)
53
+ raise Errors::InvalidModel.new("Table models must include Aws::Record")
54
+ end
55
+ end
56
+
57
+ def assert_keys(model)
58
+ if model.hash_key.nil?
59
+ raise Errors::InvalidModel.new("Table models must include a hash key")
60
+ end
61
+ end
62
+
63
+ def attribute_definitions
64
+ keys.map do |type, attr|
65
+ {
66
+ attribute_name: attr.database_name,
67
+ attribute_type: attr.dynamodb_type
68
+ }
69
+ end
70
+ end
71
+
72
+ def key_schema
73
+ keys.map do |type, attr|
74
+ {
75
+ attribute_name: attr.database_name,
76
+ key_type: type == :hash ? "HASH" : "RANGE"
77
+ }
78
+ end
79
+ end
80
+
81
+ def keys
82
+ @model.keys.inject({}) do |acc, (type, name)|
83
+ acc[type] = @model.attributes[name]
84
+ acc
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,5 @@
1
+ module Aws
2
+ module Record
3
+ VERSION = "1.0.0.pre.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-record
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.1
5
+ platform: ruby
6
+ authors:
7
+ - Amazon Web Services
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-resources
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: Provides an object mapping abstraction for Amazon DynamoDB.
28
+ email:
29
+ - alexwood@amazon.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/aws-record.rb
35
+ - lib/aws-record/record.rb
36
+ - lib/aws-record/record/attribute.rb
37
+ - lib/aws-record/record/attributes.rb
38
+ - lib/aws-record/record/attributes/boolean_marshaler.rb
39
+ - lib/aws-record/record/attributes/date_marshaler.rb
40
+ - lib/aws-record/record/attributes/date_time_marshaler.rb
41
+ - lib/aws-record/record/attributes/float_marshaler.rb
42
+ - lib/aws-record/record/attributes/integer_marshaler.rb
43
+ - lib/aws-record/record/attributes/string_marshaler.rb
44
+ - lib/aws-record/record/errors.rb
45
+ - lib/aws-record/record/item_operations.rb
46
+ - lib/aws-record/record/table_migration.rb
47
+ - lib/aws-record/record/version.rb
48
+ homepage: http://github.com/aws/aws-sdk-ruby-record
49
+ licenses:
50
+ - Apache 2.0
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">"
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.1
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.2.2
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: AWS Record library for Amazon DynamoDB
72
+ test_files: []
73
+ has_rdoc: