diff --git a/lib/arel/algebra/attributes/attribute.rb b/lib/arel/algebra/attributes/attribute.rb index f4cec828e3..03cf44a552 100644 --- a/lib/arel/algebra/attributes/attribute.rb +++ b/lib/arel/algebra/attributes/attribute.rb @@ -160,16 +160,13 @@ module Arel def type_cast_to_numeric(value, method) return unless value if value.respond_to?(:to_str) - if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/ - $1.send(method) - else - value - end + str = value.to_str.strip + return if str.empty? + return $1.send(method) if str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/ elsif value.respond_to?(method) - value.send(method) - else - raise typecast_error(value) + return value.send(method) end + raise typecast_error(value) end def typecast_error(value) diff --git a/lib/arel/algebra/attributes/boolean.rb b/lib/arel/algebra/attributes/boolean.rb index 0ca7cd6d24..d69f2465df 100644 --- a/lib/arel/algebra/attributes/boolean.rb +++ b/lib/arel/algebra/attributes/boolean.rb @@ -3,11 +3,12 @@ module Arel class Boolean < Attribute def type_cast(value) case value - when true, false then value - when nil then options[:allow_nil] ? nil : false - when 1 then true - when 0 then false - else + when true, false then value + # when nil then options[:allow_nil] ? nil : false + when nil then false + when 1 then true + when 0 then false + else case value.to_s.downcase.strip when 'true' then true when 'false' then false diff --git a/spec/arel/algebra/integration/basic_spec.rb b/spec/arel/algebra/integration/basic_spec.rb index 6ade5c40ac..7aa4f7305c 100644 --- a/spec/arel/algebra/integration/basic_spec.rb +++ b/spec/arel/algebra/integration/basic_spec.rb @@ -1,50 +1,5 @@ require 'spec_helper' -module Arel - module Testing - class Engine - attr_reader :rows - - def initialize - @rows = [] - end - - def supports(operation) - false - end - - def read(relation) - @rows.dup.map { |r| Row.new(relation, r) } - end - - def create(insert) - @rows << insert.record.tuple - insert - end - end - end -end - -class Thing < Arel::Relation - attr_reader :engine, :attributes - - def initialize(engine, attributes) - @engine, @attributes = engine, [] - attributes.each do |name, type| - @attributes << type.new(self, name) - end - end - - def format(attribute, value) - value - end - - def insert(row) - insert = super Arel::Row.new(self, row) - insert.record - end -end - def have_rows(expected) simple_matcher "have rows" do |given, matcher| found, got, expected = [], [], expected.map { |r| r.tuple } @@ -101,11 +56,14 @@ module Arel describe "Relation" do before :all do - @engine = Testing::Engine.new - @relation = Thing.new(@engine, - :id => Attributes::Integer, - :name => Attributes::String, - :age => Attributes::Integer) + @engine = Testing::Engine.new + @relation = Model.build do |r| + r.engine @engine + + r.attribute :id, Attributes::Integer + r.attribute :name, Attributes::String + r.attribute :age, Attributes::Integer + end end describe "..." do diff --git a/spec/attributes/boolean_spec.rb b/spec/attributes/boolean_spec.rb new file mode 100644 index 0000000000..bb7eeb886d --- /dev/null +++ b/spec/attributes/boolean_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +module Arel + describe "Attributes::Boolean" do + + before :all do + @relation = Model.build do |r| + r.engine Testing::Engine.new + r.attribute :awesome, Attributes::Boolean + end + end + + def type_cast(val) + @relation[:awesome].type_cast(val) + end + + describe "#type_cast" do + it "returns same value if passed a boolean" do + val = true + type_cast(val).should eql(val) + end + + it "returns boolean representation (false) of nil" do + type_cast(nil).should eql(false) + end + + it "returns boolean representation of 'true', 'false'" do + type_cast('true').should eql(true) + type_cast('false').should eql(false) + end + + it "returns boolean representation of :true, :false" do + type_cast(:true).should eql(true) + type_cast(:false).should eql(false) + end + + it "returns boolean representation of 0, 1" do + type_cast(1).should == true + type_cast(0).should == false + end + + it "calls #to_s on arbitrary objects" do + obj = Object.new + obj.extend Module.new { def to_s ; 'true' ; end } + type_cast(obj).should == true + end + + [ Object.new, 'string', '00.0', 5 ].each do |value| + it "raises exception when attempting type_cast of non-boolean value #{value.inspect}" do + lambda do + type_cast(value) + end.should raise_error(TypecastError, /could not typecast/) + end + end + end + end +end \ No newline at end of file diff --git a/spec/attributes/float_spec.rb b/spec/attributes/float_spec.rb new file mode 100644 index 0000000000..3d8acbde7b --- /dev/null +++ b/spec/attributes/float_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' +require 'bigdecimal' + +module Arel + describe "Attributes::Float" do + + before :all do + @relation = Model.build do |r| + r.engine Testing::Engine.new + r.attribute :percentage, Attributes::Float + end + end + + def type_cast(val) + @relation[:percentage].type_cast(val) + end + + describe "#type_cast" do + it "returns same value if an float" do + type_cast(24.01).should eql(24.01) + end + + it "returns nil if passed nil" do + type_cast(nil).should be_nil + end + + it "returns nil if passed empty string" do + type_cast('').should be_nil + end + + it "returns float representation of a zero string float" do + type_cast('0').should eql(0.0) + end + + it "returns float representation of a positive string integer" do + type_cast('24').should eql(24.0) + end + + it "returns float representation of a positive string integer with spaces" do + type_cast(' 24').should eql(24.0) + type_cast('24 ').should eql(24.0) + end + + it "returns float representation of a negative string float" do + type_cast('-24.23').should eql(-24.23) + end + + it "returns float representation of a negative string integer with spaces" do + type_cast('-24 ').should eql(-24.0) + type_cast(' -24').should eql(-24.0) + end + + it "returns integer representation of a zero string float" do + type_cast('0.0').should eql(0.0) + end + + it "returns integer representation of a positive string float" do + type_cast('24.35').should eql(24.35) + end + + it "returns integer representation of a positive string float with spaces" do + type_cast(' 24.35').should eql(24.35) + type_cast('24.35 ').should eql(24.35) + end + + it "returns integer representation of a negative string float" do + type_cast('-24.35').should eql(-24.35) + end + + it "returns integer representation of a negative string float with spaces" do + type_cast(' -24.35 ').should eql(-24.35) + end + + it "returns integer representation of a zero string float, with no leading digits" do + type_cast('.0').should eql(0.0) + end + + it "returns integer representation of a zero string float, with no leading digits with spaces" do + type_cast(' .0').should eql(0.0) + end + + it "returns integer representation of a positive string float, with no leading digits" do + type_cast('.41').should eql(0.41) + end + + it "returns integer representation of a zero float" do + type_cast(0.0).should eql(0.0) + end + + it "returns integer representation of a positive float" do + type_cast(24.35).should eql(24.35) + end + + it "returns integer representation of a negative float" do + type_cast(-24.35).should eql(-24.35) + end + + it "returns integer representation of a zero decimal" do + type_cast(BigDecimal('0.0')).should eql(0.0) + end + + it "returns integer representation of a positive decimal" do + type_cast(BigDecimal('24.35')).should eql(24.35) + end + + it "returns integer representation of a negative decimal" do + type_cast(BigDecimal('-24.35')).should eql(-24.35) + end + + [ Object.new, true, '00.0', '0.', 'string' ].each do |value| + it "raises exception when attempting type_cast of non-numeric value #{value.inspect}" do + lambda do + type_cast(value) + end.should raise_error(TypecastError, /could not typecast/) + end + end + end + end +end \ No newline at end of file diff --git a/spec/attributes/integer_spec.rb b/spec/attributes/integer_spec.rb new file mode 100644 index 0000000000..611bbf2b24 --- /dev/null +++ b/spec/attributes/integer_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' +require 'bigdecimal' + +module Arel + describe "Attributes::Integer" do + + before :all do + @relation = Model.build do |r| + r.engine Testing::Engine.new + r.attribute :age, Attributes::Integer + end + end + + def type_cast(val) + @relation[:age].type_cast(val) + end + + describe "#type_cast" do + it "returns same value if an integer" do + type_cast(24).should eql(24) + end + + it "returns nil if passed nil" do + type_cast(nil).should be_nil + end + + it "returns nil if passed empty string" do + type_cast('').should be_nil + end + + it "returns integer representation of a zero string integer" do + type_cast('0').should eql(0) + end + + it "returns integer representation of a positive string integer" do + type_cast('24').should eql(24) + end + + it "returns integer representation of a positive string integer with spaces" do + type_cast(' 24').should eql(24) + type_cast('24 ').should eql(24) + end + + it "returns integer representation of a negative string integer" do + type_cast('-24').should eql(-24) + end + + it "returns integer representation of a negative string integer with spaces" do + type_cast('-24 ').should eql(-24) + type_cast(' -24').should eql(-24) + end + + it "returns integer representation of a zero string float" do + type_cast('0.0').should eql(0) + end + + it "returns integer representation of a positive string float" do + type_cast('24.35').should eql(24) + end + + it "returns integer representation of a positive string float with spaces" do + type_cast(' 24.35').should eql(24) + type_cast('24.35 ').should eql(24) + end + + it "returns integer representation of a negative string float" do + type_cast('-24.35').should eql(-24) + end + + it "returns integer representation of a negative string float with spaces" do + type_cast(' -24.35 ').should eql(-24) + end + + it "returns integer representation of a zero string float, with no leading digits" do + type_cast('.0').should eql(0) + end + + it "returns integer representation of a zero string float, with no leading digits with spaces" do + type_cast(' .0').should eql(0) + end + + it "returns integer representation of a positive string float, with no leading digits" do + type_cast('.41').should eql(0) + end + + it "returns integer representation of a zero float" do + type_cast(0.0).should eql(0) + end + + it "returns integer representation of a positive float" do + type_cast(24.35).should eql(24) + end + + it "returns integer representation of a negative float" do + type_cast(-24.35).should eql(-24) + end + + it "returns integer representation of a zero decimal" do + type_cast(BigDecimal('0.0')).should eql(0) + end + + it "returns integer representation of a positive decimal" do + type_cast(BigDecimal('24.35')).should eql(24) + end + + it "returns integer representation of a negative decimal" do + type_cast(BigDecimal('-24.35')).should eql(-24) + end + + [ Object.new, true, '00.0', '0.', 'string' ].each do |value| + it "raises exception when attempting type_cast of non-numeric value #{value.inspect}" do + lambda do + type_cast(value) + end.should raise_error(TypecastError, /could not typecast/) + end + end + end + end +end \ No newline at end of file diff --git a/spec/attributes/string_spec.rb b/spec/attributes/string_spec.rb new file mode 100644 index 0000000000..f6d65dd045 --- /dev/null +++ b/spec/attributes/string_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'bigdecimal' + +module Arel + describe "Attributes::String" do + + before :all do + @relation = Model.build do |r| + r.engine Testing::Engine.new + r.attribute :name, Attributes::String + end + end + + def type_cast(val) + @relation[:name].type_cast(val) + end + + describe "#type_cast" do + it "returns same value if passed a String" do + val = "hell" + type_cast(val).should eql(val) + end + + it "returns nil if passed nil" do + type_cast(nil).should be_nil + end + + it "returns String representation of Symbol" do + type_cast(:hello).should == "hello" + end + + it "returns string representation of Integer" do + type_cast(1).should == '1' + end + + it "calls #to_s on arbitrary objects" do + obj = Object.new + obj.extend Module.new { def to_s ; 'hello' ; end } + type_cast(obj).should == 'hello' + end + end + end +end \ No newline at end of file diff --git a/spec/attributes/time_spec.rb b/spec/attributes/time_spec.rb new file mode 100644 index 0000000000..fcbe4f58e5 --- /dev/null +++ b/spec/attributes/time_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require 'bigdecimal' + +module Arel + describe "Attributes::Time" do + + before :all do + @relation = Model.build do |r| + r.engine Testing::Engine.new + r.attribute :created_at, Attributes::Time + end + end + + def type_cast(val) + @relation[:created_at].type_cast(val) + end + + describe "#type_cast" do + it "works" + end + end +end \ No newline at end of file diff --git a/spec/support/model.rb b/spec/support/model.rb new file mode 100644 index 0000000000..bd4376efcb --- /dev/null +++ b/spec/support/model.rb @@ -0,0 +1,56 @@ +module Arel + module Testing + class Engine + attr_reader :rows + + def initialize + @rows = [] + end + + def supports(operation) + false + end + + def read(relation) + @rows.dup.map { |r| Row.new(relation, r) } + end + + def create(insert) + @rows << insert.record.tuple + insert + end + end + end + + class Model < Relation + attr_reader :engine, :attributes + + def self.build + relation = new + yield relation + relation + end + + def initialize + @attributes = [] + end + + def engine(engine = nil) + @engine = engine if engine + @engine + end + + def attribute(name, type) + @attributes << type.new(self, name) + end + + def format(attribute, value) + value + end + + def insert(row) + insert = super Arel::Row.new(self, row) + insert.record + end + end +end \ No newline at end of file