Skip to content

Commit

Permalink
Merge f7f1499 into 62a7901
Browse files Browse the repository at this point in the history
  • Loading branch information
awood45 committed Jul 9, 2018
2 parents 62a7901 + f7f1499 commit 6399c39
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Aws::Record::Marshalers::EpochTimeMarshaler - Adds the `epoch_time_attr`, which behaves much like `time_attr` except the Amazon DynamoDB storage type is numeric, and the serialized value is epoch seconds.

2.1.0 (2018-06-25)
------------------

Expand Down
1 change: 1 addition & 0 deletions lib/aws-record.rb
Expand Up @@ -34,6 +34,7 @@
require_relative 'aws-record/record/marshalers/date_marshaler'
require_relative 'aws-record/record/marshalers/date_time_marshaler'
require_relative 'aws-record/record/marshalers/time_marshaler'
require_relative 'aws-record/record/marshalers/epoch_time_marshaler'
require_relative 'aws-record/record/marshalers/list_marshaler'
require_relative 'aws-record/record/marshalers/map_marshaler'
require_relative 'aws-record/record/marshalers/string_set_marshaler'
Expand Down
22 changes: 22 additions & 0 deletions lib/aws-record/record/attributes.rb
Expand Up @@ -240,6 +240,28 @@ def time_attr(name, opts = {})
attr(name, Marshalers::TimeMarshaler.new(opts), opts)
end

# Define a time-type attribute for your model which persists as
# epoch-seconds.
#
# @param [Symbol] name Name of this attribute. It should be a name
# that is safe to use as a method.
# @param [Hash] opts
# @option opts [Boolean] :hash_key Set to true if this attribute is
# the hash key for the table.
# @option opts [Boolean] :range_key Set to true if this attribute is
# the range key for the table.
# @option opts [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 opts [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.
def epoch_time_attr(name, opts = {})
opts[:dynamodb_type] = "N"
attr(name, Marshalers::EpochTimeMarshaler.new(opts), opts)
end

# Define a list-type attribute for your model.
#
# Lists do not have to be homogeneous, but they do have to be types that
Expand Down
66 changes: 66 additions & 0 deletions lib/aws-record/record/marshalers/epoch_time_marshaler.rb
@@ -0,0 +1,66 @@
# Copyright 2018 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 'time'

module Aws
module Record
module Marshalers

class EpochTimeMarshaler
def initialize(opts = {})
@use_local_time = opts[:use_local_time] ? true : false
end

def type_cast(raw_value)
value = _format(raw_value)
if !@use_local_time && value.is_a?(::Time)
value.utc
else
value
end
end

def serialize(raw_value)
time = type_cast(raw_value)
if time.nil?
nil
elsif time.is_a?(::Time)
time.to_i
else
msg = "expected a Time value or nil, got #{time.class}"
raise ArgumentError, msg
end
end

private

def _format(raw_value)
case raw_value
when nil
nil
when ''
nil
when ::Time
raw_value
when Integer # timestamp
::Time.at(raw_value)
else # Date, DateTime, or String
::Time.parse(raw_value.to_s)
end
end
end

end
end
end
5 changes: 4 additions & 1 deletion spec/aws-record/record/item_operations_spec.rb
Expand Up @@ -32,6 +32,7 @@ module Record
map_attr(:map_nil_as_nil, persist_nil: true)
map_attr(:map_no_nil_persist)
boolean_attr(:bool, database_attribute_name: "my_boolean")
epoch_time_attr(:ttl)
end
end

Expand All @@ -54,6 +55,7 @@ module Record
item.id = 1
item.date = '2015-12-14'
item.body = 'Hello!'
item.ttl = Time.parse("2018-07-09 22:02:12 UTC")
item.save!
expect(api_requests).to eq([{
table_name: "TestTable",
Expand All @@ -62,7 +64,8 @@ module Record
"MyDate" => { s: "2015-12-14" },
"body" => { s: "Hello!" },
"list_nil_to_empty" => { l: [] },
"map_nil_to_empty" => { m: {} }
"map_nil_to_empty" => { m: {} },
"ttl" => { n: "1531173732" },
},
condition_expression: "attribute_not_exists(#H)"\
" and attribute_not_exists(#R)",
Expand Down
97 changes: 97 additions & 0 deletions spec/aws-record/record/marshalers/epoch_time_marshaler_spec.rb
@@ -0,0 +1,97 @@
# Copyright 2016 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 'spec_helper'
require 'time'

module Aws
module Record
module Marshalers
describe EpochTimeMarshaler do

context 'default settings' do
before(:each) do
@marshaler = EpochTimeMarshaler.new
end

describe 'type casting' do
it 'casts nil and empty string as nil' do
expect(@marshaler.type_cast(nil)).to be_nil
expect(@marshaler.type_cast('')).to be_nil
end

it 'passes through Time objects' do
expected = Time.at(1531173732)
input = Time.at(1531173732)
expect(@marshaler.type_cast(input)).to eq(expected)
end

it 'converts timestamps to Time' do
expected = Time.at(1531173732)
input = 1531173732
expect(@marshaler.type_cast(input)).to eq(expected)
end

it 'converts DateTimes to Time' do
expected = Time.parse("2009-02-13 23:31:30 UTC")
input = DateTime.parse("2009-02-13 23:31:30 UTC")
expect(@marshaler.type_cast(input)).to eq(expected)
end

it 'converts strings to Time' do
expected = Time.parse("2009-02-13 23:31:30 UTC")
input = "2009-02-13 23:31:30 UTC"
expect(@marshaler.type_cast(input)).to eq(expected)
end

it 'converts automatically to utc' do
expected = Time.parse("2016-07-20 23:31:10 UTC")
input = "2016-07-20 16:31:10 -0700"
expect(@marshaler.type_cast(input)).to eq(expected)
end

it 'raises when unable to parse as a Time' do
expect {
@marshaler.type_cast("that time when")
}.to raise_error(ArgumentError)
end
end

describe 'serialization for storage' do
it 'serializes nil as null' do
expect(@marshaler.serialize(nil)).to eq(nil)
end

it 'serializes Time in epoch seconds' do
t = Time.parse('2018-07-09 22:02:12 UTC')
expect(@marshaler.serialize(t)).to eq(1531173732)
end
end
end

context "use local time" do
before(:each) do
@marshaler = TimeMarshaler.new(use_local_time: true)
end

it 'does not automatically convert to utc' do
expected = Time.parse("2016-07-20 16:31:10 -0700")
input = "2016-07-20 16:31:10 -0700"
expect(@marshaler.type_cast(input)).to eq(expected)
end
end

end
end
end
end

0 comments on commit 6399c39

Please sign in to comment.