# frozen_string_literal: true module Aws module Record # This class provides helper methods for +Aws::Record+ attributes. These # include marshalers for type casting of item attributes, the Amazon # DynamoDB type for use in certain table and item operation calls, and the # ability to define a database name that is separate from the name used # within the model class and item instances. class Attribute attr_reader :name, :database_name, :dynamodb_type # @param [Symbol] name Name of the attribute. It should be a name that is # safe to use as a method. # @param [Hash] options # @option options [Marshaler] :marshaler The marshaler for this attribute. # So long as you provide a marshaler which implements +#type_cast+ and # +#serialize+ that consume raw values as expected, you can bring your # own marshaler type. # @option options [String] :database_attribute_name Optional attribute # used to specify a different name for database persistence than the # `name` parameter. Must be unique (you can't have overlap between # database attribute names and the names of other attributes). # @option options [String] :dynamodb_type Generally used for keys and # index attributes, one of "S", "N", "B", "BOOL", "SS", "NS", "BS", # "M", "L". Optional if this attribute will never be used for a key or # secondary index, but most convenience methods for setting attributes # will provide this. # @option options [Boolean] :persist_nil Optional attribute used to # indicate whether nil values should be persisted. If true, explicitly # set nil values will be saved to DynamoDB as a "null" type. If false, # nil values will be ignored and not persisted. By default, is false. # @option options [Object] :default_value Optional attribute used to # define a "default value" to be used if the attribute's value on an # item is nil or not set at persistence time. Additionally, lambda can # be used as a default value. def initialize(name, options = {}) @name = name @database_name = (options[:database_attribute_name] || name).to_s @dynamodb_type = options[:dynamodb_type] @marshaler = options[:marshaler] || DefaultMarshaler @persist_nil = options[:persist_nil] dv = options[:default_value] @default_value_or_lambda = _is_lambda?(dv) ? dv : type_cast(dv) end # Attempts to type cast a raw value into the attribute's type. This call # will forward the raw value to this attribute's marshaler class. # # @return [Object] the type cast object. Return type is dependent on the # marshaler used. See your attribute's marshaler class for details. def type_cast(raw_value) cast_value = @marshaler.type_cast(raw_value) cast_value = default_value if cast_value.nil? cast_value end # Attempts to serialize a raw value into the attribute's serialized # storage type. This call will forward the raw value to this attribute's # marshaler class. # # @return [Object] the serialized object. Return type is dependent on the # marshaler used. See your attribute's marshaler class for details. def serialize(raw_value) cast_value = type_cast(raw_value) cast_value = default_value if cast_value.nil? @marshaler.serialize(cast_value) end # @return [Boolean] +true+ if this attribute will actively persist nil # values, +false+ otherwise. Default: +false+ def persist_nil? @persist_nil ? true : false end # @api private def extract(dynamodb_item) dynamodb_item[@database_name] end # @api private def default_value if _is_lambda?(@default_value_or_lambda) type_cast(@default_value_or_lambda.call) else _deep_copy(@default_value_or_lambda) end end private def _deep_copy(obj) Marshal.load(Marshal.dump(obj)) end def _is_lambda?(obj) obj.respond_to?(:call) end end # This is an identity marshaler, which performs no changes for type casting # or serialization. It is generally not recommended for use. module DefaultMarshaler def self.type_cast(raw_value, _options = {}) raw_value end def self.serialize(raw_value, _options = {}) raw_value end end end end