Class: Aws::Record::TableConfig

Inherits:
Object
  • Object
show all
Defined in:
lib/aws-record/record/table_config.rb

Overview

Aws::Record::TableConfig provides a DSL for describing and modifying the remote configuration of your DynamoDB tables. A table configuration object can perform intelligent comparisons and incremental migrations versus the current remote configuration, if the table exists, or do a full create if it does not. In this manner, table configuration becomes fully declarative.

Examples:

A basic model with configuration.

class Model
  include Aws::Record
  string_attr :uuid, hash_key: true
end

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class Model
  t.read_capacity_units 10
  t.write_capacity_units 5
end

A basic model with pay per request billing.

class Model
  include Aws::Record
  string_attr :uuid, hash_key: true
end

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class Model
  t.billing_mode "PAY_PER_REQUEST"
end

Running a conditional migration on a basic model.

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class Model
  t.read_capacity_units 10
  t.write_capacity_units 5
end

table_config.migrate! unless table_config.compatible?

A model with a global secondary index.

class Forum
  include Aws::Record
  string_attr     :forum_uuid, hash_key: true
  integer_attr    :post_id,    range_key: true
  string_attr     :post_title
  string_attr     :post_body
  string_attr     :author_username
  datetime_attr   :created_date
  datetime_attr   :updated_date
  string_set_attr :tags
  map_attr        :metadata, default_value: {}

  global_secondary_index(
    :title,
    hash_key:  :forum_uuid,
    range_key: :post_title,
    projection: {
      projection_type: "ALL"
    }
  )
end

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class Forum
  t.read_capacity_units 10
  t.write_capacity_units 5

  t.global_secondary_index(:title) do |i|
    i.read_capacity_units 5
    i.write_capacity_units 5
  end
end

A model with a Time to Live attribute

class ExpiringTokens
  string_attr :token_uuid, hash_key: true
  epoch_time_attr :ttl
end

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class ExpiringTokens
  t.read_capacity_units 10
  t.write_capacity_units 1
  t.ttl_attribute :ttl
end

Defined Under Namespace

Classes: GlobalSecondaryIndex

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTableConfig

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of TableConfig.



158
159
160
161
162
# File 'lib/aws-record/record/table_config.rb', line 158

def initialize
  @client_options = {}
  @global_secondary_indexes = {}
  @billing_mode = "PROVISIONED" # default
end

Instance Attribute Details

#clientObject

Returns the value of attribute client.



94
95
96
# File 'lib/aws-record/record/table_config.rb', line 94

def client
  @client
end

Class Method Details

.define(&block) ⇒ Object

Creates a new table configuration, using a DSL in the provided block. The DSL has the following methods:

  • #model_class A class name reference to the Aws::Record model class.

  • #read_capacity_units Sets the read capacity units for the table.

  • #write_capacity_units Sets the write capacity units for the table.

  • #global_secondary_index(index_symbol, &block) Defines a global secondary index with capacity attributes in a block:

    • #read_capacity_units Sets the read capacity units for the index.

    • #write_capacity_units Sets the write capacity units for the index.

  • #ttl_attribute Sets the attribute ID to be used as the TTL attribute, and if present, TTL will be enabled for the table.

  • #billing_mode Sets the billing mode, with the current supported options being “PROVISIONED” and “PAY_PER_REQUEST”. If using “PAY_PER_REQUEST” you must not set provisioned throughput values, and if using “PROVISIONED” you must set provisioned throughput values. Default assumption is “PROVISIONED”.

Examples:

Defining a migration with a GSI.

class Forum
  include Aws::Record
  string_attr     :forum_uuid, hash_key: true
  integer_attr    :post_id,    range_key: true
  string_attr     :post_title
  string_attr     :post_body
  string_attr     :author_username
  datetime_attr   :created_date
  datetime_attr   :updated_date
  string_set_attr :tags
  map_attr        :metadata, default_value: {}

  global_secondary_index(
    :title,
    hash_key:  :forum_uuid,
    range_key: :post_title,
    projection_type: "ALL"
  )
end

table_config = Aws::Record::TableConfig.define do |t|
  t.model_class Forum
  t.read_capacity_units 10
  t.write_capacity_units 5

  t.global_secondary_index(:title) do |i|
    i.read_capacity_units 5
    i.write_capacity_units 5
  end
end


149
150
151
152
153
154
# File 'lib/aws-record/record/table_config.rb', line 149

def define(&block)
  cfg = TableConfig.new
  cfg.instance_eval(&block)
  cfg.configure_client
  cfg
end

Instance Method Details

#billing_mode(mode) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



207
208
209
# File 'lib/aws-record/record/table_config.rb', line 207

def billing_mode(mode)
  @billing_mode = mode
end

#client_options(opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



187
188
189
# File 'lib/aws-record/record/table_config.rb', line 187

def client_options(opts)
  @client_options = opts
end

#compatible?Boolean

Checks the remote table for compatibility. Similar to #exact_match?, this will return false if the remote table does not exist. It also checks the keys, declared global secondary indexes, declared attribute definitions, and throughput for exact matches. However, if the remote end has additional attribute definitions and global secondary indexes not defined in your config, will still return true. This allows for a check that is friendly to single table inheritance use cases.

Returns:

  • (Boolean)

    true if remote is compatible, false otherwise.



272
273
274
275
276
277
278
279
# File 'lib/aws-record/record/table_config.rb', line 272

def compatible?
  begin
    resp = @client.describe_table(table_name: @model_class.table_name)
    _compatible_check(resp) && _ttl_compatibility_check
  rescue DynamoDB::Errors::ResourceNotFoundException
    false
  end
end

#configure_clientObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



192
193
194
# File 'lib/aws-record/record/table_config.rb', line 192

def configure_client
  @client = Aws::DynamoDB::Client.new(@client_options)
end

#exact_match?Boolean

Checks against the remote table’s configuration. If the remote table does not exist, guaranteed false. Otherwise, will check if the remote throughput, keys, attribute definitions, and global secondary indexes are exactly equal to your declared configuration.

Returns:

  • (Boolean)

    true if remote is an exact match, false otherwise.



287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/aws-record/record/table_config.rb', line 287

def exact_match?
  begin
    resp = @client.describe_table(table_name: @model_class.table_name)
    _throughput_equal(resp) &&
      _keys_equal(resp) &&
      _ad_equal(resp) &&
      _gsi_equal(resp) &&
      _ttl_match_check
  rescue DynamoDB::Errors::ResourceNotFoundException
    false
  end
end

#global_secondary_index(name, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



180
181
182
183
184
# File 'lib/aws-record/record/table_config.rb', line 180

def global_secondary_index(name, &block)
  gsi = GlobalSecondaryIndex.new
  gsi.instance_eval(&block)
  @global_secondary_indexes[name] = gsi
end

#migrate!Object

Performs a migration, if needed, against the remote table. If #compatible? would return true, the remote table already has the same throughput, key schema, attribute definitions, and global secondary indexes, so no further API calls are made. Otherwise, a DynamoDB table will be created or updated to match your declared configuration.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/aws-record/record/table_config.rb', line 216

def migrate!
  _validate_required_configuration
  begin
    resp = @client.describe_table(table_name: @model_class.table_name)
    if _compatible_check(resp)
      nil
    else
      # Gotcha: You need separate migrations for indexes and throughput
      unless _throughput_equal(resp)
        @client.update_table(_update_throughput_opts(resp))
        @client.wait_until(
          :table_exists,
          table_name: @model_class.table_name
        )
      end
      unless _gsi_superset(resp)
        @client.update_table(_update_index_opts(resp))
        @client.wait_until(
        :table_exists,
        table_name: @model_class.table_name
      )
      end
    end
  rescue DynamoDB::Errors::ResourceNotFoundException
    # Code Smell: Exception as control flow.
    # Can I use SDK ability to skip raising an exception for this?
    @client.create_table(_create_table_opts)
    @client.wait_until(:table_exists, table_name: @model_class.table_name)
  end
  # At this stage, we have a table and need to check for after-effects to
  # apply.
  # First up is TTL attribute. Since this migration is not exact match,
  # we will only alter TTL status if we have a TTL attribute defined. We
  # may someday support explicit TTL deletion, but we do not yet do this.
  if @ttl_attribute
    if !_ttl_compatibility_check
      client.update_time_to_live(
        table_name: @model_class.table_name,
        time_to_live_specification: {
          enabled: true,
          attribute_name: @ttl_attribute
        }
      )
    end # Else TTL is compatible and we are done.
  end # Else our work is done.
end

#model_class(model) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



165
166
167
# File 'lib/aws-record/record/table_config.rb', line 165

def model_class(model)
  @model_class = model
end

#read_capacity_units(units) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



170
171
172
# File 'lib/aws-record/record/table_config.rb', line 170

def read_capacity_units(units)
  @read_capacity_units = units
end

#ttl_attribute(attribute_symbol) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



197
198
199
200
201
202
203
204
# File 'lib/aws-record/record/table_config.rb', line 197

def ttl_attribute(attribute_symbol)
  attribute = @model_class.attributes.attribute_for(attribute_symbol)
  if attribute
    @ttl_attribute = attribute.database_name
  else
    raise ArgumentError, "Invalid attribute #{attribute_symbol} for #{@model_class}"
  end
end

#write_capacity_units(units) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



175
176
177
# File 'lib/aws-record/record/table_config.rb', line 175

def write_capacity_units(units)
  @write_capacity_units = units
end