Time handling improved to ensure UTC always used
This commit is contained in:
parent
3a3fc3c41d
commit
38257f4909
9 changed files with 662 additions and 536 deletions
|
@ -1,3 +1,8 @@
|
|||
== 1.1.0.beta2
|
||||
|
||||
* Minor enhancements:
|
||||
* Time handling improved in accordance with CouchRest 1.0.3. Always set to UTC.
|
||||
|
||||
== 1.1.0.beta
|
||||
|
||||
* Epic enhancements:
|
||||
|
|
43
lib/couchrest/model/core_extensions/time_parsing.rb
Normal file
43
lib/couchrest/model/core_extensions/time_parsing.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module CoreExtensions
|
||||
module TimeParsing
|
||||
|
||||
# Attemtps to parse a time string in ISO8601 format.
|
||||
# If no match is found, the standard time parse will be used.
|
||||
#
|
||||
# Times, unless provided with a time zone, are assumed to be in
|
||||
# UTC.
|
||||
#
|
||||
def parse_iso8601(string)
|
||||
if (string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})(Z| ?([\+|\s|\-])?(\d{2}):?(\d{2}))?/)
|
||||
# $1 = year
|
||||
# $2 = month
|
||||
# $3 = day
|
||||
# $4 = hours
|
||||
# $5 = minutes
|
||||
# $6 = seconds
|
||||
# $7 = UTC or Timezone
|
||||
# $8 = time zone direction
|
||||
# $9 = tz difference hours
|
||||
# $10 = tz difference minutes
|
||||
|
||||
if (!$7.to_s.empty? && $7 != 'Z')
|
||||
new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, "#{$8 == '-' ? '-' : '+'}#{$9}:#{$10}")
|
||||
else
|
||||
utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
|
||||
end
|
||||
else
|
||||
parse(string)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Time.class_eval do
|
||||
extend CouchRest::Model::CoreExtensions::TimeParsing
|
||||
end
|
||||
|
|
@ -131,8 +131,10 @@ module CouchRest
|
|||
end
|
||||
|
||||
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||
# decent time format by default. See Time#to_json
|
||||
# on the document whenever saving occurs.
|
||||
#
|
||||
# These properties are casted as Time objects, so they should always
|
||||
# be set to UTC.
|
||||
def timestamps!
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
||||
|
|
|
@ -1,26 +1,3 @@
|
|||
class Time
|
||||
# returns a local time value much faster than Time.parse
|
||||
def self.mktime_with_offset(string)
|
||||
string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})(([\+|\s|\-])*(\d{2}):?(\d{2}))?/
|
||||
# $1 = year
|
||||
# $2 = month
|
||||
# $3 = day
|
||||
# $4 = hours
|
||||
# $5 = minutes
|
||||
# $6 = seconds
|
||||
# $8 = time zone direction
|
||||
# $9 = tz difference
|
||||
# utc time with wrong TZ info:
|
||||
time = mktime($1, RFC2822_MONTH_NAME[$2.to_i - 1], $3, $4, $5, $6)
|
||||
if ($7)
|
||||
tz_difference = ("#{$8 == '-' ? '+' : '-'}#{$9}".to_i * 3600)
|
||||
time + tz_difference + zone_offset(time.zone)
|
||||
else
|
||||
time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module CouchRest
|
||||
module Model
|
||||
module Typecast
|
||||
|
@ -29,7 +6,11 @@ module CouchRest
|
|||
return nil if value.nil?
|
||||
klass = property.type_class
|
||||
if value.instance_of?(klass) || klass == Object
|
||||
value
|
||||
if klass == Time && !value.utc?
|
||||
value.utc # Ensure Time is always in UTC
|
||||
else
|
||||
value
|
||||
end
|
||||
elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
|
||||
send('typecast_to_'+klass.to_s.downcase, value)
|
||||
else
|
||||
|
@ -127,12 +108,11 @@ module CouchRest
|
|||
if value.is_a?(Hash)
|
||||
typecast_hash_to_time(value)
|
||||
else
|
||||
Time.mktime_with_offset(value.to_s)
|
||||
Time.parse_iso8601(value.to_s)
|
||||
end
|
||||
rescue ArgumentError
|
||||
value
|
||||
rescue TypeError
|
||||
# After failures, resort to normal time parse
|
||||
value
|
||||
end
|
||||
|
||||
|
@ -150,13 +130,13 @@ module CouchRest
|
|||
# Creates a Time instance from a Hash with keys :year, :month, :day,
|
||||
# :hour, :min, :sec
|
||||
def typecast_hash_to_time(value)
|
||||
Time.local(*extract_time(value))
|
||||
Time.utc(*extract_time(value))
|
||||
end
|
||||
|
||||
# Extracts the given args from the hash. If a value does not exist, it
|
||||
# uses the value of Time.now.
|
||||
def extract_time(value)
|
||||
now = Time.now
|
||||
now = Time.now
|
||||
[:year, :month, :day, :hour, :min, :sec].map do |segment|
|
||||
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
|
||||
end
|
||||
|
|
|
@ -46,7 +46,9 @@ require "couchrest/model/designs/view"
|
|||
|
||||
# Monkey patches applied to couchrest
|
||||
require "couchrest/model/support/couchrest"
|
||||
require "couchrest/model/support/hash"
|
||||
# Core Extensions
|
||||
require "couchrest/model/core_extensions/hash"
|
||||
require "couchrest/model/core_extensions/time_parsing"
|
||||
|
||||
# Base libraries
|
||||
require "couchrest/model/casted_model"
|
||||
|
|
77
spec/couchrest/core_extensions/time_parsing.rb
Normal file
77
spec/couchrest/core_extensions/time_parsing.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../../spec_helper', __FILE__)
|
||||
|
||||
describe "Time Parsing core extension" do
|
||||
|
||||
describe "Time" do
|
||||
|
||||
it "should respond to .parse_iso8601" do
|
||||
Time.respond_to?("parse_iso8601").should be_true
|
||||
end
|
||||
|
||||
describe ".parse_iso8601" do
|
||||
|
||||
describe "parsing" do
|
||||
|
||||
before :each do
|
||||
# Time.parse should not be called for these tests!
|
||||
Time.stub!(:parse).and_return(nil)
|
||||
end
|
||||
|
||||
it "should parse JSON time" do
|
||||
txt = "2011-04-01T19:05:30Z"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse JSON time as UTC without Z" do
|
||||
txt = "2011-04-01T19:05:30"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse basic time as UTC" do
|
||||
txt = "2011-04-01 19:05:30"
|
||||
Time.parse_iso8601(txt).should eql(Time.utc(2011, 04, 01, 19, 05, 30))
|
||||
end
|
||||
|
||||
it "should parse JSON time with zone" do
|
||||
txt = "2011-04-01T19:05:30 +02:00"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
||||
end
|
||||
|
||||
it "should parse JSON time with zone 2" do
|
||||
txt = "2011-04-01T19:05:30-0200"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "-02:00"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone" do
|
||||
txt = "2011-04-01 19:05:30 +0200"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:00"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone 2" do
|
||||
txt = "2011-04-01 19:05:30+0230"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
||||
end
|
||||
|
||||
it "should parse dodgy time with zone 3" do
|
||||
txt = "2011-04-01 19:05:30 0230"
|
||||
Time.parse_iso8601(txt).should eql(Time.new(2011, 04, 01, 19, 05, 30, "+02:30"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "resorting back to normal parse" do
|
||||
before :each do
|
||||
Time.should_receive(:parse)
|
||||
end
|
||||
it "should work with weird time" do
|
||||
txt = "16/07/1981 05:04:00"
|
||||
Time.parse_iso8601(txt)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -211,510 +211,6 @@ describe "Model properties" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "casting" do
|
||||
before(:each) do
|
||||
@course = Course.new(:title => 'Relaxation')
|
||||
end
|
||||
|
||||
describe "when value is nil" do
|
||||
it "leaves the value unchanged" do
|
||||
@course.title = nil
|
||||
@course['title'].should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is an Object" do
|
||||
it "it should not cast given value" do
|
||||
@course.participants = [{}, 'q', 1]
|
||||
@course['participants'].should == [{}, 'q', 1]
|
||||
end
|
||||
|
||||
it "should cast started_on to Date" do
|
||||
@course.started_on = Date.today
|
||||
@course['started_on'].should be_an_instance_of(Date)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is a String" do
|
||||
it "keeps string value unchanged" do
|
||||
value = "1.0"
|
||||
@course.title = value
|
||||
@course['title'].should equal(value)
|
||||
end
|
||||
|
||||
it "it casts to string representation of the value" do
|
||||
@course.title = 1.0
|
||||
@course['title'].should eql("1.0")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Float' do
|
||||
it 'returns same value if a float' do
|
||||
value = 24.0
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string integer' do
|
||||
@course.estimate = '0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string integer' do
|
||||
@course.estimate = '24'
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string integer' do
|
||||
@course.estimate = '-24'
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float' do
|
||||
@course.estimate = '0.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float' do
|
||||
@course.estimate = '24.35'
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string float' do
|
||||
@course.estimate = '-24.35'
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float, with no leading digits' do
|
||||
@course.estimate = '.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float, with no leading digits' do
|
||||
@course.estimate = '.41'
|
||||
@course['estimate'].should eql(0.41)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero integer' do
|
||||
@course.estimate = 0
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive integer' do
|
||||
@course.estimate = 24
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative integer' do
|
||||
@course.estimate = -24
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero decimal' do
|
||||
@course.estimate = BigDecimal('0.0')
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive decimal' do
|
||||
@course.estimate = BigDecimal('24.35')
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative decimal' do
|
||||
@course.estimate = BigDecimal('-24.35')
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'return float of a number with commas instead of points for decimals' do
|
||||
@course.estimate = '23,35'
|
||||
@course['estimate'].should eql(23.35)
|
||||
end
|
||||
|
||||
it "should handle numbers with commas and points" do
|
||||
@course.estimate = '1,234.00'
|
||||
@course.estimate.should eql(1234.00)
|
||||
end
|
||||
|
||||
it "should handle a mis-match of commas and points and maintain the last one" do
|
||||
@course.estimate = "1,232.434.123,323"
|
||||
@course.estimate.should eql(1232434123.323)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.estimate = " 24.35 "
|
||||
@course.estimate.should eql(24.35)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Integer' do
|
||||
it 'returns same value if an integer' do
|
||||
value = 24
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string integer' do
|
||||
@course.hours = '0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string integer' do
|
||||
@course.hours = '24'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string integer' do
|
||||
@course.hours = '-24'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string float' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float, with no leading digits' do
|
||||
@course.hours = '.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float, with no leading digits' do
|
||||
@course.hours = '.41'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero float' do
|
||||
@course.hours = 0.0
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive float' do
|
||||
@course.hours = 24.35
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative float' do
|
||||
@course.hours = -24.35
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero decimal' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive decimal' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative decimal' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.hours = " 24 "
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a BigDecimal' do
|
||||
it 'returns same value if a decimal' do
|
||||
value = BigDecimal('24.0')
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string integer' do
|
||||
@course.profit = '0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string integer' do
|
||||
@course.profit = '24'
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string integer' do
|
||||
@course.profit = '-24'
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float' do
|
||||
@course.profit = '0.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float' do
|
||||
@course.profit = '24.35'
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string float' do
|
||||
@course.profit = '-24.35'
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float, with no leading digits' do
|
||||
@course.profit = '.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float, with no leading digits' do
|
||||
@course.profit = '.41'
|
||||
@course['profit'].should eql(BigDecimal('0.41'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero integer' do
|
||||
@course.profit = 0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive integer' do
|
||||
@course.profit = 24
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative integer' do
|
||||
@course.profit = -24
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero float' do
|
||||
@course.profit = 0.0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive float' do
|
||||
@course.profit = 24.35
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative float' do
|
||||
@course.profit = -24.35
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.profit = " 24.35 "
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a DateTime' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a DateTime instance from hash values' do
|
||||
@course.updated_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['updated_at']
|
||||
|
||||
result.should be_kind_of(DateTime)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.updated_at = 'Dec, 2006'
|
||||
@course['updated_at'].month.should == 12
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-datetime values' do
|
||||
@course.updated_at = 'not-datetime'
|
||||
@course['updated_at'].should eql('not-datetime')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Date' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Date instance from hash values' do
|
||||
@course.started_on = {
|
||||
:year => '2007',
|
||||
:month => '3',
|
||||
:day => '25'
|
||||
}
|
||||
result = @course['started_on']
|
||||
|
||||
result.should be_kind_of(Date)
|
||||
result.year.should eql(2007)
|
||||
result.month.should eql(3)
|
||||
result.day.should eql(25)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.started_on = 'Dec 20th, 2006'
|
||||
@course.started_on.month.should == 12
|
||||
@course.started_on.day.should == 20
|
||||
@course.started_on.year.should == 2006
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-date values' do
|
||||
@course.started_on = 'not-date'
|
||||
@course['started_on'].should eql('not-date')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Time' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Time instance from hash values' do
|
||||
@course.ends_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['ends_at']
|
||||
|
||||
result.should be_kind_of(Time)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
t = Time.now
|
||||
@course.ends_at = t.strftime('%Y/%m/%d %H:%M:%S %z')
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
it 'parses the string without offset' do
|
||||
t = Time.now
|
||||
@course.ends_at = t.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-time values' do
|
||||
@course.ends_at = 'not-time'
|
||||
@course['ends_at'].should eql('not-time')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Class' do
|
||||
it 'returns same value if a class' do
|
||||
value = Course
|
||||
@course.klass = value
|
||||
@course['klass'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns the class if found' do
|
||||
@course.klass = 'Course'
|
||||
@course['klass'].should eql(Course)
|
||||
end
|
||||
|
||||
it 'does not typecast non-class values' do
|
||||
@course.klass = 'NoClass'
|
||||
@course['klass'].should eql('NoClass')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Boolean' do
|
||||
|
||||
[ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
|
||||
it "returns true when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
[ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
|
||||
it "returns false when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_false
|
||||
end
|
||||
end
|
||||
|
||||
[ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
|
||||
it "does not typecast value #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier" do
|
||||
@course.active = nil
|
||||
@course.active?.should be_false
|
||||
@course.active = false
|
||||
@course.active?.should be_false
|
||||
@course.active = true
|
||||
@course.active?.should be_true
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier on TrueClass" do
|
||||
@course.very_active = nil
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = false
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = true
|
||||
@course.very_active?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "properties of array of casted models" do
|
||||
|
@ -836,7 +332,7 @@ describe "Property Class" do
|
|||
property.init_method.should eql('parse')
|
||||
end
|
||||
|
||||
## Property Casting method. More thoroughly tested earlier.
|
||||
## Property Casting method. More thoroughly tested in typecast_spec.
|
||||
|
||||
describe "casting" do
|
||||
it "should cast a value" do
|
||||
|
|
521
spec/couchrest/typecast_spec.rb
Normal file
521
spec/couchrest/typecast_spec.rb
Normal file
|
@ -0,0 +1,521 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
||||
describe "Type Casting" do
|
||||
|
||||
before(:each) do
|
||||
@course = Course.new(:title => 'Relaxation')
|
||||
end
|
||||
|
||||
describe "when value is nil" do
|
||||
it "leaves the value unchanged" do
|
||||
@course.title = nil
|
||||
@course['title'].should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is an Object" do
|
||||
it "it should not cast given value" do
|
||||
@course.participants = [{}, 'q', 1]
|
||||
@course['participants'].should == [{}, 'q', 1]
|
||||
end
|
||||
|
||||
it "should cast started_on to Date" do
|
||||
@course.started_on = Date.today
|
||||
@course['started_on'].should be_an_instance_of(Date)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when type primitive is a String" do
|
||||
it "keeps string value unchanged" do
|
||||
value = "1.0"
|
||||
@course.title = value
|
||||
@course['title'].should equal(value)
|
||||
end
|
||||
|
||||
it "it casts to string representation of the value" do
|
||||
@course.title = 1.0
|
||||
@course['title'].should eql("1.0")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Float' do
|
||||
it 'returns same value if a float' do
|
||||
value = 24.0
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string integer' do
|
||||
@course.estimate = '0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string integer' do
|
||||
@course.estimate = '24'
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string integer' do
|
||||
@course.estimate = '-24'
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float' do
|
||||
@course.estimate = '0.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float' do
|
||||
@course.estimate = '24.35'
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative string float' do
|
||||
@course.estimate = '-24.35'
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero string float, with no leading digits' do
|
||||
@course.estimate = '.0'
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive string float, with no leading digits' do
|
||||
@course.estimate = '.41'
|
||||
@course['estimate'].should eql(0.41)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero integer' do
|
||||
@course.estimate = 0
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive integer' do
|
||||
@course.estimate = 24
|
||||
@course['estimate'].should eql(24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative integer' do
|
||||
@course.estimate = -24
|
||||
@course['estimate'].should eql(-24.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a zero decimal' do
|
||||
@course.estimate = BigDecimal('0.0')
|
||||
@course['estimate'].should eql(0.0)
|
||||
end
|
||||
|
||||
it 'returns float representation of a positive decimal' do
|
||||
@course.estimate = BigDecimal('24.35')
|
||||
@course['estimate'].should eql(24.35)
|
||||
end
|
||||
|
||||
it 'returns float representation of a negative decimal' do
|
||||
@course.estimate = BigDecimal('-24.35')
|
||||
@course['estimate'].should eql(-24.35)
|
||||
end
|
||||
|
||||
it 'return float of a number with commas instead of points for decimals' do
|
||||
@course.estimate = '23,35'
|
||||
@course['estimate'].should eql(23.35)
|
||||
end
|
||||
|
||||
it "should handle numbers with commas and points" do
|
||||
@course.estimate = '1,234.00'
|
||||
@course.estimate.should eql(1234.00)
|
||||
end
|
||||
|
||||
it "should handle a mis-match of commas and points and maintain the last one" do
|
||||
@course.estimate = "1,232.434.123,323"
|
||||
@course.estimate.should eql(1232434123.323)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.estimate = " 24.35 "
|
||||
@course.estimate.should eql(24.35)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.estimate = value
|
||||
@course['estimate'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Integer' do
|
||||
it 'returns same value if an integer' do
|
||||
value = 24
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string integer' do
|
||||
@course.hours = '0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string integer' do
|
||||
@course.hours = '24'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string integer' do
|
||||
@course.hours = '-24'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative string float' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero string float, with no leading digits' do
|
||||
@course.hours = '.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive string float, with no leading digits' do
|
||||
@course.hours = '.41'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero float' do
|
||||
@course.hours = 0.0
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive float' do
|
||||
@course.hours = 24.35
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative float' do
|
||||
@course.hours = -24.35
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a zero decimal' do
|
||||
@course.hours = '0.0'
|
||||
@course['hours'].should eql(0)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a positive decimal' do
|
||||
@course.hours = '24.35'
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
it 'returns integer representation of a negative decimal' do
|
||||
@course.hours = '-24.35'
|
||||
@course['hours'].should eql(-24)
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.hours = " 24 "
|
||||
@course['hours'].should eql(24)
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.hours = value
|
||||
@course['hours'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a BigDecimal' do
|
||||
it 'returns same value if a decimal' do
|
||||
value = BigDecimal('24.0')
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string integer' do
|
||||
@course.profit = '0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string integer' do
|
||||
@course.profit = '24'
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string integer' do
|
||||
@course.profit = '-24'
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float' do
|
||||
@course.profit = '0.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float' do
|
||||
@course.profit = '24.35'
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative string float' do
|
||||
@course.profit = '-24.35'
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero string float, with no leading digits' do
|
||||
@course.profit = '.0'
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive string float, with no leading digits' do
|
||||
@course.profit = '.41'
|
||||
@course['profit'].should eql(BigDecimal('0.41'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero integer' do
|
||||
@course.profit = 0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive integer' do
|
||||
@course.profit = 24
|
||||
@course['profit'].should eql(BigDecimal('24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative integer' do
|
||||
@course.profit = -24
|
||||
@course['profit'].should eql(BigDecimal('-24.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a zero float' do
|
||||
@course.profit = 0.0
|
||||
@course['profit'].should eql(BigDecimal('0.0'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a positive float' do
|
||||
@course.profit = 24.35
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
it 'returns decimal representation of a negative float' do
|
||||
@course.profit = -24.35
|
||||
@course['profit'].should eql(BigDecimal('-24.35'))
|
||||
end
|
||||
|
||||
it "should handle numbers with whitespace" do
|
||||
@course.profit = " 24.35 "
|
||||
@course['profit'].should eql(BigDecimal('24.35'))
|
||||
end
|
||||
|
||||
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
||||
it "does not typecast non-numeric value #{value.inspect}" do
|
||||
@course.profit = value
|
||||
@course['profit'].should equal(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a DateTime' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a DateTime instance from hash values' do
|
||||
@course.updated_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['updated_at']
|
||||
|
||||
result.should be_kind_of(DateTime)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.updated_at = 'Dec, 2006'
|
||||
@course['updated_at'].month.should == 12
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-datetime values' do
|
||||
@course.updated_at = 'not-datetime'
|
||||
@course['updated_at'].should eql('not-datetime')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Date' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Date instance from hash values' do
|
||||
@course.started_on = {
|
||||
:year => '2007',
|
||||
:month => '3',
|
||||
:day => '25'
|
||||
}
|
||||
result = @course['started_on']
|
||||
|
||||
result.should be_kind_of(Date)
|
||||
result.year.should eql(2007)
|
||||
result.month.should eql(3)
|
||||
result.day.should eql(25)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
@course.started_on = 'Dec 20th, 2006'
|
||||
@course.started_on.month.should == 12
|
||||
@course.started_on.day.should == 20
|
||||
@course.started_on.year.should == 2006
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not typecast non-date values' do
|
||||
@course.started_on = 'not-date'
|
||||
@course['started_on'].should eql('not-date')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Time' do
|
||||
describe 'and value given as a hash with keys like :year, :month, etc' do
|
||||
it 'builds a Time instance from hash values' do
|
||||
@course.ends_at = {
|
||||
:year => '2006',
|
||||
:month => '11',
|
||||
:day => '23',
|
||||
:hour => '12',
|
||||
:min => '0',
|
||||
:sec => '0'
|
||||
}
|
||||
result = @course['ends_at']
|
||||
|
||||
result.should be_kind_of(Time)
|
||||
result.year.should eql(2006)
|
||||
result.month.should eql(11)
|
||||
result.day.should eql(23)
|
||||
result.hour.should eql(12)
|
||||
result.min.should eql(0)
|
||||
result.sec.should eql(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'and value is a string' do
|
||||
it 'parses the string' do
|
||||
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
||||
@course.ends_at = t.strftime('%Y/%m/%d %H:%M:%S %z')
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
it 'parses the string without offset as UTC' do
|
||||
t = Time.now.utc
|
||||
@course.ends_at = t.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@course.ends_at.utc?.should be_true
|
||||
@course['ends_at'].year.should eql(t.year)
|
||||
@course['ends_at'].month.should eql(t.month)
|
||||
@course['ends_at'].day.should eql(t.day)
|
||||
@course['ends_at'].hour.should eql(t.hour)
|
||||
@course['ends_at'].min.should eql(t.min)
|
||||
@course['ends_at'].sec.should eql(t.sec)
|
||||
end
|
||||
end
|
||||
|
||||
it "converts a time value into utc" do
|
||||
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
||||
@course.ends_at = t
|
||||
@course.ends_at.utc?.should be_true
|
||||
@course.ends_at.should eql(Time.utc(2011, 4, 1, 16, 50, 32))
|
||||
end
|
||||
|
||||
it 'does not typecast non-time values' do
|
||||
@course.ends_at = 'not-time'
|
||||
@course['ends_at'].should eql('not-time')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Class' do
|
||||
it 'returns same value if a class' do
|
||||
value = Course
|
||||
@course.klass = value
|
||||
@course['klass'].should equal(value)
|
||||
end
|
||||
|
||||
it 'returns the class if found' do
|
||||
@course.klass = 'Course'
|
||||
@course['klass'].should eql(Course)
|
||||
end
|
||||
|
||||
it 'does not typecast non-class values' do
|
||||
@course.klass = 'NoClass'
|
||||
@course['klass'].should eql('NoClass')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when type primitive is a Boolean' do
|
||||
|
||||
[ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
|
||||
it "returns true when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
[ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
|
||||
it "returns false when value is #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should be_false
|
||||
end
|
||||
end
|
||||
|
||||
[ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
|
||||
it "does not typecast value #{value.inspect}" do
|
||||
@course.active = value
|
||||
@course['active'].should equal(value)
|
||||
end
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier" do
|
||||
@course.active = nil
|
||||
@course.active?.should be_false
|
||||
@course.active = false
|
||||
@course.active?.should be_false
|
||||
@course.active = true
|
||||
@course.active?.should be_true
|
||||
end
|
||||
|
||||
it "should respond to requests with ? modifier on TrueClass" do
|
||||
@course.very_active = nil
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = false
|
||||
@course.very_active?.should be_false
|
||||
@course.very_active = true
|
||||
@course.very_active?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
Loading…
Reference in a new issue