aws-sdk-rails 4.1.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +256 -0
- data/LICENSE.txt +12 -0
- data/VERSION +1 -1
- data/lib/aws/rails/middleware/elastic_beanstalk_sqsd.rb +141 -0
- data/lib/aws/rails/notifications.rb +5 -7
- data/lib/aws/rails/railtie.rb +38 -77
- data/lib/aws-sdk-rails.rb +1 -9
- metadata +14 -197
- data/app/controllers/action_mailbox/ingresses/ses/inbound_emails_controller.rb +0 -79
- data/bin/aws_sqs_active_job +0 -6
- data/config/routes.rb +0 -8
- data/lib/action_dispatch/session/dynamodb_store.rb +0 -38
- data/lib/active_job/queue_adapters/sqs_adapter/params.rb +0 -78
- data/lib/active_job/queue_adapters/sqs_adapter.rb +0 -58
- data/lib/active_job/queue_adapters/sqs_async_adapter.rb +0 -39
- data/lib/aws/rails/action_mailbox/engine.rb +0 -21
- data/lib/aws/rails/action_mailbox/rspec/email.rb +0 -50
- data/lib/aws/rails/action_mailbox/rspec/subscription_confirmation.rb +0 -37
- data/lib/aws/rails/action_mailbox/rspec.rb +0 -69
- data/lib/aws/rails/action_mailbox/s3_client.rb +0 -28
- data/lib/aws/rails/action_mailbox/sns_message_verifier.rb +0 -18
- data/lib/aws/rails/action_mailbox/sns_notification.rb +0 -99
- data/lib/aws/rails/middleware/ebs_sqs_active_job_middleware.rb +0 -118
- data/lib/aws/rails/ses_mailer.rb +0 -49
- data/lib/aws/rails/sesv2_mailer.rb +0 -60
- data/lib/aws/rails/sqs_active_job/configuration.rb +0 -184
- data/lib/aws/rails/sqs_active_job/deduplication.rb +0 -21
- data/lib/aws/rails/sqs_active_job/executor.rb +0 -77
- data/lib/aws/rails/sqs_active_job/job_runner.rb +0 -27
- data/lib/aws/rails/sqs_active_job/lambda_handler.rb +0 -63
- data/lib/aws/rails/sqs_active_job/poller.rb +0 -160
- data/lib/aws/rails/sqs_active_job.rb +0 -33
- data/lib/generators/aws_record/base.rb +0 -213
- data/lib/generators/aws_record/generated_attribute.rb +0 -138
- data/lib/generators/aws_record/model/USAGE +0 -24
- data/lib/generators/aws_record/model/model_generator.rb +0 -25
- data/lib/generators/aws_record/model/templates/model.erb +0 -48
- data/lib/generators/aws_record/model/templates/table_config.erb +0 -18
- data/lib/generators/aws_record/secondary_index.rb +0 -66
- data/lib/generators/dynamo_db/session_store_migration/USAGE +0 -13
- data/lib/generators/dynamo_db/session_store_migration/session_store_migration_generator.rb +0 -48
- data/lib/generators/dynamo_db/session_store_migration/templates/dynamo_db_session_store.yml +0 -70
- data/lib/generators/dynamo_db/session_store_migration/templates/session_store_migration.erb +0 -9
- data/lib/tasks/aws_record/migrate.rake +0 -14
- data/lib/tasks/dynamo_db/session_store.rake +0 -10
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'concurrent'
|
4
|
-
|
5
|
-
module Aws
|
6
|
-
module Rails
|
7
|
-
module SqsActiveJob
|
8
|
-
# CLI runner for polling for SQS ActiveJobs
|
9
|
-
class Executor
|
10
|
-
DEFAULTS = {
|
11
|
-
min_threads: 0,
|
12
|
-
max_threads: Integer(Concurrent.available_processor_count || Concurrent.processor_count),
|
13
|
-
auto_terminate: true,
|
14
|
-
idletime: 60, # 1 minute
|
15
|
-
fallback_policy: :abort # Concurrent::RejectedExecutionError must be handled
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
def initialize(options = {})
|
19
|
-
@executor = Concurrent::ThreadPoolExecutor.new(DEFAULTS.merge(options))
|
20
|
-
@retry_standard_errors = options[:retry_standard_errors]
|
21
|
-
@logger = options[:logger] || ActiveSupport::Logger.new($stdout)
|
22
|
-
@task_complete = Concurrent::Event.new
|
23
|
-
end
|
24
|
-
|
25
|
-
def execute(message)
|
26
|
-
post_task(message)
|
27
|
-
rescue Concurrent::RejectedExecutionError
|
28
|
-
# no capacity, wait for a task to complete
|
29
|
-
@task_complete.reset
|
30
|
-
@task_complete.wait
|
31
|
-
retry
|
32
|
-
end
|
33
|
-
|
34
|
-
def shutdown(timeout = nil)
|
35
|
-
@executor.shutdown
|
36
|
-
clean_shutdown = @executor.wait_for_termination(timeout)
|
37
|
-
if clean_shutdown
|
38
|
-
@logger.info 'Clean shutdown complete. All executing jobs finished.'
|
39
|
-
else
|
40
|
-
@logger.info "Timeout (#{timeout}) exceeded. Some jobs may not have " \
|
41
|
-
'finished cleanly. Unfinished jobs will not be removed from ' \
|
42
|
-
'the queue and can be ru-run once their visibility timeout ' \
|
43
|
-
'passes.'
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def post_task(message)
|
50
|
-
@executor.post(message) do |message|
|
51
|
-
job = JobRunner.new(message)
|
52
|
-
@logger.info("Running job: #{job.id}[#{job.class_name}]")
|
53
|
-
job.run
|
54
|
-
message.delete
|
55
|
-
rescue Aws::Json::ParseError => e
|
56
|
-
@logger.error "Unable to parse message body: #{message.data.body}. Error: #{e}."
|
57
|
-
rescue StandardError => e
|
58
|
-
job_msg = job ? "#{job.id}[#{job.class_name}]" : 'unknown job'
|
59
|
-
@logger.info "Error processing job #{job_msg}: #{e}"
|
60
|
-
@logger.debug e.backtrace.join("\n")
|
61
|
-
|
62
|
-
if @retry_standard_errors && !job.exception_executions?
|
63
|
-
@logger.info(
|
64
|
-
'retry_standard_errors is enabled and job has not ' \
|
65
|
-
"been retried by Rails. Leaving #{job_msg} in the queue."
|
66
|
-
)
|
67
|
-
else
|
68
|
-
message.delete
|
69
|
-
end
|
70
|
-
ensure
|
71
|
-
@task_complete.set
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Aws
|
4
|
-
module Rails
|
5
|
-
module SqsActiveJob
|
6
|
-
# @api private
|
7
|
-
class JobRunner
|
8
|
-
attr_reader :id, :class_name
|
9
|
-
|
10
|
-
def initialize(message)
|
11
|
-
@job_data = Aws::Json.load(message.data.body)
|
12
|
-
@class_name = @job_data['job_class'].constantize
|
13
|
-
@id = @job_data['job_id']
|
14
|
-
end
|
15
|
-
|
16
|
-
def run
|
17
|
-
ActiveJob::Base.execute @job_data
|
18
|
-
end
|
19
|
-
|
20
|
-
def exception_executions?
|
21
|
-
@job_data['exception_executions'] &&
|
22
|
-
!@job_data['exception_executions'].empty?
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sqs'
|
4
|
-
|
5
|
-
module Aws
|
6
|
-
module Rails
|
7
|
-
# A lambda event handler to run jobs from an SQS queue trigger
|
8
|
-
# Trigger the lambda from your SQS queue
|
9
|
-
# Configure the entrypoint to: +config/environment.Aws::Rails::SqsActiveJob.lambda_job_handler+
|
10
|
-
# This will load your Rails environment, and then use this method as the handler.
|
11
|
-
module SqsActiveJob
|
12
|
-
def self.lambda_job_handler(event:, context:)
|
13
|
-
return 'no records to process' unless event['Records']
|
14
|
-
|
15
|
-
event['Records'].each do |record|
|
16
|
-
sqs_msg = to_sqs_msg(record)
|
17
|
-
job = Aws::Rails::SqsActiveJob::JobRunner.new(sqs_msg)
|
18
|
-
puts("Running job: #{job.id}[#{job.class_name}]")
|
19
|
-
job.run
|
20
|
-
sqs_msg.delete
|
21
|
-
end
|
22
|
-
"Processed #{event['Records'].length} jobs."
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.to_sqs_msg(record)
|
26
|
-
msg = Aws::SQS::Types::Message.new(
|
27
|
-
body: record['body'],
|
28
|
-
md5_of_body: record['md5OfBody'],
|
29
|
-
message_attributes: to_message_attributes(record),
|
30
|
-
message_id: record['messageId'],
|
31
|
-
receipt_handle: record['receiptHandle']
|
32
|
-
)
|
33
|
-
Aws::SQS::Message.new(
|
34
|
-
queue_url: to_queue_url(record),
|
35
|
-
receipt_handle: msg.receipt_handle,
|
36
|
-
data: msg,
|
37
|
-
client: Aws::Rails::SqsActiveJob.config.client
|
38
|
-
)
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.to_message_attributes(record)
|
42
|
-
record['messageAttributes'].transform_values do |value|
|
43
|
-
{
|
44
|
-
string_value: value['stringValue'],
|
45
|
-
binary_value: value['binaryValue'],
|
46
|
-
string_list_values: ['stringListValues'],
|
47
|
-
binary_list_values: value['binaryListValues'],
|
48
|
-
data_type: value['dataType']
|
49
|
-
}
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.to_queue_url(record)
|
54
|
-
source_arn = record['eventSourceARN']
|
55
|
-
raise ArgumentError, "Invalid queue arn: #{source_arn}" unless Aws::ARNParser.arn?(source_arn)
|
56
|
-
|
57
|
-
arn = Aws::ARNParser.parse(source_arn)
|
58
|
-
sfx = Aws::Partitions::EndpointProvider.dns_suffix_for(arn.region)
|
59
|
-
"https://sqs.#{arn.region}.#{sfx}/#{arn.account_id}/#{arn.resource}"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,160 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'aws-sdk-sqs'
|
4
|
-
require 'optparse'
|
5
|
-
require 'concurrent'
|
6
|
-
|
7
|
-
module Aws
|
8
|
-
module Rails
|
9
|
-
module SqsActiveJob
|
10
|
-
class Interrupt < StandardError; end
|
11
|
-
|
12
|
-
# CLI runner for polling for SQS ActiveJobs
|
13
|
-
# Use `aws_sqs_active_job --help` for detailed usage
|
14
|
-
class Poller
|
15
|
-
DEFAULT_OPTS = {
|
16
|
-
threads: 2 * Concurrent.processor_count,
|
17
|
-
max_messages: 10,
|
18
|
-
shutdown_timeout: 15,
|
19
|
-
backpressure: 10,
|
20
|
-
retry_standard_errors: true
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
def initialize(args = ARGV)
|
24
|
-
@options = parse_args(args)
|
25
|
-
# Set_environment must be run before we boot_rails
|
26
|
-
set_environment
|
27
|
-
end
|
28
|
-
|
29
|
-
def set_environment
|
30
|
-
@environment = @options[:environment] || ENV['APP_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
31
|
-
end
|
32
|
-
|
33
|
-
def run
|
34
|
-
# exit 0
|
35
|
-
boot_rails
|
36
|
-
|
37
|
-
# cannot load config (from file or initializers) until after
|
38
|
-
# rails has been booted.
|
39
|
-
@options = DEFAULT_OPTS
|
40
|
-
.merge(Aws::Rails::SqsActiveJob.config.to_h)
|
41
|
-
.merge(@options.to_h)
|
42
|
-
validate_config
|
43
|
-
# ensure we have a logger configured
|
44
|
-
@logger = @options[:logger] || ActiveSupport::Logger.new($stdout)
|
45
|
-
@logger.info("Starting Poller with options=#{@options}")
|
46
|
-
|
47
|
-
Signal.trap('INT') { raise Interrupt }
|
48
|
-
Signal.trap('TERM') { raise Interrupt }
|
49
|
-
@executor = Executor.new(
|
50
|
-
max_threads: @options[:threads],
|
51
|
-
logger: @logger,
|
52
|
-
max_queue: @options[:backpressure],
|
53
|
-
retry_standard_errors: @options[:retry_standard_errors]
|
54
|
-
)
|
55
|
-
|
56
|
-
poll
|
57
|
-
rescue Interrupt
|
58
|
-
@logger.info 'Process Interrupted or killed - attempting to shutdown cleanly.'
|
59
|
-
shutdown
|
60
|
-
exit
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
def shutdown
|
66
|
-
@executor.shutdown(@options[:shutdown_timeout])
|
67
|
-
end
|
68
|
-
|
69
|
-
def poll
|
70
|
-
queue_url = Aws::Rails::SqsActiveJob.config.queue_url_for(@options[:queue])
|
71
|
-
@logger.info "Polling on: #{@options[:queue]} => #{queue_url}"
|
72
|
-
client = Aws::Rails::SqsActiveJob.config.client
|
73
|
-
@poller = Aws::SQS::QueuePoller.new(queue_url, client: client)
|
74
|
-
poller_options = {
|
75
|
-
skip_delete: true,
|
76
|
-
max_number_of_messages: @options[:max_messages],
|
77
|
-
visibility_timeout: @options[:visibility_timeout]
|
78
|
-
}
|
79
|
-
# Limit max_number_of_messages for FIFO queues to 1
|
80
|
-
# this ensures jobs with the same message_group_id are processed
|
81
|
-
# in order
|
82
|
-
# Jobs with different message_group_id will be processed in
|
83
|
-
# parallel and may be out of order.
|
84
|
-
poller_options[:max_number_of_messages] = 1 if Aws::Rails::SqsActiveJob.fifo?(queue_url)
|
85
|
-
|
86
|
-
single_message = poller_options[:max_number_of_messages] == 1
|
87
|
-
|
88
|
-
@poller.poll(poller_options) do |msgs|
|
89
|
-
msgs = [msgs] if single_message
|
90
|
-
@logger.info "Processing batch of #{msgs.length} messages"
|
91
|
-
msgs.each do |msg|
|
92
|
-
@executor.execute(Aws::SQS::Message.new(
|
93
|
-
queue_url: queue_url,
|
94
|
-
receipt_handle: msg.receipt_handle,
|
95
|
-
data: msg,
|
96
|
-
client: client
|
97
|
-
))
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def boot_rails
|
103
|
-
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = @environment
|
104
|
-
require 'rails'
|
105
|
-
require File.expand_path('config/environment.rb')
|
106
|
-
end
|
107
|
-
|
108
|
-
# rubocop:disable Metrics
|
109
|
-
def parse_args(argv)
|
110
|
-
out = {}
|
111
|
-
parser = ::OptionParser.new do |opts|
|
112
|
-
opts.on('-q', '--queue STRING', '[Required] Queue to poll') { |a| out[:queue] = a }
|
113
|
-
opts.on('-e', '--environment STRING',
|
114
|
-
'Rails environment (defaults to development). You can also use the APP_ENV or RAILS_ENV environment variables to specify the environment.') do |a|
|
115
|
-
out[:environment] = a
|
116
|
-
end
|
117
|
-
opts.on('-t', '--threads INTEGER', Integer,
|
118
|
-
'The maximum number of worker threads to create. Defaults to 2x the number of processors available on this system.') do |a|
|
119
|
-
out[:threads] = a
|
120
|
-
end
|
121
|
-
opts.on('-b', '--backpressure INTEGER', Integer,
|
122
|
-
'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|
|
123
|
-
out[:backpressure] = a
|
124
|
-
end
|
125
|
-
opts.on('-m', '--max_messages INTEGER', Integer,
|
126
|
-
'Max number of messages to receive in a batch from SQS.') do |a|
|
127
|
-
out[:max_messages] = a
|
128
|
-
end
|
129
|
-
opts.on('-v', '--visibility_timeout INTEGER', Integer,
|
130
|
-
'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|
|
131
|
-
out[:visibility_timeout] = a
|
132
|
-
end
|
133
|
-
opts.on('-s', '--shutdown_timeout INTEGER', Integer,
|
134
|
-
'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|
|
135
|
-
out[:shutdown_timeout] = a
|
136
|
-
end
|
137
|
-
opts.on('--[no-]retry_standard_errors [FLAG]', TrueClass,
|
138
|
-
'When set, retry all StandardErrors (leaving failed messages on the SQS Queue). These retries are ON TOP of standard Rails ActiveJob retries set by retry_on in the ActiveJob.') do |a|
|
139
|
-
out[:retry_standard_errors] = a.nil? ? true : a
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
parser.banner = 'aws_sqs_active_job [options]'
|
144
|
-
parser.on_tail '-h', '--help', 'Show help' do
|
145
|
-
puts parser
|
146
|
-
exit 1
|
147
|
-
end
|
148
|
-
|
149
|
-
parser.parse(argv)
|
150
|
-
out
|
151
|
-
end
|
152
|
-
# rubocop:enable Metrics
|
153
|
-
|
154
|
-
def validate_config
|
155
|
-
raise ArgumentError, 'You must specify the name of the queue to process jobs from' unless @options[:queue]
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../../active_job/queue_adapters/sqs_adapter'
|
4
|
-
require_relative '../../active_job/queue_adapters/sqs_adapter/params'
|
5
|
-
require_relative '../../active_job/queue_adapters/sqs_async_adapter'
|
6
|
-
require_relative 'sqs_active_job/configuration'
|
7
|
-
require_relative 'sqs_active_job/deduplication'
|
8
|
-
require_relative 'sqs_active_job/executor'
|
9
|
-
require_relative 'sqs_active_job/job_runner'
|
10
|
-
require_relative 'sqs_active_job/lambda_handler'
|
11
|
-
|
12
|
-
module Aws
|
13
|
-
module Rails
|
14
|
-
# == AWS SQS ActiveJob.
|
15
|
-
#
|
16
|
-
# SQS-based queuing backend for Active Job.
|
17
|
-
module SqsActiveJob
|
18
|
-
# @return [Configuration] the (singleton) Configuration
|
19
|
-
def self.config
|
20
|
-
@config ||= Configuration.new
|
21
|
-
end
|
22
|
-
|
23
|
-
# @yield Configuration
|
24
|
-
def self.configure
|
25
|
-
yield(config)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.fifo?(queue_url)
|
29
|
-
queue_url.ends_with? '.fifo'
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,213 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails/generators'
|
4
|
-
require_relative 'generated_attribute'
|
5
|
-
require_relative 'secondary_index'
|
6
|
-
|
7
|
-
module AwsRecord
|
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
|
53
|
-
|
54
|
-
def parse_attributes!
|
55
|
-
self.attributes = (attributes || []).map do |attr|
|
56
|
-
GeneratedAttribute.parse(attr)
|
57
|
-
rescue ArgumentError => e
|
58
|
-
@parse_errors << e
|
59
|
-
next
|
60
|
-
end
|
61
|
-
self.attributes = attributes.compact
|
62
|
-
|
63
|
-
if options['password_digest']
|
64
|
-
attributes << GeneratedAttribute.new('password_digest', :string_attr, digest: true)
|
65
|
-
end
|
66
|
-
|
67
|
-
return unless options['timestamps']
|
68
|
-
|
69
|
-
attributes << GeneratedAttribute.parse('created:datetime:default_value{Time.now}')
|
70
|
-
attributes << GeneratedAttribute.parse('updated:datetime:default_value{Time.now}')
|
71
|
-
end
|
72
|
-
|
73
|
-
def ensure_unique_fields
|
74
|
-
used_names = Set.new
|
75
|
-
duplicate_fields = []
|
76
|
-
|
77
|
-
attributes.each do |attr|
|
78
|
-
duplicate_fields << [:attribute, attr.name] if used_names.include? attr.name
|
79
|
-
used_names.add attr.name
|
80
|
-
|
81
|
-
next unless attr.options.key? :database_attribute_name
|
82
|
-
|
83
|
-
raw_db_attr_name = attr.options[:database_attribute_name].delete('"') # db attribute names are wrapped with " to make template generation easier
|
84
|
-
|
85
|
-
duplicate_fields << [:database_attribute_name, raw_db_attr_name] if used_names.include? raw_db_attr_name
|
86
|
-
|
87
|
-
used_names.add raw_db_attr_name
|
88
|
-
end
|
89
|
-
|
90
|
-
return if duplicate_fields.empty?
|
91
|
-
|
92
|
-
duplicate_fields.each do |invalid_attr|
|
93
|
-
@parse_errors << ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def ensure_hkey
|
98
|
-
uuid_member = nil
|
99
|
-
hkey_member = nil
|
100
|
-
rkey_member = nil
|
101
|
-
|
102
|
-
attributes.each do |attr|
|
103
|
-
if attr.options.key? :hash_key
|
104
|
-
if hkey_member
|
105
|
-
@parse_errors << ArgumentError.new("Redefinition of hash_key attr: #{attr.name}, original declaration of hash_key on: #{hkey_member.name}")
|
106
|
-
next
|
107
|
-
end
|
108
|
-
|
109
|
-
hkey_member = attr
|
110
|
-
elsif attr.options.key? :range_key
|
111
|
-
if rkey_member
|
112
|
-
@parse_errors << ArgumentError.new("Redefinition of range_key attr: #{attr.name}, original declaration of range_key on: #{hkey_member.name}")
|
113
|
-
next
|
114
|
-
end
|
115
|
-
|
116
|
-
rkey_member = attr
|
117
|
-
end
|
118
|
-
|
119
|
-
uuid_member = attr if attr.name.include? 'uuid'
|
120
|
-
end
|
121
|
-
|
122
|
-
return if hkey_member
|
123
|
-
|
124
|
-
if uuid_member
|
125
|
-
uuid_member.options[:hash_key] = true
|
126
|
-
else
|
127
|
-
attributes.unshift GeneratedAttribute.parse('uuid:hkey')
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def mutation_tracking_disabled?
|
132
|
-
options['disable_mutation_tracking']
|
133
|
-
end
|
134
|
-
|
135
|
-
def has_validations?
|
136
|
-
!@required_attrs.empty? || !@length_validations.empty?
|
137
|
-
end
|
138
|
-
|
139
|
-
def parse_table_config!
|
140
|
-
return unless options['table_config']
|
141
|
-
|
142
|
-
@primary_read_units, @primary_write_units = parse_rw_units('primary')
|
143
|
-
|
144
|
-
@gsi_rw_units = @gsis.to_h do |idx|
|
145
|
-
[idx.name, parse_rw_units(idx.name)]
|
146
|
-
end
|
147
|
-
|
148
|
-
options['table_config'].each_key do |config|
|
149
|
-
next if config == 'primary'
|
150
|
-
|
151
|
-
gsi = @gsis.select { |idx| idx.name == config }
|
152
|
-
|
153
|
-
@parse_errors << ArgumentError.new("Could not find a gsi declaration for #{config}") if gsi.empty?
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def parse_rw_units(name)
|
158
|
-
if options['table_config'].key? name
|
159
|
-
rw_units = options['table_config'][name]
|
160
|
-
rw_units.gsub(/[,.-]/, ':').split(':').reject(&:empty?)
|
161
|
-
else
|
162
|
-
@parse_errors << ArgumentError.new("Please provide a table_config definition for #{name}")
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def parse_gsis!
|
167
|
-
@gsis = (options['gsi'] || []).map do |raw_idx|
|
168
|
-
idx = SecondaryIndex.parse(raw_idx)
|
169
|
-
|
170
|
-
attributes = self.attributes.select { |attr| attr.name == idx.hash_key }
|
171
|
-
if attributes.empty?
|
172
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.hash_key} for gsi #{idx.name} hkey")
|
173
|
-
next
|
174
|
-
end
|
175
|
-
|
176
|
-
if idx.range_key
|
177
|
-
attributes = self.attributes.select { |attr| attr.name == idx.range_key }
|
178
|
-
if attributes.empty?
|
179
|
-
@parse_errors << ArgumentError.new("Could not find attribute #{idx.range_key} for gsi #{idx.name} rkey")
|
180
|
-
next
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
idx
|
185
|
-
rescue ArgumentError => e
|
186
|
-
@parse_errors << e
|
187
|
-
next
|
188
|
-
end
|
189
|
-
|
190
|
-
@gsis = @gsis.compact
|
191
|
-
end
|
192
|
-
|
193
|
-
def parse_validations!
|
194
|
-
@required_attrs = options['required'] ? options['required'].split(',') : []
|
195
|
-
@required_attrs.each do |val_attr|
|
196
|
-
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if attributes.none? do |attr|
|
197
|
-
attr.name == val_attr
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
@length_validations = options['length_validations'].map do |val_attr, bounds|
|
202
|
-
@parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if attributes.none? do |attr|
|
203
|
-
attr.name == val_attr
|
204
|
-
end
|
205
|
-
|
206
|
-
bounds = bounds.gsub(/[,.-]/, ':').split(':').reject(&:empty?)
|
207
|
-
[val_attr, "#{bounds[0]}..#{bounds[1]}"]
|
208
|
-
end
|
209
|
-
@length_validations = @length_validations.to_h
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|