Dynamoid/dynamoid

View on GitHub
lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb

Summary

Maintainability
A
35 mins
Test Coverage
# frozen_string_literal: true

module Dynamoid
  # @private
  module AdapterPlugin
    class AwsSdkV3
      # Mimics behavior of the yielded object on DynamoDB's update_item API (high level).
      class ItemUpdater
        ADD    = 'ADD'
        DELETE = 'DELETE'
        PUT    = 'PUT'

        attr_reader :table, :key, :range_key

        def initialize(table, key, range_key = nil)
          @table = table
          @key = key
          @range_key = range_key
          @additions = {}
          @deletions = {}
          @updates   = {}
        end

        #
        # Adds the given values to the values already stored in the corresponding columns.
        # The column must contain a Set or a number.
        #
        # @param [Hash] values keys of the hash are the columns to update, values
        #                      are the values to add. values must be a Set, Array, or Numeric
        #
        def add(values)
          @additions.merge!(sanitize_attributes(values))
        end

        #
        # Removes values from the sets of the given columns
        #
        # @param [Hash|Symbol|String] values keys of the hash are the columns, values are Arrays/Sets of items
        #                                    to remove
        #
        def delete(values)
          if values.is_a?(Hash)
            @deletions.merge!(sanitize_attributes(values))
          else
            @deletions.merge!(values.to_s => nil)
          end
        end

        #
        # Replaces the values of one or more attributes
        #
        def set(values)
          values_sanitized = sanitize_attributes(values)

          if Dynamoid.config.store_attribute_with_nil_value
            @updates.merge!(values_sanitized)
          else
            # delete explicitly attributes if assigned nil value and configured
            # to not store nil values
            values_to_update = values_sanitized.reject { |_, v| v.nil? }
            values_to_delete = values_sanitized.select { |_, v| v.nil? }

            @updates.merge!(values_to_update)
            @deletions.merge!(values_to_delete)
          end
        end

        #
        # Returns an AttributeUpdates hash suitable for passing to the V2 Client API
        #
        def attribute_updates
          result = {}

          @additions.each do |k, v|
            result[k] = {
              action: ADD,
              value: v
            }
          end

          @deletions.each do |k, v|
            result[k] = {
              action: DELETE
            }
            result[k][:value] = v unless v.nil?
          end

          @updates.each do |k, v|
            result[k] = {
              action: PUT,
              value: v
            }
          end

          result
        end

        private

        # Keep in sync with AwsSdkV3.sanitize_item.
        #
        # The only difference is that to update item we need to track whether
        # attribute value is nil or not.
        def sanitize_attributes(attributes)
          # rubocop:disable Lint/DuplicateBranch
          attributes.transform_values do |v|
            if v.is_a?(Hash)
              v.stringify_keys
            elsif v.is_a?(Set) && v.empty?
              nil
            elsif v.is_a?(String) && v.empty?
              nil
            else
              v
            end
          end
          # rubocop:enable Lint/DuplicateBranch
        end
      end
    end
  end
end