aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.markdown2
-rw-r--r--TODO49
-rw-r--r--lib/active_relation.rb2
-rw-r--r--lib/active_relation/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--lib/active_relation/extensions.rb1
-rw-r--r--lib/active_relation/extensions/array.rb2
-rw-r--r--lib/active_relation/extensions/class.rb17
-rw-r--r--lib/active_relation/extensions/object.rb2
-rw-r--r--lib/active_relation/predicates.rb2
-rw-r--r--lib/active_relation/primitives.rb2
-rw-r--r--lib/active_relation/primitives/attribute.rb13
-rw-r--r--lib/active_relation/primitives/value.rb (renamed from lib/active_relation/primitives/scalar.rb)10
-rw-r--r--lib/active_relation/relations.rb1
-rw-r--r--lib/active_relation/relations/compound.rb2
-rw-r--r--lib/active_relation/relations/deletion.rb6
-rw-r--r--lib/active_relation/relations/insertion.rb6
-rw-r--r--lib/active_relation/relations/join.rb2
-rw-r--r--lib/active_relation/relations/relation.rb43
-rw-r--r--lib/active_relation/relations/table.rb2
-rw-r--r--lib/active_relation/relations/update.rb6
-rw-r--r--lib/active_relation/relations/writing.rb5
-rw-r--r--lib/active_relation/sessions/session.rb8
-rw-r--r--lib/active_relation/sql.rb40
-rw-r--r--spec/active_relation/unit/predicates/binary_spec.rb98
-rw-r--r--spec/active_relation/unit/predicates/relation_inclusion_spec.rb20
-rw-r--r--spec/active_relation/unit/primitives/attribute_spec.rb13
-rw-r--r--spec/active_relation/unit/relations/compound_spec.rb8
-rw-r--r--spec/active_relation/unit/relations/deletion_spec.rb8
-rw-r--r--spec/active_relation/unit/relations/insertion_spec.rb10
-rw-r--r--spec/active_relation/unit/relations/join_spec.rb36
-rw-r--r--spec/active_relation/unit/relations/order_spec.rb2
-rw-r--r--spec/active_relation/unit/relations/projection_spec.rb6
-rw-r--r--spec/active_relation/unit/relations/range_spec.rb2
-rw-r--r--spec/active_relation/unit/relations/relation_spec.rb20
-rw-r--r--spec/active_relation/unit/relations/rename_spec.rb2
-rw-r--r--spec/active_relation/unit/relations/selection_spec.rb2
-rw-r--r--spec/active_relation/unit/relations/table_spec.rb19
-rw-r--r--spec/active_relation/unit/relations/update_spec.rb9
-rw-r--r--spec/active_relation/unit/session/session_spec.rb18
-rw-r--r--spec/matchers/hash_the_same_as.rb26
-rw-r--r--spec/spec_helper.rb4
41 files changed, 374 insertions, 152 deletions
diff --git a/README.markdown b/README.markdown
index 4324b0b128..89122a6dd3 100644
--- a/README.markdown
+++ b/README.markdown
@@ -3,7 +3,7 @@ ActiveRelation
## 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 ##
diff --git a/TODO b/TODO
new file mode 100644
index 0000000000..fb656ef084
--- /dev/null
+++ b/TODO
@@ -0,0 +1,49 @@
+todo:
+- 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 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
+
+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 value select working: users.project(users[:name], addresses.select(addresses[:user_id] == users[:id]).project(addresses[:id].count))
+- Session
+- 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 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.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/.DS_Store b/lib/active_relation/.DS_Store
index 2a449ff62e..9918127870 100644
--- a/lib/active_relation/.DS_Store
+++ b/lib/active_relation/.DS_Store
Binary files differ
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/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/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 d13cf9aabb..90eb73f0b0 100644
--- a/lib/active_relation/extensions/object.rb
+++ b/lib/active_relation/extensions/object.rb
@@ -1,6 +1,6 @@
class Object
def bind(relation)
- ActiveRelation::Scalar.new(self, relation)
+ ActiveRelation::Value.new(self, relation)
end
def metaclass
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.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/attribute.rb b/lib/active_relation/primitives/attribute.rb
index baaae1973c..0fed676727 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
@@ -114,15 +111,19 @@ module ActiveRelation
end
include Expressions
- def to_sql(strategy = self.strategy)
+ def to_sql(strategy = Sql::WhereCondition.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/value.rb
index fa88404ee3..096c876ecd 100644
--- a/lib/active_relation/primitives/scalar.rb
+++ b/lib/active_relation/primitives/value.rb
@@ -1,17 +1,17 @@
module ActiveRelation
- class Scalar
+ class Value
attr_reader :value, :relation
def initialize(value, relation)
@value, @relation = value, relation
end
- def to_sql(strategy = self.strategy)
- strategy.scalar value
+ def to_sql(strategy = Sql::WhereCondition.new(relation.engine))
+ strategy.value value
end
- def strategy
- ActiveRelation::Sql::Scalar.new(relation.engine)
+ def format(object)
+ object.to_sql(Sql::Value.new(relation.engine))
end
def ==(other)
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 f3d81baf27..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
@@ -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..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)
@@ -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/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 1c97cc7035..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
@@ -102,38 +104,41 @@ module ActiveRelation
false
end
- def eql?(other)
- self == other
- 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
-
- def attribute_for_name(name)
- attributes.detect { |a| a.alias_or_name.to_s == name.to_s }
+
+ def call(connection = engine.connection)
+ connection.select_all(to_sql)
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
end
- def strategy
- Sql::Predicate.new(engine)
+ def format(object)
+ object.to_sql(Sql::WhereCondition.new(engine))
end
def attributes; [] end
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/lib/active_relation/relations/update.rb b/lib/active_relation/relations/update.rb
index c50919af3e..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)
@@ -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/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
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/lib/active_relation/sql.rb b/lib/active_relation/sql.rb
index 99cfc66383..0d233b307e 100644
--- a/lib/active_relation/sql.rb
+++ b/lib/active_relation/sql.rb
@@ -4,9 +4,11 @@ module ActiveRelation
delegate :quote_table_name, :quote_column_name, :quote, :to => :engine
end
- # module Formatting Context / Strategy # unit test me!!!
- class Strategy
+ class Formatter
+ abstract :attribute, :select, :value
+
attr_reader :engine
+
include Quoting
def initialize(engine)
@@ -14,7 +16,7 @@ module ActiveRelation
end
end
- class Projection < Strategy
+ 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,13 +26,19 @@ module ActiveRelation
end
end
- class Predicate < Strategy
+ 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
- def scalar(scalar, column = nil)
- quote(scalar, column)
+ def value(value, column = nil)
+ quote(value, column)
end
def select(select_sql, aliaz)
@@ -38,35 +46,29 @@ module ActiveRelation
end
end
- class Selection < Strategy
- def scalar(scalar)
- scalar
- end
- end
-
- class Relation < Strategy
+ class SelectStatement < Formatter
def select(select_sql, aliaz)
select_sql
end
end
- class Aggregation < Strategy
+ 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
- def scalar(scalar)
- quote(scalar, @attribute.column)
+ def value(value)
+ quote(value, @attribute.column)
end
end
- class Scalar < Predicate
+ class Value < WhereCondition
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 677d8f3ab4..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)
@@ -34,30 +100,20 @@ 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
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/primitives/attribute_spec.rb b/spec/active_relation/unit/primitives/attribute_spec.rb
index 95c972d814..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
@@ -68,6 +70,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/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
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/join_spec.rb b/spec/active_relation/unit/relations/join_spec.rb
index a424239c4b..fefe069d7b 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
@@ -77,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/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..78766d3308 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
@@ -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/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/relation_spec.rb b/spec/active_relation/unit/relations/relation_spec.rb
index 8f35760801..b562ec2a2a 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
@@ -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
@@ -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
@@ -137,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
@@ -148,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/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
diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb
index 95ad7133db..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
@@ -75,9 +83,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
@@ -91,11 +98,5 @@ module ActiveRelation
Table.new(:users, engine = Engine.new).engine.should == engine
end
end
-
- describe '#reset' do
- it "" do
- pending
- 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 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/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..01a703a7d4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -26,9 +26,9 @@ 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
+ ActiveRelation::Table.engine = ActiveRelation::Engine.new(ActiveRecord::Base)
end
end \ No newline at end of file