Module: Aws::Record::Transactions

Defined in:
lib/aws-record/record/transactions.rb

Class Method Summary collapse

Class Method Details

.configure_client(opts = {}) ⇒ Object

Configures the Amazon DynamoDB client used by global transaction operations.

Please note that this method is also called internally when you first attempt to perform an operation against the remote end, if you have not already configured a client. As such, please read and understand the documentation in the AWS SDK for Ruby V3 around configuration to ensure you understand how default configuration behavior works. When in doubt, call this method to ensure your client is configured the way you want it to be configured.

Parameters:

  • opts (Hash) (defaults to: {})

    the options you wish to use to create the client. Note that if you include the option :client, all other options will be ignored. See the documentation for other options in the AWS SDK for Ruby V3.

Options Hash (opts):

  • :client (Aws::DynamoDB::Client)

    allows you to pass in your own pre-configured client.



204
205
206
207
208
209
210
211
# File 'lib/aws-record/record/transactions.rb', line 204

def configure_client(opts = {})
  provided_client = opts.delete(:client)
  opts[:user_agent_suffix] = _user_agent(
    opts.delete(:user_agent_suffix)
  )
  client = provided_client || Aws::DynamoDB::Client.new(opts)
  @@dynamodb_client = client
end

.dynamodb_clientAws::DynamoDB::Client

Returns the Amazon DynamoDB client instance.

Returns:

  • (Aws::DynamoDB::Client)

    the Amazon DynamoDB client instance.



220
221
222
# File 'lib/aws-record/record/transactions.rb', line 220

def dynamodb_client
  @@dynamodb_client ||= configure_client
end

.transact_find(opts) ⇒ OpenStruct

Provides a way to run a transactional find across multiple DynamoDB items, including transactions which get items across multiple actual or virtual tables.

Examples:

Usage Example

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

class TableTwo
  include Aws::Record
  string_attr :hk, hash_key: true
  string_attr :rk, range_key: true
end

results = Aws::Record::Transactions.transact_find(
  transact_items: [
    TableOne.tfind_opts(key: { uuid: "uuid1234" }),
    TableTwo.tfind_opts(key: { hk: "hk1", rk: "rk1"}),
    TableTwo.tfind_opts(key: { hk: "hk2", rk: "rk2"})
  ]
) # => results.responses contains nil or marshalled items
results.responses.map { |r| r.class } # [TableOne, TableTwo, TableTwo]

Parameters:

  • opts (Hash)

    Options to pass through to Aws::DynamoDB::Client#transact_get_items, with the exception of the :transact_items array, which uses the #tfind_opts operation on your model class to provide extra metadata used to marshal your items after retrieval.

Options Hash (opts):

  • :transact_items (Array)

    A set of #tfind_opts results, such as those created by the usage example.

  • :client (Aws::DynamoDB::Client)

    Optionally, you can pass in your own client to use for the transaction calls.

Returns:

  • (OpenStruct)

    Structured like the client API response from #transact_get_items, except that the responses member contains Aws::Record items marshaled into the classes used to call #tfind_opts in each positional member. See the usage example.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/aws-record/record/transactions.rb', line 44

def transact_find(opts)
  opts = opts.dup
  client = opts.delete(:client) || dynamodb_client
  transact_items = opts.delete(:transact_items) # add nil check?
  model_classes = []
  client_transact_items = transact_items.map do |tfind_opts|
    model_class = tfind_opts.delete(:model_class)
    model_classes << model_class
    tfind_opts
  end
  request_opts = opts
  request_opts[:transact_items] = client_transact_items
  client_resp = client.transact_get_items(
    request_opts
  )
  responses = client_resp.responses
  index = -1
  ret = OpenStruct.new
  ret.consumed_capacity = client_resp.consumed_capacity
  ret.missing_items = []
  ret.responses = client_resp.responses.map do |item|
    index += 1
    if item.nil? || item.item.nil?
      missing_data = {
        model_class: model_classes[index],
        key: transact_items[index][:get][:key]
      }
      ret.missing_items << missing_data
      nil
    else
      # need to translate the item keys
      raw_item = item.item
      model_class = model_classes[index]
      new_item_opts = {}
      raw_item.each do |db_name, value|
        name = model_class.attributes.db_to_attribute_name(db_name)
        new_item_opts[name] = value
      end
      item = model_class.new(new_item_opts)
      item.clean!
      item
    end
  end
  ret
end

.transact_write(opts) ⇒ Object

Provides a way to pass in aws-record items into transactional writes, as well as adding the ability to run ‘save’ commands in a transaction while allowing aws-record to determine if a :put or :update operation is most appropriate. #transact_write supports 5 different transact item modes:

  • save: Behaves much like the #save operation on the item itself. If the keys are dirty, and thus it appears to be a new item, will create a :put operation with a conditional check on the item’s existance. Note that you cannot bring your own conditional expression in this case. If you wish to force put or add your own conditional checks, use the :put operation.

  • put: Does a force put for the given item key and model.

  • update: Does an upsert for the given item.

  • delete: Deletes the given item.

  • check: Takes the result of #transact_check_expression, performing the specified check as a part of the transaction.

Examples:

Usage Example

class TableOne
  include Aws::Record
  string_attr :uuid, hash_key: true
  string_attr :body
end

class TableTwo
  include Aws::Record
  string_attr :hk, hash_key: true
  string_attr :rk, range_key: true
  string_attr :body
end

check_exp = TableOne.transact_check_expression(
  key: { uuid: "foo" },
  condition_expression: "size(#T) <= :v",
  expression_attribute_names: {
    "#T" => "body"
  },
  expression_attribute_values: {
    ":v" => 1024
  }
)
new_item = TableTwo.new(hk: "hk1", rk: "rk1", body: "Hello!")
update_item_1 = TableOne.find(uuid: "bar")
update_item_1.body = "Updated the body!"
put_item = TableOne.new(uuid: "foobar", body: "Content!")
update_item_2 = TableTwo.find(hk: "hk2", rk: "rk2")
update_item_2.body = "Update!"
delete_item = TableOne.find(uuid: "to_be_deleted")

Aws::Record::Transactions.transact_write(
  transact_items: [
    { check: check_exp },
    { save: new_item },
    { save: update_item_1 },
    {
       put: put_item,
       condition_expression: "attribute_not_exists(#H)",
       expression_attribute_names: { "#H" => "uuid" },
       return_values_on_condition_check_failure: "ALL_OLD"
    },
    { update: update_item_2 },
    { delete: delete_item }
  ]
)

Parameters:

  • opts (Hash)

    Options to pass through to Aws::DynamoDB::Client#transact_write_items with the exception of :transact_items array, which is transformed to use your item to populate the :key, :table_name, :item, and/or :update_expression parameters as appropriate. See the usage example for a comprehensive set of combinations.

Options Hash (opts):

  • :transact_items (Array)

    An array of hashes, accepting :save, :put, :delete, :update, and :check as specified.

  • :client (Aws::DynamoDB::Client)

    Optionally, you can specify a client to use for this transaction call. If not specified, the configured client for Aws::Record::Transactions is used.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/aws-record/record/transactions.rb', line 167

def transact_write(opts)
  opts = opts.dup
  client = opts.delete(:client) || dynamodb_client
  dirty_items = []
  delete_items = []
  # fetch abstraction records
  transact_items = _transform_transact_write_items(
    opts.delete(:transact_items),
    dirty_items,
    delete_items
  )
  opts[:transact_items] = transact_items
  resp = client.transact_write_items(opts)
  # mark all items clean/destroyed as needed if we didn't raise an exception
  dirty_items.each { |i| i.clean! }
  delete_items.each { |i| i.instance_variable_get("@data").destroyed = true }
  resp
end