aws-sdk-rails 3.7.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|