From 83c27c0b5e2e341307b7a160d831fb930a9552b4 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 12 Mar 2010 12:51:20 -0800 Subject: Attributes should be typed --- lib/arel/algebra.rb | 2 +- lib/arel/algebra/attribute.rb | 150 ------------------------ lib/arel/algebra/attributes.rb | 7 ++ lib/arel/algebra/attributes/attribute.rb | 181 +++++++++++++++++++++++++++++ lib/arel/algebra/attributes/boolean.rb | 20 ++++ lib/arel/algebra/attributes/decimal.rb | 9 ++ lib/arel/algebra/attributes/float.rb | 9 ++ lib/arel/algebra/attributes/integer.rb | 10 ++ lib/arel/algebra/attributes/string.rb | 10 ++ lib/arel/algebra/attributes/time.rb | 6 + lib/arel/engines/memory/relations/array.rb | 11 +- lib/arel/engines/sql.rb | 1 + lib/arel/engines/sql/attributes.rb | 40 +++++++ lib/arel/engines/sql/primitives.rb | 4 - lib/arel/engines/sql/relations/table.rb | 8 +- 15 files changed, 305 insertions(+), 163 deletions(-) delete mode 100644 lib/arel/algebra/attribute.rb create mode 100644 lib/arel/algebra/attributes.rb create mode 100644 lib/arel/algebra/attributes/attribute.rb create mode 100644 lib/arel/algebra/attributes/boolean.rb create mode 100644 lib/arel/algebra/attributes/decimal.rb create mode 100644 lib/arel/algebra/attributes/float.rb create mode 100644 lib/arel/algebra/attributes/integer.rb create mode 100644 lib/arel/algebra/attributes/string.rb create mode 100644 lib/arel/algebra/attributes/time.rb create mode 100644 lib/arel/engines/sql/attributes.rb (limited to 'lib') 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/attribute.rb b/lib/arel/algebra/attribute.rb deleted file mode 100644 index 40a7d61a53..0000000000 --- a/lib/arel/algebra/attribute.rb +++ /dev/null @@ -1,150 +0,0 @@ -require 'set' - -module Arel - class Attribute - attributes :relation, :name, :alias, :ancestor - deriving :== - delegate :engine, :christener, :to => :relation - - def initialize(relation, name, options = {}) - @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor] - end - - def named?(hypothetical_name) - (@alias || name).to_s == hypothetical_name.to_s - end - - def aggregation? - false - end - - def inspect - "" - end - - module Transformations - def self.included(klass) - klass.send :alias_method, :eql?, :== - end - - def hash - @hash ||= history.size + name.hash + relation.hash - end - - def as(aliaz = nil) - Attribute.new(relation, name, :alias => aliaz, :ancestor => self) - end - - def bind(new_relation) - relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self) - end - - def to_attribute(relation) - bind(relation) - end - end - include Transformations - - module Congruence - def history - @history ||= [self] + (ancestor ? ancestor.history : []) - end - - def join? - relation.join? - end - - def root - history.last - end - - def original_relation - @original_relation ||= original_attribute.relation - end - - def original_attribute - @original_attribute ||= history.detect { |a| !a.join? } - end - - def find_correlate_in(relation) - relation[self] || self - end - - def descends_from?(other) - history.include?(other) - end - - def /(other) - other ? (history & other.history).size : 0 - end - end - include Congruence - - module Predications - def eq(other) - Predicates::Equality.new(self, other) - end - - def lt(other) - Predicates::LessThan.new(self, other) - end - - def lteq(other) - Predicates::LessThanOrEqualTo.new(self, other) - end - - def gt(other) - Predicates::GreaterThan.new(self, other) - end - - def gteq(other) - Predicates::GreaterThanOrEqualTo.new(self, other) - end - - def matches(regexp) - Predicates::Match.new(self, regexp) - end - - def in(array) - Predicates::In.new(self, array) - end - end - include Predications - - module Expressions - def count(distinct = false) - distinct ? Distinct.new(self).count : Count.new(self) - end - - def sum - Sum.new(self) - end - - def maximum - Maximum.new(self) - end - - def minimum - Minimum.new(self) - end - - def average - Average.new(self) - end - end - include Expressions - - module Orderings - def asc - Ascending.new(self) - end - - def desc - Descending.new(self) - end - - alias_method :to_ordering, :asc - end - include Orderings - end -end 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/attributes/attribute.rb b/lib/arel/algebra/attributes/attribute.rb new file mode 100644 index 0000000000..f4cec828e3 --- /dev/null +++ b/lib/arel/algebra/attributes/attribute.rb @@ -0,0 +1,181 @@ +require 'set' + +module Arel + class TypecastError < StandardError ; end + class Attribute + attributes :relation, :name, :alias, :ancestor + deriving :== + delegate :engine, :christener, :to => :relation + + def initialize(relation, name, options = {}) + @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor] + end + + def named?(hypothetical_name) + (@alias || name).to_s == hypothetical_name.to_s + end + + def aggregation? + false + end + + def inspect + "" + end + + module Transformations + def self.included(klass) + klass.send :alias_method, :eql?, :== + end + + def hash + @hash ||= history.size + name.hash + relation.hash + end + + def as(aliaz = nil) + Attribute.new(relation, name, :alias => aliaz, :ancestor => self) + end + + def bind(new_relation) + relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self) + end + + def to_attribute(relation) + bind(relation) + end + end + include Transformations + + module Congruence + def history + @history ||= [self] + (ancestor ? ancestor.history : []) + end + + def join? + relation.join? + end + + def root + history.last + end + + def original_relation + @original_relation ||= original_attribute.relation + end + + def original_attribute + @original_attribute ||= history.detect { |a| !a.join? } + end + + def find_correlate_in(relation) + relation[self] || self + end + + def descends_from?(other) + history.include?(other) + end + + def /(other) + other ? (history & other.history).size : 0 + end + end + include Congruence + + module Predications + def eq(other) + Predicates::Equality.new(self, other) + end + + def lt(other) + Predicates::LessThan.new(self, other) + end + + def lteq(other) + Predicates::LessThanOrEqualTo.new(self, other) + end + + def gt(other) + Predicates::GreaterThan.new(self, other) + end + + def gteq(other) + Predicates::GreaterThanOrEqualTo.new(self, other) + end + + def matches(regexp) + Predicates::Match.new(self, regexp) + end + + def in(array) + Predicates::In.new(self, array) + end + end + include Predications + + module Expressions + def count(distinct = false) + distinct ? Distinct.new(self).count : Count.new(self) + end + + def sum + Sum.new(self) + end + + def maximum + Maximum.new(self) + end + + def minimum + Minimum.new(self) + end + + def average + Average.new(self) + end + end + include Expressions + + module Orderings + def asc + Ascending.new(self) + end + + def desc + Descending.new(self) + end + + 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 -- cgit v1.2.3