diff options
-rw-r--r-- | lib/active_relation/primitives/aggregation.rb | 26 | ||||
-rw-r--r-- | lib/active_relation/primitives/attribute.rb | 6 | ||||
-rw-r--r-- | lib/active_relation/relations/alias.rb | 4 | ||||
-rw-r--r-- | lib/active_relation/relations/compound.rb | 2 | ||||
-rw-r--r-- | lib/active_relation/relations/join.rb | 14 | ||||
-rw-r--r-- | lib/active_relation/relations/projection.rb | 12 | ||||
-rw-r--r-- | lib/active_relation/relations/relation.rb | 10 | ||||
-rw-r--r-- | lib/active_relation/relations/rename.rb | 28 | ||||
-rw-r--r-- | lib/active_relation/relations/schmoin.rb | 12 | ||||
-rw-r--r-- | lib/active_relation/relations/table.rb | 2 | ||||
-rw-r--r-- | lib/active_relation/sql.rb | 14 | ||||
-rw-r--r-- | spec/active_relation/primitives/aggregation_spec.rb | 38 | ||||
-rw-r--r-- | spec/active_relation/primitives/attribute_spec.rb | 45 | ||||
-rw-r--r-- | spec/active_relation/relations/alias_spec.rb | 9 | ||||
-rw-r--r-- | spec/active_relation/relations/join_spec.rb | 7 | ||||
-rw-r--r-- | spec/active_relation/relations/rename_spec.rb | 6 | ||||
-rw-r--r-- | spec/active_relation/relations/schmoin_spec.rb | 8 | ||||
-rw-r--r-- | spec/spec_helper.rb | 1 |
18 files changed, 152 insertions, 92 deletions
diff --git a/lib/active_relation/primitives/aggregation.rb b/lib/active_relation/primitives/aggregation.rb index 26348fb35e..51ceee6e66 100644 --- a/lib/active_relation/primitives/aggregation.rb +++ b/lib/active_relation/primitives/aggregation.rb @@ -1,17 +1,31 @@ module ActiveRelation class Aggregation - attr_reader :attribute, :function_sql + include Sql::Quoting - def initialize(attribute, function_sql) - @attribute, @function_sql = attribute, function_sql + attr_reader :attribute, :function_sql, :alias + delegate :relation, :to => :attribute + + def initialize(attribute, function_sql, aliaz = nil) + @attribute, @function_sql, @alias = attribute, function_sql, aliaz end - def substitute(new_relation) - Aggregation.new(attribute.substitute(new_relation), function_sql) + module Transformations + def substitute(new_relation) + Aggregation.new(attribute.substitute(new_relation), function_sql, @alias) + end + + def as(aliaz) + Aggregation.new(attribute, function_sql, aliaz) + end + + def to_attribute + Attribute.new(relation, @alias) + end end + include Transformations def to_sql(strategy = nil) - "#{function_sql}(#{attribute.to_sql})" + "#{function_sql}(#{attribute.to_sql})" + (@alias ? " AS #{quote_column_name(@alias)}" : '') end def ==(other) diff --git a/lib/active_relation/primitives/attribute.rb b/lib/active_relation/primitives/attribute.rb index eb167b1a66..3b63bf985b 100644 --- a/lib/active_relation/primitives/attribute.rb +++ b/lib/active_relation/primitives/attribute.rb @@ -18,6 +18,10 @@ module ActiveRelation def qualify self.as(qualified_name) end + + def to_attribute + self + end end include Transformations @@ -26,7 +30,7 @@ module ActiveRelation end def ==(other) - relation == other.relation and name == other.name and @alias == other.alias + self.class == other.class and relation == other.relation and name == other.name and @alias == other.alias end module Predications diff --git a/lib/active_relation/relations/alias.rb b/lib/active_relation/relations/alias.rb index f40e51475e..701ab189d0 100644 --- a/lib/active_relation/relations/alias.rb +++ b/lib/active_relation/relations/alias.rb @@ -16,10 +16,6 @@ module ActiveRelation end protected - def table_sql - "#{quote_table_name(relation.name)} AS #{quote_table_name(@alias)}" - end - def attribute(name) if unaliased_attribute = relation[name] unaliased_attribute.substitute(self) diff --git a/lib/active_relation/relations/compound.rb b/lib/active_relation/relations/compound.rb index bbc7ff1bae..e870f12aff 100644 --- a/lib/active_relation/relations/compound.rb +++ b/lib/active_relation/relations/compound.rb @@ -2,7 +2,7 @@ module ActiveRelation class Compound < Relation attr_reader :relation - delegate :attributes, :attribute, :joins, :selects, :orders, :groupings, :table_sql, :inserts, :limit, :offset, + delegate :projections, :attribute, :joins, :selects, :orders, :groupings, :table_sql, :inserts, :limit, :offset, :name, :alias, :to => :relation end end
\ No newline at end of file diff --git a/lib/active_relation/relations/join.rb b/lib/active_relation/relations/join.rb index 8ccd1e9c6c..b624df8d72 100644 --- a/lib/active_relation/relations/join.rb +++ b/lib/active_relation/relations/join.rb @@ -1,6 +1,7 @@ module ActiveRelation class Join < Relation attr_reader :join_sql, :relation1, :relation2, :predicates + delegate :table_sql, :to => :relation1 def initialize(join_sql, relation1, relation2, *predicates) @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates @@ -24,17 +25,16 @@ module ActiveRelation def selects relation1.send(:selects) + relation2.send(:selects) end - - def attributes - relation1.attributes + relation2.attributes - end - + def attribute(name) relation1[name] || relation2[name] end - delegate :table_sql, :to => :relation1 - + protected + def projections + relation1.send(:projections) + relation2.send(:projections) + end + private def join "#{join_sql} #{relation2.send(:table_sql)} ON #{predicates.collect { |p| p.to_sql(Sql::Predicate.new) }.join(' AND ')}" diff --git a/lib/active_relation/relations/projection.rb b/lib/active_relation/relations/projection.rb index 211e0607ac..9651acd021 100644 --- a/lib/active_relation/relations/projection.rb +++ b/lib/active_relation/relations/projection.rb @@ -1,17 +1,17 @@ module ActiveRelation class Projection < Compound - attr_reader :attributes - - def initialize(relation, *attributes) - @relation, @attributes = relation, attributes + attr_reader :projections + + def initialize(relation, *projections) + @relation, @projections = relation, projections end def ==(other) - self.class == other.class and relation == other.relation and attributes == other.attributes + self.class == other.class and relation == other.relation and projections == other.projections end def qualify - Projection.new(relation.qualify, *attributes.collect(&:qualify)) + Projection.new(relation.qualify, *projections.collect(&:qualify)) end end end
\ No newline at end of file diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index 18ba5491b1..f1febc497c 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -77,9 +77,13 @@ module ActiveRelation end include Operations + def attributes + projections.collect(&:to_attribute) + end + def to_sql(strategy = Sql::Select.new) strategy.select [ - "SELECT #{attributes.collect{ |a| a.to_sql(Sql::Projection.new) }.join(', ')}", + "SELECT #{projections.collect{ |a| a.to_sql(Sql::Projection.new) }.join(', ')}", "FROM #{table_sql}", (joins unless joins.blank?), ("WHERE #{selects.collect{|s| s.to_sql(Sql::Predicate.new)}.join("\n\tAND ")}" unless selects.blank?), @@ -87,7 +91,7 @@ module ActiveRelation ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank?), ("LIMIT #{limit.to_sql}" unless limit.blank?), ("OFFSET #{offset.to_sql}" unless offset.blank?) - ].compact.join("\n") + ].compact.join("\n"), self.alias end alias_method :to_s, :to_sql @@ -96,7 +100,7 @@ module ActiveRelation ActiveRecord::Base.connection end - def attributes; [] end + def projections; [] end def selects; [] end def orders; [] end def inserts; [] end diff --git a/lib/active_relation/relations/rename.rb b/lib/active_relation/relations/rename.rb index 2977804db1..c9c47f95b8 100644 --- a/lib/active_relation/relations/rename.rb +++ b/lib/active_relation/relations/rename.rb @@ -1,36 +1,36 @@ module ActiveRelation class Rename < Compound - attr_reader :schmattribute, :rename + attr_reader :autonym, :pseudonym - def initialize(relation, renames) - @schmattribute, @rename = renames.shift - @relation = renames.empty?? relation : Rename.new(relation, renames) + def initialize(relation, pseudonyms) + @autonym, @pseudonym = pseudonyms.shift + @relation = pseudonyms.empty?? relation : Rename.new(relation, pseudonyms) end def ==(other) - relation == other.relation and schmattribute == other.schmattribute and self.rename == other.rename - end - - def attributes - relation.attributes.collect(&method(:substitute)) + relation == other.relation and autonym == other.autonym and pseudonym == other.pseudonym end def qualify - Rename.new(relation.qualify, schmattribute.qualify => self.rename) + Rename.new(relation.qualify, autonym.qualify => self.pseudonym) end protected + def projections + relation.send(:projections).collect(&method(:substitute)) + end + def attribute(name) case - when name == self.rename then schmattribute.as(self.rename) - when relation[name] == schmattribute then nil + when name == pseudonym then autonym.as(pseudonym) + when relation[name] == autonym then nil else relation[name] end end private - def substitute(a) - a == schmattribute ? a.as(self.rename) : a + def substitute(attribute) + attribute == autonym ? attribute.as(pseudonym) : attribute end end end
\ No newline at end of file diff --git a/lib/active_relation/relations/schmoin.rb b/lib/active_relation/relations/schmoin.rb index 398cfb7b28..6d0cb6f171 100644 --- a/lib/active_relation/relations/schmoin.rb +++ b/lib/active_relation/relations/schmoin.rb @@ -1,6 +1,7 @@ module ActiveRelation class Schmoin < Relation attr_reader :join_sql, :relation1, :relation2, :predicates + delegate :table_sql, :to => :relation1 def initialize(join_sql, relation1, relation2, *predicates) @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates @@ -24,20 +25,19 @@ module ActiveRelation def selects relation1.send(:selects) + relation2.send(:selects) end - - def attributes - relation1.attributes + relation2.attributes + + # this is magick!!! + def projections + relation1.projections + relation2.attributes end def attribute(name) relation1[name] || relation2[name] end - delegate :table_sql, :to => :relation1 - private def join - "#{join_sql} #{relation2.send(:table_sql)} ON #{predicates.collect { |p| p.to_sql(Sql::Predicate.new) }.join(' AND ')}" + "#{join_sql} #{relation2.to_sql(Sql::Aggregation.new)} ON #{predicates.collect { |p| p.to_sql(Sql::Predicate.new) }.join(' AND ')}" end end end
\ No newline at end of file diff --git a/lib/active_relation/relations/table.rb b/lib/active_relation/relations/table.rb index 5220087318..637273f949 100644 --- a/lib/active_relation/relations/table.rb +++ b/lib/active_relation/relations/table.rb @@ -15,6 +15,8 @@ module ActiveRelation end protected + alias_method :projections, :attributes + def attribute(name) attributes_by_name[name.to_s] end diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index 89f6193cd8..bc658271c3 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -17,8 +17,8 @@ module ActiveRelation "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + (aliaz ? " AS #{quote(aliaz.to_s)}" : "") end - def select(select_sql) - "(#{select_sql})" + def select(select_sql, aliaz) + "(#{select_sql}) AS #{quote_column_name(aliaz)}" end end @@ -31,17 +31,23 @@ module ActiveRelation scalar end - def select(select_sql) + def select(select_sql, aliaz) "(#{select_sql})" end end class Select < Strategy - def select(select_sql) + def select(select_sql, aliaz) select_sql end end + class Aggregation < Strategy + def select(select_sql, aliaz) + "(#{select_sql}) AS #{quote_table_name(aliaz)}" + end + end + class Scalar < Strategy def scalar(scalar) quote(scalar) diff --git a/spec/active_relation/primitives/aggregation_spec.rb b/spec/active_relation/primitives/aggregation_spec.rb index fbd846ffed..5daf774e06 100644 --- a/spec/active_relation/primitives/aggregation_spec.rb +++ b/spec/active_relation/primitives/aggregation_spec.rb @@ -16,19 +16,47 @@ module ActiveRelation end end - describe '#substitute' do - it "distributes over the attribute" do - Aggregation.new(@relation1[:id], "SUM").substitute(@relation2). \ - should == Aggregation.new(@relation1[:id].substitute(@relation2), "SUM") + describe Aggregation::Transformations do + describe '#substitute' do + it "distributes over the attribute and alias" do + Aggregation.new(@relation1[:id], "SUM", "alias").substitute(@relation2). \ + should == Aggregation.new(@relation1[:id].substitute(@relation2), "SUM", "alias") + end + end + + describe '#as' do + it "manufactures an aliased aggregation" do + Aggregation.new(@relation1[:id], "SUM").as(:doof). \ + should == Aggregation.new(@relation1[:id], "SUM", :doof) + end + end + + describe '#to_attribute' do + it "manufactures an attribute the name of which corresponds to the aggregation's alias" do + Aggregation.new(@relation1[:id], "SUM", :schmaggregation).to_attribute. \ + should == Attribute.new(@relation1, :schmaggregation) + end + end + end + + describe '#relation' do + it "delegates to the attribute" do + Aggregation.new(@relation1[:id], "SUM").relation.should == @relation1 end end describe '#to_sql' do it 'manufactures sql with an aggregation function' do - @relation1[:id].maximum.to_sql.should be_like(""" + Aggregation.new(@relation1[:id], "MAX").to_sql.should be_like(""" MAX(`foo`.`id`) """) end + + it 'manufactures sql with an aliased aggregation function' do + Aggregation.new(@relation1[:id], "MAX", "marx").to_sql.should be_like(""" + MAX(`foo`.`id`) AS `marx` + """) + end end end end
\ No newline at end of file diff --git a/spec/active_relation/primitives/attribute_spec.rb b/spec/active_relation/primitives/attribute_spec.rb index 543217b9cf..7f5d4922b8 100644 --- a/spec/active_relation/primitives/attribute_spec.rb +++ b/spec/active_relation/primitives/attribute_spec.rb @@ -7,31 +7,43 @@ module ActiveRelation @relation2 = Table.new(:bar) end - describe '#as' do - it "manufactures an aliased attributed when provided a parameter" do - @relation1[:id].as(:alias).should == Attribute.new(@relation1, :id, :alias) + describe Attribute::Transformations do + before do + @attribute = Attribute.new(@relation1, :id) + end + + describe '#as' do + it "manufactures an aliased attributed" do + @attribute.as(:alias).should == Attribute.new(@relation1, @attribute.name, :alias) + end end - end - describe '#substitute' do - it "manufactures an attribute with the relation substituted" do - @relation1[:id].substitute(@relation2).should == Attribute.new(@relation2, :id) + describe '#substitute' do + it "manufactures an attribute with the relation substituted" do + @attribute.substitute(@relation2).should == Attribute.new(@relation2, @attribute.name) + end + end + + describe '#qualify' do + it "manufactures an attribute aliased with that attributes qualified name" do + @attribute.qualify.should == Attribute.new(@attribute.relation, @attribute.name, @attribute.qualified_name) + end + end + + describe '#to_attribute' do + it "returns self" do + @attribute.to_attribute.should == @attribute + end end end - + describe '#qualified_name' do it "manufactures an attribute name prefixed with the relation's name" do - @relation1[:id].qualified_name.should == 'foo.id' + Attribute.new(@relation1, :id).qualified_name.should == 'foo.id' end it "manufactures an attribute name prefixed with the relation's aliased name" do - @relation1.as(:bar)[:id].qualified_name.should == 'bar.id' - end - end - - describe '#qualify' do - it "manufactures an attribute aliased with that attributes qualified name" do - @relation1[:id].qualify.should == @relation1[:id].qualify + Attribute.new(@relation1.as(:bar), :id).qualified_name.should == 'bar.id' end end @@ -40,6 +52,7 @@ module ActiveRelation Attribute.new(@relation1, :name).should == Attribute.new(@relation1, :name) Attribute.new(@relation1, :name).should_not == Attribute.new(@relation1, :another_name) Attribute.new(@relation1, :name).should_not == Attribute.new(@relation2, :name) + Attribute.new(@relation1, :name).should_not == Aggregation.new(Attribute.new(@relation1, :name), "SUM") end end diff --git a/spec/active_relation/relations/alias_spec.rb b/spec/active_relation/relations/alias_spec.rb index 418af7fe66..6c203990eb 100644 --- a/spec/active_relation/relations/alias_spec.rb +++ b/spec/active_relation/relations/alias_spec.rb @@ -25,14 +25,5 @@ module ActiveRelation @alias_relation[:does_not_exist].should be_nil end end - - describe '#to_sql' do - it "manufactures an aliased select query" do - @alias_relation.to_sql.should be_like(""" - SELECT `foo`.`name`, `foo`.`id` - FROM `users` AS `foo` - """) - end - end end end
\ No newline at end of file diff --git a/spec/active_relation/relations/join_spec.rb b/spec/active_relation/relations/join_spec.rb index 25c505e371..68d7b83a12 100644 --- a/spec/active_relation/relations/join_spec.rb +++ b/spec/active_relation/relations/join_spec.rb @@ -25,6 +25,13 @@ module ActiveRelation should == Join.new("INNER JOIN", @relation1.qualify, @relation2.qualify, @predicate.qualify) end end + + describe '#attributes' do + it 'combines the attributes of the two relations' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).attributes.should == + @relation1.attributes + @relation2.attributes + end + end describe '#to_sql' do before do diff --git a/spec/active_relation/relations/rename_spec.rb b/spec/active_relation/relations/rename_spec.rb index 3f8d5e3dbd..eb0e1f48dd 100644 --- a/spec/active_relation/relations/rename_spec.rb +++ b/spec/active_relation/relations/rename_spec.rb @@ -38,12 +38,6 @@ module ActiveRelation end end - describe '#schmattribute' do - it "should be renamed" do - pending - end - end - describe '#qualify' do it "distributes over the relation and renames" do Rename.new(@relation, @relation[:id] => :schmid).qualify. \ diff --git a/spec/active_relation/relations/schmoin_spec.rb b/spec/active_relation/relations/schmoin_spec.rb index 14e583afab..e5ed9be393 100644 --- a/spec/active_relation/relations/schmoin_spec.rb +++ b/spec/active_relation/relations/schmoin_spec.rb @@ -5,17 +5,17 @@ module ActiveRelation before do @relation = Table.new(:users) photos = Table.new(:photos) - @aggregate_relation = photos.project(photos[:user_id], photos[:id].count).group(photos[:user_id]).as(:photo_count) + @aggregate_relation = photos.project(photos[:user_id], photos[:id].count).rename(photos[:id].count, :cnt) \ + .group(photos[:user_id]).as(:photo_count) @predicate = Equality.new(@aggregate_relation[:user_id], @relation[:id]) end describe '#to_sql' do it 'manufactures sql joining the two tables on the predicate, merging the selects' do - pending Schmoin.new("INNER JOIN", @relation, @aggregate_relation, @predicate).to_sql.should be_like(""" - SELECT `users`.`name` + SELECT `users`.`name`, `users`.`id`, `photo_count`.`user_id`, `photo_count`.`cnt` FROM `users` - INNER JOIN (SELECT `photos`.`user_id`, count(`photos`.`id`) FROM `photos`) AS `photo_count` + INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` ON `photo_count`.`user_id` = `users`.`id` """) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8f5d76370c..e3508aac90 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'rubygems' require 'spec' +require 'pp' dir = File.dirname(__FILE__) $LOAD_PATH.unshift "#{dir}/../lib" Dir["#{dir}/matchers/*"].each { |m| require "#{dir}/matchers/#{File.basename(m)}" } |