diff options
23 files changed, 162 insertions, 75 deletions
diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb index 0cc87bf262..c8a0ff49e5 100644 --- a/lib/active_relation/extensions/object.rb +++ b/lib/active_relation/extensions/object.rb @@ -14,4 +14,10 @@ class Object def strategy ActiveRelation::Sql::Scalar.new end + + def metaclass + class << self + self + end + end end
\ No newline at end of file diff --git a/lib/active_relation/predicates.rb b/lib/active_relation/predicates.rb index 2a36e65042..ddc6eab02b 100644 --- a/lib/active_relation/predicates.rb +++ b/lib/active_relation/predicates.rb @@ -17,7 +17,7 @@ module ActiveRelation end def bind(relation) - descend{ |x| x.bind(relation) } + descend { |x| x.bind(relation) } end def qualify diff --git a/lib/active_relation/primitives/attribute.rb b/lib/active_relation/primitives/attribute.rb index 75f19605c7..a72acb0c34 100644 --- a/lib/active_relation/primitives/attribute.rb +++ b/lib/active_relation/primitives/attribute.rb @@ -49,13 +49,10 @@ module ActiveRelation def self.included(klass) klass.class_eval do alias_method :eql?, :== + delegate :hash, :to => :name end end - def hash - relation.hash + name.hash - end - def history [self] + (ancestor ? [ancestor, ancestor.send(:history)].flatten : []) end diff --git a/lib/active_relation/relations/compound.rb b/lib/active_relation/relations/compound.rb index 115315a76a..4e583e61e8 100644 --- a/lib/active_relation/relations/compound.rb +++ b/lib/active_relation/relations/compound.rb @@ -3,6 +3,7 @@ module ActiveRelation attr_reader :relation delegate :joins, :selects, :orders, :groupings, :table_sql, :inserts, :limit, :offset, :name, :alias, :aggregation?, :alias?, :prefix_for, :column_for, + :hash, :to => :relation def attributes diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index 7536390aca..889cf59d3a 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -3,7 +3,7 @@ module ActiveRelation include Sql::Quoting def session - Session.instance + Session.new end module Enumerable @@ -103,6 +103,10 @@ module ActiveRelation def alias? false end + + def eql?(other) + self == other + end def to_sql(strategy = Sql::Relation.new) strategy.select [ @@ -117,10 +121,6 @@ module ActiveRelation ].compact.join("\n"), self.alias end alias_method :to_s, :to_sql - - def descend - yield self - end def connection ActiveRecord::Base.connection diff --git a/lib/active_relation/relations/table.rb b/lib/active_relation/relations/table.rb index 5720be30e3..271e76f362 100644 --- a/lib/active_relation/relations/table.rb +++ b/lib/active_relation/relations/table.rb @@ -1,7 +1,8 @@ module ActiveRelation class Table < Relation attr_reader :name - + delegate :hash, :to => :name + def initialize(name) @name = name.to_s end @@ -21,17 +22,25 @@ module ActiveRelation end def column_for(attribute) - self[attribute] and columns.detect { |c| c.name == attribute.name } + self[attribute] and columns.detect { |c| c.name == attribute.name.to_s } end def ==(other) self.class == other.class and name == other.name end - + def columns @columns ||= connection.columns(name, "#{name} Columns") end + + def descend + yield self + end + + def reset + @attributes = @columns = nil + end protected def table_sql diff --git a/lib/active_relation/sessions/session.rb b/lib/active_relation/sessions/session.rb index d6e2ae7169..7eb66a1395 100644 --- a/lib/active_relation/sessions/session.rb +++ b/lib/active_relation/sessions/session.rb @@ -2,8 +2,29 @@ require 'singleton' module ActiveRelation class Session - include Singleton - + class << self + def start + if @started + yield + else + begin + @started = true + @instance = new + manufacture = method(:new) + metaclass.class_eval do + define_method(:new) { @instance } + end + yield + ensure + metaclass.class_eval do + define_method(:new, &manufacture) + end + @started = false + end + end + end + end + module CRUD def connection ActiveRecord::Base.connection @@ -14,7 +35,8 @@ module ActiveRelation end def read(select) - connection.select_all(select.to_sql) + @read ||= {} + @read.has_key?(select) ? @read[select] : (@read[select] = connection.select_all(select.to_sql)) end def update(update) diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index 49ced3cbaa..0fc8b3e1be 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -2,12 +2,13 @@ module ActiveRelation module Sql module Quoting def connection - Session.instance.connection + Session.new.connection end delegate :quote_table_name, :quote_column_name, :quote, :to => :connection end + # module Formatting Context / Strategy # unit test me!!! class Strategy include Quoting end diff --git a/spec/active_relation/unit/predicates/binary_spec.rb b/spec/active_relation/unit/predicates/binary_spec.rb index 1f6656b9d1..f6466d9105 100644 --- a/spec/active_relation/unit/predicates/binary_spec.rb +++ b/spec/active_relation/unit/predicates/binary_spec.rb @@ -49,12 +49,15 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql with a binary operation' do - ConcreteBinary.new(@attribute1, @attribute2).to_sql.should be_like(""" + ConcreteBinary.new(@attribute1, @attribute2).to_sql.should be_like(" `users`.`id` <=> `photos`.`id` - """) + ") end - it 'appropriately cooerces scalars' do + it 'appropriately quotes scalars' do + ConcreteBinary.new(@attribute1, "1-asdf").to_sql.should be_like(" + `users`.`id` <=> 1 + ") end end end diff --git a/spec/active_relation/unit/predicates/relation_inclusion_spec.rb b/spec/active_relation/unit/predicates/relation_inclusion_spec.rb index 799be23085..0ac7ccb6e2 100644 --- a/spec/active_relation/unit/predicates/relation_inclusion_spec.rb +++ b/spec/active_relation/unit/predicates/relation_inclusion_spec.rb @@ -18,9 +18,9 @@ module ActiveRelation describe RelationInclusion, '#to_sql' do it "manufactures subselect sql" do - RelationInclusion.new(@attribute, @relation1).to_sql.should be_like(""" + RelationInclusion.new(@attribute, @relation1).to_sql.should be_like(" `foo`.`id` IN (SELECT `foo`.`id` FROM `foo`) - """) + ") end end end diff --git a/spec/active_relation/unit/relations/compound_spec.rb b/spec/active_relation/unit/relations/compound_spec.rb index 03de27fb05..d271529998 100644 --- a/spec/active_relation/unit/relations/compound_spec.rb +++ b/spec/active_relation/unit/relations/compound_spec.rb @@ -7,6 +7,10 @@ module ActiveRelation def initialize(relation) @relation = relation end + + def ==(other) + true + end end @relation = Table.new(:users) @compound_relation = ConcreteCompound.new(@relation) @@ -17,5 +21,13 @@ module ActiveRelation @compound_relation.attributes.should == @relation.attributes.collect { |a| a.bind(@compound_relation) } end end + + describe 'hashing' do + it 'implements hash equality' do + hash = {} + hash[@compound_relation] = 1 + hash[ConcreteCompound.new(@relation)].should == 1 + end + end end end
\ No newline at end of file diff --git a/spec/active_relation/unit/relations/deletion_spec.rb b/spec/active_relation/unit/relations/deletion_spec.rb index 1a536e7aed..1f14ae5d34 100644 --- a/spec/active_relation/unit/relations/deletion_spec.rb +++ b/spec/active_relation/unit/relations/deletion_spec.rb @@ -8,18 +8,18 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql deleting a table relation' do - Deletion.new(@relation).to_sql.should be_like(""" + Deletion.new(@relation).to_sql.should be_like(" DELETE FROM `users` - """) + ") end it 'manufactures sql deleting a selection relation' do - Deletion.new(@relation.select(@relation[:id].equals(1))).to_sql.should be_like(""" + Deletion.new(@relation.select(@relation[:id].equals(1))).to_sql.should be_like(" DELETE FROM `users` WHERE `users`.`id` = 1 - """) + ") end end end diff --git a/spec/active_relation/unit/relations/insertion_spec.rb b/spec/active_relation/unit/relations/insertion_spec.rb index a39c4aeaf3..b2b239097a 100644 --- a/spec/active_relation/unit/relations/insertion_spec.rb +++ b/spec/active_relation/unit/relations/insertion_spec.rb @@ -8,11 +8,11 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql inserting the data for one item' do - Insertion.new(@relation, @relation[:name] => "nick").to_sql.should be_like(""" + Insertion.new(@relation, @relation[:name] => "nick").to_sql.should be_like(" INSERT INTO `users` (`users`.`name`) VALUES ('nick') - """) + ") end end end diff --git a/spec/active_relation/unit/relations/join_spec.rb b/spec/active_relation/unit/relations/join_spec.rb index bc55a72e55..532dc08753 100644 --- a/spec/active_relation/unit/relations/join_spec.rb +++ b/spec/active_relation/unit/relations/join_spec.rb @@ -49,22 +49,22 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql joining the two tables on the predicate' do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).to_sql.should be_like(""" + Join.new("INNER JOIN", @relation1, @relation2, @predicate).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` FROM `users` INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` - """) + ") end it 'manufactures sql joining the two tables, merging any selects' do Join.new("INNER JOIN", @relation1.select(@relation1[:id].equals(1)), - @relation2.select(@relation2[:id].equals(2)), @predicate).to_sql.should be_like(""" + @relation2.select(@relation2[:id].equals(2)), @predicate).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` FROM `users` INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` WHERE `users`.`id` = 1 AND `photos`.`id` = 2 - """) + ") end end end @@ -89,33 +89,33 @@ module ActiveRelation describe '#to_sql' do describe 'with the aggregation on the right' do it 'manufactures sql joining the left table to a derived table' do - Join.new("INNER JOIN", @relation1, @aggregation, @predicate).to_sql.should be_like(""" + Join.new("INNER JOIN", @relation1, @aggregation, @predicate).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` FROM `users` INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` ON `users`.`id` = `photo_count`.`user_id` - """) + ") end end describe 'with the aggregation on the left' do it 'manufactures sql joining the right table to a derived table' do - Join.new("INNER JOIN", @aggregation, @relation1, @predicate).to_sql.should be_like(""" + Join.new("INNER JOIN", @aggregation, @relation1, @predicate).to_sql.should be_like(" SELECT `photo_count`.`user_id`, `photo_count`.`cnt`, `users`.`id`, `users`.`name` FROM (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` INNER JOIN `users` ON `users`.`id` = `photo_count`.`user_id` - """) + ") end end it "keeps selects on the aggregation within the derived table" do - Join.new("INNER JOIN", @relation1, @aggregation.select(@aggregation[:user_id].equals(1)), @predicate).to_sql.should be_like(""" + Join.new("INNER JOIN", @relation1, @aggregation.select(@aggregation[:user_id].equals(1)), @predicate).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` FROM `users` INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` WHERE `photos`.`user_id` = 1 GROUP BY `photos`.`user_id`) AS `photo_count` ON `users`.`id` = `photo_count`.`user_id` - """) + ") end end end diff --git a/spec/active_relation/unit/relations/order_spec.rb b/spec/active_relation/unit/relations/order_spec.rb index 15f08d70ee..032bd2b40f 100644 --- a/spec/active_relation/unit/relations/order_spec.rb +++ b/spec/active_relation/unit/relations/order_spec.rb @@ -23,11 +23,11 @@ module ActiveRelation describe '#to_sql' do it "manufactures sql with an order clause" do - Order.new(@relation, @attribute).to_sql.should be_like(""" + Order.new(@relation, @attribute).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` ORDER BY `users`.`id` - """) + ") end end end diff --git a/spec/active_relation/unit/relations/projection_spec.rb b/spec/active_relation/unit/relations/projection_spec.rb index 75a4672642..01a4d74bc6 100644 --- a/spec/active_relation/unit/relations/projection_spec.rb +++ b/spec/active_relation/unit/relations/projection_spec.rb @@ -46,16 +46,16 @@ module ActiveRelation describe '#to_sql' do it "manufactures sql with a limited select clause" do - Projection.new(@relation, @attribute).to_sql.should be_like(""" + Projection.new(@relation, @attribute).to_sql.should be_like(" SELECT `users`.`id` FROM `users` - """) + ") end it "manufactures sql with scalar selects" do - Projection.new(@relation, Projection.new(@relation, @relation[:name])).to_sql.should be_like(""" + Projection.new(@relation, Projection.new(@relation, @relation[:name])).to_sql.should be_like(" SELECT (SELECT `users`.`name` FROM `users`) FROM `users` - """) + ") end end end diff --git a/spec/active_relation/unit/relations/range_spec.rb b/spec/active_relation/unit/relations/range_spec.rb index 501a8d3641..7be2ca1b03 100644 --- a/spec/active_relation/unit/relations/range_spec.rb +++ b/spec/active_relation/unit/relations/range_spec.rb @@ -23,12 +23,12 @@ module ActiveRelation it "manufactures sql with limit and offset" do range_size = @range.last - @range.first + 1 range_start = @range.first - Range.new(@relation, @range).to_s.should be_like(""" + Range.new(@relation, @range).to_s.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` LIMIT #{range_size} OFFSET #{range_start} - """) + ") end end end diff --git a/spec/active_relation/unit/relations/relation_spec.rb b/spec/active_relation/unit/relations/relation_spec.rb index d3252c658b..3af3b8aa9c 100644 --- a/spec/active_relation/unit/relations/relation_spec.rb +++ b/spec/active_relation/unit/relations/relation_spec.rb @@ -109,22 +109,28 @@ module ActiveRelation describe Relation::Operations::Writes do describe '#delete' do it 'manufactures a deletion relation' do - mock(Session.instance).delete(Deletion.new(@relation)) - @relation.delete.should == @relation + Session.start do + mock(Session.new).delete(Deletion.new(@relation)) + @relation.delete.should == @relation + end end end describe '#insert' do it 'manufactures an insertion relation' do - mock(Session.instance).create(Insertion.new(@relation, record = {@relation[:name] => 'carl'})) - @relation.insert(record).should == @relation + Session.start do + mock(Session.new).create(Insertion.new(@relation, record = {@relation[:name] => 'carl'})) + @relation.insert(record).should == @relation + end end end describe '#update' do it 'manufactures an update relation' do - mock(Session.instance).update(Update.new(@relation, assignments = {@relation[:name] => 'bob'})) - @relation.update(assignments).should == @relation + Session.start do + mock(Session.new).update(Update.new(@relation, assignments = {@relation[:name] => 'bob'})) + @relation.update(assignments).should == @relation + end end end end diff --git a/spec/active_relation/unit/relations/rename_spec.rb b/spec/active_relation/unit/relations/rename_spec.rb index 33a89e6a49..c960f76736 100644 --- a/spec/active_relation/unit/relations/rename_spec.rb +++ b/spec/active_relation/unit/relations/rename_spec.rb @@ -54,10 +54,10 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql renaming the attribute' do - Rename.new(@relation, @relation[:id] => :schmid).to_sql.should be_like(""" + Rename.new(@relation, @relation[:id] => :schmid).to_sql.should be_like(" SELECT `users`.`id` AS 'schmid', `users`.`name` FROM `users` - """) + ") end end end diff --git a/spec/active_relation/unit/relations/selection_spec.rb b/spec/active_relation/unit/relations/selection_spec.rb index afe002186e..3a18d4ae6e 100644 --- a/spec/active_relation/unit/relations/selection_spec.rb +++ b/spec/active_relation/unit/relations/selection_spec.rb @@ -31,19 +31,19 @@ module ActiveRelation describe '#to_sql' do it "manufactures sql with where clause conditions" do - Selection.new(@relation, @predicate).to_sql.should be_like(""" + Selection.new(@relation, @predicate).to_sql.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` WHERE `users`.`id` = 1 - """) + ") end it "allows arbitrary sql" do - Selection.new(@relation, "asdf").to_sql.should be_like(""" + Selection.new(@relation, "asdf").to_sql.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` WHERE asdf - """) + ") end end end diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb index f8d4431aa7..5c7fa35e63 100644 --- a/spec/active_relation/unit/relations/table_spec.rb +++ b/spec/active_relation/unit/relations/table_spec.rb @@ -38,16 +38,16 @@ module ActiveRelation describe '#to_sql' do it "manufactures a simple select query" do - @relation.to_sql.should be_like(""" + @relation.to_sql.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` - """) + ") end end describe '#column_for' do it "" do - pending + @relation[:id].column.should == @relation.columns.detect { |c| c.name == 'id' } end end @@ -72,5 +72,13 @@ module ActiveRelation @relation.qualify.should == Rename.new(@relation, @relation[:name] => 'users.name', @relation[:id] => 'users.id') end end + + describe 'hashing' do + it "implements hash equality" do + hash = {} + hash[Table.new(:users)] = 1 + hash[Table.new(:users)].should == 1 + end + end end end
\ No newline at end of file diff --git a/spec/active_relation/unit/relations/update_spec.rb b/spec/active_relation/unit/relations/update_spec.rb index 642d652057..2cd3eb9d11 100644 --- a/spec/active_relation/unit/relations/update_spec.rb +++ b/spec/active_relation/unit/relations/update_spec.rb @@ -8,18 +8,18 @@ module ActiveRelation describe '#to_sql' do it 'manufactures sql updating attributes' do - Update.new(@relation, @relation[:name] => "nick").to_sql.should be_like(""" + Update.new(@relation, @relation[:name] => "nick").to_sql.should be_like(" UPDATE `users` SET `users`.`name` = 'nick' - """) + ") end it 'manufactures sql updating a selection relation' do - Update.new(@relation.select(@relation[:id].equals(1)), @relation[:name] => "nick").to_sql.should be_like(""" + Update.new(@relation.select(@relation[:id].equals(1)), @relation[:name] => "nick").to_sql.should be_like(" UPDATE `users` SET `users`.`name` = 'nick' WHERE `users`.`id` = 1 - """) + ") end end end diff --git a/spec/active_relation/unit/session/session_spec.rb b/spec/active_relation/unit/session/session_spec.rb index ddd748334a..118b99948c 100644 --- a/spec/active_relation/unit/session/session_spec.rb +++ b/spec/active_relation/unit/session/session_spec.rb @@ -4,13 +4,29 @@ module ActiveRelation describe Session do before do @relation = Table.new(:users) - @session = Session.instance + @session = Session.new end - describe Singleton do - it "is a singleton" do - Session.instance.should be_equal(Session.instance) - lambda { Session.new }.should raise_error + describe '::start' do + describe '::instance' do + it "it is a singleton within the started session" do + Session.start do + Session.new.should == Session.new + end + end + + it "is a singleton across nested sessions" do + Session.start do + outside = Session.new + Session.start do + Session.new.should == outside + end + end + end + + it "manufactures new sessions outside of the started session" do + Session.new.should_not == Session.new + end end end @@ -23,28 +39,34 @@ module ActiveRelation end describe '#create' do - it "should execute an insertion on the connection" do + it "executes an insertion on the connection" do mock(@session.connection).insert(@insert.to_sql) @session.create(@insert) end end describe '#read' do - it "should execute an selection on the connection" do - mock(@session.connection).select_all(@select.to_sql) + it "executes an selection on the connection" do + mock(@session.connection).select_all(@select.to_sql).once + @session.read(@select) + end + + it "is memoized" do + mock(@session.connection).select_all(@select.to_sql).once + @session.read(@select) @session.read(@select) end end describe '#update' do - it "should execute an update on the connection" do + it "executes an update on the connection" do mock(@session.connection).update(@update.to_sql) @session.update(@update) end end describe '#delete' do - it "should execute a delete on the connection" do + it "executes a delete on the connection" do mock(@session.connection).delete(@delete.to_sql) @session.delete(@delete) end |