aws-sdk-rails 3.7.1 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/aws_sqs_active_job +1 -0
- data/lib/action_dispatch/session/dynamodb_store.rb +4 -2
- data/lib/active_job/queue_adapters/amazon_sqs_adapter.rb +9 -6
- data/lib/active_job/queue_adapters/amazon_sqs_async_adapter.rb +0 -2
- data/lib/aws/rails/notifications.rb +1 -4
- data/lib/aws/rails/ses_mailer.rb +1 -0
- data/lib/aws/rails/sesv2_mailer.rb +1 -0
- data/lib/aws/rails/sqs_active_job/configuration.rb +35 -22
- data/lib/aws/rails/sqs_active_job/deduplication.rb +21 -0
- data/lib/aws/rails/sqs_active_job/executor.rb +11 -12
- data/lib/aws/rails/sqs_active_job/job_runner.rb +0 -1
- data/lib/aws/rails/sqs_active_job/lambda_handler.rb +1 -4
- data/lib/aws/rails/sqs_active_job/poller.rb +42 -29
- data/lib/aws-sdk-rails.rb +1 -0
- data/lib/generators/aws_record/base.rb +164 -164
- data/lib/generators/aws_record/generated_attribute.rb +50 -41
- data/lib/generators/aws_record/model/model_generator.rb +8 -4
- data/lib/generators/aws_record/secondary_index.rb +31 -25
- data/lib/generators/dynamo_db/session_store_migration/session_store_migration_generator.rb +2 -0
- data/lib/tasks/aws_record/migrate.rake +2 -0
- data/lib/tasks/dynamo_db/session_store.rake +2 -0
- metadata +34 -16
@@ -1,217 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails/generators'
|
2
4
|
require_relative 'generated_attribute'
|
3
5
|
require_relative 'secondary_index'
|
4
6
|
|
5
7
|
module AwsRecord
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
rescue ArgumentError => e
|
52
|
-
@parse_errors << e
|
53
|
-
next
|
54
|
-
end
|
55
|
-
end
|
56
|
-
self.attributes = self.attributes.compact
|
8
|
+
module Generators
|
9
|
+
class Base < Rails::Generators::NamedBase
|
10
|
+
argument :attributes, type: :array, default: [], banner: 'field[:type][:opts]...',
|
11
|
+
desc: 'Describes the fields in the model'
|
12
|
+
check_class_collision
|
13
|
+
|
14
|
+
class_option :disable_mutation_tracking, type: :boolean, desc: 'Disables dirty tracking'
|
15
|
+
class_option :timestamps, type: :boolean, desc: 'Adds created, updated timestamps to the model'
|
16
|
+
class_option :table_config, type: :hash, default: {}, banner: 'primary:R-W [SecondaryIndex1:R-W]...',
|
17
|
+
desc: 'Declares the r/w units for the model as well as any secondary indexes', required: true
|
18
|
+
class_option :gsi, type: :array, default: [],
|
19
|
+
banner: 'name:hkey{field_name}[,rkey{field_name},proj_type{ALL|KEYS_ONLY|INCLUDE}]...', desc: 'Allows for the declaration of secondary indexes'
|
20
|
+
class_option :table_name, type: :string, banner: 'model_table_name'
|
21
|
+
class_option :password_digest, type: :boolean, desc: 'Whether to add a password_digest field to the model'
|
22
|
+
|
23
|
+
class_option :required, type: :string, banner: 'field1...',
|
24
|
+
desc: 'A list of attributes that are required for an instance of the model'
|
25
|
+
class_option :length_validations, type: :hash, default: {}, banner: 'field1:MIN-MAX...',
|
26
|
+
desc: 'Validations on the length of attributes in a model'
|
27
|
+
|
28
|
+
attr_accessor :primary_read_units, :primary_write_units, :gsi_rw_units, :gsis, :required_attrs,
|
29
|
+
:length_validations
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize(args, *options)
|
34
|
+
options[0] << '--skip-table-config' if options[1][:behavior] == :revoke
|
35
|
+
@parse_errors = []
|
36
|
+
|
37
|
+
super
|
38
|
+
ensure_unique_fields
|
39
|
+
ensure_hkey
|
40
|
+
parse_gsis!
|
41
|
+
parse_table_config!
|
42
|
+
parse_validations!
|
43
|
+
|
44
|
+
return if @parse_errors.empty?
|
45
|
+
|
46
|
+
warn 'The following errors were encountered while trying to parse the given attributes'
|
47
|
+
$stderr.puts
|
48
|
+
warn @parse_errors
|
49
|
+
$stderr.puts
|
50
|
+
|
51
|
+
abort('Please fix the errors before proceeding.')
|
52
|
+
end
|
57
53
|
|
58
|
-
|
59
|
-
|
54
|
+
def parse_attributes!
|
55
|
+
self.attributes = (attributes || []).map do |attr|
|
56
|
+
begin
|
57
|
+
GeneratedAttribute.parse(attr)
|
58
|
+
rescue ArgumentError => e
|
59
|
+
@parse_errors << e
|
60
|
+
next
|
60
61
|
end
|
62
|
+
end
|
63
|
+
self.attributes = attributes.compact
|
61
64
|
|
62
|
-
|
63
|
-
|
64
|
-
self.attributes << GeneratedAttribute.parse("updated:datetime:default_value{Time.now}")
|
65
|
-
end
|
65
|
+
if options['password_digest']
|
66
|
+
attributes << GeneratedAttribute.new('password_digest', :string_attr, digest: true)
|
66
67
|
end
|
67
68
|
|
68
|
-
|
69
|
-
used_names = Set.new
|
70
|
-
duplicate_fields = []
|
69
|
+
return unless options['timestamps']
|
71
70
|
|
72
|
-
|
71
|
+
attributes << GeneratedAttribute.parse('created:datetime:default_value{Time.now}')
|
72
|
+
attributes << GeneratedAttribute.parse('updated:datetime:default_value{Time.now}')
|
73
|
+
end
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
used_names.add attr.name
|
75
|
+
def ensure_unique_fields
|
76
|
+
used_names = Set.new
|
77
|
+
duplicate_fields = []
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
attributes.each do |attr|
|
80
|
+
duplicate_fields << [:attribute, attr.name] if used_names.include? attr.name
|
81
|
+
used_names.add attr.name
|
81
82
|
|
82
|
-
|
83
|
-
duplicate_fields << [:database_attribute_name, raw_db_attr_name]
|
84
|
-
end
|
83
|
+
next unless attr.options.key? :database_attribute_name
|
85
84
|
|
86
|
-
|
87
|
-
end
|
88
|
-
end
|
85
|
+
raw_db_attr_name = attr.options[:database_attribute_name].delete('"') # db attribute names are wrapped with " to make template generation easier
|
89
86
|
|
90
|
-
if
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
87
|
+
duplicate_fields << [:database_attribute_name, raw_db_attr_name] if used_names.include? raw_db_attr_name
|
88
|
+
|
89
|
+
used_names.add raw_db_attr_name
|
95
90
|
end
|
96
91
|
|
97
|
-
|
98
|
-
uuid_member = nil
|
99
|
-
hkey_member = nil
|
100
|
-
rkey_member = nil
|
92
|
+
return if duplicate_fields.empty?
|
101
93
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
next
|
107
|
-
end
|
94
|
+
duplicate_fields.each do |invalid_attr|
|
95
|
+
@parse_errors << ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")
|
96
|
+
end
|
97
|
+
end
|
108
98
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
next
|
114
|
-
end
|
99
|
+
def ensure_hkey
|
100
|
+
uuid_member = nil
|
101
|
+
hkey_member = nil
|
102
|
+
rkey_member = nil
|
115
103
|
|
116
|
-
|
104
|
+
attributes.each do |attr|
|
105
|
+
if attr.options.key? :hash_key
|
106
|
+
if hkey_member
|
107
|
+
@parse_errors << ArgumentError.new("Redefinition of hash_key attr: #{attr.name}, original declaration of hash_key on: #{hkey_member.name}")
|
108
|
+
next
|
117
109
|
end
|
118
110
|
|
119
|
-
|
120
|
-
|
111
|
+
hkey_member = attr
|
112
|
+
elsif attr.options.key? :range_key
|
113
|
+
if rkey_member
|
114
|
+
@parse_errors << ArgumentError.new("Redefinition of range_key attr: #{attr.name}, original declaration of range_key on: #{hkey_member.name}")
|
115
|
+
next
|
121
116
|
end
|
122
|
-
end
|
123
117
|
|
124
|
-
|
125
|
-
if uuid_member
|
126
|
-
uuid_member.options[:hash_key] = true
|
127
|
-
else
|
128
|
-
self.attributes.unshift GeneratedAttribute.parse("uuid:hkey")
|
129
|
-
end
|
118
|
+
rkey_member = attr
|
130
119
|
end
|
131
|
-
end
|
132
120
|
|
133
|
-
|
134
|
-
options['disable_mutation_tracking']
|
121
|
+
uuid_member = attr if attr.name.include? 'uuid'
|
135
122
|
end
|
136
123
|
|
137
|
-
|
138
|
-
|
124
|
+
return if hkey_member
|
125
|
+
|
126
|
+
if uuid_member
|
127
|
+
uuid_member.options[:hash_key] = true
|
128
|
+
else
|
129
|
+
attributes.unshift GeneratedAttribute.parse('uuid:hkey')
|
139
130
|
end
|
131
|
+
end
|
140
132
|
|
141
|
-
|
142
|
-
|
133
|
+
def mutation_tracking_disabled?
|
134
|
+
options['disable_mutation_tracking']
|
135
|
+
end
|
143
136
|
|
144
|
-
|
137
|
+
def has_validations?
|
138
|
+
!@required_attrs.empty? || !@length_validations.empty?
|
139
|
+
end
|
145
140
|
|
146
|
-
|
147
|
-
|
148
|
-
}.to_h
|
141
|
+
def parse_table_config!
|
142
|
+
return unless options['table_config']
|
149
143
|
|
150
|
-
|
151
|
-
if config == "primary"
|
152
|
-
next
|
153
|
-
else
|
154
|
-
gsi = @gsis.select { |idx| idx.name == config}
|
144
|
+
@primary_read_units, @primary_write_units = parse_rw_units('primary')
|
155
145
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
146
|
+
@gsi_rw_units = @gsis.map do |idx|
|
147
|
+
[idx.name, parse_rw_units(idx.name)]
|
148
|
+
end.to_h
|
149
|
+
|
150
|
+
options['table_config'].each do |config, _rw_units|
|
151
|
+
next if config == 'primary'
|
152
|
+
|
153
|
+
gsi = @gsis.select { |idx| idx.name == config }
|
154
|
+
|
155
|
+
@parse_errors << ArgumentError.new("Could not find a gsi declaration for #{config}") if gsi.empty?
|
161
156
|
end
|
157
|
+
end
|
162
158
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
end
|
159
|
+
def parse_rw_units(name)
|
160
|
+
if options['table_config'].key? name
|
161
|
+
rw_units = options['table_config'][name]
|
162
|
+
rw_units.gsub(/[,.-]/, ':').split(':').reject(&:empty?)
|
163
|
+
else
|
164
|
+
@parse_errors << ArgumentError.new("Please provide a table_config definition for #{name}")
|
170
165
|
end
|
166
|
+
end
|
171
167
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
168
|
+
def parse_gsis!
|
169
|
+
@gsis = (options['gsi'] || []).map do |raw_idx|
|
170
|
+
begin
|
171
|
+
idx = SecondaryIndex.parse(raw_idx)
|
176
172
|
|
177
|
-
|
173
|
+
attributes = self.attributes.select { |attr| attr.name == idx.hash_key }
|
174
|
+
if attributes.empty?
|
175
|
+
@parse_errors << ArgumentError.new("Could not find attribute #{idx.hash_key} for gsi #{idx.name} hkey")
|
176
|
+
next
|
177
|
+
end
|
178
|
+
|
179
|
+
if idx.range_key
|
180
|
+
attributes = self.attributes.select { |attr| attr.name == idx.range_key }
|
178
181
|
if attributes.empty?
|
179
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.
|
182
|
+
@parse_errors << ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")
|
180
183
|
next
|
181
184
|
end
|
182
|
-
|
183
|
-
if idx.range_key
|
184
|
-
attributes = self.attributes.select { |attr| attr.name == idx.range_key}
|
185
|
-
if attributes.empty?
|
186
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")
|
187
|
-
next
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
idx
|
192
|
-
rescue ArgumentError => e
|
193
|
-
@parse_errors << e
|
194
|
-
next
|
195
185
|
end
|
196
|
-
end
|
197
186
|
|
198
|
-
|
187
|
+
idx
|
188
|
+
rescue ArgumentError => e
|
189
|
+
@parse_errors << e
|
190
|
+
next
|
191
|
+
end
|
199
192
|
end
|
200
193
|
|
201
|
-
|
202
|
-
|
203
|
-
@required_attrs.each do |val_attr|
|
204
|
-
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
|
205
|
-
end
|
194
|
+
@gsis = @gsis.compact
|
195
|
+
end
|
206
196
|
|
207
|
-
|
208
|
-
|
197
|
+
def parse_validations!
|
198
|
+
@required_attrs = options['required'] ? options['required'].split(',') : []
|
199
|
+
@required_attrs.each do |val_attr|
|
200
|
+
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if attributes.none? do |attr|
|
201
|
+
attr.name == val_attr
|
202
|
+
end
|
203
|
+
end
|
209
204
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
205
|
+
@length_validations = options['length_validations'].map do |val_attr, bounds|
|
206
|
+
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if attributes.none? do |attr|
|
207
|
+
attr.name == val_attr
|
208
|
+
end
|
209
|
+
|
210
|
+
bounds = bounds.gsub(/[,.-]/, ':').split(':').reject(&:empty?)
|
211
|
+
[val_attr, "#{bounds[0]}..#{bounds[1]}"]
|
214
212
|
end
|
213
|
+
@length_validations = @length_validations.to_h
|
215
214
|
end
|
216
215
|
end
|
217
216
|
end
|
217
|
+
end
|
@@ -1,28 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AwsRecord
|
2
4
|
module Generators
|
3
5
|
class GeneratedAttribute
|
4
|
-
|
5
|
-
|
6
|
-
INVALID_HKEY_TYPES = %i(map_attr list_attr numeric_set_attr string_set_attr)
|
6
|
+
OPTS = %w[hkey rkey persist_nil db_attr_name ddb_type default_value].freeze
|
7
|
+
INVALID_HKEY_TYPES = %i[map_attr list_attr numeric_set_attr string_set_attr].freeze
|
7
8
|
attr_reader :name, :type
|
8
9
|
attr_accessor :options
|
9
10
|
|
10
11
|
def field_type
|
11
12
|
case @type
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
when :integer_attr then :number_field
|
14
|
+
when :date_attr then :date_select
|
15
|
+
when :datetime_attr then :datetime_select
|
16
|
+
when :boolean_attr then :check_box
|
17
|
+
else :text_field
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
class << self
|
21
|
-
|
22
22
|
def parse(field_definition)
|
23
23
|
name, type, opts = field_definition.split(':')
|
24
|
-
type
|
25
|
-
|
24
|
+
type ||= 'string'
|
25
|
+
if OPTS.any? { |opt| type.include? opt }
|
26
|
+
opts = type
|
27
|
+
type = 'string'
|
28
|
+
end
|
26
29
|
|
27
30
|
opts = opts.split(',') if opts
|
28
31
|
type, opts = parse_type_and_options(name, type, opts)
|
@@ -34,65 +37,71 @@ module AwsRecord
|
|
34
37
|
private
|
35
38
|
|
36
39
|
def validate_opt_combs(name, type, opts)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
return unless opts
|
41
|
+
|
42
|
+
is_hkey = opts.key?(:hash_key)
|
43
|
+
is_rkey = opts.key?(:range_key)
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
if is_hkey && is_rkey
|
46
|
+
raise ArgumentError,
|
47
|
+
"Field #{name} cannot be a range key and hash key simultaneously"
|
43
48
|
end
|
49
|
+
return unless is_hkey && INVALID_HKEY_TYPES.include?(type)
|
50
|
+
|
51
|
+
raise ArgumentError,
|
52
|
+
"Field #{name} cannot be a hash key and be of type #{type}"
|
44
53
|
end
|
45
54
|
|
46
55
|
def parse_type_and_options(name, type, opts)
|
47
|
-
opts
|
48
|
-
|
56
|
+
opts ||= []
|
57
|
+
[parse_type(name, type), opts.map { |opt| parse_option(name, opt) }.to_h]
|
49
58
|
end
|
50
59
|
|
51
60
|
def parse_option(name, opt)
|
52
61
|
case opt
|
53
62
|
|
54
|
-
when
|
55
|
-
|
56
|
-
when
|
57
|
-
|
58
|
-
when
|
59
|
-
|
63
|
+
when 'hkey'
|
64
|
+
[:hash_key, true]
|
65
|
+
when 'rkey'
|
66
|
+
[:range_key, true]
|
67
|
+
when 'persist_nil'
|
68
|
+
[:persist_nil, true]
|
60
69
|
when /db_attr_name\{(\w+)\}/
|
61
|
-
|
70
|
+
[:database_attribute_name, "\"#{::Regexp.last_match(1)}\""]
|
62
71
|
when /ddb_type\{(S|N|B|BOOL|SS|NS|BS|M|L)\}/i
|
63
|
-
|
72
|
+
[:dynamodb_type, "\"#{::Regexp.last_match(1).upcase}\""]
|
64
73
|
when /default_value\{(.+)\}/
|
65
|
-
|
74
|
+
[:default_value, ::Regexp.last_match(1)]
|
66
75
|
else
|
67
|
-
raise ArgumentError
|
76
|
+
raise ArgumentError, "You provided an invalid option for #{name}: #{opt}"
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
71
80
|
def parse_type(name, type)
|
72
81
|
case type.downcase
|
73
82
|
|
74
|
-
when
|
83
|
+
when 'bool', 'boolean'
|
75
84
|
:boolean_attr
|
76
|
-
when
|
85
|
+
when 'date'
|
77
86
|
:date_attr
|
78
|
-
when
|
87
|
+
when 'datetime'
|
79
88
|
:datetime_attr
|
80
|
-
when
|
89
|
+
when 'float'
|
81
90
|
:float_attr
|
82
|
-
when
|
91
|
+
when 'int', 'integer'
|
83
92
|
:integer_attr
|
84
|
-
when
|
93
|
+
when 'list'
|
85
94
|
:list_attr
|
86
|
-
when
|
95
|
+
when 'map'
|
87
96
|
:map_attr
|
88
|
-
when
|
97
|
+
when 'num_set', 'numeric_set', 'nset'
|
89
98
|
:numeric_set_attr
|
90
|
-
when
|
99
|
+
when 'string_set', 's_set', 'sset'
|
91
100
|
:string_set_attr
|
92
|
-
when
|
101
|
+
when 'string'
|
93
102
|
:string_attr
|
94
103
|
else
|
95
|
-
raise ArgumentError
|
104
|
+
raise ArgumentError, "Invalid type for #{name}: #{type}"
|
96
105
|
end
|
97
106
|
end
|
98
107
|
end
|
@@ -114,8 +123,8 @@ module AwsRecord
|
|
114
123
|
end
|
115
124
|
|
116
125
|
def column_name
|
117
|
-
if @name ==
|
118
|
-
|
126
|
+
if @name == 'password_digest'
|
127
|
+
'password'
|
119
128
|
else
|
120
129
|
@name
|
121
130
|
end
|
@@ -1,21 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../base'
|
2
4
|
|
3
5
|
module AwsRecord
|
4
6
|
module Generators
|
5
7
|
class ModelGenerator < Base
|
6
8
|
def initialize(args, *options)
|
7
|
-
self.class.source_root File.expand_path('
|
9
|
+
self.class.source_root File.expand_path('templates', __dir__)
|
8
10
|
super
|
9
11
|
end
|
10
12
|
|
11
13
|
def create_model
|
12
|
-
template
|
14
|
+
template 'model.erb', File.join('app/models', class_path, "#{file_name}.rb")
|
13
15
|
end
|
14
16
|
|
15
17
|
def create_table_config
|
16
|
-
|
17
|
-
end
|
18
|
+
return unless options['table_config']
|
18
19
|
|
20
|
+
template 'table_config.erb',
|
21
|
+
File.join('db/table_config', class_path, "#{file_name}_config.rb")
|
22
|
+
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AwsRecord
|
2
4
|
module Generators
|
3
5
|
class SecondaryIndex
|
4
|
-
|
5
|
-
PROJ_TYPES = %w(ALL KEYS_ONLY INCLUDE)
|
6
|
+
PROJ_TYPES = %w[ALL KEYS_ONLY INCLUDE].freeze
|
6
7
|
attr_reader :name, :hash_key, :range_key, :projection_type
|
7
8
|
|
8
9
|
class << self
|
@@ -15,45 +16,50 @@ module AwsRecord
|
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
|
-
def parse_raw_options(raw_opts)
|
19
|
-
raw_opts = [] if not raw_opts
|
20
|
-
raw_opts.map { |opt| get_option_value(opt) }.to_h
|
21
|
-
end
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
20
|
+
def parse_raw_options(raw_opts)
|
21
|
+
raw_opts ||= []
|
22
|
+
raw_opts.map { |opt| get_option_value(opt) }.to_h
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_option_value(raw_option)
|
26
|
+
case raw_option
|
27
|
+
|
28
|
+
when /hkey\{(\w+)\}/
|
29
|
+
[:hash_key, ::Regexp.last_match(1)]
|
30
|
+
when /rkey\{(\w+)\}/
|
31
|
+
[:range_key, ::Regexp.last_match(1)]
|
32
|
+
when /proj_type\{(\w+)\}/
|
33
|
+
[:projection_type, ::Regexp.last_match(1)]
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Invalid option for secondary index #{raw_option}"
|
35
36
|
end
|
37
|
+
end
|
36
38
|
end
|
37
39
|
|
38
40
|
def initialize(name, opts)
|
39
|
-
raise ArgumentError
|
40
|
-
raise ArgumentError
|
41
|
+
raise ArgumentError, 'You must provide a name' unless name
|
42
|
+
raise ArgumentError, 'You must provide a hash key' unless opts[:hash_key]
|
41
43
|
|
42
44
|
if opts.key? :projection_type
|
43
|
-
|
44
|
-
|
45
|
+
unless PROJ_TYPES.include? opts[:projection_type]
|
46
|
+
raise ArgumentError, "Invalid projection type #{opts[:projection_type]}"
|
47
|
+
end
|
48
|
+
if opts[:projection_type] != 'ALL'
|
49
|
+
raise NotImplementedError, 'ALL is the only projection type currently supported'
|
50
|
+
end
|
45
51
|
else
|
46
|
-
opts[:projection_type] =
|
52
|
+
opts[:projection_type] = 'ALL'
|
47
53
|
end
|
48
54
|
|
49
55
|
if opts[:hash_key] == opts[:range_key]
|
50
|
-
raise ArgumentError
|
56
|
+
raise ArgumentError, "#{opts[:hash_key]} cannot be both the rkey and hkey for gsi #{name}"
|
51
57
|
end
|
52
58
|
|
53
59
|
@name = name
|
54
60
|
@hash_key = opts[:hash_key]
|
55
61
|
@range_key = opts[:range_key]
|
56
|
-
@projection_type =
|
62
|
+
@projection_type = "\"#{opts[:projection_type]}\""
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|