aws-sdk-rails 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5157818d7cbed8369158102676415247809f388f88f255890c513705075193eb
4
- data.tar.gz: 792018ac311693ce6afa82b8ae1c7547bf858a90959558f2547dcfc173cefdbe
3
+ metadata.gz: 79de6d1c1c8319fd70a36949eebb7630630bbd9b8a566b6f473d51590e48af38
4
+ data.tar.gz: a7d383cfecc470333c998f61ca380330dac77dc13dc5d302a5ba6bb34f8e5a81
5
5
  SHA512:
6
- metadata.gz: 7c0821bb9dffd184a6f21c51bc6ed5a000dd1a1f5d0a435ccd5d36668f41d95f2be3e834482f20216b83edfac47d763c25d100386efcb87b9e8a64185b2644e9
7
- data.tar.gz: be49eeaf3fa701769204a924dcfef6982360dc15387266a5d0e8ae8f8c1c44afb0b383669531799f8daabe2af65372ac37a7dbdf671ee9ba522ecd141a0f0ab8
6
+ metadata.gz: 95e0b8f6e3e565b6aeba24955bdbacff0a280de8c7f8a976a75ebadfd28710fc47c34c4fc079d95bb7d2678f60c185ec158719864eee0f8935424d4adb99d2e1
7
+ data.tar.gz: 95c7da0d1ac045139de0e1b4f251b1a118cadfa62a4eef3bfe8925df0e914325f7523fb3009cc3bb433828d4ea916f0c1e12b919a689931773317492a6811156
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.8.0
1
+ 3.9.0
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require_relative '../lib/aws/rails/sqs_active_job/poller'
4
5
 
@@ -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 config_file.exist?
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 file.exist?
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,9 +4,7 @@ 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
@@ -14,6 +12,7 @@ module ActiveJob
14
12
  def enqueue_at(job, timestamp)
15
13
  delay = (timestamp - Time.now.to_f).floor
16
14
  raise ArgumentError, 'Unable to queue a job with a delay great than 15 minutes' if delay > 15.minutes
15
+
17
16
  _enqueue(job, nil, delay_seconds: delay)
18
17
  end
19
18
 
@@ -27,11 +26,8 @@ module ActiveJob
27
26
  send_message_opts[:message_attributes] = message_attributes(job)
28
27
 
29
28
  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
29
  send_message_opts[:message_deduplication_id] =
34
- Digest::SHA256.hexdigest(Aws::Json.dump(body.except('job_id')))
30
+ Digest::SHA256.hexdigest(Aws::Json.dump(deduplication_body(job, body)))
35
31
 
36
32
  message_group_id = job.message_group_id if job.respond_to?(:message_group_id)
37
33
  message_group_id ||= Aws::Rails::SqsActiveJob.config.message_group_id
@@ -54,6 +50,13 @@ module ActiveJob
54
50
  }
55
51
  }
56
52
  end
53
+
54
+ def deduplication_body(job, body)
55
+ ex_dedup_keys = job.excluded_deduplication_keys if job.respond_to?(:excluded_deduplication_keys)
56
+ ex_dedup_keys ||= Aws::Rails::SqsActiveJob.config.excluded_deduplication_keys
57
+
58
+ body.except(*ex_dedup_keys)
59
+ end
57
60
  end
58
61
 
59
62
  # 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: 10,
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
- # Don't use this method directly: Confugration is a singleton class, use
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. If not specified will
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. A default
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 config_file.exist?
93
+ options[:config_file] ||= config_file if File.exist?(config_file)
88
94
  options = DEFAULTS
89
- .merge(file_options(options))
90
- .merge(options)
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
- self.instance_variables.each do |v|
128
+ instance_variables.each do |v|
119
129
  v_sym = v.to_s.gsub('@', '').to_sym
120
- val = self.instance_variable_get(v)
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.keys.each do |opt_name|
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 file.exist?
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["AWS_SQS_ACTIVE_JOB_CONFIG_FILE"]
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 "erb"
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
- min_threads: 0,
13
- max_threads: Concurrent.processor_count,
14
- auto_terminate: true,
15
- idletime: 60, # 1 minute
16
- fallback_policy: :caller_runs # slow down the producer thread
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(STDOUT)
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
- " finished cleanly. Unfinished jobs will not be removed from"\
51
- " the queue and can be ru-run once their visibility timeout"\
52
- " passes."
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
@@ -3,7 +3,6 @@
3
3
  module Aws
4
4
  module Rails
5
5
  module SqsActiveJob
6
-
7
6
  class JobRunner
8
7
  attr_reader :id, :class_name
9
8
 
@@ -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: self.to_message_attributes(record),
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["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
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(STDOUT)
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
- queue_url: queue_url,
93
- receipt_handle: msg.receipt_handle,
94
- data: msg,
95
- client: client
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 "rails"
104
- require File.expand_path("config/environment.rb")
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 { |opts|
110
- opts.on("-q", "--queue STRING", "[Required] Queue to poll") { |a| out[:queue] = a }
111
- opts.on("-e", "--environment STRING", "Rails environment (defaults to development). You can also use the APP_ENV or RAILS_ENV environment variables to specify the environment.") { |a| out[:environment] = a }
112
- opts.on("-t", "--threads INTEGER", Integer, "The maximum number of worker threads to create. Defaults to 2x the number of processors available on this system.") { |a| out[:threads] = a }
113
- opts.on("-b", "--backpressure INTEGER", Integer, "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.") { |a| out[:backpressure] = a }
114
- opts.on("-m", "--max_messages INTEGER", Integer, "Max number of messages to receive in a batch from SQS.") { |a| out[:max_messages] = a }
115
- opts.on("-v", "--visibility_timeout INTEGER", Integer, "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.") { |a| out[:visibility_timeout] = a }
116
- opts.on("-s", "--shutdown_timeout INTEGER", Integer, "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.") { |a| out[:shutdown_timeout] = a }
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 = "aws_sqs_active_job [options]"
120
- parser.on_tail "-h", "--help", "Show help" do
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
- module Generators
7
- class Base < Rails::Generators::NamedBase
8
- argument :attributes, type: :array, default: [], banner: "field[:type][:opts]...", desc: "Describes the fields in the model"
9
- check_class_collision
10
-
11
- class_option :disable_mutation_tracking, type: :boolean, desc: "Disables dirty tracking"
12
- class_option :timestamps, type: :boolean, desc: "Adds created, updated timestamps to the model"
13
- class_option :table_config, type: :hash, default: {}, banner: "primary:R-W [SecondaryIndex1:R-W]...", desc: "Declares the r/w units for the model as well as any secondary indexes", :required => true
14
- class_option :gsi, type: :array, default: [], banner: "name:hkey{field_name}[,rkey{field_name},proj_type{ALL|KEYS_ONLY|INCLUDE}]...", desc: "Allows for the declaration of secondary indexes"
15
- class_option :table_name, type: :string, banner: "model_table_name"
16
- class_option :password_digest, type: :boolean, desc: "Whether to add a password_digest field to the model"
17
-
18
- class_option :required, type: :string, banner: "field1...", desc: "A list of attributes that are required for an instance of the model"
19
- class_option :length_validations, type: :hash, default: {}, banner: "field1:MIN-MAX...", desc: "Validations on the length of attributes in a model"
20
-
21
- attr_accessor :primary_read_units, :primary_write_units, :gsi_rw_units, :gsis, :required_attrs, :length_validations
22
-
23
- private
24
-
25
- def initialize(args, *options)
26
- options[0] << "--skip-table-config" if options[1][:behavior] == :revoke
27
- @parse_errors = []
28
-
29
- super
30
- ensure_unique_fields
31
- ensure_hkey
32
- parse_gsis!
33
- parse_table_config!
34
- parse_validations!
35
-
36
- if !@parse_errors.empty?
37
- STDERR.puts "The following errors were encountered while trying to parse the given attributes"
38
- STDERR.puts
39
- STDERR.puts @parse_errors
40
- STDERR.puts
41
-
42
- abort("Please fix the errors before proceeding.")
43
- end
44
- end
45
-
46
- def parse_attributes!
47
-
48
- self.attributes = (attributes || []).map do |attr|
49
- begin
50
- GeneratedAttribute.parse(attr)
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
- if options['password_digest']
59
- self.attributes << GeneratedAttribute.new("password_digest", :string_attr, :digest => true)
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
- if options['timestamps']
63
- self.attributes << GeneratedAttribute.parse("created:datetime:default_value{Time.now}")
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
- def ensure_unique_fields
69
- used_names = Set.new
70
- duplicate_fields = []
69
+ return unless options['timestamps']
71
70
 
72
- self.attributes.each do |attr|
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
- if used_names.include? attr.name
75
- duplicate_fields << [:attribute, attr.name]
76
- end
77
- used_names.add attr.name
75
+ def ensure_unique_fields
76
+ used_names = Set.new
77
+ duplicate_fields = []
78
78
 
79
- if attr.options.key? :database_attribute_name
80
- raw_db_attr_name = attr.options[:database_attribute_name].delete('"') # db attribute names are wrapped with " to make template generation easier
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
- if used_names.include? raw_db_attr_name
83
- duplicate_fields << [:database_attribute_name, raw_db_attr_name]
84
- end
83
+ next unless attr.options.key? :database_attribute_name
85
84
 
86
- used_names.add raw_db_attr_name
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 !duplicate_fields.empty?
91
- duplicate_fields.each do |invalid_attr|
92
- @parse_errors << ArgumentError.new("Found duplicated field name: #{invalid_attr[1]}, in attribute#{invalid_attr[0]}")
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
- def ensure_hkey
98
- uuid_member = nil
99
- hkey_member = nil
100
- rkey_member = nil
92
+ return if duplicate_fields.empty?
101
93
 
102
- self.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
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
- 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
99
+ def ensure_hkey
100
+ uuid_member = nil
101
+ hkey_member = nil
102
+ rkey_member = nil
115
103
 
116
- rkey_member = attr
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
- if attr.name.include? "uuid"
120
- uuid_member = attr
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
- if !hkey_member
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
- def mutation_tracking_disabled?
134
- options['disable_mutation_tracking']
121
+ uuid_member = attr if attr.name.include? 'uuid'
135
122
  end
136
123
 
137
- def has_validations?
138
- !@required_attrs.empty? || !@length_validations.empty?
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
- def parse_table_config!
142
- return unless options['table_config']
133
+ def mutation_tracking_disabled?
134
+ options['disable_mutation_tracking']
135
+ end
143
136
 
144
- @primary_read_units, @primary_write_units = parse_rw_units("primary")
137
+ def has_validations?
138
+ !@required_attrs.empty? || !@length_validations.empty?
139
+ end
145
140
 
146
- @gsi_rw_units = @gsis.map { |idx|
147
- [idx.name, parse_rw_units(idx.name)]
148
- }.to_h
141
+ def parse_table_config!
142
+ return unless options['table_config']
149
143
 
150
- options['table_config'].each do |config, rw_units|
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
- if gsi.empty?
157
- @parse_errors << ArgumentError.new("Could not find a gsi declaration for #{config}")
158
- end
159
- end
160
- end
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
- def parse_rw_units(name)
164
- if !options['table_config'].key? name
165
- @parse_errors << ArgumentError.new("Please provide a table_config definition for #{name}")
166
- else
167
- rw_units = options['table_config'][name]
168
- return rw_units.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
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
- def parse_gsis!
173
- @gsis = (options['gsi'] || []).map do |raw_idx|
174
- begin
175
- idx = SecondaryIndex.parse(raw_idx)
168
+ def parse_gsis!
169
+ @gsis = (options['gsi'] || []).map do |raw_idx|
170
+ begin
171
+ idx = SecondaryIndex.parse(raw_idx)
176
172
 
177
- attributes = self.attributes.select { |attr| attr.name == idx.hash_key}
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.hash_key} for gsi #{idx.name} hkey")
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
- @gsis = @gsis.compact
187
+ idx
188
+ rescue ArgumentError => e
189
+ @parse_errors << e
190
+ next
191
+ end
199
192
  end
200
193
 
201
- def parse_validations!
202
- @required_attrs = options['required'] ? options['required'].split(',') : []
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
- @length_validations = options['length_validations'].map do |val_attr, bounds|
208
- @parse_errors << ArgumentError.new("No such field #{val_attr} in required validations") if !self.attributes.any? { |attr| attr.name == val_attr }
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
- bounds = bounds.gsub(/[,.-]/, ':').split(':').reject { |s| s.empty? }
211
- [val_attr, "#{bounds[0]}..#{bounds[1]}"]
212
- end
213
- @length_validations = @length_validations.to_h
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
- OPTS = %w(hkey rkey persist_nil db_attr_name ddb_type default_value)
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
- when :integer_attr then :number_field
13
- when :date_attr then :date_select
14
- when :datetime_attr then :datetime_select
15
- when :boolean_attr then :check_box
16
- else :text_field
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 = "string" if not type
25
- type, opts = "string", type if OPTS.any? { |opt| type.include? opt }
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
- if opts
38
- is_hkey = opts.key?(:hash_key)
39
- is_rkey = opts.key?(:range_key)
40
+ return unless opts
41
+
42
+ is_hkey = opts.key?(:hash_key)
43
+ is_rkey = opts.key?(:range_key)
40
44
 
41
- raise ArgumentError.new("Field #{name} cannot be a range key and hash key simultaneously") if is_hkey && is_rkey
42
- raise ArgumentError.new("Field #{name} cannot be a hash key and be of type #{type}") if is_hkey and INVALID_HKEY_TYPES.include? type
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 = [] if not opts
48
- return parse_type(name, type), opts.map { |opt| parse_option(name, opt) }.to_h
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 "hkey"
55
- return :hash_key, true
56
- when "rkey"
57
- return :range_key, true
58
- when "persist_nil"
59
- return :persist_nil, true
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
- return :database_attribute_name, '"' + $1 + '"'
70
+ [:database_attribute_name, "\"#{::Regexp.last_match(1)}\""]
62
71
  when /ddb_type\{(S|N|B|BOOL|SS|NS|BS|M|L)\}/i
63
- return :dynamodb_type, '"' + $1.upcase + '"'
72
+ [:dynamodb_type, "\"#{::Regexp.last_match(1).upcase}\""]
64
73
  when /default_value\{(.+)\}/
65
- return :default_value, $1
74
+ [:default_value, ::Regexp.last_match(1)]
66
75
  else
67
- raise ArgumentError.new("You provided an invalid option for #{name}: #{opt}")
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 "bool", "boolean"
83
+ when 'bool', 'boolean'
75
84
  :boolean_attr
76
- when "date"
85
+ when 'date'
77
86
  :date_attr
78
- when "datetime"
87
+ when 'datetime'
79
88
  :datetime_attr
80
- when "float"
89
+ when 'float'
81
90
  :float_attr
82
- when "int", "integer"
91
+ when 'int', 'integer'
83
92
  :integer_attr
84
- when "list"
93
+ when 'list'
85
94
  :list_attr
86
- when "map"
95
+ when 'map'
87
96
  :map_attr
88
- when "num_set", "numeric_set", "nset"
97
+ when 'num_set', 'numeric_set', 'nset'
89
98
  :numeric_set_attr
90
- when "string_set", "s_set", "sset"
99
+ when 'string_set', 's_set', 'sset'
91
100
  :string_set_attr
92
- when "string"
101
+ when 'string'
93
102
  :string_attr
94
103
  else
95
- raise ArgumentError.new("Invalid type for #{name}: #{type}")
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 == "password_digest"
118
- "password"
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('../templates', __FILE__)
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 "model.erb", File.join("app/models", class_path, "#{file_name}.rb")
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
- template "table_config.erb", File.join("db/table_config", class_path, "#{file_name}_config.rb") if options["table_config"]
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
- def get_option_value(raw_option)
24
- case raw_option
25
-
26
- when /hkey\{(\w+)\}/
27
- return :hash_key, $1
28
- when /rkey\{(\w+)\}/
29
- return :range_key, $1
30
- when /proj_type\{(\w+)\}/
31
- return :projection_type, $1
32
- else
33
- raise ArgumentError.new("Invalid option for secondary index #{raw_option}")
34
- end
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.new("You must provide a name") if not name
40
- raise ArgumentError.new("You must provide a hash key") if not opts[:hash_key]
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
- raise ArgumentError.new("Invalid projection type #{opts[:projection_type]}") if not PROJ_TYPES.include? opts[:projection_type]
44
- raise NotImplementedError.new("ALL is the only projection type currently supported") if opts[:projection_type] != "ALL"
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] = "ALL"
52
+ opts[:projection_type] = 'ALL'
47
53
  end
48
54
 
49
55
  if opts[:hash_key] == opts[:range_key]
50
- raise ArgumentError.new("#{opts[:hash_key]} cannot be both the rkey and hkey for gsi #{name}")
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 = '"' + "#{opts[:projection_type]}" + '"'
62
+ @projection_type = "\"#{opts[:projection_type]}\""
57
63
  end
58
64
  end
59
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators/named_base'
2
4
 
3
5
  # This class generates a migration file for deleting and creating
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  desc 'Run all table configs in table_config folder'
2
4
  namespace :aws_record do
3
5
  task migrate: :environment do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace 'dynamo_db' do
2
4
  namespace 'session_store' do
3
5
  desc 'Clean up old sessions in the Amazon DynamoDB session store table.'
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.8.0
4
+ version: 3.9.0
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-06-02 00:00:00.000000000 Z
11
+ date: 2023-09-28 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: railties
102
+ name: concurrent-ruby
103
103
  requirement: !ruby/object:Gem::Requirement
104
104
  requirements:
105
- - - ">="
105
+ - - "~>"
106
106
  - !ruby/object:Gem::Version
107
- version: 5.2.0
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: 5.2.0
114
+ version: '1'
115
115
  - !ruby/object:Gem::Dependency
116
- name: concurrent-ruby
116
+ name: railties
117
117
  requirement: !ruby/object:Gem::Requirement
118
118
  requirements:
119
- - - "~>"
119
+ - - ">="
120
120
  - !ruby/object:Gem::Version
121
- version: '1'
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: '1'
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
- - mamuller@amazon.com
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,7 +190,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
190
190
  requirements:
191
191
  - - ">="
192
192
  - !ruby/object:Gem::Version
193
- version: '0'
193
+ version: '2.3'
194
194
  required_rubygems_version: !ruby/object:Gem::Requirement
195
195
  requirements:
196
196
  - - ">="