aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/arel/algebra.rb1
-rw-r--r--lib/arel/algebra/attributes/attribute.rb2
-rw-r--r--lib/arel/algebra/header.rb71
-rw-r--r--lib/arel/algebra/relations/operations/join.rb3
-rw-r--r--lib/arel/algebra/relations/operations/project.rb2
-rw-r--r--lib/arel/algebra/relations/relation.rb40
-rw-r--r--lib/arel/algebra/relations/utilities/compound.rb6
-rw-r--r--lib/arel/algebra/relations/utilities/externalization.rb2
-rw-r--r--lib/arel/engines/memory/relations/array.rb9
-rw-r--r--lib/arel/engines/sql/relations/operations/join.rb2
-rw-r--r--lib/arel/engines/sql/relations/table.rb12
-rw-r--r--spec/algebra/unit/relations/join_spec.rb3
-rw-r--r--spec/algebra/unit/relations/table_spec.rb2
-rw-r--r--spec/attributes/header_spec.rb35
-rw-r--r--spec/support/matchers/be_like.rb6
-rw-r--r--spec/support/model.rb6
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