# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. require 'date' module AWS module Record module Attributes # Base class for all of the AWS::Record attributes. class BaseAttr # @param [Symbol] name Name of this attribute. It should be a name that # is safe to use as a method. # @param [Hash] options # @option options [String] :persist_as Defaults to the name of the # attribute. You can pass a string to specify what the attribute # will be named in the backend storage. # @option options [Boolean] :set (false) When true this attribute can # accept multiple unique values. def initialize name, options = {} @name = name.to_s @options = options.dup if options[:set] and !self.class.allow_set? raise ArgumentError, "invalid option :set for #{self.class}" end end # @return [String] The name of this attribute attr_reader :name # @return [Hash] Attribute options passed to the constructor. attr_reader :options # @return [Boolean] Returns true if this attribute can have # multiple values. def set? options[:set] ? true : false end # @return Returns the default value for this attribute. def default_value options[:default_value] end # @return [String] Returns the name this attribute will use # in the storage backend. def persist_as (options[:persist_as] || @name).to_s end # @param [Mixed] raw_value A single value to type cast. # @return [Mixed] Returns the type casted value. def type_cast raw_value self.class.type_cast(raw_value, options) end # @param [String] serialized_value The serialized string value. # @return [Mixed] Returns a deserialized type-casted value. def deserialize serialized_value self.class.deserialize(serialized_value, options) end # Takes the type casted value and serializes it # @param [Mixed] type_casted_value A single value to serialize. # @return [Mixed] Returns the serialized value. def serialize type_casted_value self.class.serialize(type_casted_value, options) end # @param [String] serialized_value The raw value returned from AWS. # @return [Mixed] Returns the type-casted deserialized value. def self.deserialize serialized_value, options = {} self.type_cast(serialized_value, options) end # @return [Boolean] Returns true if this attribute type can be used # with the `:set => true` option. Certain attirbutes can not # be represented with multiple values (like BooleanAttr). def self.allow_set? raise NotImplementedError end # @private protected def self.expect klass, value, &block unless value.is_a?(klass) raise ArgumentError, "expected a #{klass}, got #{value.class}" end yield if block_given? end end class StringAttr < BaseAttr # Returns the value cast to a string. Empty strings are returned as # nil by default. Type casting is done by calling #to_s on the value. # # string_attr.type_cast(123) # # => '123' # # string_attr.type_cast('') # # => nil # # string_attr.type_cast('', :preserve_empty_strings => true) # # => '' # # @param [Mixed] raw_value # @param [Hash] options # @option options [Boolean] :preserve_empty_strings (false) When true, # empty strings are preserved and not cast to nil. # @return [String,nil] The type casted value. def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then options[:preserve_empty_strings] ? '' : nil when String then raw_value else raw_value.to_s end end # Returns a serialized representation of the string value suitable for # storing in SimpleDB. # @param [String] string # @param [Hash] options # @return [String] The serialized string. def self.serialize string, options = {} unless string.is_a?(String) msg = "expected a String value, got #{string.class}" raise ArgumentError, msg end string end # @private def self.allow_set? true end end class BooleanAttr < BaseAttr def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when false, 'false', '0', 0 then false else true end end def self.serialize boolean, options = {} case boolean when false then 0 when true then 1 else msg = "expected a boolean value, got #{boolean.class}" raise ArgumentError, msg end end # @private def self.allow_set? false end end class IntegerAttr < BaseAttr # Returns value cast to an integer. Empty strings are cast to # nil by default. Type casting is done by calling #to_i on the value. # # int_attribute.type_cast('123') # #=> 123 # # int_attribute.type_cast('') # #=> nil # # @param [Mixed] raw_value The value to type cast to an integer. # @return [Integer,nil] Returns the type casted integer or nil def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when Integer then raw_value else raw_value.respond_to?(:to_i) ? raw_value.to_i : raw_value.to_s.to_i end end # Returns a serialized representation of the integer value suitable for # storing in SimpleDB. # # attribute.serialize(123) # #=> '123' # # @param [Integer] integer The number to serialize. # @param [Hash] options # @return [String] A serialized representation of the integer. def self.serialize integer, options = {} expect(Integer, integer) { integer } end # @private def self.allow_set? true end end class FloatAttr < BaseAttr def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when Float then raw_value else raw_value.respond_to?(:to_f) ? raw_value.to_f : raw_value.to_s.to_f end end def self.serialize float, options = {} expect(Float, float) { float } end # @private def self.allow_set? true end end class DateAttr < BaseAttr # Returns value cast to a Date object. Empty strings are cast to # nil. Values are cast first to strings and then passed to # Date.parse. Integers are treated as timestamps. # # date_attribute.type_cast('2000-01-02T10:11:12Z') # #=> # # # date_attribute.type_cast(1306170146) # # # # date_attribute.type_cast('') # #=> nil # # date_attribute.type_cast(nil) # #=> nil # # @param [Mixed] raw_value The value to cast to a Date object. # @param [Hash] options # @return [Date,nil] def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when Date then raw_value when Integer then begin Date.parse(Time.at(raw_value).to_s) # assumed timestamp rescue nil end else begin Date.parse(raw_value.to_s) # Time, DateTime or String rescue nil end end end # Returns a Date object encoded as a string (suitable for sorting). # # attribute.serialize(DateTime.parse('2001-01-01')) # #=> '2001-01-01' # # @param [Date] date The date to serialize. # # @param [Hash] options # # @return [String] Returns the date object serialized to a string # ('YYYY-MM-DD'). # def self.serialize date, options = {} unless date.is_a?(Date) raise ArgumentError, "expected a Date value, got #{date.class}" end date.strftime('%Y-%m-%d') end # @private def self.allow_set? true end end class DateTimeAttr < BaseAttr # Returns value cast to a DateTime object. Empty strings are cast to # nil. Values are cast first to strings and then passed to # DateTime.parse. Integers are treated as timestamps. # # datetime_attribute.type_cast('2000-01-02') # #=> # # # datetime_attribute.type_cast(1306170146) # # # # datetime_attribute.type_cast('') # #=> nil # # datetime_attribute.type_cast(nil) # #=> nil # # @param [Mixed] raw_value The value to cast to a DateTime object. # @param [Hash] options # @return [DateTime,nil] def self.type_cast raw_value, options = {} case raw_value when nil then nil when '' then nil when DateTime then raw_value when Integer then begin DateTime.parse(Time.at(raw_value).to_s) # timestamp rescue nil end else begin DateTime.parse(raw_value.to_s) # Time, Date or String rescue nil end end end # Returns a DateTime object encoded as a string (suitable for sorting). # # attribute.serialize(DateTime.parse('2001-01-01')) # #=> '2001-01-01T00:00:00:Z) # # @param [DateTime] datetime The datetime object to serialize. # @param [Hash] options # @return [String] Returns the datetime object serialized to a string # in ISO8601 format (e.g. '2011-01-02T10:11:12Z') def self.serialize datetime, options = {} unless datetime.is_a?(DateTime) msg = "expected a DateTime value, got #{datetime.class}" raise ArgumentError, msg end datetime.strftime('%Y-%m-%dT%H:%M:%S%Z') end # @private def self.allow_set? true end end end end end