diff --git a/lib/couchrest/mixins/properties.rb b/lib/couchrest/mixins/properties.rb index d0d8537..21e9ca2 100644 --- a/lib/couchrest/mixins/properties.rb +++ b/lib/couchrest/mixins/properties.rb @@ -1,4 +1,3 @@ -require 'time' require File.join(File.dirname(__FILE__), '..', 'more', 'property') class Time @@ -56,40 +55,32 @@ module CouchRest def cast_keys return unless self.class.properties self.class.properties.each do |property| - cast_property(property) + key = self.has_key?(property.name) ? property.name : property.name.to_sym + # Don't cast the property unless it has a value + next if (value = self[key]).nil? + write_property(property, value) end end - - def cast_property(property, assigned=false) - return unless property.casted - key = self.has_key?(property.name) ? property.name : property.name.to_sym - # Don't cast the property unless it has a value - return unless self[key] - if property.type.is_a?(Array) - 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 - 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 + + protected + + def write_attribute(name, value) + unless (property = property(name)).nil? + write_property(property, value) else - klass = ::CouchRest.constantize(property.type) + self[name] = value end - - unless self[key].instance_of?(klass) - self[key] = convert_property_value(property, klass, self[property.name]) - 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 def associate_casted_to_parent(casted, assigned) casted.casted_by = self if casted.respond_to?(:casted_by) @@ -143,7 +134,7 @@ module CouchRest # check if this property is going to casted options[:casted] = options[:cast_as] ? options[:cast_as] : false property = CouchRest::Property.new(name, (options.delete(:cast_as) || options.delete(:type)), options) - create_property_getter(property) + create_property_getter(property) create_property_setter(property) unless property.read_only == true properties << property end @@ -181,8 +172,7 @@ module CouchRest property_name = property.name class_eval <<-EOS def #{property_name}=(value) - self['#{property_name}'] = value - cast_property_by_name('#{property_name}') + write_attribute('#{property_name}', value) end EOS @@ -192,9 +182,7 @@ module CouchRest EOS end end - end # module ClassMethods - end end end \ No newline at end of file diff --git a/lib/couchrest/more/extended_document.rb b/lib/couchrest/more/extended_document.rb index 73c1b17..1ea82ae 100644 --- a/lib/couchrest/more/extended_document.rb +++ b/lib/couchrest/more/extended_document.rb @@ -73,9 +73,9 @@ module CouchRest # on the document whenever saving occurs. CouchRest uses a pretty # decent time format by default. See Time#to_json def self.timestamps! - class_eval <<-EOS, __FILE__, __LINE__ + 1 - property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false) - property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false) + class_eval <<-EOS, __FILE__, __LINE__ + property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false) + property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false) set_callback :save, :before do |object| object['updated_at'] = Time.now diff --git a/lib/couchrest/more/property.rb b/lib/couchrest/more/property.rb index efede74..43e9a62 100644 --- a/lib/couchrest/more/property.rb +++ b/lib/couchrest/more/property.rb @@ -1,9 +1,13 @@ +require 'time' +require 'bigdecimal' +require 'bigdecimal/util' + module CouchRest - + # Basic attribute support for adding getter/setter + validation class Property attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options - + # attribute to define def initialize(name, type = nil, options = {}) @name = name.to_s @@ -11,20 +15,184 @@ module CouchRest parse_options(options) self 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 - + def parse_type(type) if type.nil? @type = 'String' elsif type.is_a?(Array) && type.empty? - @type = 'Array' + @type = ['Object'] else @type = type.is_a?(Array) ? [type.first.to_s] : type.to_s end end - + def parse_options(options) return if options.empty? @validation_format = options.delete(:format) if options[:format] @@ -35,7 +203,7 @@ module CouchRest @init_method = options[:init_method] ? options.delete(:init_method) : 'new' @options = options end - + end end diff --git a/lib/couchrest/validation/validators/required_field_validator.rb b/lib/couchrest/validation/validators/required_field_validator.rb index d8edd81..0c64ccf 100644 --- a/lib/couchrest/validation/validators/required_field_validator.rb +++ b/lib/couchrest/validation/validators/required_field_validator.rb @@ -37,7 +37,7 @@ module CouchRest def call(target) 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) error_message = @options[:message] || default_error(property) @@ -66,7 +66,7 @@ module CouchRest # Returns false for other property types. # Returns false for non-properties. def boolean_type?(property) - property ? property.type == TrueClass : false + property ? property.type == 'Boolean' : false end end # class RequiredFieldValidator diff --git a/spec/couchrest/more/casted_model_spec.rb b/spec/couchrest/more/casted_model_spec.rb index a2a8a96..704b85c 100644 --- a/spec/couchrest/more/casted_model_spec.rb +++ b/spec/couchrest/more/casted_model_spec.rb @@ -12,7 +12,7 @@ class WithCastedModelMixin < Hash include CouchRest::CastedModel property :name property :no_value - property :details, :default => {} + property :details, :type => 'Object', :default => {} property :casted_attribute, :cast_as => 'WithCastedModelMixin' end diff --git a/spec/couchrest/more/extended_doc_spec.rb b/spec/couchrest/more/extended_doc_spec.rb index f055576..6d558ce 100644 --- a/spec/couchrest/more/extended_doc_spec.rb +++ b/spec/couchrest/more/extended_doc_spec.rb @@ -9,11 +9,11 @@ describe "ExtendedDocument" do class WithDefaultValues < CouchRest::ExtendedDocument use_database TEST_SERVER.default_database - property :preset, :default => {:right => 10, :top_align => false} - property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time' - property :tags, :default => [] + property :preset, :type => 'Object', :default => {:right => 10, :top_align => false} + property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time' + property :tags, :type => ['String'], :default => [] property :read_only_with_default, :default => 'generic', :read_only => true - property :default_false, :default => false + property :default_false, :type => 'Boolean', :default => false property :name timestamps! end @@ -383,7 +383,7 @@ describe "ExtendedDocument" do "professor" => { "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 @course = Course.get r['id'] @@ -394,8 +394,8 @@ describe "ExtendedDocument" do it "should instantiate the professor as a person" do @course['professor'].last_name.should == "Hinchliff" end - it "should instantiate the final_test_at as a Time" do - @course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800") + it "should instantiate the ends_at as a Time" do + @course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800") end end diff --git a/spec/couchrest/more/property_spec.rb b/spec/couchrest/more/property_spec.rb index 0e25916..a98e1a9 100644 --- a/spec/couchrest/more/property_spec.rb +++ b/spec/couchrest/more/property_spec.rb @@ -7,6 +7,7 @@ require File.join(FIXTURE_PATH, 'more', 'service') require File.join(FIXTURE_PATH, 'more', 'event') require File.join(FIXTURE_PATH, 'more', 'cat') require File.join(FIXTURE_PATH, 'more', 'user') +require File.join(FIXTURE_PATH, 'more', 'course') describe "ExtendedDocument properties" do @@ -42,11 +43,11 @@ describe "ExtendedDocument properties" do it "should let you use an alias for a casted attribute" do @card.cast_alias = Person.new(:name => "Aimonetti") - @card.cast_alias.name.should == "Aimonetti" - @card.calias.name.should == "Aimonetti" + @card.cast_alias.name.should == ["Aimonetti"] + @card.calias.name.should == ["Aimonetti"] card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"}) - card.cast_alias.name.should == "Aimonetti" - card.calias.name.should == "Aimonetti" + card.cast_alias.name.should == ["Aimonetti"] + card.calias.name.should == ["Aimonetti"] end it "should be auto timestamped" do @@ -157,109 +158,446 @@ describe "ExtendedDocument properties" do end describe "casting" do - describe "cast keys to any type" do - before(:all) do - event_doc = { :subject => "Some event", :occurs_at => Time.now, :end_date => Date.today } - e = Event.database.save_doc event_doc + before(:each) do + @course = Course.new(:title => 'Relaxation') + end - @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 - it "should cast occurs_at to Time" do - @event['occurs_at'].should be_an_instance_of(Time) + 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 @event['end_date'].should be_an_instance_of(Date) end end - describe "casting to Float object" do - class RootBeerFloat < CouchRest::ExtendedDocument - use_database DB - property :price, :cast_as => 'Float' + 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 "should convert a string into a float if casted as so" do - RootBeerFloat.new(:price => '12.50').price.should == 12.50 - RootBeerFloat.new(:price => '9').price.should == 9.0 - RootBeerFloat.new(:price => '-9').price.should == -9.0 + it "it casts to string representation of the value" do + @course.title = 1.0 + @course['title'].should eql("1.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 + + 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 "should work fine when a float is being passed" do - RootBeerFloat.new(:price => 9.99).price.should == 9.99 + + 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 + + [ 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 + + [ 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 + + [ 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 + 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 "casting to a boolean value" do - class RootBeerFloat < CouchRest::ExtendedDocument - use_database DB - property :tasty, :cast_as => :boolean + 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 - it "should add an accessor with a '?' for boolean attributes that returns true or false" do - RootBeerFloat.new(:tasty => true).tasty?.should == true - RootBeerFloat.new(:tasty => 'you bet').tasty?.should == true - 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 + [ 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 - it "should return the real value when the default accessor is used" do - RootBeerFloat.new(:tasty => true).tasty.should == true - RootBeerFloat.new(:tasty => 'you bet').tasty.should == 'you bet' - RootBeerFloat.new(:tasty => 123).tasty.should == 123 - RootBeerFloat.new(:tasty => 'false').tasty.should == 'false' - RootBeerFloat.new(:tasty => false).tasty.should == false - RootBeerFloat.new(:tasty => nil).tasty.should == nil + [ '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 - -describe "a newly created casted model" do - before(:each) do - reset_test_db! - @cat = Cat.new(:name => 'Toonces') - @squeaky_mouse = CatToy.new(:name => 'Squeaky') - end - - describe "assigned assigned to a casted property" do - it "should have casted_by set to its parent" do - @squeaky_mouse.casted_by.should be_nil - @cat.favorite_toy = @squeaky_mouse - @squeaky_mouse.casted_by.should === @cat - end - end - - describe "appended to a casted collection" do - it "should have casted_by set to its parent" do - @squeaky_mouse.casted_by.should be_nil - @cat.toys << @squeaky_mouse - @squeaky_mouse.casted_by.should === @cat - @cat.save - @cat.toys.first.casted_by.should === @cat - end - end - - describe "list assigned to a casted collection" do - it "should have casted_by set on all elements" do - toy1 = CatToy.new(:name => 'Feather') - toy2 = CatToy.new(:name => 'Mouse') - @cat.toys = [toy1, toy2] - toy1.casted_by.should === @cat - toy2.casted_by.should === @cat - @cat.save - @cat = Cat.get(@cat.id) - @cat.toys[0].casted_by.should === @cat - @cat.toys[1].casted_by.should === @cat - end end end diff --git a/spec/fixtures/more/article.rb b/spec/fixtures/more/article.rb index dbc9e8c..0185d3b 100644 --- a/spec/fixtures/more/article.rb +++ b/spec/fixtures/more/article.rb @@ -20,10 +20,10 @@ class Article < CouchRest::ExtendedDocument return sum(values); }" - property :date + property :date, :type => 'Date' property :slug, :read_only => true property :title - property :tags + property :tags, :type => ['String'] timestamps! diff --git a/spec/fixtures/more/course.rb b/spec/fixtures/more/course.rb index b639d34..eada30f 100644 --- a/spec/fixtures/more/course.rb +++ b/spec/fixtures/more/course.rb @@ -5,9 +5,17 @@ class Course < CouchRest::ExtendedDocument use_database TEST_SERVER.default_database property :title - property :questions, :cast_as => ['Question'] - property :professor, :cast_as => 'Person' - property :final_test_at, :cast_as => 'Time' + property :questions, :cast_as => ['Question'] + property :professor, :cast_as => 'Person' + 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 :dept, :ducktype => true diff --git a/spec/fixtures/more/person.rb b/spec/fixtures/more/person.rb index de9e72c..b200cb4 100644 --- a/spec/fixtures/more/person.rb +++ b/spec/fixtures/more/person.rb @@ -1,7 +1,7 @@ class Person < Hash include ::CouchRest::CastedModel - property :name property :pet, :cast_as => 'Cat' + property :name, :type => ['String'] def last_name name.last diff --git a/spec/fixtures/more/question.rb b/spec/fixtures/more/question.rb index bec7803..0d29e21 100644 --- a/spec/fixtures/more/question.rb +++ b/spec/fixtures/more/question.rb @@ -2,5 +2,5 @@ class Question < Hash include ::CouchRest::CastedModel property :q - property :a + property :a, :type => 'Object' end \ No newline at end of file diff --git a/spec/fixtures/more/service.rb b/spec/fixtures/more/service.rb index 6ecf276..77b28ef 100644 --- a/spec/fixtures/more/service.rb +++ b/spec/fixtures/more/service.rb @@ -7,6 +7,6 @@ class Service < CouchRest::ExtendedDocument # Official Schema property :name, :length => 4...20 - property :price, :type => Integer + property :price, :type => 'Integer' end \ No newline at end of file