diff options
author | Carl Lerche <carllerche@mac.com> | 2010-03-12 12:51:20 -0800 |
---|---|---|
committer | Carl Lerche <carllerche@mac.com> | 2010-03-12 12:52:07 -0800 |
commit | 83c27c0b5e2e341307b7a160d831fb930a9552b4 (patch) | |
tree | fc41004d425cbc9827e82f550959ed40e8c12978 | |
parent | 602722922c8365afcb3e9bed3721d61756322353 (diff) | |
download | rails-83c27c0b5e2e341307b7a160d831fb930a9552b4.tar.gz rails-83c27c0b5e2e341307b7a160d831fb930a9552b4.tar.bz2 rails-83c27c0b5e2e341307b7a160d831fb930a9552b4.zip |
Attributes should be typed
24 files changed, 188 insertions, 39 deletions
diff --git a/lib/arel/algebra.rb b/lib/arel/algebra.rb index 980c558918..83f6a54326 100644 --- a/lib/arel/algebra.rb +++ b/lib/arel/algebra.rb @@ -1,6 +1,6 @@ require 'arel/algebra/core_extensions' -require 'arel/algebra/attribute' +require 'arel/algebra/attributes' require 'arel/algebra/expression' require 'arel/algebra/ordering' require 'arel/algebra/predicates' diff --git a/lib/arel/algebra/attributes.rb b/lib/arel/algebra/attributes.rb new file mode 100644 index 0000000000..98302b6b18 --- /dev/null +++ b/lib/arel/algebra/attributes.rb @@ -0,0 +1,7 @@ +require "arel/algebra/attributes/attribute" +require "arel/algebra/attributes/boolean" +require "arel/algebra/attributes/decimal" +require "arel/algebra/attributes/float" +require "arel/algebra/attributes/integer" +require "arel/algebra/attributes/string" +require "arel/algebra/attributes/time"
\ No newline at end of file diff --git a/lib/arel/algebra/attribute.rb b/lib/arel/algebra/attributes/attribute.rb index 40a7d61a53..f4cec828e3 100644 --- a/lib/arel/algebra/attribute.rb +++ b/lib/arel/algebra/attributes/attribute.rb @@ -1,6 +1,7 @@ require 'set' module Arel + class TypecastError < StandardError ; end class Attribute attributes :relation, :name, :alias, :ancestor deriving :== @@ -146,5 +147,35 @@ module Arel alias_method :to_ordering, :asc end include Orderings + + module Types + def type_cast(value) + if root == self + raise NotImplementedError, "#type_cast should be implemented in a subclass." + else + root.type_cast(value) + end + end + + 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 + elsif value.respond_to?(method) + value.send(method) + else + raise typecast_error(value) + end + end + + def typecast_error(value) + raise TypecastError, "could not typecast #{value.inspect} to #{self.class.name.split('::').last}" + end + end + include Types end end diff --git a/lib/arel/algebra/attributes/boolean.rb b/lib/arel/algebra/attributes/boolean.rb new file mode 100644 index 0000000000..0ca7cd6d24 --- /dev/null +++ b/lib/arel/algebra/attributes/boolean.rb @@ -0,0 +1,20 @@ +module Arel + module Attributes + 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 + case value.to_s.downcase.strip + when 'true' then true + when 'false' then false + else raise typecast_error(value) + end + end + end + end + end +end diff --git a/lib/arel/algebra/attributes/decimal.rb b/lib/arel/algebra/attributes/decimal.rb new file mode 100644 index 0000000000..bf6587fa34 --- /dev/null +++ b/lib/arel/algebra/attributes/decimal.rb @@ -0,0 +1,9 @@ +module Arel + module Attributes + class Decimal < Attribute + def type_cast(val) + type_cast_to_numeric(val, :to_d) + end + end + end +end diff --git a/lib/arel/algebra/attributes/float.rb b/lib/arel/algebra/attributes/float.rb new file mode 100644 index 0000000000..01c95e69f9 --- /dev/null +++ b/lib/arel/algebra/attributes/float.rb @@ -0,0 +1,9 @@ +module Arel + module Attributes + class Float < Attribute + def type_cast(val) + type_cast_to_numeric(val, :to_f) + end + end + end +end diff --git a/lib/arel/algebra/attributes/integer.rb b/lib/arel/algebra/attributes/integer.rb new file mode 100644 index 0000000000..9a564565ff --- /dev/null +++ b/lib/arel/algebra/attributes/integer.rb @@ -0,0 +1,10 @@ +module Arel + module Attributes + class Integer < Attribute + def type_cast(val) + type_cast_to_numeric(val, :to_i) + end + end + end +end +
\ No newline at end of file diff --git a/lib/arel/algebra/attributes/string.rb b/lib/arel/algebra/attributes/string.rb new file mode 100644 index 0000000000..5ea91a59d8 --- /dev/null +++ b/lib/arel/algebra/attributes/string.rb @@ -0,0 +1,10 @@ +module Arel + module Attributes + class String < Attribute + def type_cast(value) + return unless value + value.to_s + end + end + end +end diff --git a/lib/arel/algebra/attributes/time.rb b/lib/arel/algebra/attributes/time.rb new file mode 100644 index 0000000000..7a2de726c8 --- /dev/null +++ b/lib/arel/algebra/attributes/time.rb @@ -0,0 +1,6 @@ +module Arel + module Attributes + class Time < Attribute + end + end +end diff --git a/lib/arel/engines/memory/relations/array.rb b/lib/arel/engines/memory/relations/array.rb index 5e7c0a4ab1..577e327b19 100644 --- a/lib/arel/engines/memory/relations/array.rb +++ b/lib/arel/engines/memory/relations/array.rb @@ -1,16 +1,21 @@ module Arel class Array < Relation - attributes :array, :attribute_names + attributes :array, :attribute_names_and_types include Recursion::BaseCase deriving :==, :initialize + def initialize(array, attribute_names_and_types) + @array, @attribute_names_and_types = array, attribute_names_and_types + end + def engine @engine ||= Memory::Engine.new end def attributes - @attributes ||= @attribute_names.collect do |name| - name.to_attribute(self) + @attributes ||= @attribute_names_and_types.collect do |attribute, type| + attribute = type.new(self, attribute) if Symbol === attribute + attribute end end diff --git a/lib/arel/engines/sql.rb b/lib/arel/engines/sql.rb index dc40428b77..a7721eb909 100644 --- a/lib/arel/engines/sql.rb +++ b/lib/arel/engines/sql.rb @@ -1,3 +1,4 @@ +require 'arel/engines/sql/attributes' require 'arel/engines/sql/engine' require 'arel/engines/sql/relations' require 'arel/engines/sql/primitives' diff --git a/lib/arel/engines/sql/attributes.rb b/lib/arel/engines/sql/attributes.rb new file mode 100644 index 0000000000..2d315d53fc --- /dev/null +++ b/lib/arel/engines/sql/attributes.rb @@ -0,0 +1,40 @@ +module Arel + module Sql + module Attributes + def self.for(column) + case column.type + when :string then String + when :text then String + when :integer then Integer + when :float then Float + when :decimal then Decimal + when :date then Time + when :datetime then Time + when :timestamp then Time + when :time then Time + when :binary then String + when :boolean then Boolean + else + raise NotImplementedError, "Column type `#{column.type}` is not currently handled" + end + end + + def initialize(column, *args) + @column = column + super(*args) + end + + def type_cast(value) + @column.type_cast(value) + end + + %w(Boolean Decimal Float Integer String Time).each do |klass| + class_eval <<-R + class #{klass} < Arel::Attributes::#{klass} + include Attributes + end + R + end + end + end +end
\ No newline at end of file diff --git a/lib/arel/engines/sql/primitives.rb b/lib/arel/engines/sql/primitives.rb index 6cce46a441..666579331a 100644 --- a/lib/arel/engines/sql/primitives.rb +++ b/lib/arel/engines/sql/primitives.rb @@ -16,10 +16,6 @@ module Arel original_relation.column_for(self) end - def type_cast(value) - root.relation.format(self, value) - end - def format(object) object.to_sql(Sql::Attribute.new(self)) end diff --git a/lib/arel/engines/sql/relations/table.rb b/lib/arel/engines/sql/relations/table.rb index d10b761ea3..c0d3386463 100644 --- a/lib/arel/engines/sql/relations/table.rb +++ b/lib/arel/engines/sql/relations/table.rb @@ -41,7 +41,9 @@ module Arel def attributes return @attributes if defined?(@attributes) if table_exists? - @attributes = columns.collect { |column| Attribute.new(self, column.name.to_sym) } + @attributes = columns.collect do |column| + Sql::Attributes.for(column).new(column, self, column.name.to_sym) + end else [] end @@ -55,10 +57,6 @@ module Arel @hash ||= :name.hash end - def format(attribute, value) - attribute.column.type_cast(value) - end - def column_for(attribute) has_attribute?(attribute) and columns.detect { |c| c.name == attribute.name.to_s } end diff --git a/spec/arel/algebra/integration/basic_spec.rb b/spec/arel/algebra/integration/basic_spec.rb index 84b8105f66..6ade5c40ac 100644 --- a/spec/arel/algebra/integration/basic_spec.rb +++ b/spec/arel/algebra/integration/basic_spec.rb @@ -29,8 +29,10 @@ class Thing < Arel::Relation attr_reader :engine, :attributes def initialize(engine, attributes) - @engine = engine - @attributes = attributes.map { |a| a.to_attribute(self) } + @engine, @attributes = engine, [] + attributes.each do |name, type| + @attributes << type.new(self, name) + end end def format(attribute, value) @@ -95,26 +97,31 @@ share_examples_for 'A Relation' do end end -describe "Arel::Relation" do - - before :all do - @engine = Arel::Testing::Engine.new - @relation = Thing.new(@engine, [:id, :name, :age]) - end +module Arel + describe "Relation" do - describe "..." do before :all do - @expected = (1..20).map { |i| @relation.insert([i, nil, 2 * i]) } + @engine = Testing::Engine.new + @relation = Thing.new(@engine, + :id => Attributes::Integer, + :name => Attributes::String, + :age => Attributes::Integer) end - it_should_behave_like 'A Relation' - end + describe "..." do + before :all do + @expected = (1..20).map { |i| @relation.insert([i, nil, 2 * i]) } + end - describe "#insert" do - it "inserts the row into the engine" do - @relation.insert([1, 'Foo', 10]) - @engine.rows.should == [[1, 'Foo', 10]] + it_should_behave_like 'A Relation' + end + + describe "#insert" do + it "inserts the row into the engine" do + @relation.insert([1, 'Foo', 10]) + @engine.rows.should == [[1, 'Foo', 10]] + end end - end + end end
\ No newline at end of file diff --git a/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb b/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb index 0dfcff1ee8..606f3154c7 100644 --- a/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb +++ b/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'bryan' ], [2, 'emilio' ], [3, 'nick'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) @photos = Table.new(:photos) @photos.delete @photos.insert(@photos[:id] => 1, @photos[:user_id] => 1, @photos[:camera_id] => 6) diff --git a/spec/arel/engines/memory/unit/relations/array_spec.rb b/spec/arel/engines/memory/unit/relations/array_spec.rb index 9a834148b1..dcec2afa19 100644 --- a/spec/arel/engines/memory/unit/relations/array_spec.rb +++ b/spec/arel/engines/memory/unit/relations/array_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#attributes' do diff --git a/spec/arel/engines/memory/unit/relations/insert_spec.rb b/spec/arel/engines/memory/unit/relations/insert_spec.rb index 222e525c7b..987e708e0b 100644 --- a/spec/arel/engines/memory/unit/relations/insert_spec.rb +++ b/spec/arel/engines/memory/unit/relations/insert_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do diff --git a/spec/arel/engines/memory/unit/relations/join_spec.rb b/spec/arel/engines/memory/unit/relations/join_spec.rb index 112434ae1d..ed5fe89ef0 100644 --- a/spec/arel/engines/memory/unit/relations/join_spec.rb +++ b/spec/arel/engines/memory/unit/relations/join_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) @relation2 = @relation1.alias end diff --git a/spec/arel/engines/memory/unit/relations/order_spec.rb b/spec/arel/engines/memory/unit/relations/order_spec.rb index 21d77a2a24..9546449bfb 100644 --- a/spec/arel/engines/memory/unit/relations/order_spec.rb +++ b/spec/arel/engines/memory/unit/relations/order_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do diff --git a/spec/arel/engines/memory/unit/relations/project_spec.rb b/spec/arel/engines/memory/unit/relations/project_spec.rb index e688b93a39..92ed9fa74b 100644 --- a/spec/arel/engines/memory/unit/relations/project_spec.rb +++ b/spec/arel/engines/memory/unit/relations/project_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do diff --git a/spec/arel/engines/memory/unit/relations/skip_spec.rb b/spec/arel/engines/memory/unit/relations/skip_spec.rb index 0c2077db80..089db24cea 100644 --- a/spec/arel/engines/memory/unit/relations/skip_spec.rb +++ b/spec/arel/engines/memory/unit/relations/skip_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do diff --git a/spec/arel/engines/memory/unit/relations/take_spec.rb b/spec/arel/engines/memory/unit/relations/take_spec.rb index 4b08a63d22..16b99872c5 100644 --- a/spec/arel/engines/memory/unit/relations/take_spec.rb +++ b/spec/arel/engines/memory/unit/relations/take_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do diff --git a/spec/arel/engines/memory/unit/relations/where_spec.rb b/spec/arel/engines/memory/unit/relations/where_spec.rb index 8d0af4b52d..b45c009d83 100644 --- a/spec/arel/engines/memory/unit/relations/where_spec.rb +++ b/spec/arel/engines/memory/unit/relations/where_spec.rb @@ -7,7 +7,7 @@ module Arel [1, 'duck' ], [2, 'duck' ], [3, 'goose'] - ], [:id, :name]) + ], [[:id, Attributes::Integer], [:name, Attributes::String]]) end describe '#call' do |