aws-sdk-rails 3.8.0 → 3.9.1
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 +11 -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/sqs_active_job/configuration.rb +29 -17
- 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 +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1491284a363942f9a0d4fe8712449caf2989869377fdb283632bec9c11b71b0b
|
4
|
+
data.tar.gz: c1f65e37b61266b80a20c06c21a39f84f7283ade2b9a590e5b80d3963ada71c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456ead7237daf5d83f55f4858f11d40a3d213a3bd8f8ded27c56be10e94e7aba6708a2adad8739b324991f28d22b97e32b8272d073f8f0bc082d390abfff8267
|
7
|
+
data.tar.gz: 97da8e92ec42c8404af945049feae80044aa2d6d1fed276eaf40bd50baa2b07826cfd201ed7eda00a92a9d1636749aaec0cc1adbc159a300c1d5b9690c86fc96
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.9.1
|
data/bin/aws_sqs_active_job
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aws-sessionstore-dynamodb'
|
2
4
|
require 'action_dispatch/middleware/session/abstract_store'
|
3
5
|
|
@@ -19,7 +21,7 @@ module ActionDispatch
|
|
19
21
|
include SessionObject
|
20
22
|
|
21
23
|
def initialize(app, options = {})
|
22
|
-
options[:config_file] ||= config_file if
|
24
|
+
options[:config_file] ||= config_file if File.exist?(config_file)
|
23
25
|
options[:secret_key] ||= Rails.application.secret_key_base
|
24
26
|
super
|
25
27
|
end
|
@@ -28,7 +30,7 @@ module ActionDispatch
|
|
28
30
|
|
29
31
|
def config_file
|
30
32
|
file = Rails.root.join("config/dynamo_db_session_store/#{Rails.env}.yml")
|
31
|
-
file = Rails.root.join('config/dynamo_db_session_store.yml') unless
|
33
|
+
file = Rails.root.join('config/dynamo_db_session_store.yml') unless File.exist?(file)
|
32
34
|
file
|
33
35
|
end
|
34
36
|
end
|
@@ -4,16 +4,17 @@ require 'aws-sdk-sqs'
|
|
4
4
|
|
5
5
|
module ActiveJob
|
6
6
|
module QueueAdapters
|
7
|
-
|
8
7
|
class AmazonSqsAdapter
|
9
|
-
|
10
8
|
def enqueue(job)
|
11
9
|
_enqueue(job)
|
12
10
|
end
|
13
11
|
|
14
12
|
def enqueue_at(job, timestamp)
|
15
13
|
delay = (timestamp - Time.now.to_f).floor
|
14
|
+
|
15
|
+
delay = 0 if delay.negative?
|
16
16
|
raise ArgumentError, 'Unable to queue a job with a delay great than 15 minutes' if delay > 15.minutes
|
17
|
+
|
17
18
|
_enqueue(job, nil, delay_seconds: delay)
|
18
19
|
end
|
19
20
|
|
@@ -27,11 +28,8 @@ module ActiveJob
|
|
27
28
|
send_message_opts[:message_attributes] = message_attributes(job)
|
28
29
|
|
29
30
|
if Aws::Rails::SqsActiveJob.fifo?(queue_url)
|
30
|
-
# job_id is unique per initialization of job
|
31
|
-
# Remove it from message dup id to ensure run-once behavior
|
32
|
-
# with ActiveJob retries
|
33
31
|
send_message_opts[:message_deduplication_id] =
|
34
|
-
Digest::SHA256.hexdigest(Aws::Json.dump(body
|
32
|
+
Digest::SHA256.hexdigest(Aws::Json.dump(deduplication_body(job, body)))
|
35
33
|
|
36
34
|
message_group_id = job.message_group_id if job.respond_to?(:message_group_id)
|
37
35
|
message_group_id ||= Aws::Rails::SqsActiveJob.config.message_group_id
|
@@ -54,6 +52,13 @@ module ActiveJob
|
|
54
52
|
}
|
55
53
|
}
|
56
54
|
end
|
55
|
+
|
56
|
+
def deduplication_body(job, body)
|
57
|
+
ex_dedup_keys = job.excluded_deduplication_keys if job.respond_to?(:excluded_deduplication_keys)
|
58
|
+
ex_dedup_keys ||= Aws::Rails::SqsActiveJob.config.excluded_deduplication_keys
|
59
|
+
|
60
|
+
body.except(*ex_dedup_keys)
|
61
|
+
end
|
57
62
|
end
|
58
63
|
|
59
64
|
# create an alias to allow `:amazon` to be used as the adapter name
|
@@ -5,7 +5,6 @@ require 'concurrent'
|
|
5
5
|
|
6
6
|
module ActiveJob
|
7
7
|
module QueueAdapters
|
8
|
-
|
9
8
|
# == Async adapter for Amazon SQS ActiveJob
|
10
9
|
#
|
11
10
|
# This adapter queues jobs asynchronously (ie non-blocking). Error handler can be configured
|
@@ -15,7 +14,6 @@ module ActiveJob
|
|
15
14
|
#
|
16
15
|
# config.active_job.queue_adapter = :amazon_sqs_async
|
17
16
|
class AmazonSqsAsyncAdapter < AmazonSqsAdapter
|
18
|
-
|
19
17
|
private
|
20
18
|
|
21
19
|
def _enqueue(job, body = nil, send_message_opts = {})
|
@@ -5,14 +5,12 @@ require 'active_support/notifications'
|
|
5
5
|
|
6
6
|
module Aws
|
7
7
|
module Rails
|
8
|
-
|
9
8
|
# Instruments client operation calls for ActiveSupport::Notifications
|
10
9
|
# Each client operation will produce an event with name:
|
11
10
|
# <operation>.<service>.aws
|
12
11
|
# @api private
|
13
12
|
class Notifications < Seahorse::Client::Plugin
|
14
|
-
|
15
|
-
def add_handlers(handlers, config)
|
13
|
+
def add_handlers(handlers, _config)
|
16
14
|
# This plugin needs to be first
|
17
15
|
# which means it is called first in the stack, to start recording time,
|
18
16
|
# and returns last
|
@@ -20,7 +18,6 @@ module Aws
|
|
20
18
|
end
|
21
19
|
|
22
20
|
class Handler < Seahorse::Client::Handler
|
23
|
-
|
24
21
|
def call(context)
|
25
22
|
event_name = "#{context.operation_name}.#{context.config.api.metadata['serviceId']}.aws"
|
26
23
|
ActiveSupport::Notifications.instrument(event_name, context: context) do
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module Aws
|
4
4
|
module Rails
|
5
5
|
module SqsActiveJob
|
6
|
-
|
7
6
|
# @return [Configuration] the (singleton) Configuration
|
8
7
|
def self.config
|
9
8
|
@config ||= Configuration.new
|
@@ -21,23 +20,25 @@ module Aws
|
|
21
20
|
# Configuration for AWS SQS ActiveJob.
|
22
21
|
# Use +Aws::Rails::SqsActiveJob.config+ to access the singleton config instance.
|
23
22
|
class Configuration
|
24
|
-
|
25
23
|
# Default configuration options
|
26
24
|
# @api private
|
27
25
|
DEFAULTS = {
|
28
|
-
max_messages:
|
26
|
+
max_messages: 10,
|
29
27
|
shutdown_timeout: 15,
|
30
28
|
queues: {},
|
31
29
|
logger: ::Rails.logger,
|
32
|
-
message_group_id: 'SqsActiveJobGroup'
|
33
|
-
|
30
|
+
message_group_id: 'SqsActiveJobGroup',
|
31
|
+
excluded_deduplication_keys: ['job_id']
|
32
|
+
}.freeze
|
34
33
|
|
35
34
|
# @api private
|
36
35
|
attr_accessor :queues, :max_messages, :visibility_timeout,
|
37
36
|
:shutdown_timeout, :client, :logger,
|
38
37
|
:async_queue_error_handler, :message_group_id
|
39
38
|
|
40
|
-
|
39
|
+
attr_reader :excluded_deduplication_keys
|
40
|
+
|
41
|
+
# Don't use this method directly: Configuration is a singleton class, use
|
41
42
|
# +Aws::Rails::SqsActiveJob.config+ to access the singleton config.
|
42
43
|
#
|
43
44
|
# @param [Hash] options
|
@@ -67,7 +68,7 @@ module Aws
|
|
67
68
|
# for the poller.
|
68
69
|
#
|
69
70
|
# @option options [String] :config_file
|
70
|
-
# Override file to load configuration from.
|
71
|
+
# Override file to load configuration from. If not specified will
|
71
72
|
# attempt to load from config/aws_sqs_active_job.yml.
|
72
73
|
#
|
73
74
|
# @option options [String] :message_group_id (SqsActiveJobGroup)
|
@@ -81,16 +82,25 @@ module Aws
|
|
81
82
|
# +active_job.queue_adapter = :amazon_sqs_async+. Called with:
|
82
83
|
# [error, job, job_options]
|
83
84
|
#
|
84
|
-
# @option options [SQS::Client] :client SQS Client to use.
|
85
|
+
# @option options [SQS::Client] :client SQS Client to use. A default
|
85
86
|
# client will be created if none is provided.
|
87
|
+
#
|
88
|
+
# @option options [Array] :excluded_deduplication_keys (['job_id'])
|
89
|
+
# The type of keys stored in the array should be String or Symbol.
|
90
|
+
# Using this option, job_id is implicitly added to the keys.
|
91
|
+
|
86
92
|
def initialize(options = {})
|
87
|
-
options[:config_file] ||= config_file if
|
93
|
+
options[:config_file] ||= config_file if File.exist?(config_file)
|
88
94
|
options = DEFAULTS
|
89
|
-
|
90
|
-
|
95
|
+
.merge(file_options(options))
|
96
|
+
.merge(options)
|
91
97
|
set_attributes(options)
|
92
98
|
end
|
93
99
|
|
100
|
+
def excluded_deduplication_keys=(keys)
|
101
|
+
@excluded_deduplication_keys = keys.map(&:to_s) | ['job_id']
|
102
|
+
end
|
103
|
+
|
94
104
|
def client
|
95
105
|
@client ||= begin
|
96
106
|
client = Aws::SQS::Client.new
|
@@ -115,9 +125,9 @@ module Aws
|
|
115
125
|
# @api private
|
116
126
|
def to_h
|
117
127
|
h = {}
|
118
|
-
|
128
|
+
instance_variables.each do |v|
|
119
129
|
v_sym = v.to_s.gsub('@', '').to_sym
|
120
|
-
val =
|
130
|
+
val = instance_variable_get(v)
|
121
131
|
h[v_sym] = val
|
122
132
|
end
|
123
133
|
h
|
@@ -127,7 +137,7 @@ module Aws
|
|
127
137
|
|
128
138
|
# Set accessible attributes after merged options.
|
129
139
|
def set_attributes(options)
|
130
|
-
options.
|
140
|
+
options.each_key do |opt_name|
|
131
141
|
instance_variable_set("@#{opt_name}", options[opt_name])
|
132
142
|
client.config.user_agent_frameworks << 'aws-sdk-rails' if opt_name == :client
|
133
143
|
end
|
@@ -144,7 +154,7 @@ module Aws
|
|
144
154
|
|
145
155
|
def config_file
|
146
156
|
file = ::Rails.root.join("config/aws_sqs_active_job/#{::Rails.env}.yml")
|
147
|
-
file = ::Rails.root.join('config/aws_sqs_active_job.yml') unless
|
157
|
+
file = ::Rails.root.join('config/aws_sqs_active_job.yml') unless File.exist?(file)
|
148
158
|
file
|
149
159
|
end
|
150
160
|
|
@@ -156,20 +166,22 @@ module Aws
|
|
156
166
|
|
157
167
|
# @return [String] Configuration path found in environment or YAML file.
|
158
168
|
def config_file_path(options)
|
159
|
-
options[:config_file] || ENV
|
169
|
+
options[:config_file] || ENV.fetch('AWS_SQS_ACTIVE_JOB_CONFIG_FILE', nil)
|
160
170
|
end
|
161
171
|
|
162
172
|
def load_yaml(file_path)
|
163
|
-
require
|
173
|
+
require 'erb'
|
164
174
|
source = ERB.new(File.read(file_path)).result
|
165
175
|
|
166
176
|
# Avoid incompatible changes with Psych 4.0.0
|
167
177
|
# https://bugs.ruby-lang.org/issues/17866
|
178
|
+
# rubocop:disable Security/YAMLLoad
|
168
179
|
begin
|
169
180
|
YAML.load(source, aliases: true) || {}
|
170
181
|
rescue ArgumentError
|
171
182
|
YAML.load(source) || {}
|
172
183
|
end
|
184
|
+
# rubocop:enable Security/YAMLLoad
|
173
185
|
end
|
174
186
|
end
|
175
187
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Rails
|
5
|
+
# SQS ActiveJob modules
|
6
|
+
module SqsActiveJob
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :excluded_deduplication_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
# class methods for SQS ActiveJob.
|
14
|
+
module ClassMethods
|
15
|
+
def deduplicate_without(*keys)
|
16
|
+
self.excluded_deduplication_keys = keys.map(&:to_s) | ['job_id']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -7,18 +7,17 @@ module Aws
|
|
7
7
|
module SqsActiveJob
|
8
8
|
# CLI runner for polling for SQS ActiveJobs
|
9
9
|
class Executor
|
10
|
-
|
11
10
|
DEFAULTS = {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
min_threads: 0,
|
12
|
+
max_threads: Concurrent.processor_count,
|
13
|
+
auto_terminate: true,
|
14
|
+
idletime: 60, # 1 minute
|
15
|
+
fallback_policy: :caller_runs # slow down the producer thread
|
17
16
|
}.freeze
|
18
17
|
|
19
18
|
def initialize(options = {})
|
20
19
|
@executor = Concurrent::ThreadPoolExecutor.new(DEFAULTS.merge(options))
|
21
|
-
@logger = options[:logger] || ActiveSupport::Logger.new(
|
20
|
+
@logger = options[:logger] || ActiveSupport::Logger.new($stdout)
|
22
21
|
end
|
23
22
|
|
24
23
|
# TODO: Consider catching the exception and sleeping instead of using :caller_runs
|
@@ -40,16 +39,16 @@ module Aws
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
|
-
def shutdown(timeout=nil)
|
42
|
+
def shutdown(timeout = nil)
|
44
43
|
@executor.shutdown
|
45
44
|
clean_shutdown = @executor.wait_for_termination(timeout)
|
46
45
|
if clean_shutdown
|
47
46
|
@logger.info 'Clean shutdown complete. All executing jobs finished.'
|
48
47
|
else
|
49
|
-
@logger.info "Timeout (#{timeout}) exceeded. Some jobs may not have"\
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
@logger.info "Timeout (#{timeout}) exceeded. Some jobs may not have " \
|
49
|
+
'finished cleanly. Unfinished jobs will not be removed from ' \
|
50
|
+
'the queue and can be ru-run once their visibility timeout ' \
|
51
|
+
'passes.'
|
53
52
|
end
|
54
53
|
end
|
55
54
|
end
|
@@ -5,7 +5,6 @@ require 'aws-sdk-sqs'
|
|
5
5
|
module Aws
|
6
6
|
module Rails
|
7
7
|
module SqsActiveJob
|
8
|
-
|
9
8
|
# A lambda event handler to run jobs from an SQS queue trigger
|
10
9
|
# Trigger the lambda from your SQS queue
|
11
10
|
# Configure the entrypoint to: +config/environment.Aws::Rails::SqsActiveJob.lambda_job_handler+
|
@@ -23,13 +22,11 @@ module Aws
|
|
23
22
|
"Processed #{event['Records'].length} jobs."
|
24
23
|
end
|
25
24
|
|
26
|
-
private
|
27
|
-
|
28
25
|
def self.to_sqs_msg(record)
|
29
26
|
msg = Aws::SQS::Types::Message.new(
|
30
27
|
body: record['body'],
|
31
28
|
md5_of_body: record['md5OfBody'],
|
32
|
-
message_attributes:
|
29
|
+
message_attributes: to_message_attributes(record),
|
33
30
|
message_id: record['messageId'],
|
34
31
|
receipt_handle: record['receiptHandle']
|
35
32
|
)
|
@@ -7,19 +7,17 @@ require 'concurrent'
|
|
7
7
|
module Aws
|
8
8
|
module Rails
|
9
9
|
module SqsActiveJob
|
10
|
-
|
11
|
-
class Interrupt < Exception; end
|
10
|
+
class Interrupt < StandardError; end
|
12
11
|
|
13
12
|
# CLI runner for polling for SQS ActiveJobs
|
14
13
|
# Use `aws_sqs_active_job --help` for detailed usage
|
15
14
|
class Poller
|
16
|
-
|
17
15
|
DEFAULT_OPTS = {
|
18
|
-
threads: 2*Concurrent.processor_count,
|
16
|
+
threads: 2 * Concurrent.processor_count,
|
19
17
|
max_messages: 10,
|
20
18
|
shutdown_timeout: 15,
|
21
19
|
backpressure: 10
|
22
|
-
}
|
20
|
+
}.freeze
|
23
21
|
|
24
22
|
def initialize(args = ARGV)
|
25
23
|
@options = parse_args(args)
|
@@ -28,7 +26,7 @@ module Aws
|
|
28
26
|
end
|
29
27
|
|
30
28
|
def set_environment
|
31
|
-
@environment = @options[:environment] || ENV[
|
29
|
+
@environment = @options[:environment] || ENV['APP_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
32
30
|
end
|
33
31
|
|
34
32
|
def run
|
@@ -42,10 +40,9 @@ module Aws
|
|
42
40
|
.merge(@options.to_h)
|
43
41
|
validate_config
|
44
42
|
# ensure we have a logger configured
|
45
|
-
@logger = @options[:logger] || ActiveSupport::Logger.new(
|
43
|
+
@logger = @options[:logger] || ActiveSupport::Logger.new($stdout)
|
46
44
|
@logger.info("Starting Poller with options=#{@options}")
|
47
45
|
|
48
|
-
|
49
46
|
Signal.trap('INT') { raise Interrupt }
|
50
47
|
Signal.trap('TERM') { raise Interrupt }
|
51
48
|
@executor = Executor.new(max_threads: @options[:threads], logger: @logger, max_queue: @options[:backpressure])
|
@@ -78,9 +75,7 @@ module Aws
|
|
78
75
|
# in order
|
79
76
|
# Jobs with different message_group_id will be processed in
|
80
77
|
# parallel and may be out of order.
|
81
|
-
if Aws::Rails::SqsActiveJob.fifo?(queue_url)
|
82
|
-
poller_options[:max_number_of_messages] = 1
|
83
|
-
end
|
78
|
+
poller_options[:max_number_of_messages] = 1 if Aws::Rails::SqsActiveJob.fifo?(queue_url)
|
84
79
|
|
85
80
|
single_message = poller_options[:max_number_of_messages] == 1
|
86
81
|
|
@@ -89,35 +84,53 @@ module Aws
|
|
89
84
|
@logger.info "Processing batch of #{msgs.length} messages"
|
90
85
|
msgs.each do |msg|
|
91
86
|
@executor.execute(Aws::SQS::Message.new(
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
87
|
+
queue_url: queue_url,
|
88
|
+
receipt_handle: msg.receipt_handle,
|
89
|
+
data: msg,
|
90
|
+
client: client
|
91
|
+
))
|
97
92
|
end
|
98
93
|
end
|
99
94
|
end
|
100
95
|
|
101
96
|
def boot_rails
|
102
97
|
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = @environment
|
103
|
-
require
|
104
|
-
require File.expand_path(
|
98
|
+
require 'rails'
|
99
|
+
require File.expand_path('config/environment.rb')
|
105
100
|
end
|
106
101
|
|
107
102
|
def parse_args(argv)
|
108
103
|
out = {}
|
109
|
-
parser = ::OptionParser.new
|
110
|
-
opts.on(
|
111
|
-
opts.on(
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
opts.on(
|
116
|
-
|
117
|
-
|
104
|
+
parser = ::OptionParser.new do |opts|
|
105
|
+
opts.on('-q', '--queue STRING', '[Required] Queue to poll') { |a| out[:queue] = a }
|
106
|
+
opts.on('-e', '--environment STRING',
|
107
|
+
'Rails environment (defaults to development). You can also use the APP_ENV or RAILS_ENV environment variables to specify the environment.') do |a|
|
108
|
+
out[:environment] = a
|
109
|
+
end
|
110
|
+
opts.on('-t', '--threads INTEGER', Integer,
|
111
|
+
'The maximum number of worker threads to create. Defaults to 2x the number of processors available on this system.') do |a|
|
112
|
+
out[:threads] = a
|
113
|
+
end
|
114
|
+
opts.on('-b', '--backpressure INTEGER', Integer,
|
115
|
+
'The maximum number of messages to have waiting in the Executor queue. This should be a low, but non zero number. Messages in the Executor queue cannot be picked up by other processes and will slow down shutdown.') do |a|
|
116
|
+
out[:backpressure] = a
|
117
|
+
end
|
118
|
+
opts.on('-m', '--max_messages INTEGER', Integer,
|
119
|
+
'Max number of messages to receive in a batch from SQS.') do |a|
|
120
|
+
out[:max_messages] = a
|
121
|
+
end
|
122
|
+
opts.on('-v', '--visibility_timeout INTEGER', Integer,
|
123
|
+
'The visibility timeout is the number of seconds that a message will not be processable by any other consumers. You should set this value to be longer than your expected job runtime to prevent other processes from picking up an running job. See the SQS Visibility Timeout Documentation at https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html.') do |a|
|
124
|
+
out[:visibility_timeout] = a
|
125
|
+
end
|
126
|
+
opts.on('-s', '--shutdown_timeout INTEGER', Integer,
|
127
|
+
'The amount of time to wait for a clean shutdown. Jobs that are unable to complete in this time will not be deleted from the SQS queue and will be retryable after the visibility timeout.') do |a|
|
128
|
+
out[:shutdown_timeout] = a
|
129
|
+
end
|
130
|
+
end
|
118
131
|
|
119
|
-
parser.banner =
|
120
|
-
parser.on_tail
|
132
|
+
parser.banner = 'aws_sqs_active_job [options]'
|
133
|
+
parser.on_tail '-h', '--help', 'Show help' do
|
121
134
|
puts parser
|
122
135
|
exit 1
|
123
136
|
end
|
data/lib/aws-sdk-rails.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative 'aws/rails/sesv2_mailer'
|
|
5
5
|
require_relative 'aws/rails/railtie'
|
6
6
|
require_relative 'aws/rails/notifications'
|
7
7
|
require_relative 'aws/rails/sqs_active_job/configuration'
|
8
|
+
require_relative 'aws/rails/sqs_active_job/deduplication'
|
8
9
|
require_relative 'aws/rails/sqs_active_job/executor'
|
9
10
|
require_relative 'aws/rails/sqs_active_job/job_runner'
|
10
11
|
require_relative 'aws/rails/sqs_active_job/lambda_handler'
|
@@ -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_key do |config|
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-sdk-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amazon Web Services
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-record
|
@@ -99,33 +99,33 @@ dependencies:
|
|
99
99
|
- !ruby/object:Gem::Version
|
100
100
|
version: '2'
|
101
101
|
- !ruby/object:Gem::Dependency
|
102
|
-
name:
|
102
|
+
name: concurrent-ruby
|
103
103
|
requirement: !ruby/object:Gem::Requirement
|
104
104
|
requirements:
|
105
|
-
- - "
|
105
|
+
- - "~>"
|
106
106
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
107
|
+
version: '1'
|
108
108
|
type: :runtime
|
109
109
|
prerelease: false
|
110
110
|
version_requirements: !ruby/object:Gem::Requirement
|
111
111
|
requirements:
|
112
|
-
- - "
|
112
|
+
- - "~>"
|
113
113
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
114
|
+
version: '1'
|
115
115
|
- !ruby/object:Gem::Dependency
|
116
|
-
name:
|
116
|
+
name: railties
|
117
117
|
requirement: !ruby/object:Gem::Requirement
|
118
118
|
requirements:
|
119
|
-
- - "
|
119
|
+
- - ">="
|
120
120
|
- !ruby/object:Gem::Version
|
121
|
-
version:
|
121
|
+
version: 5.2.0
|
122
122
|
type: :runtime
|
123
123
|
prerelease: false
|
124
124
|
version_requirements: !ruby/object:Gem::Requirement
|
125
125
|
requirements:
|
126
|
-
- - "
|
126
|
+
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
128
|
+
version: 5.2.0
|
129
129
|
- !ruby/object:Gem::Dependency
|
130
130
|
name: rails
|
131
131
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,8 +142,7 @@ dependencies:
|
|
142
142
|
version: '0'
|
143
143
|
description: Integrates the AWS Ruby SDK with Ruby on Rails
|
144
144
|
email:
|
145
|
-
-
|
146
|
-
- alexwoo@amazon.com
|
145
|
+
- aws-dr-rubygems@amazon.com
|
147
146
|
executables:
|
148
147
|
- aws_sqs_active_job
|
149
148
|
extensions: []
|
@@ -161,6 +160,7 @@ files:
|
|
161
160
|
- lib/aws/rails/ses_mailer.rb
|
162
161
|
- lib/aws/rails/sesv2_mailer.rb
|
163
162
|
- lib/aws/rails/sqs_active_job/configuration.rb
|
163
|
+
- lib/aws/rails/sqs_active_job/deduplication.rb
|
164
164
|
- lib/aws/rails/sqs_active_job/executor.rb
|
165
165
|
- lib/aws/rails/sqs_active_job/job_runner.rb
|
166
166
|
- lib/aws/rails/sqs_active_job/lambda_handler.rb
|
@@ -190,14 +190,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
190
|
requirements:
|
191
191
|
- - ">="
|
192
192
|
- !ruby/object:Gem::Version
|
193
|
-
version: '
|
193
|
+
version: '2.3'
|
194
194
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
195
|
requirements:
|
196
196
|
- - ">="
|
197
197
|
- !ruby/object:Gem::Version
|
198
198
|
version: '0'
|
199
199
|
requirements: []
|
200
|
-
rubygems_version: 3.4.
|
200
|
+
rubygems_version: 3.4.22
|
201
201
|
signing_key:
|
202
202
|
specification_version: 4
|
203
203
|
summary: AWS SDK for Ruby on Rails Plugin
|