attempt to merge wildchild/type_cast, still failing specs

This commit is contained in:
Will Leinweber 2010-02-17 16:35:42 -06:00
commit 15ea10eb9c
12 changed files with 652 additions and 150 deletions

View file

@ -1,4 +1,3 @@
require 'time'
require File.join(File.dirname(__FILE__), '..', 'more', 'property') require File.join(File.dirname(__FILE__), '..', 'more', 'property')
class Time class Time
@ -56,39 +55,31 @@ module CouchRest
def cast_keys def cast_keys
return unless self.class.properties return unless self.class.properties
self.class.properties.each do |property| self.class.properties.each do |property|
cast_property(property)
end
end
def cast_property(property, assigned=false)
return unless property.casted
key = self.has_key?(property.name) ? property.name : property.name.to_sym key = self.has_key?(property.name) ? property.name : property.name.to_sym
# Don't cast the property unless it has a value # Don't cast the property unless it has a value
return unless self[key] next if (value = self[key]).nil?
if property.type.is_a?(Array) write_property(property, value)
klass = ::CouchRest.constantize(property.type[0])
arr = self[key].dup.collect do |value|
unless value.instance_of?(klass)
value = convert_property_value(property, klass, value)
end end
associate_casted_to_parent(value, assigned)
value
end
self[key] = klass != String ? CastedArray.new(arr) : arr
self[key].casted_by = self if self[key].respond_to?(:casted_by)
else
if property.type == 'boolean'
klass = TrueClass
else
klass = ::CouchRest.constantize(property.type)
end end
unless self[key].instance_of?(klass) protected
self[key] = convert_property_value(property, klass, self[property.name])
def write_attribute(name, value)
unless (property = property(name)).nil?
write_property(property, value)
else
self[name] = value
end end
associate_casted_to_parent(self[property.name], assigned)
end end
def write_property(property, value)
value = property.typecast(value)
value.casted_by = self if value.respond_to?(:casted_by)
self[property.name] = value
end
def property(name)
properties.find {|p| p.name == name.to_s}
end end
def associate_casted_to_parent(casted, assigned) def associate_casted_to_parent(casted, assigned)
@ -181,8 +172,7 @@ module CouchRest
property_name = property.name property_name = property.name
class_eval <<-EOS class_eval <<-EOS
def #{property_name}=(value) def #{property_name}=(value)
self['#{property_name}'] = value write_attribute('#{property_name}', value)
cast_property_by_name('#{property_name}')
end end
EOS EOS
@ -192,9 +182,7 @@ module CouchRest
EOS EOS
end end
end end
end # module ClassMethods end # module ClassMethods
end end
end end
end end

View file

@ -73,9 +73,9 @@ module CouchRest
# on the document whenever saving occurs. CouchRest uses a pretty # on the document whenever saving occurs. CouchRest uses a pretty
# decent time format by default. See Time#to_json # decent time format by default. See Time#to_json
def self.timestamps! def self.timestamps!
class_eval <<-EOS, __FILE__, __LINE__ + 1 class_eval <<-EOS, __FILE__, __LINE__
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false) property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false)
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false) property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false)
set_callback :save, :before do |object| set_callback :save, :before do |object|
object['updated_at'] = Time.now object['updated_at'] = Time.now

View file

@ -1,3 +1,7 @@
require 'time'
require 'bigdecimal'
require 'bigdecimal/util'
module CouchRest module CouchRest
# Basic attribute support for adding getter/setter + validation # Basic attribute support for adding getter/setter + validation
@ -12,6 +16,170 @@ module CouchRest
self self
end end
def typecast(value)
do_typecast(value, type, init_method)
end
protected
def do_typecast(value, target, init_method)
return nil if value.nil?
if target == 'String' then typecast_to_string(value)
elsif target == 'Boolean' then typecast_to_boolean(value)
elsif target == 'Integer' then typecast_to_integer(value)
elsif target == 'Float' then typecast_to_float(value)
elsif target == 'BigDecimal' then typecast_to_bigdecimal(value)
elsif target == 'DateTime' then typecast_to_datetime(value)
elsif target == 'Time' then typecast_to_time(value)
elsif target == 'Date' then typecast_to_date(value)
elsif target == 'Class' then typecast_to_class(value)
elsif target.is_a?(Array) then typecast_array(value, target, init_method)
else
@klass ||= ::CouchRest.constantize(target)
value.kind_of?(@klass) ? value : @klass.send(init_method, value.dup)
end
end
def typecast_array(value, target, init_method)
value.map { |v| do_typecast(v, target[0], init_method) }
end
# Typecast a value to an Integer
def typecast_to_integer(value)
value.kind_of?(Integer) ? value : typecast_to_numeric(value, :to_i)
end
# Typecast a value to a String
def typecast_to_string(value)
value.to_s
end
# Typecast a value to a true or false
def typecast_to_boolean(value)
return value if value == true || value == false
if value.kind_of?(Integer)
return true if value == 1
return false if value == 0
elsif value.respond_to?(:to_str)
return true if %w[ true 1 t ].include?(value.to_str.downcase)
return false if %w[ false 0 f ].include?(value.to_str.downcase)
end
value
end
# Typecast a value to a BigDecimal
def typecast_to_bigdecimal(value)
return value if value.kind_of?(BigDecimal)
if value.kind_of?(Integer)
value.to_s.to_d
else
typecast_to_numeric(value, :to_d)
end
end
# Typecast a value to a Float
def typecast_to_float(value)
return value if value.kind_of?(Float)
typecast_to_numeric(value, :to_f)
end
# Match numeric string
def typecast_to_numeric(value, method)
if value.respond_to?(:to_str)
if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
$1.send(method)
else
value
end
elsif value.respond_to?(method)
value.send(method)
else
value
end
end
# Typecasts an arbitrary value to a DateTime.
# Handles both Hashes and DateTime instances.
def typecast_to_datetime(value)
return value if value.kind_of?(DateTime)
if value.is_a?(Hash)
typecast_hash_to_datetime(value)
else
DateTime.parse(value.to_s)
end
rescue ArgumentError
value
end
# Typecasts an arbitrary value to a Date
# Handles both Hashes and Date instances.
def typecast_to_date(value)
return value if value.kind_of?(Date)
if value.is_a?(Hash)
typecast_hash_to_date(value)
else
Date.parse(value.to_s)
end
rescue ArgumentError
value
end
# Typecasts an arbitrary value to a Time
# Handles both Hashes and Time instances.
def typecast_to_time(value)
return value if value.kind_of?(Time)
if value.is_a?(Hash)
typecast_hash_to_time(value)
else
Time.mktime_with_offset(value.to_s)
end
rescue ArgumentError
value
rescue TypeError
value
end
# Creates a DateTime instance from a Hash with keys :year, :month, :day,
# :hour, :min, :sec
def typecast_hash_to_datetime(value)
DateTime.new(*extract_time(value))
end
# Creates a Date instance from a Hash with keys :year, :month, :day
def typecast_hash_to_date(value)
Date.new(*extract_time(value)[0, 3])
end
# 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))
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
[:year, :month, :day, :hour, :min, :sec].map do |segment|
typecast_to_numeric(value.fetch(segment, now.send(segment)), :to_i)
end
end
# Typecast a value to a Class
def typecast_to_class(value)
return value if value.kind_of?(Class)
::CouchRest.constantize(value.to_s)
rescue NameError
value
end
private private
@ -19,7 +187,7 @@ module CouchRest
if type.nil? if type.nil?
@type = 'String' @type = 'String'
elsif type.is_a?(Array) && type.empty? elsif type.is_a?(Array) && type.empty?
@type = 'Array' @type = ['Object']
else else
@type = type.is_a?(Array) ? [type.first.to_s] : type.to_s @type = type.is_a?(Array) ? [type.first.to_s] : type.to_s
end end

View file

@ -37,7 +37,7 @@ module CouchRest
def call(target) def call(target)
value = target.validation_property_value(field_name) value = target.validation_property_value(field_name)
property = target.validation_property(field_name) property = target.validation_property(field_name.to_s)
return true if present?(value, property) return true if present?(value, property)
error_message = @options[:message] || default_error(property) error_message = @options[:message] || default_error(property)
@ -66,7 +66,7 @@ module CouchRest
# Returns false for other property types. # Returns false for other property types.
# Returns false for non-properties. # Returns false for non-properties.
def boolean_type?(property) def boolean_type?(property)
property ? property.type == TrueClass : false property ? property.type == 'Boolean' : false
end end
end # class RequiredFieldValidator end # class RequiredFieldValidator

View file

@ -12,7 +12,7 @@ class WithCastedModelMixin < Hash
include CouchRest::CastedModel include CouchRest::CastedModel
property :name property :name
property :no_value property :no_value
property :details, :default => {} property :details, :type => 'Object', :default => {}
property :casted_attribute, :cast_as => 'WithCastedModelMixin' property :casted_attribute, :cast_as => 'WithCastedModelMixin'
end end

View file

@ -9,11 +9,11 @@ describe "ExtendedDocument" do
class WithDefaultValues < CouchRest::ExtendedDocument class WithDefaultValues < CouchRest::ExtendedDocument
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
property :preset, :default => {:right => 10, :top_align => false} property :preset, :type => 'Object', :default => {:right => 10, :top_align => false}
property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time' property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
property :tags, :default => [] property :tags, :type => ['String'], :default => []
property :read_only_with_default, :default => 'generic', :read_only => true property :read_only_with_default, :default => 'generic', :read_only => true
property :default_false, :default => false property :default_false, :type => 'Boolean', :default => false
property :name property :name
timestamps! timestamps!
end end
@ -383,7 +383,7 @@ describe "ExtendedDocument" do
"professor" => { "professor" => {
"name" => ["Mark", "Hinchliff"] "name" => ["Mark", "Hinchliff"]
}, },
"final_test_at" => "2008/12/19 13:00:00 +0800" "ends_at" => "2008/12/19 13:00:00 +0800"
} }
r = Course.database.save_doc course_doc r = Course.database.save_doc course_doc
@course = Course.get r['id'] @course = Course.get r['id']
@ -394,8 +394,8 @@ describe "ExtendedDocument" do
it "should instantiate the professor as a person" do it "should instantiate the professor as a person" do
@course['professor'].last_name.should == "Hinchliff" @course['professor'].last_name.should == "Hinchliff"
end end
it "should instantiate the final_test_at as a Time" do it "should instantiate the ends_at as a Time" do
@course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800") @course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
end end
end end

View file

@ -7,6 +7,7 @@ require File.join(FIXTURE_PATH, 'more', 'service')
require File.join(FIXTURE_PATH, 'more', 'event') require File.join(FIXTURE_PATH, 'more', 'event')
require File.join(FIXTURE_PATH, 'more', 'cat') require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'user') require File.join(FIXTURE_PATH, 'more', 'user')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "ExtendedDocument properties" do describe "ExtendedDocument properties" do
@ -42,11 +43,11 @@ describe "ExtendedDocument properties" do
it "should let you use an alias for a casted attribute" do it "should let you use an alias for a casted attribute" do
@card.cast_alias = Person.new(:name => "Aimonetti") @card.cast_alias = Person.new(:name => "Aimonetti")
@card.cast_alias.name.should == "Aimonetti" @card.cast_alias.name.should == ["Aimonetti"]
@card.calias.name.should == "Aimonetti" @card.calias.name.should == ["Aimonetti"]
card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"}) card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"})
card.cast_alias.name.should == "Aimonetti" card.cast_alias.name.should == ["Aimonetti"]
card.calias.name.should == "Aimonetti" card.calias.name.should == ["Aimonetti"]
end end
it "should be auto timestamped" do it "should be auto timestamped" do
@ -157,108 +158,445 @@ describe "ExtendedDocument properties" do
end end
describe "casting" do describe "casting" do
describe "cast keys to any type" do before(:each) do
before(:all) do @course = Course.new(:title => 'Relaxation')
event_doc = { :subject => "Some event", :occurs_at => Time.now, :end_date => Date.today } end
e = Event.database.save_doc event_doc
@event = Event.get e['id'] describe "when value is nil" do
it "leaves the value unchanged" do
@course.title = nil
@course['title'].should == nil
end end
it "should cast occurs_at to Time" do
@event['occurs_at'].should be_an_instance_of(Time)
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 eql([{}, 'q', 1])
end
it "should cast end_date to Date" do it "should cast end_date to Date" do
@event['end_date'].should be_an_instance_of(Date) @event['end_date'].should be_an_instance_of(Date)
end end
end end
describe "casting to Float object" do describe "when type primitive is a String" do
class RootBeerFloat < CouchRest::ExtendedDocument it "keeps string value unchanged" do
use_database DB value = "1.0"
property :price, :cast_as => 'Float' @course.title = value
@course['title'].should equal(value)
end end
it "should convert a string into a float if casted as so" do it "it casts to string representation of the value" do
RootBeerFloat.new(:price => '12.50').price.should == 12.50 @course.title = 1.0
RootBeerFloat.new(:price => '9').price.should == 9.0 @course['title'].should eql("1.0")
RootBeerFloat.new(:price => '-9').price.should == -9.0
end
it "should not convert a string if it's not a string that can be cast as a float" do
RootBeerFloat.new(:price => 'test').price.should == 'test'
end
it "should work fine when a float is being passed" do
RootBeerFloat.new(:price => 9.99).price.should == 9.99
end end
end end
describe "casting to a boolean value" do describe 'when type primitive is a Float' do
class RootBeerFloat < CouchRest::ExtendedDocument it 'returns same value if a float' do
use_database DB value = 24.0
property :tasty, :cast_as => :boolean @course.estimate = value
@course['estimate'].should equal(value)
end end
it "should add an accessor with a '?' for boolean attributes that returns true or false" do it 'returns float representation of a zero string integer' do
RootBeerFloat.new(:tasty => true).tasty?.should == true @course.estimate = '0'
RootBeerFloat.new(:tasty => 'you bet').tasty?.should == true @course['estimate'].should eql(0.0)
RootBeerFloat.new(:tasty => 123).tasty?.should == true
RootBeerFloat.new(:tasty => false).tasty?.should == false
RootBeerFloat.new(:tasty => 'false').tasty?.should == false
RootBeerFloat.new(:tasty => 'FaLsE').tasty?.should == false
RootBeerFloat.new(:tasty => nil).tasty?.should == false
end end
it "should return the real value when the default accessor is used" do it 'returns float representation of a positive string integer' do
RootBeerFloat.new(:tasty => true).tasty.should == true @course.estimate = '24'
RootBeerFloat.new(:tasty => 'you bet').tasty.should == 'you bet' @course['estimate'].should eql(24.0)
RootBeerFloat.new(:tasty => 123).tasty.should == 123 end
RootBeerFloat.new(:tasty => 'false').tasty.should == 'false'
RootBeerFloat.new(:tasty => false).tasty.should == false it 'returns float representation of a negative string integer' do
RootBeerFloat.new(:tasty => nil).tasty.should == nil @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
[ 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
end end
end describe 'when type primitive is a Integer' do
end it 'returns same value if an integer' do
value = 24
describe "a newly created casted model" do @course.hours = value
before(:each) do @course['hours'].should equal(value)
reset_test_db!
@cat = Cat.new(:name => 'Toonces')
@squeaky_mouse = CatToy.new(:name => 'Squeaky')
end end
describe "assigned assigned to a casted property" do it 'returns integer representation of a zero string integer' do
it "should have casted_by set to its parent" do @course.hours = '0'
@squeaky_mouse.casted_by.should be_nil @course['hours'].should eql(0)
@cat.favorite_toy = @squeaky_mouse end
@squeaky_mouse.casted_by.should === @cat
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
[ 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
end end
describe "appended to a casted collection" do describe 'when type primitive is a BigDecimal' do
it "should have casted_by set to its parent" do it 'returns same value if a decimal' do
@squeaky_mouse.casted_by.should be_nil value = BigDecimal('24.0')
@cat.toys << @squeaky_mouse @course.profit = value
@squeaky_mouse.casted_by.should === @cat @course['profit'].should equal(value)
@cat.save end
@cat.toys.first.casted_by.should === @cat
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
[ 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
end end
describe "list assigned to a casted collection" do describe 'when type primitive is a DateTime' do
it "should have casted_by set on all elements" do describe 'and value given as a hash with keys like :year, :month, etc' do
toy1 = CatToy.new(:name => 'Feather') it 'builds a DateTime instance from hash values' do
toy2 = CatToy.new(:name => 'Mouse') @course.updated_at = {
@cat.toys = [toy1, toy2] :year => '2006',
toy1.casted_by.should === @cat :month => '11',
toy2.casted_by.should === @cat :day => '23',
@cat.save :hour => '12',
@cat = Cat.get(@cat.id) :min => '0',
@cat.toys[0].casted_by.should === @cat :sec => '0'
@cat.toys[1].casted_by.should === @cat }
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
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
end end
end end
end end

View file

@ -20,10 +20,10 @@ class Article < CouchRest::ExtendedDocument
return sum(values); return sum(values);
}" }"
property :date property :date, :type => 'Date'
property :slug, :read_only => true property :slug, :read_only => true
property :title property :title
property :tags property :tags, :type => ['String']
timestamps! timestamps!

View file

@ -7,7 +7,15 @@ class Course < CouchRest::ExtendedDocument
property :title property :title
property :questions, :cast_as => ['Question'] property :questions, :cast_as => ['Question']
property :professor, :cast_as => 'Person' property :professor, :cast_as => 'Person'
property :final_test_at, :cast_as => 'Time' property :participants, :type => ['Object']
property :ends_at, :type => 'Time'
property :estimate, :type => 'Float'
property :hours, :type => 'Integer'
property :profit, :type => 'BigDecimal'
property :started_on, :type => 'Date'
property :updated_at, :type => 'DateTime'
property :active, :type => 'Boolean'
property :klass, :type => 'Class'
view_by :title view_by :title
view_by :dept, :ducktype => true view_by :dept, :ducktype => true

View file

@ -1,7 +1,7 @@
class Person < Hash class Person < Hash
include ::CouchRest::CastedModel include ::CouchRest::CastedModel
property :name
property :pet, :cast_as => 'Cat' property :pet, :cast_as => 'Cat'
property :name, :type => ['String']
def last_name def last_name
name.last name.last

View file

@ -2,5 +2,5 @@ class Question < Hash
include ::CouchRest::CastedModel include ::CouchRest::CastedModel
property :q property :q
property :a property :a, :type => 'Object'
end end

View file

@ -7,6 +7,6 @@ class Service < CouchRest::ExtendedDocument
# Official Schema # Official Schema
property :name, :length => 4...20 property :name, :length => 4...20
property :price, :type => Integer property :price, :type => 'Integer'
end end