dynamoid 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.document +5 -0
- data/.gitignore +67 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +20 -2
- data/Gemfile +3 -1
- data/{README.markdown → README.md} +30 -6
- data/Rakefile +17 -20
- data/dynamoid.gemspec +45 -68
- data/lib/dynamoid.rb +20 -18
- data/lib/dynamoid/adapter.rb +11 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +313 -48
- data/lib/dynamoid/associations/association.rb +11 -0
- data/lib/dynamoid/associations/many_association.rb +4 -2
- data/lib/dynamoid/associations/single_association.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +12 -10
- data/lib/dynamoid/document.rb +5 -3
- data/lib/dynamoid/errors.rb +17 -3
- data/lib/dynamoid/fields.rb +19 -5
- data/lib/dynamoid/finders.rb +65 -0
- data/lib/dynamoid/indexes.rb +273 -0
- data/lib/dynamoid/persistence.rb +14 -3
- data/lib/dynamoid/validations.rb +27 -0
- data/lib/dynamoid/version.rb +3 -0
- metadata +41 -15
data/lib/dynamoid/adapter.rb
CHANGED
@@ -17,7 +17,7 @@ module Dynamoid
|
|
17
17
|
|
18
18
|
def tables
|
19
19
|
if !@tables_.value
|
20
|
-
@tables_.swap{|value, args| benchmark('Cache Tables') {list_tables}}
|
20
|
+
@tables_.swap{|value, args| benchmark('Cache Tables') { list_tables } }
|
21
21
|
end
|
22
22
|
@tables_.value
|
23
23
|
end
|
@@ -131,7 +131,16 @@ module Dynamoid
|
|
131
131
|
end
|
132
132
|
end
|
133
133
|
|
134
|
-
|
134
|
+
# @since 0.2.0
|
135
|
+
def delete_table(table_name, *args)
|
136
|
+
if tables.include?(table_name)
|
137
|
+
benchmark('Delete Table') { adapter.delete_table(table_name, *args) }
|
138
|
+
idx = tables.index(table_name)
|
139
|
+
tables.delete_at(idx)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
[:batch_get_item, :delete_item, :get_item, :list_tables, :put_item, :truncate].each do |m|
|
135
144
|
# Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
|
136
145
|
#
|
137
146
|
# @since 0.2.0
|
@@ -3,6 +3,33 @@ module Dynamoid
|
|
3
3
|
|
4
4
|
# The AwsSdkV2 adapter provides support for the aws-sdk version 2 for ruby.
|
5
5
|
class AwsSdkV2
|
6
|
+
EQ = "EQ".freeze
|
7
|
+
RANGE_MAP = {
|
8
|
+
range_greater_than: 'GT',
|
9
|
+
range_less_than: 'LT',
|
10
|
+
range_gte: 'GE',
|
11
|
+
range_lte: 'LE',
|
12
|
+
range_begins_with: 'BEGINS_WITH',
|
13
|
+
range_between: 'BETWEEN',
|
14
|
+
range_eq: 'EQ'
|
15
|
+
}
|
16
|
+
HASH_KEY = "HASH".freeze
|
17
|
+
RANGE_KEY = "RANGE".freeze
|
18
|
+
STRING_TYPE = "S".freeze
|
19
|
+
NUM_TYPE = "N".freeze
|
20
|
+
BINARY_TYPE = "B".freeze
|
21
|
+
TABLE_STATUSES = {
|
22
|
+
creating: "CREATING",
|
23
|
+
updating: "UPDATING",
|
24
|
+
deleting: "DELETING",
|
25
|
+
active: "ACTIVE"
|
26
|
+
}.freeze
|
27
|
+
PARSE_TABLE_STATUS = ->(resp, lookup = :table) {
|
28
|
+
# lookup is table for describe_table API
|
29
|
+
# lookup is table_description for create_table API
|
30
|
+
# because Amazon, damnit.
|
31
|
+
resp.send(lookup).table_status
|
32
|
+
}
|
6
33
|
attr_reader :table_cache
|
7
34
|
|
8
35
|
# Establish the connection to DynamoDB.
|
@@ -100,41 +127,91 @@ module Dynamoid
|
|
100
127
|
# @param [String] table_name the name of the table to create
|
101
128
|
# @param [Symbol] key the table's primary key (defaults to :id)
|
102
129
|
# @param [Hash] options provide a range key here if the table has a composite key
|
103
|
-
#
|
130
|
+
# @option options [Array<Dynamoid::Indexes::Index>] local_secondary_indexes
|
131
|
+
# @option options [Array<Dynamoid::Indexes::Index>] global_secondary_indexes
|
132
|
+
# @option options [Symbol] hash_key_type The type of the hash key
|
133
|
+
# @option options [Boolean] sync Wait for table status to be ACTIVE?
|
104
134
|
# @since 1.0.0
|
105
135
|
def create_table(table_name, key = :id, options = {})
|
106
136
|
Dynamoid.logger.info "Creating #{table_name} table. This could take a while."
|
107
137
|
read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
|
108
138
|
write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
|
109
|
-
range_key = options[:range_key]
|
110
139
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
attribute_definitions
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
140
|
+
secondary_indexes = options.slice(
|
141
|
+
:local_secondary_indexes,
|
142
|
+
:global_secondary_indexes
|
143
|
+
)
|
144
|
+
ls_indexes = options[:local_secondary_indexes]
|
145
|
+
gs_indexes = options[:global_secondary_indexes]
|
146
|
+
|
147
|
+
key_schema = {
|
148
|
+
:hash_key_schema => { key => (options[:hash_key_type] || :string) },
|
149
|
+
:range_key_schema => options[:range_key]
|
150
|
+
}
|
151
|
+
attribute_definitions = build_all_attribute_definitions(
|
152
|
+
key_schema,
|
153
|
+
secondary_indexes
|
154
|
+
)
|
155
|
+
key_schema = aws_key_schema(
|
156
|
+
key_schema[:hash_key_schema],
|
157
|
+
key_schema[:range_key_schema]
|
158
|
+
)
|
159
|
+
|
160
|
+
client_opts = {
|
161
|
+
table_name: table_name,
|
127
162
|
provisioned_throughput: {
|
128
163
|
read_capacity_units: read_capacity,
|
129
164
|
write_capacity_units: write_capacity
|
130
165
|
},
|
131
166
|
key_schema: key_schema,
|
132
167
|
attribute_definitions: attribute_definitions
|
133
|
-
|
168
|
+
}
|
169
|
+
|
170
|
+
if ls_indexes.present?
|
171
|
+
client_opts[:local_secondary_indexes] = ls_indexes.map do |index|
|
172
|
+
index_to_aws_hash(index)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if gs_indexes.present?
|
177
|
+
client_opts[:global_secondary_indexes] = gs_indexes.map do |index|
|
178
|
+
index_to_aws_hash(index)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
resp = client.create_table(client_opts)
|
182
|
+
options[:sync] = true if !options.has_key?(:sync) && ls_indexes.present? || gs_indexes.present?
|
183
|
+
until_past_table_status(table_name) if options[:sync] &&
|
184
|
+
(status = PARSE_TABLE_STATUS.call(resp, :table_description)) &&
|
185
|
+
status != TABLE_STATUSES[:creating]
|
186
|
+
# Response to original create_table, which, if options[:sync]
|
187
|
+
# may have an outdated table_description.table_status of "CREATING"
|
188
|
+
resp
|
134
189
|
rescue Aws::DynamoDB::Errors::ResourceInUseException => e
|
135
190
|
Dynamoid.logger.error "Table #{table_name} cannot be created as it already exists"
|
136
191
|
end
|
137
192
|
|
193
|
+
# Create a table on DynamoDB *synchronously*.
|
194
|
+
# This usually takes a long time to complete.
|
195
|
+
# CreateTable is normally an asynchronous operation.
|
196
|
+
# You can optionally define secondary indexes on the new table,
|
197
|
+
# as part of the CreateTable operation.
|
198
|
+
# If you want to create multiple tables with secondary indexes on them,
|
199
|
+
# you must create the tables sequentially.
|
200
|
+
# Only one table with secondary indexes can be
|
201
|
+
# in the CREATING state at any given time.
|
202
|
+
# See: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#create_table-instance_method
|
203
|
+
#
|
204
|
+
# @param [String] table_name the name of the table to create
|
205
|
+
# @param [Symbol] key the table's primary key (defaults to :id)
|
206
|
+
# @param [Hash] options provide a range key here if the table has a composite key
|
207
|
+
# @option options [Array<Dynamoid::Indexes::Index>] local_secondary_indexes
|
208
|
+
# @option options [Array<Dynamoid::Indexes::Index>] global_secondary_indexes
|
209
|
+
# @option options [Symbol] hash_key_type The type of the hash key
|
210
|
+
# @since 1.2.0
|
211
|
+
def create_table_synchronously(table_name, key = :id, options = {})
|
212
|
+
create_table(table_name, key, options.merge(sync: true))
|
213
|
+
end
|
214
|
+
|
138
215
|
# Removes an item from DynamoDB.
|
139
216
|
#
|
140
217
|
# @param [String] table_name the name of the table
|
@@ -160,11 +237,22 @@ module Dynamoid
|
|
160
237
|
# Deletes an entire table from DynamoDB.
|
161
238
|
#
|
162
239
|
# @param [String] table_name the name of the table to destroy
|
240
|
+
# @option options [Boolean] sync Wait for table status check to raise ResourceNotFoundException
|
163
241
|
#
|
164
242
|
# @since 1.0.0
|
165
|
-
def delete_table(table_name)
|
166
|
-
client.delete_table(table_name: table_name)
|
167
|
-
|
243
|
+
def delete_table(table_name, options = {})
|
244
|
+
resp = client.delete_table(table_name: table_name)
|
245
|
+
until_past_table_status(table_name, :deleting) if options[:sync] &&
|
246
|
+
(status = PARSE_TABLE_STATUS.call(resp, :table_description)) &&
|
247
|
+
status != TABLE_STATUSES[:deleting]
|
248
|
+
table_cache.delete(table_name)
|
249
|
+
rescue Aws::DynamoDB::Errors::ResourceInUseException => e
|
250
|
+
Dynamoid.logger.error "Table #{table_name} cannot be deleted as it is in use"
|
251
|
+
raise e
|
252
|
+
end
|
253
|
+
|
254
|
+
def delete_table_synchronously(table_name, options = {})
|
255
|
+
delete_table(table_name, options.merge(sync: true))
|
168
256
|
end
|
169
257
|
|
170
258
|
# @todo Add a DescribeTable method.
|
@@ -275,14 +363,21 @@ module Dynamoid
|
|
275
363
|
# @todo Provide support for various other options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#query-instance_method
|
276
364
|
def query(table_name, opts = {})
|
277
365
|
table = describe_table(table_name)
|
278
|
-
hk = table.hash_key.to_s
|
279
|
-
rng = table.range_key.to_s
|
280
|
-
q = opts.slice(
|
366
|
+
hk = (opts[:hash_key].present? ? opts[:hash_key] : table.hash_key).to_s
|
367
|
+
rng = (opts[:range_key].present? ? opts[:range_key] : table.range_key).to_s
|
368
|
+
q = opts.slice(
|
369
|
+
:consistent_read,
|
370
|
+
:scan_index_forward,
|
371
|
+
:limit,
|
372
|
+
:select,
|
373
|
+
:index_name
|
374
|
+
)
|
281
375
|
|
282
376
|
opts.delete(:consistent_read)
|
283
377
|
opts.delete(:scan_index_forward)
|
284
378
|
opts.delete(:limit)
|
285
379
|
opts.delete(:select)
|
380
|
+
opts.delete(:index_name)
|
286
381
|
|
287
382
|
opts.delete(:next_token).tap do |token|
|
288
383
|
break unless token
|
@@ -325,17 +420,6 @@ module Dynamoid
|
|
325
420
|
}
|
326
421
|
end
|
327
422
|
|
328
|
-
EQ = "EQ".freeze
|
329
|
-
|
330
|
-
RANGE_MAP = {
|
331
|
-
range_greater_than: 'GT',
|
332
|
-
range_less_than: 'LT',
|
333
|
-
range_gte: 'GE',
|
334
|
-
range_lte: 'LE',
|
335
|
-
range_begins_with: 'BEGINS_WITH',
|
336
|
-
range_between: 'BETWEEN'
|
337
|
-
}
|
338
|
-
|
339
423
|
# Scan the DynamoDB table. This is usually a very slow operation as it naively filters all data on
|
340
424
|
# the DynamoDB servers.
|
341
425
|
#
|
@@ -378,7 +462,6 @@ module Dynamoid
|
|
378
462
|
end
|
379
463
|
end
|
380
464
|
|
381
|
-
|
382
465
|
#
|
383
466
|
# Truncates all records in the given table
|
384
467
|
#
|
@@ -391,7 +474,8 @@ module Dynamoid
|
|
391
474
|
rk = table.range_key
|
392
475
|
|
393
476
|
scan(table_name, {}, {}).each do |attributes|
|
394
|
-
opts = {
|
477
|
+
opts = {}
|
478
|
+
opts[:range_key] = attributes[rk.to_sym] if rk
|
395
479
|
delete_item(table_name, attributes[hk], opts)
|
396
480
|
end
|
397
481
|
end
|
@@ -402,18 +486,55 @@ module Dynamoid
|
|
402
486
|
|
403
487
|
protected
|
404
488
|
|
405
|
-
|
406
|
-
|
407
|
-
|
489
|
+
def check_table_status?(counter, resp, expect_status)
|
490
|
+
status = PARSE_TABLE_STATUS.call(resp)
|
491
|
+
again = counter < Dynamoid::Config.sync_retry_max_times &&
|
492
|
+
status == TABLE_STATUSES[expect_status]
|
493
|
+
{again: again, status: status, counter: counter}
|
494
|
+
end
|
495
|
+
|
496
|
+
def until_past_table_status(table_name, status = :creating)
|
497
|
+
counter = 0
|
498
|
+
resp = nil
|
499
|
+
begin
|
500
|
+
check = {again: true}
|
501
|
+
while check[:again]
|
502
|
+
sleep Dynamoid::Config.sync_retry_wait_seconds
|
503
|
+
resp = client.describe_table({ table_name: table_name })
|
504
|
+
check = check_table_status?(counter, resp, status)
|
505
|
+
Dynamoid.logger.info "Checked table status for #{table_name} (check #{check.inspect})"
|
506
|
+
counter += 1
|
507
|
+
end
|
508
|
+
# If you issue a DescribeTable request immediately after a CreateTable
|
509
|
+
# request, DynamoDB might return a ResourceNotFoundException.
|
510
|
+
# This is because DescribeTable uses an eventually consistent query,
|
511
|
+
# and the metadata for your table might not be available at that moment.
|
512
|
+
# Wait for a few seconds, and then try the DescribeTable request again.
|
513
|
+
# See: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#describe_table-instance_method
|
514
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException => e
|
515
|
+
case status
|
516
|
+
when :creating then
|
517
|
+
if counter >= Dynamoid::Config.sync_retry_max_times
|
518
|
+
Dynamoid.logger.warn "Waiting on table metadata for #{table_name} (check #{counter})"
|
519
|
+
retry # start over at first line of begin, does not reset counter
|
520
|
+
else
|
521
|
+
Dynamoid.logger.error "Exhausted max retries (Dynamoid::Config.sync_retry_max_times) waiting on table metadata for #{table_name} (check #{counter})"
|
522
|
+
raise e
|
523
|
+
end
|
524
|
+
else
|
525
|
+
# When deleting a table, "not found" is the goal.
|
526
|
+
Dynamoid.logger.info "Checked table status for #{table_name}: Not Found (check #{check.inspect})"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
408
530
|
|
409
531
|
#Converts from symbol to the API string for the given data type
|
410
532
|
# E.g. :number -> 'N'
|
411
533
|
def api_type(type)
|
412
534
|
case(type)
|
413
|
-
when :string
|
414
|
-
when :number
|
415
|
-
when :
|
416
|
-
when :boolean then BOOLEAN_TYPE
|
535
|
+
when :string then STRING_TYPE
|
536
|
+
when :number then NUM_TYPE
|
537
|
+
when :binary then BINARY_TYPE
|
417
538
|
else raise "Unknown type: #{type}"
|
418
539
|
end
|
419
540
|
end
|
@@ -445,9 +566,6 @@ module Dynamoid
|
|
445
566
|
expected
|
446
567
|
end
|
447
568
|
|
448
|
-
HASH_KEY = "HASH".freeze
|
449
|
-
RANGE_KEY = "RANGE".freeze
|
450
|
-
|
451
569
|
#
|
452
570
|
# New, semi-arbitrary API to get data on the table
|
453
571
|
#
|
@@ -466,6 +584,153 @@ module Dynamoid
|
|
466
584
|
end
|
467
585
|
end
|
468
586
|
|
587
|
+
# Converts a Dynamoid::Indexes::Index to an AWS API-compatible hash.
|
588
|
+
# This resulting hash is of the form:
|
589
|
+
#
|
590
|
+
# {
|
591
|
+
# index_name: String
|
592
|
+
# keys: {
|
593
|
+
# hash_key: aws_key_schema (hash)
|
594
|
+
# range_key: aws_key_schema (hash)
|
595
|
+
# }
|
596
|
+
# projection: {
|
597
|
+
# projection_type: (ALL, KEYS_ONLY, INCLUDE) String
|
598
|
+
# non_key_attributes: (optional) Array
|
599
|
+
# }
|
600
|
+
# provisioned_throughput: {
|
601
|
+
# read_capacity_units: Integer
|
602
|
+
# write_capacity_units: Integer
|
603
|
+
# }
|
604
|
+
# }
|
605
|
+
#
|
606
|
+
# @param [Dynamoid::Indexes::Index] index the index.
|
607
|
+
# @return [Hash] hash representing an AWS Index definition.
|
608
|
+
def index_to_aws_hash(index)
|
609
|
+
key_schema = aws_key_schema(index.hash_key_schema, index.range_key_schema)
|
610
|
+
|
611
|
+
hash = {
|
612
|
+
:index_name => index.name,
|
613
|
+
:key_schema => key_schema,
|
614
|
+
:projection => {
|
615
|
+
:projection_type => index.projection_type.to_s.upcase
|
616
|
+
}
|
617
|
+
}
|
618
|
+
|
619
|
+
# If the projection type is include, specify the non key attributes
|
620
|
+
if index.projection_type == :include
|
621
|
+
hash[:projection][:non_key_attributes] = index.projected_attributes
|
622
|
+
end
|
623
|
+
|
624
|
+
# Only global secondary indexes have a separate throughput.
|
625
|
+
if index.type == :global_secondary
|
626
|
+
hash[:provisioned_throughput] = {
|
627
|
+
:read_capacity_units => index.read_capacity,
|
628
|
+
:write_capacity_units => index.write_capacity
|
629
|
+
}
|
630
|
+
end
|
631
|
+
hash
|
632
|
+
end
|
633
|
+
|
634
|
+
# Converts hash_key_schema and range_key_schema to aws_key_schema
|
635
|
+
# @param [Hash] hash_key_schema eg: {:id => :string}
|
636
|
+
# @param [Hash] range_key_schema eg: {:created_at => :number}
|
637
|
+
# @return [Array]
|
638
|
+
def aws_key_schema(hash_key_schema, range_key_schema)
|
639
|
+
schema = [{
|
640
|
+
attribute_name: hash_key_schema.keys.first.to_s,
|
641
|
+
key_type: HASH_KEY
|
642
|
+
}]
|
643
|
+
|
644
|
+
if range_key_schema.present?
|
645
|
+
schema << {
|
646
|
+
attribute_name: range_key_schema.keys.first.to_s,
|
647
|
+
key_type: RANGE_KEY
|
648
|
+
}
|
649
|
+
end
|
650
|
+
schema
|
651
|
+
end
|
652
|
+
|
653
|
+
# Builds aws attributes definitions based off of primary hash/range and
|
654
|
+
# secondary indexes
|
655
|
+
#
|
656
|
+
# @param key_data
|
657
|
+
# @option key_data [Hash] hash_key_schema - eg: {:id => :string}
|
658
|
+
# @option key_data [Hash] range_key_schema - eg: {:created_at => :number}
|
659
|
+
# @param [Hash] secondary_indexes
|
660
|
+
# @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :local_secondary_indexes
|
661
|
+
# @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :global_secondary_indexes
|
662
|
+
def build_all_attribute_definitions(key_schema, secondary_indexes = {})
|
663
|
+
ls_indexes = secondary_indexes[:local_secondary_indexes]
|
664
|
+
gs_indexes = secondary_indexes[:global_secondary_indexes]
|
665
|
+
|
666
|
+
attribute_definitions = []
|
667
|
+
|
668
|
+
attribute_definitions << build_attribute_definitions(
|
669
|
+
key_schema[:hash_key_schema],
|
670
|
+
key_schema[:range_key_schema]
|
671
|
+
)
|
672
|
+
|
673
|
+
if ls_indexes.present?
|
674
|
+
ls_indexes.map do |index|
|
675
|
+
attribute_definitions << build_attribute_definitions(
|
676
|
+
index.hash_key_schema,
|
677
|
+
index.range_key_schema
|
678
|
+
)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
if gs_indexes.present?
|
683
|
+
gs_indexes.map do |index|
|
684
|
+
attribute_definitions << build_attribute_definitions(
|
685
|
+
index.hash_key_schema,
|
686
|
+
index.range_key_schema
|
687
|
+
)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
attribute_definitions.flatten!
|
692
|
+
# uniq these definitions because range keys might be common between
|
693
|
+
# primary and secondary indexes
|
694
|
+
attribute_definitions.uniq!
|
695
|
+
attribute_definitions
|
696
|
+
end
|
697
|
+
|
698
|
+
|
699
|
+
# Builds an attribute definitions based on hash key and range key
|
700
|
+
# @params [Hash] hash_key_schema - eg: {:id => :string}
|
701
|
+
# @params [Hash] range_key_schema - eg: {:created_at => :datetime}
|
702
|
+
# @return [Array]
|
703
|
+
def build_attribute_definitions(hash_key_schema, range_key_schema = nil)
|
704
|
+
attrs = []
|
705
|
+
|
706
|
+
attrs << attribute_definition_element(
|
707
|
+
hash_key_schema.keys.first,
|
708
|
+
hash_key_schema.values.first
|
709
|
+
)
|
710
|
+
|
711
|
+
if range_key_schema.present?
|
712
|
+
attrs << attribute_definition_element(
|
713
|
+
range_key_schema.keys.first,
|
714
|
+
range_key_schema.values.first
|
715
|
+
)
|
716
|
+
end
|
717
|
+
|
718
|
+
attrs
|
719
|
+
end
|
720
|
+
|
721
|
+
# Builds an aws attribute definition based on name and dynamoid type
|
722
|
+
# @params [Symbol] name - eg: :id
|
723
|
+
# @params [Symbol] dynamoid_type - eg: :string
|
724
|
+
# @return [Hash]
|
725
|
+
def attribute_definition_element(name, dynamoid_type)
|
726
|
+
aws_type = api_type(dynamoid_type)
|
727
|
+
|
728
|
+
{
|
729
|
+
:attribute_name => name.to_s,
|
730
|
+
:attribute_type => aws_type
|
731
|
+
}
|
732
|
+
end
|
733
|
+
|
469
734
|
#
|
470
735
|
# Represents a table. Exposes data from the "DescribeTable" API call, and also
|
471
736
|
# provides methods for coercing values to the proper types based on the table's schema data
|