diff options
-rw-r--r-- | lib/arel/algebra.rb | 1 | ||||
-rw-r--r-- | lib/arel/algebra/attributes/attribute.rb | 2 | ||||
-rw-r--r-- | lib/arel/algebra/header.rb | 71 | ||||
-rw-r--r-- | lib/arel/algebra/relations/operations/join.rb | 3 | ||||
-rw-r--r-- | lib/arel/algebra/relations/operations/project.rb | 2 | ||||
-rw-r--r-- | lib/arel/algebra/relations/relation.rb | 40 | ||||
-rw-r--r-- | lib/arel/algebra/relations/utilities/compound.rb | 6 | ||||
-rw-r--r-- | lib/arel/algebra/relations/utilities/externalization.rb | 2 | ||||
-rw-r--r-- | lib/arel/engines/memory/relations/array.rb | 9 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/operations/join.rb | 2 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/table.rb | 12 | ||||
-rw-r--r-- | spec/algebra/unit/relations/join_spec.rb | 3 | ||||
-rw-r--r-- | spec/algebra/unit/relations/table_spec.rb | 2 | ||||
-rw-r--r-- | spec/attributes/header_spec.rb | 35 | ||||
-rw-r--r-- | spec/support/matchers/be_like.rb | 6 | ||||
-rw-r--r-- | spec/support/model.rb | 6 |
16 files changed, 160 insertions, 42 deletions
diff --git a/lib/arel/algebra.rb b/lib/arel/algebra.rb index 83f6a54326..bc7b2fef2d 100644 --- a/lib/arel/algebra.rb +++ b/lib/arel/algebra.rb @@ -1,6 +1,7 @@ require 'arel/algebra/core_extensions' require 'arel/algebra/attributes' +require 'arel/algebra/header' require 'arel/algebra/expression' require 'arel/algebra/ordering' require 'arel/algebra/predicates' diff --git a/lib/arel/algebra/attributes/attribute.rb b/lib/arel/algebra/attributes/attribute.rb index 331f218463..afcbdd8301 100644 --- a/lib/arel/algebra/attributes/attribute.rb +++ b/lib/arel/algebra/attributes/attribute.rb @@ -29,7 +29,7 @@ module Arel end def hash - @hash ||= history.size + name.hash + relation.hash + @hash ||= name.hash + root.relation.hash end def as(aliaz = nil) diff --git a/lib/arel/algebra/header.rb b/lib/arel/algebra/header.rb new file mode 100644 index 0000000000..ec45488dbf --- /dev/null +++ b/lib/arel/algebra/header.rb @@ -0,0 +1,71 @@ +module Arel + class Header + include Enumerable + + def initialize(attrs = []) + @attributes = attrs.to_ary + @names = Hash.new do |h,k| + h[k] = @attributes.detect { |a| a.named?(k) } + end + end + + def each(&block) + to_ary.each(&block) + self + end + + def [](key) + case key + when String, Symbol then find_by_name(key) + when Attribute then find_by_attribute(key) + end + end + + def ==(other) + to_set == other.to_set + end + + def union(other) + new(to_ary | other) + end + + alias | union + + def to_ary + @attributes + end + + def bind(relation) + Header.new(map { |a| a.bind(relation) }) + end + + # TMP + def index(i) + to_ary.index(i) + end + + private + + def new(attrs) + self.class.new(attrs) + end + + def matching(attribute) + # (@matching_attributes ||= attributes.inject({}) do |hash, a| + # (hash[a.is_a?(Value) ? a.value : a.root] ||= []) << a + # hash + # end)[attribute.root] || [] + select { |a| !a.is_a?(Value) && a.root == attribute.root } + end + + def find_by_name(name) + @names[name.to_sym] + end + + def find_by_attribute(attr) + matching(attr).max do |a, b| + (a.original_attribute / attr) <=> (b.original_attribute / attr) + end + end + end +end
\ No newline at end of file diff --git a/lib/arel/algebra/relations/operations/join.rb b/lib/arel/algebra/relations/operations/join.rb index 300cd31bcd..21bcfaa62d 100644 --- a/lib/arel/algebra/relations/operations/join.rb +++ b/lib/arel/algebra/relations/operations/join.rb @@ -19,8 +19,7 @@ module Arel end def attributes - @attributes ||= (relation1.externalize.attributes + - relation2.externalize.attributes).collect { |a| a.bind(self) } + @attributes ||= (relation1.externalize.attributes | relation2.externalize.attributes).bind(self) end def wheres diff --git a/lib/arel/algebra/relations/operations/project.rb b/lib/arel/algebra/relations/operations/project.rb index a1140e91c1..49d0e1be36 100644 --- a/lib/arel/algebra/relations/operations/project.rb +++ b/lib/arel/algebra/relations/operations/project.rb @@ -10,7 +10,7 @@ module Arel end def attributes - @attributes ||= projections.collect { |p| p.bind(self) } + @attributes ||= Header.new(projections).bind(self) end def externalizable? diff --git a/lib/arel/algebra/relations/relation.rb b/lib/arel/algebra/relations/relation.rb index 1c1ded15c9..ef2108dcaa 100644 --- a/lib/arel/algebra/relations/relation.rb +++ b/lib/arel/algebra/relations/relation.rb @@ -84,16 +84,14 @@ module Arel module AttributeAccessable def [](index) - @cached_attributes ||= {} - @cached_attributes[index] ||= case index - when Symbol, String - find_attribute_matching_name(index) - when Attribute, Expression - find_attribute_matching_attribute(index) - when ::Array - # TESTME - index.collect { |i| self[i] } + attr = attributes[index] + + # Handles a strange ActiveRecord case + if !attr && (index.is_a?(String) || index.is_a?(Symbol)) + attr = Attribute.new(self, index) end + + attr end def find_attribute_matching_name(name) @@ -127,18 +125,18 @@ module Arel include AttributeAccessable module DefaultOperations - def attributes; [] end - def projections; [] end - def wheres; [] end - def orders; [] end - def inserts; [] end - def groupings; [] end - def havings; [] end - def joins(formatter = nil); nil end # FIXME - def taken; nil end - def skipped; nil end - def sources; [] end - def locked; [] end + def attributes; Header.new end + def projections; [] end + def wheres; [] end + def orders; [] end + def inserts; [] end + def groupings; [] end + def havings; [] end + def joins(formatter = nil); nil end # FIXME + def taken; nil end + def skipped; nil end + def sources; [] end + def locked; [] end end include DefaultOperations end diff --git a/lib/arel/algebra/relations/utilities/compound.rb b/lib/arel/algebra/relations/utilities/compound.rb index 7039b82575..416717310c 100644 --- a/lib/arel/algebra/relations/utilities/compound.rb +++ b/lib/arel/algebra/relations/utilities/compound.rb @@ -13,7 +13,7 @@ module Arel @requires end - [:attributes, :wheres, :groupings, :orders, :havings, :projections].each do |operation_name| + [:wheres, :groupings, :orders, :havings, :projections].each do |operation_name| class_eval <<-OPERATION, __FILE__, __LINE__ def #{operation_name} @#{operation_name} ||= relation.#{operation_name}.collect { |o| o.bind(self) } @@ -21,6 +21,10 @@ module Arel OPERATION end + def attributes + @attributes ||= relation.attributes.bind(self) + end + def hash @hash ||= :relation.hash end diff --git a/lib/arel/algebra/relations/utilities/externalization.rb b/lib/arel/algebra/relations/utilities/externalization.rb index 795a3919f2..edd8f99221 100644 --- a/lib/arel/algebra/relations/utilities/externalization.rb +++ b/lib/arel/algebra/relations/utilities/externalization.rb @@ -8,7 +8,7 @@ module Arel end def attributes - @attributes ||= relation.attributes.collect { |a| a.to_attribute(self) } + @attributes ||= Header.new(relation.attributes.map { |a| a.to_attribute(self) }) end end diff --git a/lib/arel/engines/memory/relations/array.rb b/lib/arel/engines/memory/relations/array.rb index 6486dcbcc1..d8751fa626 100644 --- a/lib/arel/engines/memory/relations/array.rb +++ b/lib/arel/engines/memory/relations/array.rb @@ -15,9 +15,12 @@ module Arel end def attributes - @attributes ||= @attribute_names_and_types.collect do |attribute, type| - attribute = type.new(self, attribute) if Symbol === attribute - attribute + @attributes ||= begin + attrs = @attribute_names_and_types.collect do |attribute, type| + attribute = type.new(self, attribute) if Symbol === attribute + attribute + end + Header.new(attrs) end end diff --git a/lib/arel/engines/sql/relations/operations/join.rb b/lib/arel/engines/sql/relations/operations/join.rb index 7fad6400ad..9733657365 100644 --- a/lib/arel/engines/sql/relations/operations/join.rb +++ b/lib/arel/engines/sql/relations/operations/join.rb @@ -10,7 +10,7 @@ module Arel join_sql, relation2.externalize.table_sql(formatter), ("ON" unless predicates.blank?), - (ons + relation2.externalize.wheres).collect { |p| p.bind(environment).to_sql(Sql::WhereClause.new(environment)) }.join(' AND ') + (ons + relation2.externalize.wheres).collect { |p| p.bind(environment.relation).to_sql(Sql::WhereClause.new(environment)) }.join(' AND ') ].compact.join(" ") [relation1.joins(environment), this_join, relation2.joins(environment)].compact.join(" ") end diff --git a/lib/arel/engines/sql/relations/table.rb b/lib/arel/engines/sql/relations/table.rb index 8ee7a94357..7940fd781f 100644 --- a/lib/arel/engines/sql/relations/table.rb +++ b/lib/arel/engines/sql/relations/table.rb @@ -42,11 +42,14 @@ module Arel def attributes return @attributes if defined?(@attributes) if table_exists? - @attributes = columns.collect do |column| - Sql::Attributes.for(column).new(column, self, column.name.to_sym) + @attributes ||= begin + attrs = columns.collect do |column| + Sql::Attributes.for(column).new(column, self, column.name.to_sym) + end + Header.new(attrs) end else - [] + Header.new end end @@ -67,7 +70,8 @@ module Arel end def reset - @attributes = @columns = nil + @columns = nil + @attributes = Header.new([]) end def ==(other) diff --git a/spec/algebra/unit/relations/join_spec.rb b/spec/algebra/unit/relations/join_spec.rb index 9c1422c571..5fa3269052 100644 --- a/spec/algebra/unit/relations/join_spec.rb +++ b/spec/algebra/unit/relations/join_spec.rb @@ -18,8 +18,7 @@ module Arel describe '#attributes' do it 'combines the attributes of the two relations' do join = InnerJoin.new(@relation1, @relation2, @predicate) - join.attributes.should == - (@relation1.attributes + @relation2.attributes).collect { |a| a.bind(join) } + join.attributes.should == (@relation1.attributes | @relation2.attributes).bind(join) end end end diff --git a/spec/algebra/unit/relations/table_spec.rb b/spec/algebra/unit/relations/table_spec.rb index d93446f1b9..d1c7cc46ba 100644 --- a/spec/algebra/unit/relations/table_spec.rb +++ b/spec/algebra/unit/relations/table_spec.rb @@ -9,7 +9,7 @@ module Arel describe '[]' do describe 'when given a', Symbol do it "manufactures an attribute if the symbol names an attribute within the relation" do - check @relation[:id].should == Attribute.new(@relation, :id) + check @relation[:id].should == Attributes::Integer.new(@relation, :id) end end diff --git a/spec/attributes/header_spec.rb b/spec/attributes/header_spec.rb new file mode 100644 index 0000000000..5811041734 --- /dev/null +++ b/spec/attributes/header_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +module Arel + describe "Header" do + before :all do + @relation = Model.build do |r| + r.attribute :id, Attributes::Integer + r.attribute :name, Attributes::String + r.attribute :age, Attributes::Integer + end + + @other = Model.build do |r| + r.attribute :foo, Attributes::String + end + + @subset = Model.build do |r| + r.attribute :id, Attributes::Integer + end + end + + it "finds attributes by name" do + @relation.attributes[:name].should == Attributes::String.new(@relation, :name) + end + + describe "#union" do + it "keeps all attributes from disjoint headers" do + (@relation.attributes.union @other.attributes).to_ary.should have(4).items + end + + it "keeps all attributes from both relations even if they seem like subsets" do + (@relation.attributes.union @subset.attributes).to_ary.should have(4).items + end + end + end +end
\ No newline at end of file diff --git a/spec/support/matchers/be_like.rb b/spec/support/matchers/be_like.rb index 0608abbbb4..ca49b91274 100644 --- a/spec/support/matchers/be_like.rb +++ b/spec/support/matchers/be_like.rb @@ -1,12 +1,12 @@ module Matchers class BeLike def initialize(expected) - @expected = expected + @expected = expected.gsub(/\s+/, ' ').strip end def matches?(actual) - @actual = actual - @expected.gsub(/\s+/, ' ').strip == @actual.gsub(/\s+/, ' ').strip + @actual = actual.gsub(/\s+/, ' ').strip + @expected == @actual end def failure_message diff --git a/spec/support/model.rb b/spec/support/model.rb index 10a14d7092..be692e53ec 100644 --- a/spec/support/model.rb +++ b/spec/support/model.rb @@ -25,7 +25,7 @@ module Arel class Model include Relation - attr_reader :engine, :attributes + attr_reader :engine def self.build relation = new @@ -46,6 +46,10 @@ module Arel @attributes << type.new(self, name) end + def attributes + Header.new(@attributes) + end + def format(attribute, value) value end |