From a29ceffc9476c99ff02f0617d2e38627c526bac2 Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Tue, 11 Mar 2008 22:42:47 -0700 Subject: implemented hashing macro; implemented custom matcher testing this macro --- lib/active_relation/.DS_Store | Bin 6148 -> 6148 bytes lib/active_relation/extensions/object.rb | 10 +++++++- lib/active_relation/primitives/attribute.rb | 5 +--- lib/active_relation/relations/relation.rb | 4 ---- lib/active_relation/relations/table.rb | 2 +- .../unit/primitives/attribute_spec.rb | 7 ++++++ spec/active_relation/unit/relations/table_spec.rb | 5 ++-- spec/matchers/hash_the_same_as.rb | 26 +++++++++++++++++++++ spec/spec_helper.rb | 2 +- 9 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 spec/matchers/hash_the_same_as.rb diff --git a/lib/active_relation/.DS_Store b/lib/active_relation/.DS_Store index 2a449ff62e..9918127870 100644 Binary files a/lib/active_relation/.DS_Store and b/lib/active_relation/.DS_Store differ diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb index d13cf9aabb..c1269ee37b 100644 --- a/lib/active_relation/extensions/object.rb +++ b/lib/active_relation/extensions/object.rb @@ -1,4 +1,12 @@ -class Object +class Object + def self.hash_on(delegatee) + def eql?(other) + self == other + end + + delegate :hash, :to => delegatee + end + def bind(relation) ActiveRelation::Scalar.new(self, relation) end diff --git a/lib/active_relation/primitives/attribute.rb b/lib/active_relation/primitives/attribute.rb index baaae1973c..20677f824b 100644 --- a/lib/active_relation/primitives/attribute.rb +++ b/lib/active_relation/primitives/attribute.rb @@ -48,10 +48,7 @@ module ActiveRelation module Congruence def self.included(klass) - klass.class_eval do - alias_method :eql?, :== - delegate :hash, :to => :name - end + klass.hash_on :name end def history diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index 1c97cc7035..f9823bf92d 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -102,10 +102,6 @@ module ActiveRelation false end - def eql?(other) - self == other - end - def to_sql(strategy = Sql::Relation.new(engine)) strategy.select [ "SELECT #{attributes.collect{ |a| a.to_sql(Sql::Projection.new(engine)) }.join(', ')}", diff --git a/lib/active_relation/relations/table.rb b/lib/active_relation/relations/table.rb index 84eb1213ee..4682298608 100644 --- a/lib/active_relation/relations/table.rb +++ b/lib/active_relation/relations/table.rb @@ -3,7 +3,7 @@ module ActiveRelation cattr_accessor :engine attr_reader :name, :engine - delegate :hash, :to => :name + hash_on :name def initialize(name, engine = Table.engine) @name, @engine = name.to_s, engine diff --git a/spec/active_relation/unit/primitives/attribute_spec.rb b/spec/active_relation/unit/primitives/attribute_spec.rb index 95c972d814..d424bf4dff 100644 --- a/spec/active_relation/unit/primitives/attribute_spec.rb +++ b/spec/active_relation/unit/primitives/attribute_spec.rb @@ -68,6 +68,13 @@ module ActiveRelation Attribute.new(@relation, :name).should =~ Attribute.new(@relation, :name, :ancestor => Attribute.new(@relation, :name)) end end + + describe 'hashing' do + it "implements hash equality" do + Attribute.new(@relation, 'name').should hash_the_same_as(Attribute.new(@relation, 'name')) + Attribute.new(@relation, 'name').should_not hash_the_same_as(Attribute.new(@relation, 'id')) + end + end end describe '#to_sql' do diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb index 95ad7133db..41ed2dc404 100644 --- a/spec/active_relation/unit/relations/table_spec.rb +++ b/spec/active_relation/unit/relations/table_spec.rb @@ -75,9 +75,8 @@ module ActiveRelation describe 'hashing' do it "implements hash equality" do - hash = {} - hash[Table.new(:users)] = 1 - hash[Table.new(:users)].should == 1 + Table.new(:users).should hash_the_same_as(Table.new(:users)) + Table.new(:users).should_not hash_the_same_as(Table.new(:photos)) end end diff --git a/spec/matchers/hash_the_same_as.rb b/spec/matchers/hash_the_same_as.rb new file mode 100644 index 0000000000..86e98f31a3 --- /dev/null +++ b/spec/matchers/hash_the_same_as.rb @@ -0,0 +1,26 @@ +module HashTheSameAsMatcher + class HashTheSameAs + def initialize(expected) + @expected = expected + end + + def matches?(target) + @target = target + hash = {} + hash[@expected] = :some_arbitrary_value + hash[@target] == :some_arbitrary_value + end + + def failure_message + "expected #{@target} to hash the same as #{@expected}; they must be `eql?` and have the same `#hash` value" + end + + def negative_failure_message + "expected #{@target} to hash differently than #{@expected}; they must not be `eql?` or have a differing `#hash` values" + end + end + + def hash_the_same_as(expected) + HashTheSameAs.new(expected) + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cd9c8e96cb..6d00064e81 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -26,7 +26,7 @@ class Hash end Spec::Runner.configure do |config| - config.include(BeLikeMatcher) + config.include(BeLikeMatcher, HashTheSameAsMatcher) config.mock_with :rr config.before do ActiveRelation::Table.engine = ActiveRecord::Base.connection -- cgit v1.2.3 From 12ef6a5ad15078d2f634d3c6cdfc453848f90122 Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Tue, 11 Mar 2008 23:22:26 -0700 Subject: refactored session's interaction with engine/connection - follows law of demeter - Table.engine uses AR::Base adapter --- TODO | 48 ++++++++++++++++++++++ lib/active_relation/relations/deletion.rb | 4 ++ lib/active_relation/relations/insertion.rb | 4 ++ lib/active_relation/relations/relation.rb | 4 ++ lib/active_relation/relations/update.rb | 4 ++ lib/active_relation/sessions/session.rb | 8 ++-- .../unit/relations/deletion_spec.rb | 8 ++++ .../unit/relations/insertion_spec.rb | 10 ++++- .../unit/relations/relation_spec.rb | 10 ++++- spec/active_relation/unit/relations/update_spec.rb | 9 ++++ spec/active_relation/unit/session/session_spec.rb | 18 ++++---- spec/spec_helper.rb | 2 +- 12 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000000..45bc9c4f2f --- /dev/null +++ b/TODO @@ -0,0 +1,48 @@ +todo: +- clarify distinction between engine and connection: an engine is a connection pool, plus the quoting operations +- #relation is now on scalar, attribute and relation; you must admit it's name is confusing given that e.g., relation already has a strategy (Sql::Relation) ... should it be called predicate strategy? operand1.to_sql(operand2.predicate) maybe prefer operand1.cast(operand2) or project or in light of +- clean up integration tests in join - i find the aggregate stuff too integrationy; unit testing would be better +- test relation, table reset +- cache expiry on write + - rewrite of querycache test in light of this +- relation inclusion when given an array (1,2,3,4) should quote the elements using the appropriate quoting strategy taken from the attribute + - descend on array, along with bind written in terms of it +- standardize quoting + - use strings everywhere, not symbols ? +- rename and test sql strategies + - the parallel names are confusing since it's not obvious what's what. plus, we lack unit tests; the biggest complaint is a lack of clarity concerning what purpose these serve in the system. Unit tests wont illustrate much unless we use concrete examples. +- rename the tion (Selection) classes so that words that don't end in tion don't seem inconsistent +- re-evaluate bind + +done: +. Relation <=> Relation -> InnerJoinOperation +. Relation << Relation -> LeftOuterJoinOperation +. InnerJoinOperation.on(*Predicate) -> InnerJoinRelation +. LeftOuterJoinOperation.on(*Predicate) -> LeftOuterJoinRelation +. Relation[Symbol] -> Attribute +. Relation[Range] -> Relation +. Attribute == Attribute -> EqualityPredicate +. Attribute >= Attribute -> GreaterThanOrEqualToPredicate +. Relation.include?(Column) -> Predicate +. Relation.project(*Column) -> ProjectionRelation +. Relation.select(*Predicate) -> SelectionRelation +. Relation.order(*Column) -> OrderRelation +. #to_sql +. Remove Builder +. Namespace +. Audit SqlAlchemy for missing features +- Generalized denormalizations on any aggregation (count, yes, but also max, min, average) +- Remove operator overloading of << and <=> for joins. Make it just foo.join(bar) and foo.outer_join(bar). +- Remove operator overloading of == for predicates. make it a.equals(b) (note lack of question mark). +- hookup more predicates (=, <=, =>) +- get some basic aggregations working: users.project(user[:points].max) +- Alias Table Names +- When joining with any sort of aggregation, it needs to be a nested select +- get a scalar select working: users.project(users[:name], addresses.select(addresses[:user_id] == users[:id]).project(addresses[:id].count)) +- Session +- sublimate scalars to deal with the fact that they must be quoted per engine +- clean-up singleton monstrosity +- extract hashing module +- hash custom matcher +- make session engine stuff follow laws of demeter - currently doing some odd method chaining? rethink who is responsible for what + - session just calls execute, passing in a connection; by default it gets a connection from the relation. diff --git a/lib/active_relation/relations/deletion.rb b/lib/active_relation/relations/deletion.rb index f3d81baf27..c5906e8bc8 100644 --- a/lib/active_relation/relations/deletion.rb +++ b/lib/active_relation/relations/deletion.rb @@ -12,6 +12,10 @@ module ActiveRelation ].compact.join("\n") end + def call(connection = engine.connection) + connection.delete(to_sql) + end + def ==(other) self.class == other.class and relation == other.relation diff --git a/lib/active_relation/relations/insertion.rb b/lib/active_relation/relations/insertion.rb index 16fe3d5f46..036db1a319 100644 --- a/lib/active_relation/relations/insertion.rb +++ b/lib/active_relation/relations/insertion.rb @@ -15,6 +15,10 @@ module ActiveRelation ].join("\n") end + def call(connection = engine.connection) + connection.insert(to_sql) + end + def ==(other) self.class == other.class and relation == other.relation and diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index f9823bf92d..69935a7be0 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -115,6 +115,10 @@ module ActiveRelation ].compact.join("\n"), self.alias end alias_method :to_s, :to_sql + + def call(connection = engine.connection) + connection.select_all(to_sql) + end def attribute_for_name(name) attributes.detect { |a| a.alias_or_name.to_s == name.to_s } diff --git a/lib/active_relation/relations/update.rb b/lib/active_relation/relations/update.rb index c50919af3e..0914cda035 100644 --- a/lib/active_relation/relations/update.rb +++ b/lib/active_relation/relations/update.rb @@ -16,6 +16,10 @@ module ActiveRelation ].join("\n") end + def call(connection = engine.connection) + connection.update(to_sql) + end + def ==(other) self.class == other.class and relation == other.relation and diff --git a/lib/active_relation/sessions/session.rb b/lib/active_relation/sessions/session.rb index 4bdbe69978..fe917a0e4d 100644 --- a/lib/active_relation/sessions/session.rb +++ b/lib/active_relation/sessions/session.rb @@ -25,22 +25,22 @@ module ActiveRelation module CRUD def create(insert) - insert.engine.insert(insert.to_sql) + insert.call(insert.engine.connection) end def read(select) @read ||= Hash.new do |hash, select| - hash[select] = select.engine.select_all(select.to_sql) + hash[select] = select.call(select.engine.connection) end @read[select] end def update(update) - update.engine.update(update.to_sql) + update.call(update.engine.connection) end def delete(delete) - delete.engine.delete(delete.to_sql) + delete.call(delete.engine.connection) end end include CRUD diff --git a/spec/active_relation/unit/relations/deletion_spec.rb b/spec/active_relation/unit/relations/deletion_spec.rb index 1f14ae5d34..27d879e96f 100644 --- a/spec/active_relation/unit/relations/deletion_spec.rb +++ b/spec/active_relation/unit/relations/deletion_spec.rb @@ -22,5 +22,13 @@ module ActiveRelation ") end end + + describe '#call' do + it 'executes a delete on the connection' do + deletion = Deletion.new(@relation) + mock(connection = Object.new).delete(deletion.to_sql) + deletion.call(connection) + end + end end end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/insertion_spec.rb b/spec/active_relation/unit/relations/insertion_spec.rb index 91bf7773c1..f081743c50 100644 --- a/spec/active_relation/unit/relations/insertion_spec.rb +++ b/spec/active_relation/unit/relations/insertion_spec.rb @@ -4,16 +4,24 @@ module ActiveRelation describe Insertion do before do @relation = Table.new(:users) + @insertion = Insertion.new(@relation, @relation[:name] => "nick".bind(@relation)) end describe '#to_sql' do it 'manufactures sql inserting the data for one item' do - Insertion.new(@relation, @relation[:name] => "nick".bind(@relation)).to_sql.should be_like(" + @insertion.to_sql.should be_like(" INSERT INTO `users` (`users`.`name`) VALUES ('nick') ") end end + + describe '#call' do + it 'executes an insert on the connection' do + mock(connection = Object.new).insert(@insertion.to_sql) + @insertion.call(connection) + end + end end end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/relation_spec.rb b/spec/active_relation/unit/relations/relation_spec.rb index 8f35760801..2e9f7fd3c8 100644 --- a/spec/active_relation/unit/relations/relation_spec.rb +++ b/spec/active_relation/unit/relations/relation_spec.rb @@ -69,7 +69,7 @@ module ActiveRelation describe '#as' do it "manufactures an alias relation" do - @relation.as(:thucydides).should == Alias.new(@relation, :thucydides) + @relation.as(:paul).should == Alias.new(@relation, :paul) end end @@ -99,6 +99,14 @@ module ActiveRelation end end + describe '#call' do + it 'executes a select_all on the connection' do + mock(connection = Object.new).select_all(@relation.to_sql) + @relation.call(connection) + end + end + + describe '#aggregate' do before do @expression1 = @attribute1.sum diff --git a/spec/active_relation/unit/relations/update_spec.rb b/spec/active_relation/unit/relations/update_spec.rb index cad14fd5ec..848760de83 100644 --- a/spec/active_relation/unit/relations/update_spec.rb +++ b/spec/active_relation/unit/relations/update_spec.rb @@ -22,5 +22,14 @@ module ActiveRelation ") end end + + describe '#call' do + it 'executes an update on the connection' do + update = Update.new(@relation, @relation[:name] => "nick".bind(@relation)) + mock(connection = Object.new).update(update.to_sql) + update.call(connection) + end + end + end end \ No newline at end of file diff --git a/spec/active_relation/unit/session/session_spec.rb b/spec/active_relation/unit/session/session_spec.rb index 89d96ef323..9fba6cc6b2 100644 --- a/spec/active_relation/unit/session/session_spec.rb +++ b/spec/active_relation/unit/session/session_spec.rb @@ -35,39 +35,39 @@ module ActiveRelation @insert = Insertion.new(@relation, @relation[:name] => 'nick'.bind(@relation)) @update = Update.new(@relation, @relation[:name] => 'nick'.bind(@relation)) @delete = Deletion.new(@relation) - @select = @relation + @read = @relation end describe '#create' do it "executes an insertion on the connection" do - mock(@insert.engine).insert(@insert.to_sql) + mock(@insert).call(@insert.engine.connection) @session.create(@insert) end end describe '#read' do it "executes an selection on the connection" do - mock(@select.engine).select_all(@select.to_sql).once - @session.read(@select) + mock(@read).call(@read.engine.connection) + @session.read(@read) end it "is memoized" do - mock(@select.engine).select_all(@select.to_sql).once - @session.read(@select) - @session.read(@select) + mock(@read).call(@read.engine.connection).once + @session.read(@read) + @session.read(@read) end end describe '#update' do it "executes an update on the connection" do - mock(@update.engine).update(@update.to_sql) + mock(@update).call(@update.engine.connection) @session.update(@update) end end describe '#delete' do it "executes a delete on the connection" do - mock(@delete.engine).delete(@delete.to_sql) + mock(@delete).call(@delete.engine.connection) @session.delete(@delete) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6d00064e81..01a703a7d4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,6 +29,6 @@ Spec::Runner.configure do |config| config.include(BeLikeMatcher, HashTheSameAsMatcher) config.mock_with :rr config.before do - ActiveRelation::Table.engine = ActiveRecord::Base.connection + ActiveRelation::Table.engine = ActiveRelation::Engine.new(ActiveRecord::Base) end end \ No newline at end of file -- cgit v1.2.3 From 8a62a733c107a33edee8cabc908c3ebc3b280e8b Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Tue, 11 Mar 2008 23:39:40 -0700 Subject: renamed strategy method to format - strategy (the method on scalar and attribute) is a complex double-dispatching scheme to format (to_sql) a scalar in the light of the particular attribute; that is, it casts strings to integers if the column is int, etc. --- TODO | 3 ++- lib/active_relation/predicates.rb | 2 +- lib/active_relation/primitives/attribute.rb | 8 ++++++-- lib/active_relation/primitives/scalar.rb | 6 +++--- lib/active_relation/relations/relation.rb | 4 ++-- lib/active_relation/sql.rb | 14 +++++++------- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/TODO b/TODO index 45bc9c4f2f..5ef1573c38 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,5 @@ todo: - clarify distinction between engine and connection: an engine is a connection pool, plus the quoting operations -- #relation is now on scalar, attribute and relation; you must admit it's name is confusing given that e.g., relation already has a strategy (Sql::Relation) ... should it be called predicate strategy? operand1.to_sql(operand2.predicate) maybe prefer operand1.cast(operand2) or project or in light of - clean up integration tests in join - i find the aggregate stuff too integrationy; unit testing would be better - test relation, table reset - cache expiry on write @@ -46,3 +45,5 @@ done: - hash custom matcher - make session engine stuff follow laws of demeter - currently doing some odd method chaining? rethink who is responsible for what - session just calls execute, passing in a connection; by default it gets a connection from the relation. +- #strategy is now on scalar, attribute and relation; you must admit it's name is confusing given that e.g., relation already has a strategy (Sql::Relation) ... should it be called predicate strategy? operand1.to_sql(operand2.predicate) maybe prefer operand1.cast(operand2) or project or in light of + - renamed to #format: operand1.format(operand2) diff --git a/lib/active_relation/predicates.rb b/lib/active_relation/predicates.rb index ba926a86e5..12ddd1b48d 100644 --- a/lib/active_relation/predicates.rb +++ b/lib/active_relation/predicates.rb @@ -25,7 +25,7 @@ module ActiveRelation end def to_sql(strategy = nil) - "#{operand1.to_sql(operand2.strategy)} #{predicate_sql} #{operand2.to_sql(operand1.strategy)}" + "#{operand2.format(operand1)} #{predicate_sql} #{operand1.format(operand2)}" end def descend diff --git a/lib/active_relation/primitives/attribute.rb b/lib/active_relation/primitives/attribute.rb index 20677f824b..bbea7b4554 100644 --- a/lib/active_relation/primitives/attribute.rb +++ b/lib/active_relation/primitives/attribute.rb @@ -111,15 +111,19 @@ module ActiveRelation end include Expressions - def to_sql(strategy = self.strategy) + def to_sql(strategy = Sql::Predicate.new(engine)) strategy.attribute prefix, name, self.alias end + def format(object) + object.to_sql(strategy) + end + + private def strategy Sql::Attribute.new(self) end - private def prefix relation.prefix_for(self) end diff --git a/lib/active_relation/primitives/scalar.rb b/lib/active_relation/primitives/scalar.rb index fa88404ee3..d428541a50 100644 --- a/lib/active_relation/primitives/scalar.rb +++ b/lib/active_relation/primitives/scalar.rb @@ -6,12 +6,12 @@ module ActiveRelation @value, @relation = value, relation end - def to_sql(strategy = self.strategy) + def to_sql(strategy = Sql::Predicate.new(relation.engine)) strategy.scalar value end - def strategy - ActiveRelation::Sql::Scalar.new(relation.engine) + def format(object) + object.to_sql(Sql::Scalar.new(relation.engine)) end def ==(other) diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index 69935a7be0..ee2e00aa21 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -132,8 +132,8 @@ module ActiveRelation self end - def strategy - Sql::Predicate.new(engine) + def format(object) + object.to_sql(Sql::Predicate.new(engine)) end def attributes; [] end diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index 99cfc66383..ff00223ce7 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -4,8 +4,8 @@ module ActiveRelation delegate :quote_table_name, :quote_column_name, :quote, :to => :engine end - # module Formatting Context / Strategy # unit test me!!! - class Strategy + # unit test me!!! + class Formatter attr_reader :engine include Quoting @@ -14,7 +14,7 @@ module ActiveRelation end end - class Projection < Strategy + class Projection < Formatter def attribute(relation_name, attribute_name, aliaz) "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + (aliaz ? " AS #{quote(aliaz.to_s)}" : "") end @@ -24,7 +24,7 @@ module ActiveRelation end end - class Predicate < Strategy + class Predicate < Formatter def attribute(relation_name, attribute_name, aliaz) "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" end @@ -38,19 +38,19 @@ module ActiveRelation end end - class Selection < Strategy + class Selection < Formatter def scalar(scalar) scalar end end - class Relation < Strategy + class Relation < Formatter def select(select_sql, aliaz) select_sql end end - class Aggregation < Strategy + class Aggregation < Formatter def select(select_sql, aliaz) "(#{select_sql}) AS #{engine.quote_table_name(aliaz)}" end -- cgit v1.2.3 From 181f28e2f9ddd1aa8e78709c45368ade9585287a Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Wed, 12 Mar 2008 00:06:41 -0700 Subject: pending tests. better coverage though i dislike the strategy --- TODO | 4 ++-- spec/active_relation/unit/relations/relation_spec.rb | 6 +++++- spec/active_relation/unit/relations/table_spec.rb | 5 +++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index 5ef1573c38..f502b48a3d 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ todo: -- clarify distinction between engine and connection: an engine is a connection pool, plus the quoting operations - clean up integration tests in join - i find the aggregate stuff too integrationy; unit testing would be better - test relation, table reset - cache expiry on write @@ -8,7 +7,8 @@ todo: - descend on array, along with bind written in terms of it - standardize quoting - use strings everywhere, not symbols ? -- rename and test sql strategies +- rename sql strategies +- test sql strategies - the parallel names are confusing since it's not obvious what's what. plus, we lack unit tests; the biggest complaint is a lack of clarity concerning what purpose these serve in the system. Unit tests wont illustrate much unless we use concrete examples. - rename the tion (Selection) classes so that words that don't end in tion don't seem inconsistent - re-evaluate bind diff --git a/spec/active_relation/unit/relations/relation_spec.rb b/spec/active_relation/unit/relations/relation_spec.rb index 2e9f7fd3c8..5e434e52a6 100644 --- a/spec/active_relation/unit/relations/relation_spec.rb +++ b/spec/active_relation/unit/relations/relation_spec.rb @@ -156,7 +156,11 @@ module ActiveRelation describe Relation::Enumerable do it "is enumerable" do - pending + pending "I don't like this mock-based test" + data = [1,2,3] + mock.instance_of(Session).read(anything) { data } + @relation.collect.should == data + @relation.first.should == data.first end end end diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb index 41ed2dc404..e62f84d93a 100644 --- a/spec/active_relation/unit/relations/table_spec.rb +++ b/spec/active_relation/unit/relations/table_spec.rb @@ -92,8 +92,9 @@ module ActiveRelation end describe '#reset' do - it "" do - pending + it "reloads columns from the database" do + lambda { stub(@relation).columns { [] } }.should_not change { @relation.attributes } + lambda { @relation.reset }.should change { @relation.attributes } end end end -- cgit v1.2.3 From 46da601b2f5507325bba279d5c12ed88ce9e685e Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Wed, 12 Mar 2008 00:12:41 -0700 Subject: more inadequate tests --- spec/active_relation/unit/primitives/attribute_spec.rb | 6 ++++-- spec/active_relation/unit/relations/table_spec.rb | 15 ++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/spec/active_relation/unit/primitives/attribute_spec.rb b/spec/active_relation/unit/primitives/attribute_spec.rb index d424bf4dff..bdd22721b3 100644 --- a/spec/active_relation/unit/primitives/attribute_spec.rb +++ b/spec/active_relation/unit/primitives/attribute_spec.rb @@ -39,8 +39,10 @@ module ActiveRelation end describe '#column' do - it "" do - pending + it "returns the corresponding column in the relation" do + pending "damn mock based tests are too easy" + stub(@relation).column_for(@attribute) { 'bruisers' } + @attribute.column.should == 'bruisers' end end diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb index e62f84d93a..6286ea9de1 100644 --- a/spec/active_relation/unit/relations/table_spec.rb +++ b/spec/active_relation/unit/relations/table_spec.rb @@ -65,6 +65,14 @@ module ActiveRelation Attribute.new(@relation, :name) ] end + + describe '#reset' do + it "reloads columns from the database" do + pending + lambda { stub(@relation.engine).columns { [] } }.should_not change { @relation.attributes } + lambda { @relation.reset }.should change { @relation.attributes } + end + end end describe '#qualify' do @@ -90,12 +98,5 @@ module ActiveRelation Table.new(:users, engine = Engine.new).engine.should == engine end end - - describe '#reset' do - it "reloads columns from the database" do - lambda { stub(@relation).columns { [] } }.should_not change { @relation.attributes } - lambda { @relation.reset }.should change { @relation.attributes } - end - end end end \ No newline at end of file -- cgit v1.2.3 From 2654c29bfdb2ccddfed8cceaaaba06e46892bdb9 Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Wed, 12 Mar 2008 22:41:30 -0700 Subject: test coverage of #prefix_for on join. - it delegates to the relation containing the attribute - if the relation containing the attribute is an alias, it returns the alias --- README | 4 +-- .../active_relation/unit/predicates/binary_spec.rb | 2 +- spec/active_relation/unit/relations/join_spec.rb | 30 ++++++++++++++++++++-- spec/active_relation/unit/relations/order_spec.rb | 2 +- .../unit/relations/projection_spec.rb | 4 +-- spec/active_relation/unit/relations/range_spec.rb | 2 +- spec/active_relation/unit/relations/rename_spec.rb | 2 +- .../unit/relations/selection_spec.rb | 2 +- 8 files changed, 37 insertions(+), 11 deletions(-) diff --git a/README b/README index d60a4ec0bb..54a698c768 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ == Abstract == -ActiveRelation is a Relational Algebra for Ruby. It simplifies the generation of both the simplest and the most complex of SQL queries and it transparently adapts to various RDBMS systems. It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on innovative object and collection modeling as opposed to database compatibility and query generation. +ActiveRelation is a Relational Algebra for Ruby. It 1) simplifies the generation of both the simplest and the most complex of SQL queries and it 2) transparently adapts to various RDBMS systems. It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on innovative object and collection modeling as opposed to database compatibility and query generation. == A Gentle Introduction == @@ -58,7 +58,7 @@ The best property of the Relational is compositionality, or closure under all op .select(users[:name].equals('amy')) \ .project(users[:id]) \ # => SELECT users.id FROM users WHERE users.name = 'amy' -s + == Contributions == diff --git a/spec/active_relation/unit/predicates/binary_spec.rb b/spec/active_relation/unit/predicates/binary_spec.rb index 677d8f3ab4..d552d8b501 100644 --- a/spec/active_relation/unit/predicates/binary_spec.rb +++ b/spec/active_relation/unit/predicates/binary_spec.rb @@ -34,7 +34,7 @@ module ActiveRelation end describe '#descend' do - it "distributes over the predicates and attributes" do + it "distributes a block over the predicates and attributes" do ConcreteBinary.new(@attribute1, @attribute2).descend(&:qualify). \ should == ConcreteBinary.new(@attribute1.qualify, @attribute2.qualify) end diff --git a/spec/active_relation/unit/relations/join_spec.rb b/spec/active_relation/unit/relations/join_spec.rb index a424239c4b..fdc4a4cdf2 100644 --- a/spec/active_relation/unit/relations/join_spec.rb +++ b/spec/active_relation/unit/relations/join_spec.rb @@ -25,6 +25,13 @@ module ActiveRelation end end + describe '#qualify' do + it 'descends' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).qualify. \ + should == Join.new("INNER JOIN", @relation1, @relation2, @predicate).descend(&:qualify) + end + end + describe '#descend' do it 'distributes over the relations and predicates' do Join.new("INNER JOIN", @relation1, @relation2, @predicate).qualify. \ @@ -33,8 +40,27 @@ module ActiveRelation end describe '#prefix_for' do - it 'needs a test' do - pending + describe 'when the joined relations are simple' do + it "returns the name of the relation containing the attribute" do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation1[:id]) \ + .should == @relation1.prefix_for(@relation1[:id]) + Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation2[:id]) \ + .should == @relation2.prefix_for(@relation2[:id]) + + end + end + + describe 'when one of the joined relations is an alias' do + before do + @aliased_relation = @relation1.as(:alias) + end + + it "returns the alias of the relation containing the attribute" do + Join.new("INNER JOIN", @aliased_relation, @relation2, @predicate).prefix_for(@aliased_relation[:id]) \ + .should == @aliased_relation.alias + Join.new("INNER JOIN", @aliased_relation, @relation2, @predicate).prefix_for(@relation2[:id]) \ + .should == @relation2.prefix_for(@relation2[: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 032bd2b40f..6a9ce07024 100644 --- a/spec/active_relation/unit/relations/order_spec.rb +++ b/spec/active_relation/unit/relations/order_spec.rb @@ -15,7 +15,7 @@ module ActiveRelation end describe '#descend' do - it "distributes over the relation and attributes" do + it "distributes a block over the relation and attributes" do Order.new(@relation, @attribute).descend(&:qualify). \ should == Order.new(@relation.descend(&:qualify), @attribute.qualify) end diff --git a/spec/active_relation/unit/relations/projection_spec.rb b/spec/active_relation/unit/relations/projection_spec.rb index 01a4d74bc6..9acfead8b8 100644 --- a/spec/active_relation/unit/relations/projection_spec.rb +++ b/spec/active_relation/unit/relations/projection_spec.rb @@ -31,14 +31,14 @@ module ActiveRelation end describe '#qualify' do - it "distributes over the relation and attributes" do + it "descends" do Projection.new(@relation, @attribute).qualify. \ should == Projection.new(@relation, @attribute).descend(&:qualify) end end describe '#descend' do - it "distributes over the relation and attributes" do + it "distributes a block over the relation and attributes" do Projection.new(@relation, @attribute).descend(&:qualify). \ should == Projection.new(@relation.descend(&:qualify), @attribute.qualify) end diff --git a/spec/active_relation/unit/relations/range_spec.rb b/spec/active_relation/unit/relations/range_spec.rb index 7be2ca1b03..a0207c7342 100644 --- a/spec/active_relation/unit/relations/range_spec.rb +++ b/spec/active_relation/unit/relations/range_spec.rb @@ -14,7 +14,7 @@ module ActiveRelation end describe '#descend' do - it "distributes over the relation" do + it "distributes a block over the relation" do Range.new(@relation, @range).descend(&:qualify).should == Range.new(@relation.descend(&:qualify), @range) end end diff --git a/spec/active_relation/unit/relations/rename_spec.rb b/spec/active_relation/unit/relations/rename_spec.rb index c960f76736..9a63c4fc80 100644 --- a/spec/active_relation/unit/relations/rename_spec.rb +++ b/spec/active_relation/unit/relations/rename_spec.rb @@ -46,7 +46,7 @@ module ActiveRelation end describe '#descend' do - it "distributes over the relation and renames" do + it "distributes a block over the relation and renames" do Rename.new(@relation, @relation[:id] => :schmid).descend(&:qualify). \ should == Rename.new(@relation.descend(&:qualify), @relation[:id].qualify => :schmid) end diff --git a/spec/active_relation/unit/relations/selection_spec.rb b/spec/active_relation/unit/relations/selection_spec.rb index d5e0c6a9f6..4bb3817bf5 100644 --- a/spec/active_relation/unit/relations/selection_spec.rb +++ b/spec/active_relation/unit/relations/selection_spec.rb @@ -23,7 +23,7 @@ module ActiveRelation end describe '#descend' do - it "distributes over the relation and predicates" do + it "distributes a block over the relation and predicates" do Selection.new(@relation, @predicate).descend(&:qualify). \ should == Selection.new(@relation.descend(&:qualify), @predicate.descend(&:qualify)) end -- cgit v1.2.3 From b1acebaaf0823c093853ade5700bbf5117b4f31a Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Wed, 12 Mar 2008 23:31:18 -0700 Subject: - renamed scalar to value - added better test coverage and documentation of binary spec #to_sql --- TODO | 6 +- lib/active_relation/extensions/array.rb | 2 +- lib/active_relation/extensions/object.rb | 2 +- lib/active_relation/primitives.rb | 2 +- lib/active_relation/primitives/scalar.rb | 25 ------ lib/active_relation/primitives/value.rb | 25 ++++++ lib/active_relation/sql.rb | 14 ++-- .../active_relation/unit/predicates/binary_spec.rb | 96 +++++++++++++++++----- .../unit/predicates/relation_inclusion_spec.rb | 20 ----- spec/active_relation/unit/relations/join_spec.rb | 6 +- .../unit/relations/projection_spec.rb | 2 +- .../unit/relations/relation_spec.rb | 4 +- 12 files changed, 120 insertions(+), 84 deletions(-) delete mode 100644 lib/active_relation/primitives/scalar.rb create mode 100644 lib/active_relation/primitives/value.rb delete mode 100644 spec/active_relation/unit/predicates/relation_inclusion_spec.rb diff --git a/TODO b/TODO index f502b48a3d..fb656ef084 100644 --- a/TODO +++ b/TODO @@ -37,13 +37,13 @@ done: - get some basic aggregations working: users.project(user[:points].max) - Alias Table Names - When joining with any sort of aggregation, it needs to be a nested select -- get a scalar select working: users.project(users[:name], addresses.select(addresses[:user_id] == users[:id]).project(addresses[:id].count)) +- get a value select working: users.project(users[:name], addresses.select(addresses[:user_id] == users[:id]).project(addresses[:id].count)) - Session -- sublimate scalars to deal with the fact that they must be quoted per engine +- sublimate values to deal with the fact that they must be quoted per engine - clean-up singleton monstrosity - extract hashing module - hash custom matcher - make session engine stuff follow laws of demeter - currently doing some odd method chaining? rethink who is responsible for what - session just calls execute, passing in a connection; by default it gets a connection from the relation. -- #strategy is now on scalar, attribute and relation; you must admit it's name is confusing given that e.g., relation already has a strategy (Sql::Relation) ... should it be called predicate strategy? operand1.to_sql(operand2.predicate) maybe prefer operand1.cast(operand2) or project or in light of +- #strategy is now on value, attribute and relation; you must admit it's name is confusing given that e.g., relation already has a strategy (Sql::Relation) ... should it be called predicate strategy? operand1.to_sql(operand2.predicate) maybe prefer operand1.cast(operand2) or project or in light of - renamed to #format: operand1.format(operand2) diff --git a/lib/active_relation/extensions/array.rb b/lib/active_relation/extensions/array.rb index aa4354a78a..4bd20d8121 100644 --- a/lib/active_relation/extensions/array.rb +++ b/lib/active_relation/extensions/array.rb @@ -2,7 +2,7 @@ class Array def to_hash Hash[*flatten] end - + def to_sql(strategy = nil) "(#{collect(&:to_sql).join(', ')})" end diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb index c1269ee37b..ab874150ed 100644 --- a/lib/active_relation/extensions/object.rb +++ b/lib/active_relation/extensions/object.rb @@ -8,7 +8,7 @@ class Object end def bind(relation) - ActiveRelation::Scalar.new(self, relation) + ActiveRelation::Value.new(self, relation) end def metaclass diff --git a/lib/active_relation/primitives.rb b/lib/active_relation/primitives.rb index 7629256034..9909734d24 100644 --- a/lib/active_relation/primitives.rb +++ b/lib/active_relation/primitives.rb @@ -1,4 +1,4 @@ require 'active_relation/primitives/attribute' -require 'active_relation/primitives/scalar' +require 'active_relation/primitives/value' require 'active_relation/primitives/expression' diff --git a/lib/active_relation/primitives/scalar.rb b/lib/active_relation/primitives/scalar.rb deleted file mode 100644 index d428541a50..0000000000 --- a/lib/active_relation/primitives/scalar.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveRelation - class Scalar - attr_reader :value, :relation - - def initialize(value, relation) - @value, @relation = value, relation - end - - def to_sql(strategy = Sql::Predicate.new(relation.engine)) - strategy.scalar value - end - - def format(object) - object.to_sql(Sql::Scalar.new(relation.engine)) - end - - def ==(other) - value == other.value - end - - def qualify - self - end - end -end \ No newline at end of file diff --git a/lib/active_relation/primitives/value.rb b/lib/active_relation/primitives/value.rb new file mode 100644 index 0000000000..ce9497cf34 --- /dev/null +++ b/lib/active_relation/primitives/value.rb @@ -0,0 +1,25 @@ +module ActiveRelation + class Value + attr_reader :value, :relation + + def initialize(value, relation) + @value, @relation = value, relation + end + + def to_sql(strategy = Sql::Predicate.new(relation.engine)) + strategy.value value + end + + def format(object) + object.to_sql(Sql::Value.new(relation.engine)) + end + + def ==(other) + value == other.value + end + + def qualify + self + end + end +end \ No newline at end of file diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index ff00223ce7..fb2177a55b 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -29,8 +29,8 @@ module ActiveRelation "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" end - def scalar(scalar, column = nil) - quote(scalar, column) + def value(value, column = nil) + quote(value, column) end def select(select_sql, aliaz) @@ -39,8 +39,8 @@ module ActiveRelation end class Selection < Formatter - def scalar(scalar) - scalar + def value(value) + value end end @@ -61,12 +61,12 @@ module ActiveRelation @attribute, @engine = attribute, attribute.engine end - def scalar(scalar) - quote(scalar, @attribute.column) + def value(value) + quote(value, @attribute.column) end end - class Scalar < Predicate + class Value < Predicate end end end \ No newline at end of file diff --git a/spec/active_relation/unit/predicates/binary_spec.rb b/spec/active_relation/unit/predicates/binary_spec.rb index d552d8b501..13b3f10a8c 100644 --- a/spec/active_relation/unit/predicates/binary_spec.rb +++ b/spec/active_relation/unit/predicates/binary_spec.rb @@ -3,17 +3,83 @@ require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') module ActiveRelation describe Binary do before do - @relation1 = Table.new(:users) - @relation2 = Table.new(:photos) - @attribute1 = @relation1[:id] - @attribute2 = @relation2[:id] + @relation = Table.new(:users) + @attribute1 = @relation[:id] + @attribute2 = @relation[:name] + @value = "1-asdf".bind(@relation) class ConcreteBinary < Binary def predicate_sql "<=>" end end end - + + describe '#to_sql' do + describe 'when relating two attributes' do + it 'manufactures sql with a binary operation' do + ConcreteBinary.new(@attribute1, @attribute2).to_sql.should be_like(" + `users`.`id` <=> `users`.`name` + ") + end + end + + describe 'when relating an attribute and a value' do + describe 'when relating to an integer attribute' do + it 'formats values as integers' do + ConcreteBinary.new(@attribute1, @value).to_sql.should be_like(" + `users`.`id` <=> 1 + ") + end + end + + describe 'when relating to a string attribute' do + it 'formats values as strings' do + ConcreteBinary.new(@attribute2, @value).to_sql.should be_like(" + `users`.`name` <=> '1-asdf' + ") + end + end + end + + describe 'when relating two values' do + before do + @another_value = 2.bind(@relation) + end + + it 'quotes values appropriate to their type' do + ConcreteBinary.new(string = @value, integer = @another_value).to_sql.should be_like(" + '1-asdf' <=> 2 + ") + end + end + + describe 'when relating to an array' do + it 'manufactures sql with a list' do + pending + array = [1, 2, 3] + ConcreteBinary.new(@attribute1, array).to_sql.should be_like(" + `users`.`id` <=> (1,2,3) + ") + end + + it 'formats values in the array in the type of the attribute' do + pending + array = ['1-asdf', 2, 3] + ConcreteBinary.new(@attribute1, array).to_sql.should be_like(" + `users`.`id` <=> (1,2,3) + ") + end + end + + describe 'when relating to a relation' do + it 'manufactures sql with a subselect' do + ConcreteBinary.new(@attribute1, @relation).to_sql.should be_like(" + `users`.`id` <=> (SELECT `users`.`id`, `users`.`name` FROM `users`) + ") + end + end + end + describe '==' do it "obtains if attribute1 and attribute2 are identical" do Binary.new(@attribute1, @attribute2).should == Binary.new(@attribute1, @attribute2) @@ -41,23 +107,13 @@ module ActiveRelation end describe '#bind' do - it "distributes over the predicates and attributes" do - ConcreteBinary.new(@attribute1, @attribute2).bind(@relation2). \ - should == ConcreteBinary.new(@attribute1.bind(@relation2), @attribute2.bind(@relation2)) - end - end - - describe '#to_sql' do - it 'manufactures sql with a binary operation' do - ConcreteBinary.new(@attribute1, @attribute2).to_sql.should be_like(" - `users`.`id` <=> `photos`.`id` - ") + before do + @another_relation = Table.new(:photos) end - it 'appropriately quotes scalars' do - ConcreteBinary.new(@attribute1, "1-asdf".bind(@relation1)).to_sql.should be_like(" - `users`.`id` <=> 1 - ") + it "descends" do + ConcreteBinary.new(@attribute1, @attribute2).bind(@another_relation). \ + should == ConcreteBinary.new(@attribute1.bind(@another_relation), @attribute2.bind(@another_relation)) 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 deleted file mode 100644 index af5846b747..0000000000 --- a/spec/active_relation/unit/predicates/relation_inclusion_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe RelationInclusion do - before do - users = Table.new(:users) - @relation = users.project(users[:id]) - @attribute = @relation[:id] - end - - describe RelationInclusion, '#to_sql' do - it "manufactures subselect sql" do - # remove when sufficient coverage of sql strategies exists - RelationInclusion.new(@attribute, @relation).to_sql.should be_like(" - `users`.`id` IN (SELECT `users`.`id` FROM `users`) - ") - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/join_spec.rb b/spec/active_relation/unit/relations/join_spec.rb index fdc4a4cdf2..fefe069d7b 100644 --- a/spec/active_relation/unit/relations/join_spec.rb +++ b/spec/active_relation/unit/relations/join_spec.rb @@ -103,10 +103,10 @@ module ActiveRelation describe 'with aggregated relations' do before do - @aggregation = @relation2 \ + @aggregation = @relation2 \ .aggregate(@relation2[:user_id], @relation2[:id].count) \ - .group(@relation2[:user_id]) \ - .rename(@relation2[:id].count, :cnt) \ + .group(@relation2[:user_id]) \ + .rename(@relation2[:id].count, :cnt) \ .as('photo_count') end diff --git a/spec/active_relation/unit/relations/projection_spec.rb b/spec/active_relation/unit/relations/projection_spec.rb index 9acfead8b8..78766d3308 100644 --- a/spec/active_relation/unit/relations/projection_spec.rb +++ b/spec/active_relation/unit/relations/projection_spec.rb @@ -52,7 +52,7 @@ module ActiveRelation ") end - it "manufactures sql with scalar selects" do + it "manufactures sql with value selects" do Projection.new(@relation, Projection.new(@relation, @relation[:name])).to_sql.should be_like(" SELECT (SELECT `users`.`name` FROM `users`) FROM `users` ") diff --git a/spec/active_relation/unit/relations/relation_spec.rb b/spec/active_relation/unit/relations/relation_spec.rb index 5e434e52a6..b562ec2a2a 100644 --- a/spec/active_relation/unit/relations/relation_spec.rb +++ b/spec/active_relation/unit/relations/relation_spec.rb @@ -89,7 +89,7 @@ module ActiveRelation end it "accepts arbitrary strings" do - @relation.select("arbitrary").should == Selection.new(@relation, Scalar.new("arbitrary", @relation)) + @relation.select("arbitrary").should == Selection.new(@relation, Value.new("arbitrary", @relation)) end end @@ -145,7 +145,7 @@ module ActiveRelation describe '#update' do it 'manufactures an update relation' do Session.start do - assignments = {@relation[:name] => Scalar.new('bob', @relation)} + assignments = {@relation[:name] => Value.new('bob', @relation)} mock(Session.new).update(Update.new(@relation, assignments.bind(@relation))) @relation.update(assignments).should == @relation end -- cgit v1.2.3 From c823c2c2cc3e5d0a709d67e585e73fde0f11512f Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Thu, 13 Mar 2008 22:39:51 -0700 Subject: renamed sql formatting strategies to correspond with sql grammar rule names in the mysql bnf --- lib/active_relation/primitives/attribute.rb | 2 +- lib/active_relation/primitives/value.rb | 2 +- lib/active_relation/relations/join.rb | 2 +- lib/active_relation/relations/relation.rb | 18 +++++++++--------- lib/active_relation/sql.rb | 27 +++++++++++++-------------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/active_relation/primitives/attribute.rb b/lib/active_relation/primitives/attribute.rb index bbea7b4554..0fed676727 100644 --- a/lib/active_relation/primitives/attribute.rb +++ b/lib/active_relation/primitives/attribute.rb @@ -111,7 +111,7 @@ module ActiveRelation end include Expressions - def to_sql(strategy = Sql::Predicate.new(engine)) + def to_sql(strategy = Sql::WhereCondition.new(engine)) strategy.attribute prefix, name, self.alias end diff --git a/lib/active_relation/primitives/value.rb b/lib/active_relation/primitives/value.rb index ce9497cf34..096c876ecd 100644 --- a/lib/active_relation/primitives/value.rb +++ b/lib/active_relation/primitives/value.rb @@ -6,7 +6,7 @@ module ActiveRelation @value, @relation = value, relation end - def to_sql(strategy = Sql::Predicate.new(relation.engine)) + def to_sql(strategy = Sql::WhereCondition.new(relation.engine)) strategy.value value end diff --git a/lib/active_relation/relations/join.rb b/lib/active_relation/relations/join.rb index 043b237de7..4a57795280 100644 --- a/lib/active_relation/relations/join.rb +++ b/lib/active_relation/relations/join.rb @@ -61,7 +61,7 @@ module ActiveRelation delegate :engine, :to => :relation def table_sql - relation.aggregation?? relation.to_sql(Sql::Aggregation.new(engine)) : relation.send(:table_sql) + relation.aggregation?? relation.to_sql(Sql::TableReference.new(engine)) : relation.send(:table_sql) end def selects diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index ee2e00aa21..9847cf2b7c 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -102,16 +102,16 @@ module ActiveRelation false end - def to_sql(strategy = Sql::Relation.new(engine)) + def to_sql(strategy = Sql::SelectStatement.new(engine)) strategy.select [ - "SELECT #{attributes.collect{ |a| a.to_sql(Sql::Projection.new(engine)) }.join(', ')}", + "SELECT #{attributes.collect{ |a| a.to_sql(Sql::SelectExpression.new(engine)) }.join(', ')}", "FROM #{table_sql}", - (joins unless joins.blank? ), - ("WHERE #{selects.collect{|s| s.to_sql(Sql::Selection.new(engine))}.join("\n\tAND ")}" unless selects.blank? ), - ("ORDER BY #{orders.collect(&:to_sql)}" unless orders.blank? ), - ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), - ("LIMIT #{limit}" unless limit.blank? ), - ("OFFSET #{offset}" unless offset.blank? ) + (joins unless joins.blank? ), + ("WHERE #{selects.collect{|s| s.to_sql(Sql::WhereClause.new(engine))}.join("\n\tAND ")}" unless selects.blank? ), + ("ORDER BY #{orders.collect(&:to_sql)}" unless orders.blank? ), + ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), + ("LIMIT #{limit}" unless limit.blank? ), + ("OFFSET #{offset}" unless offset.blank? ) ].compact.join("\n"), self.alias end alias_method :to_s, :to_sql @@ -133,7 +133,7 @@ module ActiveRelation end def format(object) - object.to_sql(Sql::Predicate.new(engine)) + object.to_sql(Sql::WhereCondition.new(engine)) end def attributes; [] end diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index fb2177a55b..b38e6dc392 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -4,7 +4,6 @@ module ActiveRelation delegate :quote_table_name, :quote_column_name, :quote, :to => :engine end - # unit test me!!! class Formatter attr_reader :engine include Quoting @@ -14,7 +13,7 @@ module ActiveRelation end end - class Projection < Formatter + class SelectExpression < Formatter def attribute(relation_name, attribute_name, aliaz) "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + (aliaz ? " AS #{quote(aliaz.to_s)}" : "") end @@ -24,7 +23,13 @@ module ActiveRelation end end - class Predicate < Formatter + class WhereClause < Formatter + def value(value) + value + end + end + + class WhereCondition < Formatter def attribute(relation_name, attribute_name, aliaz) "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" end @@ -38,25 +43,19 @@ module ActiveRelation end end - class Selection < Formatter - def value(value) - value - end - end - - class Relation < Formatter + class SelectStatement < Formatter def select(select_sql, aliaz) select_sql end end - class Aggregation < Formatter + class TableReference < Formatter def select(select_sql, aliaz) - "(#{select_sql}) AS #{engine.quote_table_name(aliaz)}" + "(#{select_sql}) AS #{quote_table_name(aliaz)}" end end - class Attribute < Predicate + class Attribute < WhereCondition def initialize(attribute) @attribute, @engine = attribute, attribute.engine end @@ -66,7 +65,7 @@ module ActiveRelation end end - class Value < Predicate + class Value < WhereCondition end end end \ No newline at end of file -- cgit v1.2.3 From a8cf09a5b6bd4019e0f68e70d62d6e6be6b3784b Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Thu, 13 Mar 2008 22:46:12 -0700 Subject: added abstract declaration --- lib/active_relation.rb | 2 +- lib/active_relation/extensions.rb | 1 + lib/active_relation/extensions/class.rb | 17 +++++++++++++++++ lib/active_relation/extensions/object.rb | 10 +--------- lib/active_relation/sql.rb | 2 ++ spec/active_relation/unit/relations/compound_spec.rb | 8 -------- 6 files changed, 22 insertions(+), 18 deletions(-) create mode 100644 lib/active_relation/extensions/class.rb diff --git a/lib/active_relation.rb b/lib/active_relation.rb index f04825ad4c..5b5c91706f 100644 --- a/lib/active_relation.rb +++ b/lib/active_relation.rb @@ -4,8 +4,8 @@ require 'rubygems' require 'activesupport' require 'activerecord' -require 'active_relation/sql' require 'active_relation/extensions' +require 'active_relation/sql' require 'active_relation/predicates' require 'active_relation/relations' require 'active_relation/engines' diff --git a/lib/active_relation/extensions.rb b/lib/active_relation/extensions.rb index 8a024947ed..7268a5549b 100644 --- a/lib/active_relation/extensions.rb +++ b/lib/active_relation/extensions.rb @@ -1,3 +1,4 @@ require 'active_relation/extensions/object' +require 'active_relation/extensions/class' require 'active_relation/extensions/array' require 'active_relation/extensions/hash' diff --git a/lib/active_relation/extensions/class.rb b/lib/active_relation/extensions/class.rb new file mode 100644 index 0000000000..0e5b728c26 --- /dev/null +++ b/lib/active_relation/extensions/class.rb @@ -0,0 +1,17 @@ +class Class + def abstract(*methods) + methods.each do |method| + define_method method do + raise NotImplementedError + end + end + end + + def hash_on(delegatee) + define_method :eql? do |other| + self == other + end + + delegate :hash, :to => delegatee + end +end \ No newline at end of file diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb index ab874150ed..90eb73f0b0 100644 --- a/lib/active_relation/extensions/object.rb +++ b/lib/active_relation/extensions/object.rb @@ -1,12 +1,4 @@ -class Object - def self.hash_on(delegatee) - def eql?(other) - self == other - end - - delegate :hash, :to => delegatee - end - +class Object def bind(relation) ActiveRelation::Value.new(self, relation) end diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index b38e6dc392..e5a4eed08b 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -8,6 +8,8 @@ module ActiveRelation attr_reader :engine include Quoting + abstract :attribute, :select, :value + def initialize(engine) @engine = engine end diff --git a/spec/active_relation/unit/relations/compound_spec.rb b/spec/active_relation/unit/relations/compound_spec.rb index d271529998..e8fcd12e2c 100644 --- a/spec/active_relation/unit/relations/compound_spec.rb +++ b/spec/active_relation/unit/relations/compound_spec.rb @@ -21,13 +21,5 @@ 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 -- cgit v1.2.3 From 384008b04b48735a73dbba5a7d4a18e794c897ad Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Thu, 13 Mar 2008 22:52:18 -0700 Subject: annotated abstraction - in compound - created superclass for the create/insert/update write operations, marked :call as abstract --- lib/active_relation/relations.rb | 1 + lib/active_relation/relations/compound.rb | 2 ++ lib/active_relation/relations/deletion.rb | 2 +- lib/active_relation/relations/insertion.rb | 2 +- lib/active_relation/relations/update.rb | 2 +- lib/active_relation/sql.rb | 5 +++-- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/active_relation/relations.rb b/lib/active_relation/relations.rb index f992fca8be..7776fd3d18 100644 --- a/lib/active_relation/relations.rb +++ b/lib/active_relation/relations.rb @@ -1,5 +1,6 @@ require 'active_relation/relations/relation' require 'active_relation/relations/compound' +require 'active_relation/relations/writing' require 'active_relation/relations/table' require 'active_relation/relations/join' require 'active_relation/relations/aggregation' diff --git a/lib/active_relation/relations/compound.rb b/lib/active_relation/relations/compound.rb index a10b7588e7..fac0939c6f 100644 --- a/lib/active_relation/relations/compound.rb +++ b/lib/active_relation/relations/compound.rb @@ -1,5 +1,7 @@ module ActiveRelation class Compound < Relation + abstract :==, :descend + attr_reader :relation delegate :joins, :selects, :orders, :groupings, :table_sql, :inserts, :limit, :offset, :name, :alias, :aggregation?, :alias?, :prefix_for, :column_for, diff --git a/lib/active_relation/relations/deletion.rb b/lib/active_relation/relations/deletion.rb index c5906e8bc8..0fcf523efe 100644 --- a/lib/active_relation/relations/deletion.rb +++ b/lib/active_relation/relations/deletion.rb @@ -1,5 +1,5 @@ module ActiveRelation - class Deletion < Compound + class Deletion < Writing def initialize(relation) @relation = relation end diff --git a/lib/active_relation/relations/insertion.rb b/lib/active_relation/relations/insertion.rb index 036db1a319..ce065b64c1 100644 --- a/lib/active_relation/relations/insertion.rb +++ b/lib/active_relation/relations/insertion.rb @@ -1,5 +1,5 @@ module ActiveRelation - class Insertion < Compound + class Insertion < Writing attr_reader :record def initialize(relation, record) diff --git a/lib/active_relation/relations/update.rb b/lib/active_relation/relations/update.rb index 0914cda035..a51f248866 100644 --- a/lib/active_relation/relations/update.rb +++ b/lib/active_relation/relations/update.rb @@ -1,5 +1,5 @@ module ActiveRelation - class Update < Compound + class Update < Writing attr_reader :assignments def initialize(relation, assignments) diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb index e5a4eed08b..0d233b307e 100644 --- a/lib/active_relation/sql.rb +++ b/lib/active_relation/sql.rb @@ -5,10 +5,11 @@ module ActiveRelation end class Formatter + abstract :attribute, :select, :value + attr_reader :engine - include Quoting - abstract :attribute, :select, :value + include Quoting def initialize(engine) @engine = engine -- cgit v1.2.3 From cd9efb9eec6198f08b4fd5d754a33aa1b1669b37 Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Thu, 13 Mar 2008 22:56:00 -0700 Subject: annotated abstract methods on relation --- lib/active_relation/relations/relation.rb | 17 +++++++++++------ lib/active_relation/relations/writing.rb | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 lib/active_relation/relations/writing.rb diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb index 9847cf2b7c..15782cde06 100644 --- a/lib/active_relation/relations/relation.rb +++ b/lib/active_relation/relations/relation.rb @@ -1,5 +1,7 @@ module ActiveRelation class Relation + abstract :attributes, :selects, :orders, :inserts, :groupings, :joins, :limit, :offset, :alias + def session Session.new end @@ -119,14 +121,17 @@ module ActiveRelation def call(connection = engine.connection) connection.select_all(to_sql) end - - def attribute_for_name(name) - attributes.detect { |a| a.alias_or_name.to_s == name.to_s } - end + + module AttributeAccessors + def attribute_for_name(name) + attributes.detect { |a| a.alias_or_name.to_s == name.to_s } + end - def attribute_for_attribute(attribute) - attributes.detect { |a| a =~ attribute } + def attribute_for_attribute(attribute) + attributes.detect { |a| a =~ attribute } + end end + include AttributeAccessors def bind(relation) self diff --git a/lib/active_relation/relations/writing.rb b/lib/active_relation/relations/writing.rb new file mode 100644 index 0000000000..3054e9b72f --- /dev/null +++ b/lib/active_relation/relations/writing.rb @@ -0,0 +1,5 @@ +module ActiveRelation + class Writing < Compound + abstract :call, :to_sql + end +end \ No newline at end of file -- cgit v1.2.3