Dynamoid/dynamoid

View on GitHub
lib/dynamoid/transaction_write/action.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true

module Dynamoid
  class TransactionWrite
    class Action
      VALID_OPTIONS = %i[skip_callbacks skip_validation raise_validation_error skip_existence_check].freeze
      attr_accessor :model, :attributes, :options, :additions, :deletions, :removals

      def initialize(model_or_model_class, attributes = {}, options = {})
        if model_or_model_class.is_a?(Dynamoid::Document)
          self.model = model_or_model_class
        else
          @model_class = model_or_model_class
        end
        self.attributes = attributes
        self.options = options || {}
        self.additions = {}
        self.deletions = {}
        self.removals = []
        invalid_keys = self.options.keys - VALID_OPTIONS
        raise ArgumentError, "Invalid options found: '#{invalid_keys}'" if invalid_keys.present?

        yield(self) if block_given?
      end

      def model_class
        model&.class || @model_class
      end

      # returns hash_key from the model if it exists or first element from id
      def hash_key
        return model.hash_key if model

        attributes[model_class.hash_key]
      end

      # returns range_key from the model if it exists or second element from id, or nil
      def range_key
        return nil unless model_class.range_key?
        return model.attributes[model_class.range_key] if model

        attributes[model_class.range_key]
      end

      # sets a value in the attributes
      def set(values)
        attributes.merge!(values)
      end

      # increments a number or adds to a set, starts at 0 or [] if it doesn't yet exist
      def add(values)
        additions.merge!(values)
      end

      # deletes a value or values from a set type or simply sets a field to nil
      def delete(field_or_values)
        if field_or_values.is_a?(Hash)
          deletions.merge!(field_or_values)
        else
          # adds to array of fields for use in REMOVE update expression
          removals << field_or_values
        end
      end

      def find_from_attributes(model_or_model_class, attributes)
        model_class = model_or_model_class.is_a?(Dynamoid::Document) ? model_or_model_class.class : model_or_model_class
        if attributes.is_a?(Hash)
          raise Dynamoid::Errors::MissingHashKey unless attributes[model_class.hash_key].present?

          model_class.find(attributes[model_class.hash_key],
                           range_key: model_class.range_key? ? attributes[model_class.range_key] : nil,
                           consistent_read: true)
        else
          model_class.find(attributes, consistent_read: true)
        end
      end

      def skip_callbacks?
        !!options[:skip_callbacks]
      end

      def skip_validation?
        !!options[:skip_validation]
      end

      def valid?
        model&.valid?
      end

      def raise_validation_error?
        !!options[:raise_validation_error]
      end

      def run_callbacks
        yield if block_given?
      end

      def changes_applied
        !!model&.changes_applied
      end

      def add_timestamps(attributes, skip_created_at: false)
        return attributes if options[:skip_timestamps] || !model_class&.timestamps_enabled?

        result = attributes.clone
        timestamp = DateTime.now.in_time_zone(Time.zone)
        result[:created_at] ||= timestamp unless skip_created_at
        result[:updated_at] ||= timestamp
        result
      end

      def touch_model_timestamps(skip_created_at: false)
        return if !model || options[:skip_timestamps] || !model_class.timestamps_enabled?

        timestamp = DateTime.now.in_time_zone(Time.zone)
        model.updated_at = timestamp
        model.created_at ||= timestamp unless skip_created_at
      end

      def write_attributes_to_model
        return unless model && attributes.present?

        attributes.each { |attribute, value| model.write_attribute(attribute, value) }
      end

      # copied from the protected method in AwsSdkV3
      def sanitize_item(attributes)
        config_value = Dynamoid.config.store_attribute_with_nil_value
        store_attribute_with_nil_value = config_value.nil? ? false : !!config_value

        attributes.reject do |_, v|
          ((v.is_a?(Set) || v.is_a?(String)) && v.empty?) ||
            (!store_attribute_with_nil_value && v.nil?)
        end.transform_values do |v|
          v.is_a?(Hash) ? v.stringify_keys : v
        end
      end

      def to_h
        raise 'override me'
      end
    end
  end
end