From d51139751eae2be6ee32b44edec39fcf09ed2333 Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Fri, 18 Apr 2008 12:59:29 -0700 Subject: officially renamed active_relation to arel --- README | 10 +- doc/TODO | 2 +- lib/active_relation.rb | 12 -- lib/active_relation/.DS_Store | Bin 6148 -> 0 bytes lib/active_relation/engines.rb | 1 - lib/active_relation/engines/engine.rb | 18 -- lib/active_relation/extensions.rb | 6 - lib/active_relation/extensions/array.rb | 13 -- lib/active_relation/extensions/class.rb | 17 -- lib/active_relation/extensions/hash.rb | 7 - lib/active_relation/extensions/nil_class.rb | 5 - lib/active_relation/extensions/object.rb | 19 -- lib/active_relation/extensions/range.rb | 9 - lib/active_relation/predicates.rb | 63 ------ lib/active_relation/primitives.rb | 4 - lib/active_relation/primitives/attribute.rb | 141 -------------- lib/active_relation/primitives/expression.rb | 44 ----- lib/active_relation/primitives/value.rb | 27 --- lib/active_relation/relations.rb | 17 -- lib/active_relation/relations/alias.rb | 19 -- lib/active_relation/relations/compound.rb | 16 -- lib/active_relation/relations/deletion.rb | 25 --- lib/active_relation/relations/grouping.rb | 19 -- lib/active_relation/relations/insertion.rb | 28 --- lib/active_relation/relations/join.rb | 92 --------- lib/active_relation/relations/nil.rb | 12 -- lib/active_relation/relations/order.rb | 21 -- lib/active_relation/relations/projection.rb | 23 --- lib/active_relation/relations/relation.rb | 158 --------------- lib/active_relation/relations/selection.rb | 21 -- lib/active_relation/relations/skip.rb | 15 -- lib/active_relation/relations/table.rb | 48 ----- lib/active_relation/relations/take.rb | 15 -- lib/active_relation/relations/update.rb | 30 --- lib/active_relation/relations/writing.rb | 4 - lib/active_relation/sessions/session.rb | 56 ------ lib/active_relation/sql.rb | 97 ---------- lib/arel.rb | 12 ++ lib/arel/.DS_Store | Bin 0 -> 6148 bytes lib/arel/engines.rb | 1 + lib/arel/engines/engine.rb | 18 ++ lib/arel/extensions.rb | 6 + lib/arel/extensions/array.rb | 13 ++ lib/arel/extensions/class.rb | 17 ++ lib/arel/extensions/hash.rb | 7 + lib/arel/extensions/nil_class.rb | 5 + lib/arel/extensions/object.rb | 19 ++ lib/arel/extensions/range.rb | 9 + lib/arel/predicates.rb | 63 ++++++ lib/arel/primitives.rb | 4 + lib/arel/primitives/attribute.rb | 141 ++++++++++++++ lib/arel/primitives/expression.rb | 44 +++++ lib/arel/primitives/value.rb | 27 +++ lib/arel/relations.rb | 17 ++ lib/arel/relations/alias.rb | 19 ++ lib/arel/relations/compound.rb | 16 ++ lib/arel/relations/deletion.rb | 25 +++ lib/arel/relations/grouping.rb | 19 ++ lib/arel/relations/insertion.rb | 28 +++ lib/arel/relations/join.rb | 92 +++++++++ lib/arel/relations/nil.rb | 12 ++ lib/arel/relations/order.rb | 21 ++ lib/arel/relations/projection.rb | 23 +++ lib/arel/relations/relation.rb | 158 +++++++++++++++ lib/arel/relations/selection.rb | 21 ++ lib/arel/relations/skip.rb | 15 ++ lib/arel/relations/table.rb | 48 +++++ lib/arel/relations/take.rb | 15 ++ lib/arel/relations/update.rb | 30 +++ lib/arel/relations/writing.rb | 4 + lib/arel/sessions/session.rb | 56 ++++++ lib/arel/sql.rb | 97 ++++++++++ .../active_relation/unit/predicates/binary_spec.rb | 71 ------- .../unit/predicates/equality_spec.rb | 49 ----- spec/active_relation/unit/predicates/in_spec.rb | 58 ------ .../unit/primitives/attribute_spec.rb | 177 ----------------- .../unit/primitives/expression_spec.rb | 45 ----- spec/active_relation/unit/primitives/value_spec.rb | 28 --- spec/active_relation/unit/relations/alias_spec.rb | 16 -- .../unit/relations/compound_spec.rb | 31 --- .../unit/relations/deletion_spec.rb | 42 ---- .../unit/relations/grouping_spec.rb | 33 ---- .../unit/relations/insertion_spec.rb | 71 ------- spec/active_relation/unit/relations/join_spec.rb | 169 ---------------- spec/active_relation/unit/relations/order_spec.rb | 77 -------- .../unit/relations/projection_spec.rb | 81 -------- .../unit/relations/relation_spec.rb | 215 --------------------- .../unit/relations/selection_spec.rb | 43 ----- spec/active_relation/unit/relations/skip_spec.rb | 20 -- spec/active_relation/unit/relations/table_spec.rb | 95 --------- spec/active_relation/unit/relations/take_spec.rb | 20 -- spec/active_relation/unit/relations/update_spec.rb | 81 -------- spec/active_relation/unit/session/session_spec.rb | 92 --------- spec/arel/unit/predicates/binary_spec.rb | 71 +++++++ spec/arel/unit/predicates/equality_spec.rb | 49 +++++ spec/arel/unit/predicates/in_spec.rb | 58 ++++++ spec/arel/unit/primitives/attribute_spec.rb | 177 +++++++++++++++++ spec/arel/unit/primitives/expression_spec.rb | 45 +++++ spec/arel/unit/primitives/value_spec.rb | 28 +++ spec/arel/unit/relations/alias_spec.rb | 16 ++ spec/arel/unit/relations/compound_spec.rb | 31 +++ spec/arel/unit/relations/deletion_spec.rb | 42 ++++ spec/arel/unit/relations/grouping_spec.rb | 33 ++++ spec/arel/unit/relations/insertion_spec.rb | 71 +++++++ spec/arel/unit/relations/join_spec.rb | 169 ++++++++++++++++ spec/arel/unit/relations/order_spec.rb | 77 ++++++++ spec/arel/unit/relations/projection_spec.rb | 81 ++++++++ spec/arel/unit/relations/relation_spec.rb | 215 +++++++++++++++++++++ spec/arel/unit/relations/selection_spec.rb | 43 +++++ spec/arel/unit/relations/skip_spec.rb | 20 ++ spec/arel/unit/relations/table_spec.rb | 95 +++++++++ spec/arel/unit/relations/take_spec.rb | 20 ++ spec/arel/unit/relations/update_spec.rb | 81 ++++++++ spec/arel/unit/session/session_spec.rb | 92 +++++++++ spec/spec_helper.rb | 4 +- 115 files changed, 2624 insertions(+), 2624 deletions(-) delete mode 100644 lib/active_relation.rb delete mode 100644 lib/active_relation/.DS_Store delete mode 100644 lib/active_relation/engines.rb delete mode 100644 lib/active_relation/engines/engine.rb delete mode 100644 lib/active_relation/extensions.rb delete mode 100644 lib/active_relation/extensions/array.rb delete mode 100644 lib/active_relation/extensions/class.rb delete mode 100644 lib/active_relation/extensions/hash.rb delete mode 100644 lib/active_relation/extensions/nil_class.rb delete mode 100644 lib/active_relation/extensions/object.rb delete mode 100644 lib/active_relation/extensions/range.rb delete mode 100644 lib/active_relation/predicates.rb delete mode 100644 lib/active_relation/primitives.rb delete mode 100644 lib/active_relation/primitives/attribute.rb delete mode 100644 lib/active_relation/primitives/expression.rb delete mode 100644 lib/active_relation/primitives/value.rb delete mode 100644 lib/active_relation/relations.rb delete mode 100644 lib/active_relation/relations/alias.rb delete mode 100644 lib/active_relation/relations/compound.rb delete mode 100644 lib/active_relation/relations/deletion.rb delete mode 100644 lib/active_relation/relations/grouping.rb delete mode 100644 lib/active_relation/relations/insertion.rb delete mode 100644 lib/active_relation/relations/join.rb delete mode 100644 lib/active_relation/relations/nil.rb delete mode 100644 lib/active_relation/relations/order.rb delete mode 100644 lib/active_relation/relations/projection.rb delete mode 100644 lib/active_relation/relations/relation.rb delete mode 100644 lib/active_relation/relations/selection.rb delete mode 100644 lib/active_relation/relations/skip.rb delete mode 100644 lib/active_relation/relations/table.rb delete mode 100644 lib/active_relation/relations/take.rb delete mode 100644 lib/active_relation/relations/update.rb delete mode 100644 lib/active_relation/relations/writing.rb delete mode 100644 lib/active_relation/sessions/session.rb delete mode 100644 lib/active_relation/sql.rb create mode 100644 lib/arel.rb create mode 100644 lib/arel/.DS_Store create mode 100644 lib/arel/engines.rb create mode 100644 lib/arel/engines/engine.rb create mode 100644 lib/arel/extensions.rb create mode 100644 lib/arel/extensions/array.rb create mode 100644 lib/arel/extensions/class.rb create mode 100644 lib/arel/extensions/hash.rb create mode 100644 lib/arel/extensions/nil_class.rb create mode 100644 lib/arel/extensions/object.rb create mode 100644 lib/arel/extensions/range.rb create mode 100644 lib/arel/predicates.rb create mode 100644 lib/arel/primitives.rb create mode 100644 lib/arel/primitives/attribute.rb create mode 100644 lib/arel/primitives/expression.rb create mode 100644 lib/arel/primitives/value.rb create mode 100644 lib/arel/relations.rb create mode 100644 lib/arel/relations/alias.rb create mode 100644 lib/arel/relations/compound.rb create mode 100644 lib/arel/relations/deletion.rb create mode 100644 lib/arel/relations/grouping.rb create mode 100644 lib/arel/relations/insertion.rb create mode 100644 lib/arel/relations/join.rb create mode 100644 lib/arel/relations/nil.rb create mode 100644 lib/arel/relations/order.rb create mode 100644 lib/arel/relations/projection.rb create mode 100644 lib/arel/relations/relation.rb create mode 100644 lib/arel/relations/selection.rb create mode 100644 lib/arel/relations/skip.rb create mode 100644 lib/arel/relations/table.rb create mode 100644 lib/arel/relations/take.rb create mode 100644 lib/arel/relations/update.rb create mode 100644 lib/arel/relations/writing.rb create mode 100644 lib/arel/sessions/session.rb create mode 100644 lib/arel/sql.rb delete mode 100644 spec/active_relation/unit/predicates/binary_spec.rb delete mode 100644 spec/active_relation/unit/predicates/equality_spec.rb delete mode 100644 spec/active_relation/unit/predicates/in_spec.rb delete mode 100644 spec/active_relation/unit/primitives/attribute_spec.rb delete mode 100644 spec/active_relation/unit/primitives/expression_spec.rb delete mode 100644 spec/active_relation/unit/primitives/value_spec.rb delete mode 100644 spec/active_relation/unit/relations/alias_spec.rb delete mode 100644 spec/active_relation/unit/relations/compound_spec.rb delete mode 100644 spec/active_relation/unit/relations/deletion_spec.rb delete mode 100644 spec/active_relation/unit/relations/grouping_spec.rb delete mode 100644 spec/active_relation/unit/relations/insertion_spec.rb delete mode 100644 spec/active_relation/unit/relations/join_spec.rb delete mode 100644 spec/active_relation/unit/relations/order_spec.rb delete mode 100644 spec/active_relation/unit/relations/projection_spec.rb delete mode 100644 spec/active_relation/unit/relations/relation_spec.rb delete mode 100644 spec/active_relation/unit/relations/selection_spec.rb delete mode 100644 spec/active_relation/unit/relations/skip_spec.rb delete mode 100644 spec/active_relation/unit/relations/table_spec.rb delete mode 100644 spec/active_relation/unit/relations/take_spec.rb delete mode 100644 spec/active_relation/unit/relations/update_spec.rb delete mode 100644 spec/active_relation/unit/session/session_spec.rb create mode 100644 spec/arel/unit/predicates/binary_spec.rb create mode 100644 spec/arel/unit/predicates/equality_spec.rb create mode 100644 spec/arel/unit/predicates/in_spec.rb create mode 100644 spec/arel/unit/primitives/attribute_spec.rb create mode 100644 spec/arel/unit/primitives/expression_spec.rb create mode 100644 spec/arel/unit/primitives/value_spec.rb create mode 100644 spec/arel/unit/relations/alias_spec.rb create mode 100644 spec/arel/unit/relations/compound_spec.rb create mode 100644 spec/arel/unit/relations/deletion_spec.rb create mode 100644 spec/arel/unit/relations/grouping_spec.rb create mode 100644 spec/arel/unit/relations/insertion_spec.rb create mode 100644 spec/arel/unit/relations/join_spec.rb create mode 100644 spec/arel/unit/relations/order_spec.rb create mode 100644 spec/arel/unit/relations/projection_spec.rb create mode 100644 spec/arel/unit/relations/relation_spec.rb create mode 100644 spec/arel/unit/relations/selection_spec.rb create mode 100644 spec/arel/unit/relations/skip_spec.rb create mode 100644 spec/arel/unit/relations/table_spec.rb create mode 100644 spec/arel/unit/relations/take_spec.rb create mode 100644 spec/arel/unit/relations/update_spec.rb create mode 100644 spec/arel/unit/session/session_spec.rb diff --git a/README b/README index 87e9678df6..bb8c6eb71a 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ == Abstract == -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. +Arel 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 == @@ -10,9 +10,9 @@ Generating a query with ARel is simple. For example, in order to produce you construct a table relation and convert it to sql: - ActiveRelation::Table.new(:users).to_sql + Arel::Table.new(:users).to_sql -In fact, you will probably never call `#to_sql`. Let `users = ActiveRelation::Table.new(:users)`. Rather, you'll work with data from the table directly. You can iterate through all rows in the `users` table like this: +In fact, you will probably never call `#to_sql`. Let `users = Arel::Table.new(:users)`. Rather, you'll work with data from the table directly. You can iterate through all rows in the `users` table like this: users.each { |user| ... } @@ -24,7 +24,7 @@ As you can see, Arel converts the rows from the database into a hash, the values == Relational Algebra == -Arel is based on the Relational Algebra, a mathematical model that is also the inspiration for relational databases. ActiveRelation::Relation objects do not represent queries per se (i.e., they are not object-representations of `SELECT`, `INSERT`, `UPDATE`, or `DELETE` statements), rather they represent a collection of data that you can select from, insert into, update, and delete. For example, to insert a row into the users table, do the following: +Arel is based on the Relational Algebra, a mathematical model that is also the inspiration for relational databases. Arel::Relation objects do not represent queries per se (i.e., they are not object-representations of `SELECT`, `INSERT`, `UPDATE`, or `DELETE` statements), rather they represent a collection of data that you can select from, insert into, update, and delete. For example, to insert a row into the users table, do the following: users.insert({users[:name] => 'amy'}) # => INSERT INTO users (users.name) VALUES ('amy') @@ -62,4 +62,4 @@ The best property of the Relational is compositionality, or closure under all op == Contributions == -I appreciate all contributions to ActiveRelation. There is only one "unusual" requirement I have concerning code style: all specs should be written without mocks, using concrete examples to elicit testable behavior. This has two benefits: it 1) ensures the tests serve as concrete documentation and 2) suits the functional nature of this library, which emphasizes algebraic transformation rather than decoupled components. \ No newline at end of file +I appreciate all contributions to Arel. There is only one "unusual" requirement I have concerning code style: all specs should be written without mocks, using concrete examples to elicit testable behavior. This has two benefits: it 1) ensures the tests serve as concrete documentation and 2) suits the functional nature of this library, which emphasizes algebraic transformation rather than decoupled components. \ No newline at end of file diff --git a/doc/TODO b/doc/TODO index 7a6be568e8..a51a730c90 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,5 +1,5 @@ todo: -- rename ActiveRelation to arel +- rename Arel to arel - incorporate linq vocabularity - fix complex joining cases: - extract adapters diff --git a/lib/active_relation.rb b/lib/active_relation.rb deleted file mode 100644 index 5b5c91706f..0000000000 --- a/lib/active_relation.rb +++ /dev/null @@ -1,12 +0,0 @@ -$LOAD_PATH.unshift(File.dirname(__FILE__)) - -require 'rubygems' -require 'activesupport' -require 'activerecord' - -require 'active_relation/extensions' -require 'active_relation/sql' -require 'active_relation/predicates' -require 'active_relation/relations' -require 'active_relation/engines' -require 'active_relation/primitives' \ No newline at end of file diff --git a/lib/active_relation/.DS_Store b/lib/active_relation/.DS_Store deleted file mode 100644 index 9918127870..0000000000 Binary files a/lib/active_relation/.DS_Store and /dev/null differ diff --git a/lib/active_relation/engines.rb b/lib/active_relation/engines.rb deleted file mode 100644 index 55eb817b88..0000000000 --- a/lib/active_relation/engines.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_relation/engines/engine' \ No newline at end of file diff --git a/lib/active_relation/engines/engine.rb b/lib/active_relation/engines/engine.rb deleted file mode 100644 index d5b312607e..0000000000 --- a/lib/active_relation/engines/engine.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActiveRelation - # this file is currently just a hack to adapt between activerecord::base which holds the connection specification - # and active relation. ultimately, this file should be in effect what the connection specification is in active record; - # that is: a spec of the database (url, password, etc.), a quoting adapter layer, and a connection pool. - class Engine - def initialize(ar = nil) - @ar = ar - end - - def connection - @ar.connection - end - - def method_missing(method, *args, &block) - @ar.connection.send(method, *args, &block) - end - end -end \ No newline at end of file diff --git a/lib/active_relation/extensions.rb b/lib/active_relation/extensions.rb deleted file mode 100644 index ded830eb8c..0000000000 --- a/lib/active_relation/extensions.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'active_relation/extensions/object' -require 'active_relation/extensions/class' -require 'active_relation/extensions/array' -require 'active_relation/extensions/hash' -require 'active_relation/extensions/range' -require 'active_relation/extensions/nil_class' \ No newline at end of file diff --git a/lib/active_relation/extensions/array.rb b/lib/active_relation/extensions/array.rb deleted file mode 100644 index 793c06aad8..0000000000 --- a/lib/active_relation/extensions/array.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Array - def to_hash - Hash[*flatten] - end - - def to_sql(formatter = nil) - "(" + collect { |e| e.to_sql(formatter) }.join(', ') + ")" - end - - def inclusion_predicate_sql - "IN" - end -end \ No newline at end of file diff --git a/lib/active_relation/extensions/class.rb b/lib/active_relation/extensions/class.rb deleted file mode 100644 index 0e5b728c26..0000000000 --- a/lib/active_relation/extensions/class.rb +++ /dev/null @@ -1,17 +0,0 @@ -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/hash.rb b/lib/active_relation/extensions/hash.rb deleted file mode 100644 index 7472b5aa73..0000000000 --- a/lib/active_relation/extensions/hash.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Hash - def bind(relation) - inject({}) do |bound, (key, value)| - bound.merge(key.bind(relation) => value.bind(relation)) - end - end -end \ No newline at end of file diff --git a/lib/active_relation/extensions/nil_class.rb b/lib/active_relation/extensions/nil_class.rb deleted file mode 100644 index 729c4cada7..0000000000 --- a/lib/active_relation/extensions/nil_class.rb +++ /dev/null @@ -1,5 +0,0 @@ -class NilClass - def equality_predicate_sql - 'IS' - end -end \ No newline at end of file diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb deleted file mode 100644 index 25cef989a4..0000000000 --- a/lib/active_relation/extensions/object.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Object - def bind(relation) - ActiveRelation::Value.new(self, relation) - end - - def to_sql(formatter = nil) - formatter.scalar self - end - - def equality_predicate_sql - '=' - end - - def metaclass - class << self - self - end - end -end \ No newline at end of file diff --git a/lib/active_relation/extensions/range.rb b/lib/active_relation/extensions/range.rb deleted file mode 100644 index d7329efe34..0000000000 --- a/lib/active_relation/extensions/range.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Range - def to_sql(formatter = nil) - formatter.range self.begin, self.end - end - - def inclusion_predicate_sql - "BETWEEN" - end -end \ No newline at end of file diff --git a/lib/active_relation/predicates.rb b/lib/active_relation/predicates.rb deleted file mode 100644 index 2cab1721d0..0000000000 --- a/lib/active_relation/predicates.rb +++ /dev/null @@ -1,63 +0,0 @@ -module ActiveRelation - class Predicate - def ==(other) - self.class == other.class - end - end - - class Binary < Predicate - attr_reader :operand1, :operand2 - - def initialize(operand1, operand2) - @operand1, @operand2 = operand1, operand2 - end - - def ==(other) - super and @operand1 == other.operand1 and @operand2 == other.operand2 - end - - def bind(relation) - self.class.new(operand1.bind(relation), operand2.bind(relation)) - end - - def to_sql(formatter = nil) - "#{operand1.to_sql} #{predicate_sql} #{operand1.format(operand2)}" - end - end - - class Equality < Binary - def ==(other) - self.class == other.class and - ((operand1 == other.operand1 and operand2 == other.operand2) or - (operand1 == other.operand2 and operand2 == other.operand1)) - end - - def predicate_sql - operand2.equality_predicate_sql - end - end - - class GreaterThanOrEqualTo < Binary - def predicate_sql; '>=' end - end - - class GreaterThan < Binary - def predicate_sql; '>' end - end - - class LessThanOrEqualTo < Binary - def predicate_sql; '<=' end - end - - class LessThan < Binary - def predicate_sql; '<' end - end - - class Match < Binary - alias_method :regexp, :operand2 - end - - class In < Binary - def predicate_sql; operand2.inclusion_predicate_sql end - end -end \ No newline at end of file diff --git a/lib/active_relation/primitives.rb b/lib/active_relation/primitives.rb deleted file mode 100644 index 9909734d24..0000000000 --- a/lib/active_relation/primitives.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_relation/primitives/attribute' -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 deleted file mode 100644 index 5b1ef4d7c0..0000000000 --- a/lib/active_relation/primitives/attribute.rb +++ /dev/null @@ -1,141 +0,0 @@ -module ActiveRelation - class Attribute - attr_reader :relation, :name, :alias, :ancestor - delegate :engine, :to => :relation - - def initialize(relation, name, options = {}) - @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor] - end - - def alias_or_name - @alias || name - end - - def aggregation? - false - end - - module Transformations - def as(aliaz = nil) - Attribute.new(relation, name, :alias => aliaz, :ancestor => self) - end - - def bind(new_relation) - relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self) - end - - def to_attribute - self - end - end - include Transformations - - def qualified_name - "#{prefix}.#{name}" - end - - def column - relation.column_for(self) - end - - def ==(other) - self.class == other.class and - relation == other.relation and - name == other.name and - @alias == other.alias and - ancestor == other.ancestor - end - - module Congruence - def self.included(klass) - klass.hash_on :name - end - - def history - [self] + (ancestor ? [ancestor, ancestor.history].flatten : []) - end - - def =~(other) - !(history & other.history).empty? - end - - def %(other) - if other then (history - other.history) + (other.history - history) - else history - end - end - end - include Congruence - - module Predications - def eq(other) - Equality.new(self, other) - end - - def lt(other) - LessThan.new(self, other) - end - - def lteq(other) - LessThanOrEqualTo.new(self, other) - end - - def gt(other) - GreaterThan.new(self, other) - end - - def gteq(other) - GreaterThanOrEqualTo.new(self, other) - end - - def matches(regexp) - Match.new(self, regexp) - end - - def in(array) - In.new(self, array) - end - end - include Predications - - module Expressions - def count - Expression.new(self, "COUNT") - end - - def sum - Expression.new(self, "SUM") - end - - def maximum - Expression.new(self, "MAX") - end - - def minimum - Expression.new(self, "MIN") - end - - def average - Expression.new(self, "AVG") - end - end - include Expressions - - def to_sql(formatter = Sql::WhereCondition.new(engine)) - formatter.attribute prefix, name, self.alias - end - - def format(object) - object.to_sql(formatter) - end - - private - def formatter - Sql::Attribute.new(self) - end - - def prefix - relation.prefix_for(self) - end - end -end \ No newline at end of file diff --git a/lib/active_relation/primitives/expression.rb b/lib/active_relation/primitives/expression.rb deleted file mode 100644 index ae4c61d1b1..0000000000 --- a/lib/active_relation/primitives/expression.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActiveRelation - class Expression < Attribute - include Sql::Quoting - - attr_reader :attribute, :function_sql - delegate :relation, :to => :attribute - alias_method :name, :alias - - def initialize(attribute, function_sql, aliaz = nil, ancestor = nil) - @attribute, @function_sql, @alias, @ancestor = attribute, function_sql, aliaz, ancestor - end - - module Transformations - def as(aliaz) - Expression.new(attribute, function_sql, aliaz, self) - end - - def bind(new_relation) - new_relation == relation ? self : Expression.new(attribute.bind(new_relation), function_sql, @alias, self) - end - - def to_attribute - Attribute.new(relation, @alias, :ancestor => self) - end - end - include Transformations - - def to_sql(formatter = nil) - "#{function_sql}(#{attribute.to_sql})" + (@alias ? " AS #{quote_column_name(@alias)}" : '') - end - - def aggregation? - true - end - - def ==(other) - self.class == other.class and - attribute == other.attribute and - function_sql == other.function_sql and - ancestor == other.ancestor and - @alias == other.alias - 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 deleted file mode 100644 index 9042aea067..0000000000 --- a/lib/active_relation/primitives/value.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ActiveRelation - class Value - attr_reader :value, :relation - - delegate :inclusion_predicate_sql, :equality_predicate_sql, :to => :value - - def initialize(value, relation) - @value, @relation = value, relation - end - - def to_sql(formatter = Sql::WhereCondition.new(relation.engine)) - formatter.value value - end - - def format(object) - object.to_sql(Sql::Value.new(relation.engine)) - end - - def ==(other) - value == other.value - end - - def bind(relation) - Value.new(value, relation) - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations.rb b/lib/active_relation/relations.rb deleted file mode 100644 index d8e211c853..0000000000 --- a/lib/active_relation/relations.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'active_relation/relations/relation' -require 'active_relation/relations/nil' -require 'active_relation/relations/compound' -require 'active_relation/relations/writing' -require 'active_relation/relations/table' -require 'active_relation/relations/join' -require 'active_relation/relations/grouping' -require 'active_relation/relations/projection' -require 'active_relation/relations/selection' -require 'active_relation/relations/order' -require 'active_relation/relations/take' -require 'active_relation/relations/skip' -require 'active_relation/relations/deletion' -require 'active_relation/relations/insertion' -require 'active_relation/relations/update' -require 'active_relation/relations/alias' -require 'active_relation/sessions/session' \ No newline at end of file diff --git a/lib/active_relation/relations/alias.rb b/lib/active_relation/relations/alias.rb deleted file mode 100644 index 4cb4913da5..0000000000 --- a/lib/active_relation/relations/alias.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveRelation - class Alias < Compound - attr_reader :alias - - def initialize(relation, aliaz) - @relation, @alias = relation, aliaz - end - - def alias? - true - end - - def ==(other) - self.class == other.class and - relation == other.relation and - @alias == other.alias - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/compound.rb b/lib/active_relation/relations/compound.rb deleted file mode 100644 index 7c84482115..0000000000 --- a/lib/active_relation/relations/compound.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ActiveRelation - class Compound < Relation - attr_reader :relation - - hash_on :relation - - delegate :joins, :selects, :orders, :groupings, :table_sql, :inserts, :taken, - :skipped, :name, :alias, :aggregation?, :alias?, :prefix_for, :column_for, - :engine, - :to => :relation - - def attributes - relation.attributes.collect { |a| a.bind(self) } - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/deletion.rb b/lib/active_relation/relations/deletion.rb deleted file mode 100644 index f1d121d68f..0000000000 --- a/lib/active_relation/relations/deletion.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveRelation - class Deletion < Writing - def initialize(relation) - @relation = relation - end - - def to_sql(formatter = nil) - [ - "DELETE", - "FROM #{table_sql}", - ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ), - ("LIMIT #{taken}" unless taken.blank? ), - ].compact.join("\n") - end - - def call(connection = engine.connection) - connection.delete(to_sql) - end - - def ==(other) - self.class == other.class and - relation == other.relation - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/grouping.rb b/lib/active_relation/relations/grouping.rb deleted file mode 100644 index 57323e90f3..0000000000 --- a/lib/active_relation/relations/grouping.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveRelation - class Grouping < Compound - attr_reader :expressions, :groupings - - def initialize(relation, *groupings) - @relation, @groupings = relation, groupings.collect { |g| g.bind(relation) } - end - - def ==(other) - self.class == other.class and - relation == other.relation and - groupings == other.groupings - end - - def aggregation? - true - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/insertion.rb b/lib/active_relation/relations/insertion.rb deleted file mode 100644 index 58defe4b01..0000000000 --- a/lib/active_relation/relations/insertion.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveRelation - class Insertion < Writing - attr_reader :record - - def initialize(relation, record) - @relation, @record = relation, record.bind(relation) - end - - def to_sql(formatter = nil) - [ - "INSERT", - "INTO #{table_sql}", - "(#{record.keys.collect(&:to_sql).join(', ')})", - "VALUES (#{record.collect { |key, value| key.format(value) }.join(', ')})" - ].join("\n") - end - - def call(connection = engine.connection) - connection.insert(to_sql) - end - - def ==(other) - self.class == other.class and - relation == other.relation and - record == other.record - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/join.rb b/lib/active_relation/relations/join.rb deleted file mode 100644 index a3e45f7f81..0000000000 --- a/lib/active_relation/relations/join.rb +++ /dev/null @@ -1,92 +0,0 @@ -module ActiveRelation - class Join < Relation - attr_reader :join_sql, :relation1, :relation2, :predicates - - delegate :engine, :to => :relation1 - - hash_on :relation1 - - def initialize(join_sql, relation1, relation2 = Nil.new, *predicates) - @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates - end - - def ==(other) - self.class == other.class and - predicates == other.predicates and ( - (relation1 == other.relation1 and relation2 == other.relation2) or - (relation2 == other.relation1 and relation1 == other.relation2) - ) - end - - def attributes - (externalize(relation1).attributes + - externalize(relation2).attributes).collect { |a| a.bind(self) } - end - - def prefix_for(attribute) - if relation1[attribute] && !relation2[attribute] - externalize(relation1).prefix_for(attribute) - elsif relation2[attribute] && !relation1[attribute] - externalize(relation2).prefix_for(attribute) - else - if (attribute % relation1[attribute]).size < (attribute % relation2[attribute]).size - externalize(relation1).prefix_for(attribute) - else - externalize(relation2).prefix_for(attribute) - end - end - end - - def joins - this_join = [ - join_sql, - externalize(relation2).table_sql, - ("ON" unless predicates.blank?), - predicates.collect { |p| p.bind(self).to_sql }.join(' AND ') - ].compact.join(" ") - [relation1.joins, relation2.joins, this_join].compact.join(" ") - end - - def selects - externalize(relation1).selects + externalize(relation2).selects - end - - def table_sql - externalize(relation1).table_sql - end - - private - def externalize(relation) - Externalizer.new(relation) - end - - Externalizer = Struct.new(:relation) do - delegate :engine, :to => :relation - - def table_sql - case - when relation.aggregation? - relation.to_sql(Sql::TableReference.new(engine)) - when relation.alias? - relation.table_sql + ' AS ' + engine.quote_table_name(relation.alias.to_s) - else - relation.table_sql - end - end - - def selects - relation.aggregation?? [] : relation.selects - end - - def attributes - relation.aggregation?? relation.attributes.collect(&:to_attribute) : relation.attributes - end - - def prefix_for(attribute) - if relation[attribute] - relation.alias?? relation.alias : relation.prefix_for(attribute) - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/nil.rb b/lib/active_relation/relations/nil.rb deleted file mode 100644 index 289ba834d8..0000000000 --- a/lib/active_relation/relations/nil.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ActiveRelation - class Nil < Relation - def table_sql; '' end - - def to_s; '' end - - def ==(other) - self.class == other.class - end - end - -end \ No newline at end of file diff --git a/lib/active_relation/relations/order.rb b/lib/active_relation/relations/order.rb deleted file mode 100644 index b5495fad67..0000000000 --- a/lib/active_relation/relations/order.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveRelation - class Order < Compound - attr_reader :ordering - - def initialize(relation, *orders) - ordering = orders.pop - @relation = orders.empty?? relation : Order.new(relation, *orders) - @ordering = ordering.bind(@relation) - end - - def ==(other) - self.class == other.class and - relation == other.relation and - ordering == other.ordering - end - - def orders - relation.orders + [ordering] - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/projection.rb b/lib/active_relation/relations/projection.rb deleted file mode 100644 index 0f8ea5175c..0000000000 --- a/lib/active_relation/relations/projection.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveRelation - class Projection < Compound - attr_reader :projections - - def initialize(relation, *projections) - @relation, @projections = relation, projections - end - - def attributes - projections.collect { |p| p.bind(self) } - end - - def ==(other) - self.class == other.class and - relation == other.relation and - projections == other.projections - end - - def aggregation? - attributes.any?(&:aggregation?) - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/relation.rb b/lib/active_relation/relations/relation.rb deleted file mode 100644 index 8ab266c53d..0000000000 --- a/lib/active_relation/relations/relation.rb +++ /dev/null @@ -1,158 +0,0 @@ -module ActiveRelation - class Relation - def session - Session.new - end - - module Enumerable - include ::Enumerable - - def each(&block) - session.read(self).each(&block) - end - - def first - session.read(self).first - end - end - include Enumerable - - module Operations - def join(other = nil) - case other - when String - Join.new(other, self) - when Relation - JoinOperation.new("INNER JOIN", self, other) - else - self - end - end - - def outer_join(other) - JoinOperation.new("LEFT OUTER JOIN", self, other) - end - - def [](index) - case index - when Symbol, String - attribute_for_name(index) - when Attribute, Expression - attribute_for_attribute(index) - end - end - - def select(*predicates) - predicates.all?(&:blank?) ? self : Selection.new(self, *predicates) - end - - def project(*attributes) - attributes.all?(&:blank?) ? self : Projection.new(self, *attributes) - end - - def as(aliaz = nil) - aliaz.blank?? self : Alias.new(self, aliaz) - end - - def order(*attributes) - attributes.all?(&:blank?) ? self : Order.new(self, *attributes) - end - - def take(taken = nil) - taken.blank?? self : Take.new(self, taken) - end - - def skip(skipped = nil) - skipped.blank?? self : Skip.new(self, skipped) - end - - def group(*groupings) - groupings.all?(&:blank?) ? self : Grouping.new(self, *groupings) - end - - module Writes - def insert(record) - session.create Insertion.new(self, record); self - end - - def update(assignments) - session.update Update.new(self, assignments); self - end - - def delete - session.delete Deletion.new(self); self - end - end - include Writes - - JoinOperation = Struct.new(:join_sql, :relation1, :relation2) do - def on(*predicates) - Join.new(join_sql, relation1, relation2, *predicates) - end - end - end - include Operations - - module Externalizable - def aggregation? - false - end - - def alias? - false - end - end - include Externalizable - - def to_sql(formatter = Sql::SelectStatement.new(engine)) - formatter.select [ - "SELECT #{attributes.collect { |a| a.to_sql(Sql::SelectClause.new(engine)) }.join(', ')}", - "FROM #{table_sql}", - (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 { |o| o.to_sql(Sql::OrderClause.new(engine)) }.join(', ')}" unless orders.blank? ), - ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), - ("LIMIT #{taken}" unless taken.blank? ), - ("OFFSET #{skipped}" unless skipped.blank? ) - ].compact.join("\n"), self.alias - end - alias_method :to_s, :to_sql - - def inclusion_predicate_sql - "IN" - end - - 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 } - end - end - include AttributeAccessors - - def bind(relation) - self - end - - def format(object) - object.to_sql(Sql::WhereCondition.new(engine)) - end - - def attributes; [] end - def selects; [] end - def orders; [] end - def inserts; [] end - def groupings; [] end - def joins; nil end - def taken; nil end - def skipped; nil end - def alias; nil end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/selection.rb b/lib/active_relation/relations/selection.rb deleted file mode 100644 index fb90405b01..0000000000 --- a/lib/active_relation/relations/selection.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveRelation - class Selection < Compound - attr_reader :predicate - - def initialize(relation, *predicates) - predicate = predicates.shift - @relation = predicates.empty?? relation : Selection.new(relation, *predicates) - @predicate = predicate.bind(@relation) - end - - def ==(other) - self.class == other.class and - relation == other.relation and - predicate == other.predicate - end - - def selects - relation.selects + [predicate] - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/skip.rb b/lib/active_relation/relations/skip.rb deleted file mode 100644 index 5973dff8b9..0000000000 --- a/lib/active_relation/relations/skip.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveRelation - class Skip < Compound - attr_reader :skipped - - def initialize(relation, skipped) - @relation, @skipped = relation, skipped - end - - def ==(other) - self.class == other.class and - relation == other.relation and - skipped == other.skipped - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/table.rb b/lib/active_relation/relations/table.rb deleted file mode 100644 index e645f71030..0000000000 --- a/lib/active_relation/relations/table.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveRelation - class Table < Relation - cattr_accessor :engine - attr_reader :name, :engine - - hash_on :name - - def initialize(name, engine = Table.engine) - @name, @engine = name.to_s, engine - end - - def attributes - @attributes ||= columns.collect do |column| - Attribute.new(self, column.name.to_sym) - end - end - - def prefix_for(attribute) - self[attribute] and name - end - - def column_for(attribute) - self[attribute] and columns.detect { |c| c.name == attribute.name.to_s } - end - - def ==(other) - self.class == other.class and - name == other.name - end - - def columns - @columns ||= engine.columns(name, "#{name} Columns") - end - - def reset - @attributes = @columns = nil - end - - def table_sql - engine.quote_table_name(name) - end - - private - def qualifications - attributes.zip(attributes.collect(&:qualified_name)).to_hash - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/take.rb b/lib/active_relation/relations/take.rb deleted file mode 100644 index d9d439b3f5..0000000000 --- a/lib/active_relation/relations/take.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveRelation - class Take < Compound - attr_reader :taken - - def initialize(relation, taken) - @relation, @taken = relation, taken - end - - def ==(other) - self.class == other.class and - relation == other.relation and - taken == other.taken - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/update.rb b/lib/active_relation/relations/update.rb deleted file mode 100644 index 6262ead187..0000000000 --- a/lib/active_relation/relations/update.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActiveRelation - class Update < Writing - attr_reader :assignments - - def initialize(relation, assignments) - @relation, @assignments = relation, assignments.bind(relation) - end - - def to_sql(formatter = nil) - [ - "UPDATE #{table_sql} SET", - assignments.collect do |attribute, value| - "#{value.format(attribute)} = #{attribute.format(value)}" - end.join(",\n"), - ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ), - ("LIMIT #{taken}" unless taken.blank? ) - ].join("\n") - end - - def call(connection = engine.connection) - connection.update(to_sql) - end - - def ==(other) - self.class == other.class and - relation == other.relation and - assignments == other.assignments - end - end -end \ No newline at end of file diff --git a/lib/active_relation/relations/writing.rb b/lib/active_relation/relations/writing.rb deleted file mode 100644 index 2714ce4d0c..0000000000 --- a/lib/active_relation/relations/writing.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ActiveRelation - class Writing < Compound - end -end \ No newline at end of file diff --git a/lib/active_relation/sessions/session.rb b/lib/active_relation/sessions/session.rb deleted file mode 100644 index fe917a0e4d..0000000000 --- a/lib/active_relation/sessions/session.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'singleton' - -module ActiveRelation - class Session - class << self - attr_accessor :instance - alias_method :manufacture, :new - - def start - if @started - yield - else - begin - @started = true - @instance = manufacture - metaclass.send :alias_method, :new, :instance - yield - ensure - metaclass.send :alias_method, :new, :manufacture - @started = false - end - end - end - end - - module CRUD - def create(insert) - insert.call(insert.engine.connection) - end - - def read(select) - @read ||= Hash.new do |hash, select| - hash[select] = select.call(select.engine.connection) - end - @read[select] - end - - def update(update) - update.call(update.engine.connection) - end - - def delete(delete) - delete.call(delete.engine.connection) - end - end - include CRUD - - module Transactions - end - include Transactions - - module UnitOfWork - end - include UnitOfWork - end -end \ No newline at end of file diff --git a/lib/active_relation/sql.rb b/lib/active_relation/sql.rb deleted file mode 100644 index 0b9b0fc18b..0000000000 --- a/lib/active_relation/sql.rb +++ /dev/null @@ -1,97 +0,0 @@ -module ActiveRelation - module Sql - module Quoting - delegate :quote_table_name, :quote_column_name, :quote, :to => :engine - end - - class Formatter - attr_reader :engine - - include Quoting - - def initialize(engine) - @engine = engine - end - end - - class SelectClause < 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 - - def select(select_sql, aliaz) - "(#{select_sql})" + (aliaz ? " AS #{quote(aliaz)}" : "") - end - - def value(value) - value - end - end - - class PassThrough < Formatter - def value(value) - value - end - end - - class WhereClause < PassThrough - end - - class OrderClause < PassThrough - def attribute(relation_name, attribute_name, aliaz) - "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" - end - end - - class WhereCondition < Formatter - def attribute(relation_name, attribute_name, aliaz) - "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" - end - - def value(value) - value.to_sql(self) - end - - def scalar(value, column = nil) - quote(value, column) - end - - def select(select_sql, aliaz) - "(#{select_sql})" - end - end - - class SelectStatement < Formatter - def select(select_sql, aliaz) - select_sql - end - end - - class TableReference < Formatter - def select(select_sql, aliaz) - "(#{select_sql}) AS #{quote_table_name(aliaz)}" - end - end - - class Attribute < WhereCondition - def initialize(attribute) - @attribute, @engine = attribute, attribute.engine - end - - def scalar(scalar) - quote(scalar, @attribute.column) - end - - def array(array) - "(" + array.collect { |e| e.to_sql(self) }.join(', ') + ")" - end - - def range(left, right) - "#{left} AND #{right}" - end - end - - class Value < WhereCondition - end - end -end \ No newline at end of file diff --git a/lib/arel.rb b/lib/arel.rb new file mode 100644 index 0000000000..75489c9c6e --- /dev/null +++ b/lib/arel.rb @@ -0,0 +1,12 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) + +require 'rubygems' +require 'activesupport' +require 'activerecord' + +require 'arel/extensions' +require 'arel/sql' +require 'arel/predicates' +require 'arel/relations' +require 'arel/engines' +require 'arel/primitives' \ No newline at end of file diff --git a/lib/arel/.DS_Store b/lib/arel/.DS_Store new file mode 100644 index 0000000000..9918127870 Binary files /dev/null and b/lib/arel/.DS_Store differ diff --git a/lib/arel/engines.rb b/lib/arel/engines.rb new file mode 100644 index 0000000000..bb71537e9c --- /dev/null +++ b/lib/arel/engines.rb @@ -0,0 +1 @@ +require 'arel/engines/engine' \ No newline at end of file diff --git a/lib/arel/engines/engine.rb b/lib/arel/engines/engine.rb new file mode 100644 index 0000000000..b0b7b4e955 --- /dev/null +++ b/lib/arel/engines/engine.rb @@ -0,0 +1,18 @@ +module Arel + # this file is currently just a hack to adapt between activerecord::base which holds the connection specification + # and active relation. ultimately, this file should be in effect what the connection specification is in active record; + # that is: a spec of the database (url, password, etc.), a quoting adapter layer, and a connection pool. + class Engine + def initialize(ar = nil) + @ar = ar + end + + def connection + @ar.connection + end + + def method_missing(method, *args, &block) + @ar.connection.send(method, *args, &block) + end + end +end \ No newline at end of file diff --git a/lib/arel/extensions.rb b/lib/arel/extensions.rb new file mode 100644 index 0000000000..160cf36e5b --- /dev/null +++ b/lib/arel/extensions.rb @@ -0,0 +1,6 @@ +require 'arel/extensions/object' +require 'arel/extensions/class' +require 'arel/extensions/array' +require 'arel/extensions/hash' +require 'arel/extensions/range' +require 'arel/extensions/nil_class' \ No newline at end of file diff --git a/lib/arel/extensions/array.rb b/lib/arel/extensions/array.rb new file mode 100644 index 0000000000..793c06aad8 --- /dev/null +++ b/lib/arel/extensions/array.rb @@ -0,0 +1,13 @@ +class Array + def to_hash + Hash[*flatten] + end + + def to_sql(formatter = nil) + "(" + collect { |e| e.to_sql(formatter) }.join(', ') + ")" + end + + def inclusion_predicate_sql + "IN" + end +end \ No newline at end of file diff --git a/lib/arel/extensions/class.rb b/lib/arel/extensions/class.rb new file mode 100644 index 0000000000..0e5b728c26 --- /dev/null +++ b/lib/arel/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/arel/extensions/hash.rb b/lib/arel/extensions/hash.rb new file mode 100644 index 0000000000..7472b5aa73 --- /dev/null +++ b/lib/arel/extensions/hash.rb @@ -0,0 +1,7 @@ +class Hash + def bind(relation) + inject({}) do |bound, (key, value)| + bound.merge(key.bind(relation) => value.bind(relation)) + end + end +end \ No newline at end of file diff --git a/lib/arel/extensions/nil_class.rb b/lib/arel/extensions/nil_class.rb new file mode 100644 index 0000000000..729c4cada7 --- /dev/null +++ b/lib/arel/extensions/nil_class.rb @@ -0,0 +1,5 @@ +class NilClass + def equality_predicate_sql + 'IS' + end +end \ No newline at end of file diff --git a/lib/arel/extensions/object.rb b/lib/arel/extensions/object.rb new file mode 100644 index 0000000000..779098f7ea --- /dev/null +++ b/lib/arel/extensions/object.rb @@ -0,0 +1,19 @@ +class Object + def bind(relation) + Arel::Value.new(self, relation) + end + + def to_sql(formatter = nil) + formatter.scalar self + end + + def equality_predicate_sql + '=' + end + + def metaclass + class << self + self + end + end +end \ No newline at end of file diff --git a/lib/arel/extensions/range.rb b/lib/arel/extensions/range.rb new file mode 100644 index 0000000000..d7329efe34 --- /dev/null +++ b/lib/arel/extensions/range.rb @@ -0,0 +1,9 @@ +class Range + def to_sql(formatter = nil) + formatter.range self.begin, self.end + end + + def inclusion_predicate_sql + "BETWEEN" + end +end \ No newline at end of file diff --git a/lib/arel/predicates.rb b/lib/arel/predicates.rb new file mode 100644 index 0000000000..ccaec1ad93 --- /dev/null +++ b/lib/arel/predicates.rb @@ -0,0 +1,63 @@ +module Arel + class Predicate + def ==(other) + self.class == other.class + end + end + + class Binary < Predicate + attr_reader :operand1, :operand2 + + def initialize(operand1, operand2) + @operand1, @operand2 = operand1, operand2 + end + + def ==(other) + super and @operand1 == other.operand1 and @operand2 == other.operand2 + end + + def bind(relation) + self.class.new(operand1.bind(relation), operand2.bind(relation)) + end + + def to_sql(formatter = nil) + "#{operand1.to_sql} #{predicate_sql} #{operand1.format(operand2)}" + end + end + + class Equality < Binary + def ==(other) + self.class == other.class and + ((operand1 == other.operand1 and operand2 == other.operand2) or + (operand1 == other.operand2 and operand2 == other.operand1)) + end + + def predicate_sql + operand2.equality_predicate_sql + end + end + + class GreaterThanOrEqualTo < Binary + def predicate_sql; '>=' end + end + + class GreaterThan < Binary + def predicate_sql; '>' end + end + + class LessThanOrEqualTo < Binary + def predicate_sql; '<=' end + end + + class LessThan < Binary + def predicate_sql; '<' end + end + + class Match < Binary + alias_method :regexp, :operand2 + end + + class In < Binary + def predicate_sql; operand2.inclusion_predicate_sql end + end +end \ No newline at end of file diff --git a/lib/arel/primitives.rb b/lib/arel/primitives.rb new file mode 100644 index 0000000000..d84713d3d5 --- /dev/null +++ b/lib/arel/primitives.rb @@ -0,0 +1,4 @@ +require 'arel/primitives/attribute' +require 'arel/primitives/value' +require 'arel/primitives/expression' + diff --git a/lib/arel/primitives/attribute.rb b/lib/arel/primitives/attribute.rb new file mode 100644 index 0000000000..280fc9f439 --- /dev/null +++ b/lib/arel/primitives/attribute.rb @@ -0,0 +1,141 @@ +module Arel + class Attribute + attr_reader :relation, :name, :alias, :ancestor + delegate :engine, :to => :relation + + def initialize(relation, name, options = {}) + @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor] + end + + def alias_or_name + @alias || name + end + + def aggregation? + false + end + + module Transformations + def as(aliaz = nil) + Attribute.new(relation, name, :alias => aliaz, :ancestor => self) + end + + def bind(new_relation) + relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self) + end + + def to_attribute + self + end + end + include Transformations + + def qualified_name + "#{prefix}.#{name}" + end + + def column + relation.column_for(self) + end + + def ==(other) + self.class == other.class and + relation == other.relation and + name == other.name and + @alias == other.alias and + ancestor == other.ancestor + end + + module Congruence + def self.included(klass) + klass.hash_on :name + end + + def history + [self] + (ancestor ? [ancestor, ancestor.history].flatten : []) + end + + def =~(other) + !(history & other.history).empty? + end + + def %(other) + if other then (history - other.history) + (other.history - history) + else history + end + end + end + include Congruence + + module Predications + def eq(other) + Equality.new(self, other) + end + + def lt(other) + LessThan.new(self, other) + end + + def lteq(other) + LessThanOrEqualTo.new(self, other) + end + + def gt(other) + GreaterThan.new(self, other) + end + + def gteq(other) + GreaterThanOrEqualTo.new(self, other) + end + + def matches(regexp) + Match.new(self, regexp) + end + + def in(array) + In.new(self, array) + end + end + include Predications + + module Expressions + def count + Expression.new(self, "COUNT") + end + + def sum + Expression.new(self, "SUM") + end + + def maximum + Expression.new(self, "MAX") + end + + def minimum + Expression.new(self, "MIN") + end + + def average + Expression.new(self, "AVG") + end + end + include Expressions + + def to_sql(formatter = Sql::WhereCondition.new(engine)) + formatter.attribute prefix, name, self.alias + end + + def format(object) + object.to_sql(formatter) + end + + private + def formatter + Sql::Attribute.new(self) + end + + def prefix + relation.prefix_for(self) + end + end +end \ No newline at end of file diff --git a/lib/arel/primitives/expression.rb b/lib/arel/primitives/expression.rb new file mode 100644 index 0000000000..bf674cc9e1 --- /dev/null +++ b/lib/arel/primitives/expression.rb @@ -0,0 +1,44 @@ +module Arel + class Expression < Attribute + include Sql::Quoting + + attr_reader :attribute, :function_sql + delegate :relation, :to => :attribute + alias_method :name, :alias + + def initialize(attribute, function_sql, aliaz = nil, ancestor = nil) + @attribute, @function_sql, @alias, @ancestor = attribute, function_sql, aliaz, ancestor + end + + module Transformations + def as(aliaz) + Expression.new(attribute, function_sql, aliaz, self) + end + + def bind(new_relation) + new_relation == relation ? self : Expression.new(attribute.bind(new_relation), function_sql, @alias, self) + end + + def to_attribute + Attribute.new(relation, @alias, :ancestor => self) + end + end + include Transformations + + def to_sql(formatter = nil) + "#{function_sql}(#{attribute.to_sql})" + (@alias ? " AS #{quote_column_name(@alias)}" : '') + end + + def aggregation? + true + end + + def ==(other) + self.class == other.class and + attribute == other.attribute and + function_sql == other.function_sql and + ancestor == other.ancestor and + @alias == other.alias + end + end +end \ No newline at end of file diff --git a/lib/arel/primitives/value.rb b/lib/arel/primitives/value.rb new file mode 100644 index 0000000000..650557559a --- /dev/null +++ b/lib/arel/primitives/value.rb @@ -0,0 +1,27 @@ +module Arel + class Value + attr_reader :value, :relation + + delegate :inclusion_predicate_sql, :equality_predicate_sql, :to => :value + + def initialize(value, relation) + @value, @relation = value, relation + end + + def to_sql(formatter = Sql::WhereCondition.new(relation.engine)) + formatter.value value + end + + def format(object) + object.to_sql(Sql::Value.new(relation.engine)) + end + + def ==(other) + value == other.value + end + + def bind(relation) + Value.new(value, relation) + end + end +end \ No newline at end of file diff --git a/lib/arel/relations.rb b/lib/arel/relations.rb new file mode 100644 index 0000000000..96aa8e9d35 --- /dev/null +++ b/lib/arel/relations.rb @@ -0,0 +1,17 @@ +require 'arel/relations/relation' +require 'arel/relations/nil' +require 'arel/relations/compound' +require 'arel/relations/writing' +require 'arel/relations/table' +require 'arel/relations/join' +require 'arel/relations/grouping' +require 'arel/relations/projection' +require 'arel/relations/selection' +require 'arel/relations/order' +require 'arel/relations/take' +require 'arel/relations/skip' +require 'arel/relations/deletion' +require 'arel/relations/insertion' +require 'arel/relations/update' +require 'arel/relations/alias' +require 'arel/sessions/session' \ No newline at end of file diff --git a/lib/arel/relations/alias.rb b/lib/arel/relations/alias.rb new file mode 100644 index 0000000000..2dab52515f --- /dev/null +++ b/lib/arel/relations/alias.rb @@ -0,0 +1,19 @@ +module Arel + class Alias < Compound + attr_reader :alias + + def initialize(relation, aliaz) + @relation, @alias = relation, aliaz + end + + def alias? + true + end + + def ==(other) + self.class == other.class and + relation == other.relation and + @alias == other.alias + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/compound.rb b/lib/arel/relations/compound.rb new file mode 100644 index 0000000000..4ffac6d1c3 --- /dev/null +++ b/lib/arel/relations/compound.rb @@ -0,0 +1,16 @@ +module Arel + class Compound < Relation + attr_reader :relation + + hash_on :relation + + delegate :joins, :selects, :orders, :groupings, :table_sql, :inserts, :taken, + :skipped, :name, :alias, :aggregation?, :alias?, :prefix_for, :column_for, + :engine, + :to => :relation + + def attributes + relation.attributes.collect { |a| a.bind(self) } + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/deletion.rb b/lib/arel/relations/deletion.rb new file mode 100644 index 0000000000..6c802ba905 --- /dev/null +++ b/lib/arel/relations/deletion.rb @@ -0,0 +1,25 @@ +module Arel + class Deletion < Writing + def initialize(relation) + @relation = relation + end + + def to_sql(formatter = nil) + [ + "DELETE", + "FROM #{table_sql}", + ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ), + ("LIMIT #{taken}" unless taken.blank? ), + ].compact.join("\n") + end + + def call(connection = engine.connection) + connection.delete(to_sql) + end + + def ==(other) + self.class == other.class and + relation == other.relation + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/grouping.rb b/lib/arel/relations/grouping.rb new file mode 100644 index 0000000000..ccca600360 --- /dev/null +++ b/lib/arel/relations/grouping.rb @@ -0,0 +1,19 @@ +module Arel + class Grouping < Compound + attr_reader :expressions, :groupings + + def initialize(relation, *groupings) + @relation, @groupings = relation, groupings.collect { |g| g.bind(relation) } + end + + def ==(other) + self.class == other.class and + relation == other.relation and + groupings == other.groupings + end + + def aggregation? + true + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/insertion.rb b/lib/arel/relations/insertion.rb new file mode 100644 index 0000000000..37e7be8757 --- /dev/null +++ b/lib/arel/relations/insertion.rb @@ -0,0 +1,28 @@ +module Arel + class Insertion < Writing + attr_reader :record + + def initialize(relation, record) + @relation, @record = relation, record.bind(relation) + end + + def to_sql(formatter = nil) + [ + "INSERT", + "INTO #{table_sql}", + "(#{record.keys.collect(&:to_sql).join(', ')})", + "VALUES (#{record.collect { |key, value| key.format(value) }.join(', ')})" + ].join("\n") + end + + def call(connection = engine.connection) + connection.insert(to_sql) + end + + def ==(other) + self.class == other.class and + relation == other.relation and + record == other.record + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/join.rb b/lib/arel/relations/join.rb new file mode 100644 index 0000000000..fb51ea0260 --- /dev/null +++ b/lib/arel/relations/join.rb @@ -0,0 +1,92 @@ +module Arel + class Join < Relation + attr_reader :join_sql, :relation1, :relation2, :predicates + + delegate :engine, :to => :relation1 + + hash_on :relation1 + + def initialize(join_sql, relation1, relation2 = Nil.new, *predicates) + @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates + end + + def ==(other) + self.class == other.class and + predicates == other.predicates and ( + (relation1 == other.relation1 and relation2 == other.relation2) or + (relation2 == other.relation1 and relation1 == other.relation2) + ) + end + + def attributes + (externalize(relation1).attributes + + externalize(relation2).attributes).collect { |a| a.bind(self) } + end + + def prefix_for(attribute) + if relation1[attribute] && !relation2[attribute] + externalize(relation1).prefix_for(attribute) + elsif relation2[attribute] && !relation1[attribute] + externalize(relation2).prefix_for(attribute) + else + if (attribute % relation1[attribute]).size < (attribute % relation2[attribute]).size + externalize(relation1).prefix_for(attribute) + else + externalize(relation2).prefix_for(attribute) + end + end + end + + def joins + this_join = [ + join_sql, + externalize(relation2).table_sql, + ("ON" unless predicates.blank?), + predicates.collect { |p| p.bind(self).to_sql }.join(' AND ') + ].compact.join(" ") + [relation1.joins, relation2.joins, this_join].compact.join(" ") + end + + def selects + externalize(relation1).selects + externalize(relation2).selects + end + + def table_sql + externalize(relation1).table_sql + end + + private + def externalize(relation) + Externalizer.new(relation) + end + + Externalizer = Struct.new(:relation) do + delegate :engine, :to => :relation + + def table_sql + case + when relation.aggregation? + relation.to_sql(Sql::TableReference.new(engine)) + when relation.alias? + relation.table_sql + ' AS ' + engine.quote_table_name(relation.alias.to_s) + else + relation.table_sql + end + end + + def selects + relation.aggregation?? [] : relation.selects + end + + def attributes + relation.aggregation?? relation.attributes.collect(&:to_attribute) : relation.attributes + end + + def prefix_for(attribute) + if relation[attribute] + relation.alias?? relation.alias : relation.prefix_for(attribute) + end + end + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/nil.rb b/lib/arel/relations/nil.rb new file mode 100644 index 0000000000..3c1d413953 --- /dev/null +++ b/lib/arel/relations/nil.rb @@ -0,0 +1,12 @@ +module Arel + class Nil < Relation + def table_sql; '' end + + def to_s; '' end + + def ==(other) + self.class == other.class + end + end + +end \ No newline at end of file diff --git a/lib/arel/relations/order.rb b/lib/arel/relations/order.rb new file mode 100644 index 0000000000..91526da02c --- /dev/null +++ b/lib/arel/relations/order.rb @@ -0,0 +1,21 @@ +module Arel + class Order < Compound + attr_reader :ordering + + def initialize(relation, *orders) + ordering = orders.pop + @relation = orders.empty?? relation : Order.new(relation, *orders) + @ordering = ordering.bind(@relation) + end + + def ==(other) + self.class == other.class and + relation == other.relation and + ordering == other.ordering + end + + def orders + relation.orders + [ordering] + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/projection.rb b/lib/arel/relations/projection.rb new file mode 100644 index 0000000000..f09d4f894b --- /dev/null +++ b/lib/arel/relations/projection.rb @@ -0,0 +1,23 @@ +module Arel + class Projection < Compound + attr_reader :projections + + def initialize(relation, *projections) + @relation, @projections = relation, projections + end + + def attributes + projections.collect { |p| p.bind(self) } + end + + def ==(other) + self.class == other.class and + relation == other.relation and + projections == other.projections + end + + def aggregation? + attributes.any?(&:aggregation?) + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/relation.rb b/lib/arel/relations/relation.rb new file mode 100644 index 0000000000..60fb7bd00a --- /dev/null +++ b/lib/arel/relations/relation.rb @@ -0,0 +1,158 @@ +module Arel + class Relation + def session + Session.new + end + + module Enumerable + include ::Enumerable + + def each(&block) + session.read(self).each(&block) + end + + def first + session.read(self).first + end + end + include Enumerable + + module Operations + def join(other = nil) + case other + when String + Join.new(other, self) + when Relation + JoinOperation.new("INNER JOIN", self, other) + else + self + end + end + + def outer_join(other) + JoinOperation.new("LEFT OUTER JOIN", self, other) + end + + def [](index) + case index + when Symbol, String + attribute_for_name(index) + when Attribute, Expression + attribute_for_attribute(index) + end + end + + def select(*predicates) + predicates.all?(&:blank?) ? self : Selection.new(self, *predicates) + end + + def project(*attributes) + attributes.all?(&:blank?) ? self : Projection.new(self, *attributes) + end + + def as(aliaz = nil) + aliaz.blank?? self : Alias.new(self, aliaz) + end + + def order(*attributes) + attributes.all?(&:blank?) ? self : Order.new(self, *attributes) + end + + def take(taken = nil) + taken.blank?? self : Take.new(self, taken) + end + + def skip(skipped = nil) + skipped.blank?? self : Skip.new(self, skipped) + end + + def group(*groupings) + groupings.all?(&:blank?) ? self : Grouping.new(self, *groupings) + end + + module Writes + def insert(record) + session.create Insertion.new(self, record); self + end + + def update(assignments) + session.update Update.new(self, assignments); self + end + + def delete + session.delete Deletion.new(self); self + end + end + include Writes + + JoinOperation = Struct.new(:join_sql, :relation1, :relation2) do + def on(*predicates) + Join.new(join_sql, relation1, relation2, *predicates) + end + end + end + include Operations + + module Externalizable + def aggregation? + false + end + + def alias? + false + end + end + include Externalizable + + def to_sql(formatter = Sql::SelectStatement.new(engine)) + formatter.select [ + "SELECT #{attributes.collect { |a| a.to_sql(Sql::SelectClause.new(engine)) }.join(', ')}", + "FROM #{table_sql}", + (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 { |o| o.to_sql(Sql::OrderClause.new(engine)) }.join(', ')}" unless orders.blank? ), + ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), + ("LIMIT #{taken}" unless taken.blank? ), + ("OFFSET #{skipped}" unless skipped.blank? ) + ].compact.join("\n"), self.alias + end + alias_method :to_s, :to_sql + + def inclusion_predicate_sql + "IN" + end + + 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 } + end + end + include AttributeAccessors + + def bind(relation) + self + end + + def format(object) + object.to_sql(Sql::WhereCondition.new(engine)) + end + + def attributes; [] end + def selects; [] end + def orders; [] end + def inserts; [] end + def groupings; [] end + def joins; nil end + def taken; nil end + def skipped; nil end + def alias; nil end + end +end \ No newline at end of file diff --git a/lib/arel/relations/selection.rb b/lib/arel/relations/selection.rb new file mode 100644 index 0000000000..38a40e1b76 --- /dev/null +++ b/lib/arel/relations/selection.rb @@ -0,0 +1,21 @@ +module Arel + class Selection < Compound + attr_reader :predicate + + def initialize(relation, *predicates) + predicate = predicates.shift + @relation = predicates.empty?? relation : Selection.new(relation, *predicates) + @predicate = predicate.bind(@relation) + end + + def ==(other) + self.class == other.class and + relation == other.relation and + predicate == other.predicate + end + + def selects + relation.selects + [predicate] + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/skip.rb b/lib/arel/relations/skip.rb new file mode 100644 index 0000000000..f17e439ebf --- /dev/null +++ b/lib/arel/relations/skip.rb @@ -0,0 +1,15 @@ +module Arel + class Skip < Compound + attr_reader :skipped + + def initialize(relation, skipped) + @relation, @skipped = relation, skipped + end + + def ==(other) + self.class == other.class and + relation == other.relation and + skipped == other.skipped + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/table.rb b/lib/arel/relations/table.rb new file mode 100644 index 0000000000..cdc03df623 --- /dev/null +++ b/lib/arel/relations/table.rb @@ -0,0 +1,48 @@ +module Arel + class Table < Relation + cattr_accessor :engine + attr_reader :name, :engine + + hash_on :name + + def initialize(name, engine = Table.engine) + @name, @engine = name.to_s, engine + end + + def attributes + @attributes ||= columns.collect do |column| + Attribute.new(self, column.name.to_sym) + end + end + + def prefix_for(attribute) + self[attribute] and name + end + + def column_for(attribute) + self[attribute] and columns.detect { |c| c.name == attribute.name.to_s } + end + + def ==(other) + self.class == other.class and + name == other.name + end + + def columns + @columns ||= engine.columns(name, "#{name} Columns") + end + + def reset + @attributes = @columns = nil + end + + def table_sql + engine.quote_table_name(name) + end + + private + def qualifications + attributes.zip(attributes.collect(&:qualified_name)).to_hash + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/take.rb b/lib/arel/relations/take.rb new file mode 100644 index 0000000000..d2743d7a6e --- /dev/null +++ b/lib/arel/relations/take.rb @@ -0,0 +1,15 @@ +module Arel + class Take < Compound + attr_reader :taken + + def initialize(relation, taken) + @relation, @taken = relation, taken + end + + def ==(other) + self.class == other.class and + relation == other.relation and + taken == other.taken + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/update.rb b/lib/arel/relations/update.rb new file mode 100644 index 0000000000..f1f6776f15 --- /dev/null +++ b/lib/arel/relations/update.rb @@ -0,0 +1,30 @@ +module Arel + class Update < Writing + attr_reader :assignments + + def initialize(relation, assignments) + @relation, @assignments = relation, assignments.bind(relation) + end + + def to_sql(formatter = nil) + [ + "UPDATE #{table_sql} SET", + assignments.collect do |attribute, value| + "#{value.format(attribute)} = #{attribute.format(value)}" + end.join(",\n"), + ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ), + ("LIMIT #{taken}" unless taken.blank? ) + ].join("\n") + end + + def call(connection = engine.connection) + connection.update(to_sql) + end + + def ==(other) + self.class == other.class and + relation == other.relation and + assignments == other.assignments + end + end +end \ No newline at end of file diff --git a/lib/arel/relations/writing.rb b/lib/arel/relations/writing.rb new file mode 100644 index 0000000000..b871e5a520 --- /dev/null +++ b/lib/arel/relations/writing.rb @@ -0,0 +1,4 @@ +module Arel + class Writing < Compound + end +end \ No newline at end of file diff --git a/lib/arel/sessions/session.rb b/lib/arel/sessions/session.rb new file mode 100644 index 0000000000..becf23b8b6 --- /dev/null +++ b/lib/arel/sessions/session.rb @@ -0,0 +1,56 @@ +require 'singleton' + +module Arel + class Session + class << self + attr_accessor :instance + alias_method :manufacture, :new + + def start + if @started + yield + else + begin + @started = true + @instance = manufacture + metaclass.send :alias_method, :new, :instance + yield + ensure + metaclass.send :alias_method, :new, :manufacture + @started = false + end + end + end + end + + module CRUD + def create(insert) + insert.call(insert.engine.connection) + end + + def read(select) + @read ||= Hash.new do |hash, select| + hash[select] = select.call(select.engine.connection) + end + @read[select] + end + + def update(update) + update.call(update.engine.connection) + end + + def delete(delete) + delete.call(delete.engine.connection) + end + end + include CRUD + + module Transactions + end + include Transactions + + module UnitOfWork + end + include UnitOfWork + end +end \ No newline at end of file diff --git a/lib/arel/sql.rb b/lib/arel/sql.rb new file mode 100644 index 0000000000..b6d646c047 --- /dev/null +++ b/lib/arel/sql.rb @@ -0,0 +1,97 @@ +module Arel + module Sql + module Quoting + delegate :quote_table_name, :quote_column_name, :quote, :to => :engine + end + + class Formatter + attr_reader :engine + + include Quoting + + def initialize(engine) + @engine = engine + end + end + + class SelectClause < 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 + + def select(select_sql, aliaz) + "(#{select_sql})" + (aliaz ? " AS #{quote(aliaz)}" : "") + end + + def value(value) + value + end + end + + class PassThrough < Formatter + def value(value) + value + end + end + + class WhereClause < PassThrough + end + + class OrderClause < PassThrough + def attribute(relation_name, attribute_name, aliaz) + "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + end + end + + class WhereCondition < Formatter + def attribute(relation_name, attribute_name, aliaz) + "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + end + + def value(value) + value.to_sql(self) + end + + def scalar(value, column = nil) + quote(value, column) + end + + def select(select_sql, aliaz) + "(#{select_sql})" + end + end + + class SelectStatement < Formatter + def select(select_sql, aliaz) + select_sql + end + end + + class TableReference < Formatter + def select(select_sql, aliaz) + "(#{select_sql}) AS #{quote_table_name(aliaz)}" + end + end + + class Attribute < WhereCondition + def initialize(attribute) + @attribute, @engine = attribute, attribute.engine + end + + def scalar(scalar) + quote(scalar, @attribute.column) + end + + def array(array) + "(" + array.collect { |e| e.to_sql(self) }.join(', ') + ")" + end + + def range(left, right) + "#{left} AND #{right}" + end + end + + 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 deleted file mode 100644 index 56c568b078..0000000000 --- a/spec/active_relation/unit/predicates/binary_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Binary do - before do - @relation = Table.new(:users) - @attribute1 = @relation[:id] - @attribute2 = @relation[:name] - 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 - before do - @value = "1-asdf" - end - - 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 - end - - describe '==' do - it "obtains if attribute1 and attribute2 are identical" do - Binary.new(@attribute1, @attribute2).should == Binary.new(@attribute1, @attribute2) - Binary.new(@attribute1, @attribute2).should_not == Binary.new(@attribute1, @attribute1) - end - - it "obtains if the concrete type of the predicates are identical" do - Binary.new(@attribute1, @attribute2).should == Binary.new(@attribute1, @attribute2) - Binary.new(@attribute1, @attribute2).should_not == ConcreteBinary.new(@attribute1, @attribute2) - end - end - - describe '#bind' do - before do - @another_relation = Table.new(:photos) - end - - it "descends" do - ConcreteBinary.new(@attribute1, @attribute2).bind(@another_relation). \ - should == ConcreteBinary.new(@attribute1.bind(@another_relation), @attribute2.bind(@another_relation)) - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/predicates/equality_spec.rb b/spec/active_relation/unit/predicates/equality_spec.rb deleted file mode 100644 index 5415b35925..0000000000 --- a/spec/active_relation/unit/predicates/equality_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Equality do - before do - @relation1 = Table.new(:users) - @relation2 = Table.new(:photos) - @attribute1 = @relation1[:id] - @attribute2 = @relation2[:user_id] - end - - describe '==' do - it "obtains if attribute1 and attribute2 are identical" do - Equality.new(@attribute1, @attribute2).should == Equality.new(@attribute1, @attribute2) - Equality.new(@attribute1, @attribute2).should_not == Equality.new(@attribute1, @attribute1) - end - - it "obtains if the concrete type of the predicates are identical" do - Equality.new(@attribute1, @attribute2).should_not == Binary.new(@attribute1, @attribute2) - end - - it "is commutative on the attributes" do - Equality.new(@attribute1, @attribute2).should == Equality.new(@attribute2, @attribute1) - end - end - - describe '#to_sql' do - describe 'when relating to a non-nil value' do - it "manufactures an equality predicate" do - Equality.new(@attribute1, @attribute2).to_sql.should be_like(" - `users`.`id` = `photos`.`user_id` - ") - end - end - - describe 'when relation to a nil value' do - before do - @nil = nil - end - - it "manufactures an is null predicate" do - Equality.new(@attribute1, @nil).to_sql.should be_like(" - `users`.`id` IS NULL - ") - end - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/predicates/in_spec.rb b/spec/active_relation/unit/predicates/in_spec.rb deleted file mode 100644 index 4ca7867dd0..0000000000 --- a/spec/active_relation/unit/predicates/in_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe In do - before do - @relation = Table.new(:users) - @attribute = @relation[:id] - end - - describe '#to_sql' do - describe 'when relating to an array' do - describe 'when the array\'s elements are the same type as the attribute' do - before do - @array = [1, 2, 3] - end - - it 'manufactures sql with a comma separated list' do - In.new(@attribute, @array).to_sql.should be_like(" - `users`.`id` IN (1, 2, 3) - ") - end - end - - describe 'when the array\'s elements are not same type as the attribute' do - before do - @array = ['1-asdf', 2, 3] - end - - it 'formats values in the array as the type of the attribute' do - In.new(@attribute, @array).to_sql.should be_like(" - `users`.`id` IN (1, 2, 3) - ") - end - end - end - - describe 'when relating to a range' do - before do - @range = 1..2 - end - - it 'manufactures sql with a between' do - In.new(@attribute, @range).to_sql.should be_like(" - `users`.`id` BETWEEN 1 AND 2 - ") - end - end - - describe 'when relating to a relation' do - it 'manufactures sql with a subselect' do - In.new(@attribute, @relation).to_sql.should be_like(" - `users`.`id` IN (SELECT `users`.`id`, `users`.`name` FROM `users`) - ") - end - 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 deleted file mode 100644 index 471ae91b5b..0000000000 --- a/spec/active_relation/unit/primitives/attribute_spec.rb +++ /dev/null @@ -1,177 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Attribute do - before do - @relation = Table.new(:users) - @attribute = Attribute.new(@relation, :id) - end - - describe Attribute::Transformations do - describe '#as' do - it "manufactures an aliased attributed" do - @attribute.as(:alias).should == Attribute.new(@relation, @attribute.name, :alias => :alias, :ancestor => @attribute) - end - end - - describe '#bind' do - it "manufactures an attribute with the relation bound and self as an ancestor" do - derived_relation = @relation.select(@relation[:id].eq(1)) - @attribute.bind(derived_relation).should == Attribute.new(derived_relation, @attribute.name, :ancestor => @attribute) - end - - it "returns self if the substituting to the same relation" do - @attribute.bind(@relation).should == @attribute - end - end - - describe '#to_attribute' do - it "returns self" do - @attribute.to_attribute.should == @attribute - end - end - end - - describe '#column' do - it "returns the corresponding column in the relation" do - @attribute.column.should == @relation.column_for(@attribute) - end - end - - describe '#qualified_name' do - it "manufactures an attribute name prefixed with the relation's name" do - @attribute.qualified_name.should == "#{@relation.prefix_for(@attribute)}.id" - end - end - - describe '#engine' do - it "delegates to its relation" do - Attribute.new(@relation, :id).engine.should == @relation.engine - end - end - - describe Attribute::Congruence do - describe '=~' do - - it "obtains if the attributes are identical" do - Attribute.new(@relation, :name).should =~ Attribute.new(@relation, :name) - end - - it "obtains if the attributes have an overlapping history" do - Attribute.new(@relation, :name, :ancestor => Attribute.new(@relation, :name)).should =~ Attribute.new(@relation, :name) - 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 - describe 'for a simple attribute' do - it "manufactures sql with an alias" do - @attribute.to_sql.should be_like("`users`.`id`") - end - end - - describe 'for an attribute in a join relation where the source relation is aliased' do - before do - another_relation = Table.new(:photos) - @join_with_alias = @relation.as(:alias).join(another_relation).on(@relation[:id].eq(another_relation[:user_id])) - end - - it "manufactures sql with an alias" do - @join_with_alias[@attribute].to_sql.should be_like("`alias`.`id`") - end - end - end - - describe Attribute::Predications do - before do - @attribute = Attribute.new(@relation, :name) - end - - describe '#eq' do - it "manufactures an equality predicate" do - @attribute.eq('name').should == Equality.new(@attribute, 'name') - end - end - - describe '#lt' do - it "manufactures a less-than predicate" do - @attribute.lt(10).should == LessThan.new(@attribute, 10) - end - end - - describe '#lteq' do - it "manufactures a less-than or equal-to predicate" do - @attribute.lteq(10).should == LessThanOrEqualTo.new(@attribute, 10) - end - end - - describe '#gt' do - it "manufactures a greater-than predicate" do - @attribute.gt(10).should == GreaterThan.new(@attribute, 10) - end - end - - describe '#gteq' do - it "manufactures a greater-than or equal-to predicate" do - @attribute.gteq(10).should == GreaterThanOrEqualTo.new(@attribute, 10) - end - end - - describe '#matches' do - it "manufactures a match predicate" do - @attribute.matches(/.*/).should == Match.new(@attribute, /.*/) - end - end - - describe '#in' do - it "manufactures an in predicate" do - @attribute.in(1..30).should == In.new(@attribute, (1..30)) - end - end - end - - describe Attribute::Expressions do - before do - @attribute = Attribute.new(@relation, :name) - end - - describe '#count' do - it "manufactures a count Expression" do - @attribute.count.should == Expression.new(@attribute, "COUNT") - end - end - - describe '#sum' do - it "manufactures a sum Expression" do - @attribute.sum.should == Expression.new(@attribute, "SUM") - end - end - - describe '#maximum' do - it "manufactures a maximum Expression" do - @attribute.maximum.should == Expression.new(@attribute, "MAX") - end - end - - describe '#minimum' do - it "manufactures a minimum Expression" do - @attribute.minimum.should == Expression.new(@attribute, "MIN") - end - end - - describe '#average' do - it "manufactures an average Expression" do - @attribute.average.should == Expression.new(@attribute, "AVG") - end - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/primitives/expression_spec.rb b/spec/active_relation/unit/primitives/expression_spec.rb deleted file mode 100644 index c15b073ba3..0000000000 --- a/spec/active_relation/unit/primitives/expression_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Expression do - before do - @relation = Table.new(:users) - @attribute = @relation[:id] - end - - describe Expression::Transformations do - before do - @expression = Expression.new(@attribute, "COUNT") - end - - describe '#bind' do - it "manufactures an attribute with a rebound relation and self as the ancestor" do - derived_relation = @relation.select(@relation[:id].eq(1)) - @expression.bind(derived_relation).should == Expression.new(@attribute.bind(derived_relation), "COUNT", nil, @expression) - end - - it "returns self if the substituting to the same relation" do - @expression.bind(@relation).should == @expression - end - end - - describe '#as' do - it "manufactures an aliased expression" do - @expression.as(:alias).should == Expression.new(@attribute, "COUNT", :alias, @expression) - end - end - - describe '#to_attribute' do - it "manufactures an attribute with the expression as an ancestor" do - @expression.to_attribute.should == Attribute.new(@expression.relation, @expression.alias, :ancestor => @expression) - end - end - end - - describe '#to_sql' do - it "manufactures sql with the expression and alias" do - Expression.new(@attribute, "COUNT", :alias).to_sql.should == "COUNT(`users`.`id`) AS `alias`" - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/primitives/value_spec.rb b/spec/active_relation/unit/primitives/value_spec.rb deleted file mode 100644 index ab632ef987..0000000000 --- a/spec/active_relation/unit/primitives/value_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Value do - before do - @relation = Table.new(:users) - end - - describe '#to_sql' do - it "appropriately quotes the value" do - Value.new(1, @relation).to_sql.should be_like('1') - Value.new('asdf', @relation).to_sql.should be_like("'asdf'") - end - end - - describe '#format' do - it "returns the sql of the provided object" do - Value.new(1, @relation).format(@relation[:id]).should == @relation[:id].to_sql - end - end - - describe '#bind' do - it "manufactures a new value whose relation is the provided relation" do - Value.new(1, @relation).bind(another_relation = Table.new(:photos)).should == Value.new(1, another_relation) - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/alias_spec.rb b/spec/active_relation/unit/relations/alias_spec.rb deleted file mode 100644 index 4a2144db82..0000000000 --- a/spec/active_relation/unit/relations/alias_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Alias do - before do - @relation = Table.new(:users) - @alias_relation = Alias.new(@relation, :foo) - end - - describe '#alias' do - it "returns the alias" do - @alias_relation.alias.should == :foo - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/compound_spec.rb b/spec/active_relation/unit/relations/compound_spec.rb deleted file mode 100644 index 54a89f3f57..0000000000 --- a/spec/active_relation/unit/relations/compound_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Compound do - before do - class ConcreteCompound < Compound - def initialize(relation) - @relation = relation - end - - def ==(other) - true - end - end - @relation = Table.new(:users) - @compound_relation = ConcreteCompound.new(@relation) - end - - describe '#attributes' do - it 'manufactures attributes associated with the compound relation' do - @compound_relation.attributes.should == @relation.attributes.collect { |a| a.bind(@compound_relation) } - end - end - - describe 'hashing' do - it 'implements hash equality' do - ConcreteCompound.new(@relation).should hash_the_same_as(ConcreteCompound.new(@relation)) - 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 deleted file mode 100644 index 08f7bc03bb..0000000000 --- a/spec/active_relation/unit/relations/deletion_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Deletion do - before do - @relation = Table.new(:users) - end - - describe '#to_sql' do - it 'manufactures sql deleting a table relation' do - Deletion.new(@relation).to_sql.should be_like(" - DELETE - FROM `users` - ") - end - - it 'manufactures sql deleting a selection relation' do - Deletion.new(@relation.select(@relation[:id].eq(1))).to_sql.should be_like(" - DELETE - FROM `users` - WHERE `users`.`id` = 1 - ") - end - - it "manufactures sql deleting a ranged relation" do - Deletion.new(@relation.take(1)).to_sql.should be_like(" - DELETE - FROM `users` - LIMIT 1 - ") - 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/grouping_spec.rb b/spec/active_relation/unit/relations/grouping_spec.rb deleted file mode 100644 index 4b5badbb8b..0000000000 --- a/spec/active_relation/unit/relations/grouping_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Grouping do - before do - @relation = Table.new(:users) - @attribute = @relation[:id] - end - - describe '#to_sql' do - describe 'when given a predicate' do - it "manufactures sql with where clause conditions" do - Grouping.new(@relation, @attribute).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - GROUP BY `users`.`id` - ") - end - end - - describe 'when given a string' do - it "passes the string through to the where clause" do - pending 'it should not quote asdf' - Grouping.new(@relation, 'asdf').to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - GROUP BY asdf - ") - end - 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 deleted file mode 100644 index 543e2d51a9..0000000000 --- a/spec/active_relation/unit/relations/insertion_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Insertion do - before do - @relation = Table.new(:users) - end - - describe '#to_sql' do - it 'manufactures sql inserting data when given multiple rows' do - pending 'it should insert multiple rows' - @insertion = Insertion.new(@relation, [@relation[:name] => "nick", @relation[:name] => "bryan"]) - - @insertion.to_sql.should be_like(" - INSERT - INTO `users` - (`users`.`name`) VALUES ('nick'), ('bryan') - ") - end - - it 'manufactures sql inserting data when given multiple values' do - @insertion = Insertion.new(@relation, @relation[:id] => "1", @relation[:name] => "nick") - - @insertion.to_sql.should be_like(" - INSERT - INTO `users` - (`users`.`id`, `users`.`name`) VALUES (1, 'nick') - ") - end - - describe 'when given values whose types correspond to the types of the attributes' do - before do - @insertion = Insertion.new(@relation, @relation[:name] => "nick") - end - - it 'manufactures sql inserting data' do - @insertion.to_sql.should be_like(" - INSERT - INTO `users` - (`users`.`name`) VALUES ('nick') - ") - end - end - - describe 'when given values whose types differ from from the types of the attributes' do - before do - @insertion = Insertion.new(@relation, @relation[:id] => '1-asdf') - end - - it 'manufactures sql inserting data' do - @insertion.to_sql.should be_like(" - INSERT - INTO `users` - (`users`.`id`) VALUES (1) - ") - end - end - end - - describe '#call' do - before do - @insertion = Insertion.new(@relation, @relation[:name] => "nick") - end - - 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 deleted file mode 100644 index c1a13cee6e..0000000000 --- a/spec/active_relation/unit/relations/join_spec.rb +++ /dev/null @@ -1,169 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Join do - before do - @relation1 = Table.new(:users) - @relation2 = Table.new(:photos) - @predicate = @relation1[:id].eq(@relation2[:user_id]) - end - - describe '==' do - before do - @another_predicate = @relation1[:id].eq(1) - @another_relation = Table.new(:cameras) - end - - it 'obtains if the two relations and the predicate are identical' do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).should == Join.new("INNER JOIN", @relation1, @relation2, @predicate) - Join.new("INNER JOIN", @relation1, @relation2, @predicate).should_not == Join.new("INNER JOIN", @relation1, @another_relation, @predicate) - Join.new("INNER JOIN", @relation1, @relation2, @predicate).should_not == Join.new("INNER JOIN", @relation1, @relation2, @another_predicate) - end - - it 'is commutative on the relations' do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).should == Join.new("INNER JOIN", @relation2, @relation1, @predicate) - end - end - - describe 'hashing' do - it 'implements hash equality' do - Join.new("INNER JOIN", @relation1, @relation2, @predicate) \ - .should hash_the_same_as(Join.new("INNER JOIN", @relation1, @relation2, @predicate)) - end - end - - describe '#prefix_for' do - 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 - - describe '#engine' do - it "delegates to a relation's engine" do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).engine.should == @relation1.engine - end - end - - describe 'when joining simple relations' do - describe '#attributes' do - it 'combines the attributes of the two relations' do - join = Join.new("INNER JOIN", @relation1, @relation2, @predicate) - join.attributes.should == - (@relation1.attributes + @relation2.attributes).collect { |a| a.bind(join) } - end - end - - describe '#to_sql' do - it 'manufactures sql joining the two tables on the predicate' do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` - FROM `users` - INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` - ") - end - - it 'manufactures sql joining the two tables, merging any selects' do - Join.new("INNER JOIN", @relation1.select(@relation1[:id].eq(1)), - @relation2.select(@relation2[:id].eq(2)), @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` - FROM `users` - INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` - WHERE `users`.`id` = 1 - AND `photos`.`id` = 2 - ") - end - end - end - - describe 'when joining aggregated relations' do - before do - @aggregation = @relation2 \ - .group(@relation2[:user_id]) \ - .project(@relation2[:user_id], @relation2[:id].count.as(:cnt)) \ - .as('photo_count') - end - - describe '#attributes' do - it 'it transforms aggregate expressions into attributes' do - join_with_aggregation = Join.new("INNER JOIN", @relation1, @aggregation, @predicate) - join_with_aggregation.attributes.should == - (@relation1.attributes + @aggregation.attributes).collect(&:to_attribute).collect { |a| a.bind(join_with_aggregation) } - end - end - - describe '#to_sql' do - describe 'with the aggregation on the right' do - it 'manufactures sql joining the left table to a derived table' do - Join.new("INNER JOIN", @relation1, @aggregation, @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` - FROM `users` - INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` - ON `users`.`id` = `photo_count`.`user_id` - ") - end - end - - describe 'with the aggregation on the left' do - it 'manufactures sql joining the right table to a derived table' do - Join.new("INNER JOIN", @aggregation, @relation1, @predicate).to_sql.should be_like(" - SELECT `photo_count`.`user_id`, `photo_count`.`cnt`, `users`.`id`, `users`.`name` - FROM (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` - INNER JOIN `users` - ON `users`.`id` = `photo_count`.`user_id` - ") - end - end - - it "keeps selects on the aggregation within the derived table" do - Join.new("INNER JOIN", @relation1, @aggregation.select(@aggregation[:user_id].eq(1)), @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` - FROM `users` - INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` WHERE `photos`.`user_id` = 1 GROUP BY `photos`.`user_id`) AS `photo_count` - ON `users`.`id` = `photo_count`.`user_id` - ") - end - end - end - - describe 'when joining aliased relations' do - it 'aliases the table and attributes properly' do - aliased_relation = @relation1.as(:alias) - @relation1.join(aliased_relation).on(@relation1[:id].eq(aliased_relation[:id])).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `alias`.`id`, `alias`.`name` - FROM `users` - INNER JOIN `users` AS `alias` - ON `users`.`id` = `alias`.`id` - ") - end - end - - describe 'when joining with a string' do - it "passes the string through to the where clause" do - Join.new("INNER JOIN asdf ON fdsa", @relation1).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - INNER JOIN asdf ON fdsa - ") - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/order_spec.rb b/spec/active_relation/unit/relations/order_spec.rb deleted file mode 100644 index e8a2f4c227..0000000000 --- a/spec/active_relation/unit/relations/order_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Order do - before do - @relation = Table.new(:users) - @attribute = @relation[:id] - end - - describe '#initialize' do - before do - @another_attribtue = @relation[:name] - end - - it "manufactures nested Order relations if multiple predicates are provided" do - Order.new(@relation, @predicate, @another_attribute). \ - should == Order.new(Order.new(@relation, @another_attribute), @predicate) - end - end - - describe '#to_sql' do - describe "when given an attribute" do - it "manufactures sql with an order clause populated by the attribute" do - Order.new(@relation, @attribute).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - ORDER BY `users`.`id` - ") - end - end - - describe "when given multiple attributes" do - before do - @another_attribute = @relation[:name] - end - - it "manufactures sql with an order clause populated by comma-separated attributes" do - Order.new(@relation, @attribute, @another_attribute).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - ORDER BY `users`.`id`, `users`.`name` - ") - end - end - - describe "when given a string" do - before do - @string = "asdf" - end - - it "passes the string through to the order clause" do - Order.new(@relation, @string).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - ORDER BY asdf - ") - end - end - - describe "when ordering an ordered relation" do - before do - @ordered_relation = Order.new(@relation, @attribute) - @another_attribute = @relation[:name] - end - - it "manufactures sql with an order clause populated by comma-separated attributes" do - Order.new(@ordered_relation, @another_attribute).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - ORDER BY `users`.`id`, `users`.`name` - ") - end - end - end - end -end - \ No newline at end of file diff --git a/spec/active_relation/unit/relations/projection_spec.rb b/spec/active_relation/unit/relations/projection_spec.rb deleted file mode 100644 index 5936cbc45f..0000000000 --- a/spec/active_relation/unit/relations/projection_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Projection do - before do - @relation = Table.new(:users) - @attribute = @relation[:id] - end - - describe '#attributes' do - before do - @projection = Projection.new(@relation, @attribute) - end - - it "manufactures attributes associated with the projection relation" do - @projection.attributes.should == [@attribute].collect { |a| a.bind(@projection) } - end - end - - describe '==' do - before do - @another_relation = Table.new(:photos) - @another_attribute = @relation[:name] - end - - it "obtains if the relations and attributes are identical" do - Projection.new(@relation, @attribute).should == Projection.new(@relation, @attribute) - Projection.new(@relation, @attribute).should_not == Projection.new(@another_relation, @attribute) - Projection.new(@relation, @attribute).should_not == Projection.new(@relation, @another_attribute) - end - end - - describe '#to_sql' do - describe 'when given an attribute' do - it "manufactures sql with a limited select clause" do - Projection.new(@relation, @attribute).to_sql.should be_like(" - SELECT `users`.`id` - FROM `users` - ") - end - end - - describe 'when given a relation' do - before do - @scalar_relation = Projection.new(@relation, @relation[:name]) - end - - it "manufactures sql with scalar selects" do - Projection.new(@relation, @scalar_relation).to_sql.should be_like(" - SELECT (SELECT `users`.`name` FROM `users`) FROM `users` - ") - end - end - - describe 'when given a string' do - it "passes the string through to the select clause" do - Projection.new(@relation, 'asdf').to_sql.should be_like(" - SELECT asdf FROM `users` - ") - end - end - end - - describe Projection::Externalizable do - describe '#aggregation?' do - describe 'when the projections are attributes' do - it 'returns false' do - Projection.new(@relation, @attribute).should_not be_aggregation - end - end - - describe 'when the projections include an aggregation' do - it "obtains" do - Projection.new(@relation, @attribute.sum).should be_aggregation - end - end - 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 deleted file mode 100644 index c40974e136..0000000000 --- a/spec/active_relation/unit/relations/relation_spec.rb +++ /dev/null @@ -1,215 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Relation do - before do - @relation = Table.new(:users) - @attribute1 = @relation[:id] - @attribute2 = @relation[:name] - end - - describe '[]' do - describe 'when given an', Attribute do - it "return the attribute congruent to the provided attribute" do - @relation[@attribute1].should == @attribute1 - end - end - - describe 'when given a', Symbol, String do - it "returns the attribute with the same name, if it exists" do - @relation[:id].should == @attribute1 - @relation['id'].should == @attribute1 - @relation[:does_not_exist].should be_nil - end - end - end - - describe Relation::Externalizable do - describe '#aggregation?' do - it "returns false" do - @relation.should_not be_aggregation - end - end - - describe '#alias?' do - it "returns false" do - @relation.should_not be_alias - end - end - end - - describe Relation::Operations do - describe 'joins' do - before do - @predicate = @relation[:id].eq(@relation[:id]) - end - - describe '#join' do - describe 'when given a relation' do - it "manufactures an inner join operation between those two relations" do - @relation.join(@relation).on(@predicate). \ - should == Join.new("INNER JOIN", @relation, @relation, @predicate) - end - end - - describe "when given a string" do - it "manufactures a join operation with the string passed through" do - @relation.join(arbitrary_string = "ASDF").should == Join.new(arbitrary_string, @relation) - end - end - - describe "when given something blank" do - it "returns self" do - @relation.join.should == @relation - end - end - end - - describe '#outer_join' do - it "manufactures a left outer join operation between those two relations" do - @relation.outer_join(@relation).on(@predicate). \ - should == Join.new("LEFT OUTER JOIN", @relation, @relation, @predicate) - end - end - end - - describe '#project' do - it "manufactures a projection relation" do - @relation.project(@attribute1, @attribute2). \ - should == Projection.new(@relation, @attribute1, @attribute2) - end - - describe "when given blank attributes" do - it "returns self" do - @relation.project.should == @relation - end - end - end - - describe '#as' do - it "manufactures an alias relation" do - @relation.as(:paul).should == Alias.new(@relation, :paul) - end - - describe 'when given a blank alias' do - it 'returns self' do - @relation.as.should == @relation - end - end - end - - describe '#select' do - before do - @predicate = Equality.new(@attribute1, @attribute2) - end - - it "manufactures a selection relation" do - @relation.select(@predicate).should == Selection.new(@relation, @predicate) - end - - it "accepts arbitrary strings" do - @relation.select("arbitrary").should == Selection.new(@relation, "arbitrary") - end - - describe 'when given a blank predicate' do - it 'returns self' do - @relation.select.should == @relation - end - end - end - - describe '#order' do - it "manufactures an order relation" do - @relation.order(@attribute1, @attribute2).should == Order.new(@relation, @attribute1, @attribute2) - end - - describe 'when given a blank ordering' do - it 'returns self' do - @relation.order.should == @relation - end - end - end - - describe '#take' do - it "manufactures a take relation" do - @relation.take(5).should == Take.new(@relation, 5) - end - - describe 'when given a blank number of items' do - it 'returns self' do - @relation.take.should == @relation - end - end - end - - describe '#skip' do - it "manufactures a skip relation" do - @relation.skip(4).should == Skip.new(@relation, 4) - end - - describe 'when given a blank number of items' do - it 'returns self' do - @relation.skip.should == @relation - end - end - end - - describe '#group' do - it 'manufactures a group relation' do - @relation.group(@attribute1, @attribute2).should == Grouping.new(@relation, @attribute1, @attribute2) - end - - describe 'when given blank groupings' do - it 'returns self' do - @relation.group.should == @relation - end - end - end - - describe Relation::Operations::Writes do - describe '#delete' do - it 'manufactures a deletion relation' do - Session.start do - mock(Session.new).delete(Deletion.new(@relation)) - @relation.delete.should == @relation - end - end - end - - describe '#insert' do - it 'manufactures an insertion relation' do - Session.start do - record = {@relation[:name] => 'carl'} - mock(Session.new).create(Insertion.new(@relation, record)) - @relation.insert(record).should == @relation - end - end - end - - describe '#update' do - it 'manufactures an update relation' do - Session.start do - assignments = {@relation[:name] => Value.new('bob', @relation)} - mock(Session.new).update(Update.new(@relation, assignments)) - @relation.update(assignments).should == @relation - end - end - end - end - end - - describe Relation::Enumerable do - it "implements enumerable" do - @relation.collect.should == @relation.session.read(@relation) - @relation.first.should == @relation.session.read(@relation).first - 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 - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/selection_spec.rb b/spec/active_relation/unit/relations/selection_spec.rb deleted file mode 100644 index d87e075f98..0000000000 --- a/spec/active_relation/unit/relations/selection_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Selection do - before do - @relation = Table.new(:users) - @predicate = @relation[:id].eq(1) - end - - describe '#initialize' do - before do - @another_predicate = @relation[:name].lt(2) - end - - it "manufactures nested selection relations if multiple predicates are provided" do - Selection.new(@relation, @predicate, @another_predicate). \ - should == Selection.new(Selection.new(@relation, @another_predicate), @predicate) - end - end - - describe '#to_sql' do - describe 'when given a predicate' do - it "manufactures sql with where clause conditions" do - Selection.new(@relation, @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - WHERE `users`.`id` = 1 - ") - end - end - - describe 'when given a string' do - it "passes the string through to the where clause" do - Selection.new(@relation, 'asdf').to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - WHERE asdf - ") - end - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/skip_spec.rb b/spec/active_relation/unit/relations/skip_spec.rb deleted file mode 100644 index 219bfdd80e..0000000000 --- a/spec/active_relation/unit/relations/skip_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Skip do - before do - @relation = Table.new(:users) - @skipped = 4 - end - - describe '#to_sql' do - it "manufactures sql with limit and offset" do - Skip.new(@relation, @skipped).to_s.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - OFFSET #{@skipped} - ") - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/table_spec.rb b/spec/active_relation/unit/relations/table_spec.rb deleted file mode 100644 index 2751d9cb63..0000000000 --- a/spec/active_relation/unit/relations/table_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Table do - before do - @relation = Table.new(:users) - end - - describe '[]' do - describe 'when given a', Symbol do - it "manufactures an attribute if the symbol names an attribute within the relation" do - @relation[:id].should == Attribute.new(@relation, :id) - @relation[:does_not_exist].should be_nil - end - end - - describe 'when given an', Attribute do - it "returns the attribute if the attribute is within the relation" do - @relation[@relation[:id]].should == @relation[:id] - end - - it "returns nil if the attribtue is not within the relation" do - another_relation = Table.new(:photos) - @relation[another_relation[:id]].should be_nil - end - end - - describe 'when given an', Expression do - before do - @expression = @relation[:id].count - end - - it "returns the Expression if the Expression is within the relation" do - @relation[@expression].should be_nil - end - end - end - - describe '#to_sql' do - it "manufactures a simple select query" do - @relation.to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - ") - end - end - - describe '#column_for' do - it "returns the column corresponding to the attribute" do - @relation.column_for(@relation[:id]).should == @relation.columns.detect { |c| c.name == 'id' } - end - end - - describe '#prefix_for' do - it "returns the table name if the relation contains the attribute" do - @relation.prefix_for(@relation[:id]).should == 'users' - @relation.prefix_for(:does_not_exist).should be_nil - end - end - - describe '#attributes' do - it 'manufactures attributes corresponding to columns in the table' do - @relation.attributes.should == [ - Attribute.new(@relation, :id), - Attribute.new(@relation, :name) - ] - end - - describe '#reset' do - it "reloads columns from the database" do - lambda { stub(@relation.engine).columns { [] } }.should_not change { @relation.attributes } - lambda { @relation.reset }.should change { @relation.attributes } - end - end - end - - describe 'hashing' do - it "implements hash equality" do - 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 - - describe '#engine' do - it "defaults to global engine" do - Table.engine = engine = Engine.new - Table.new(:users).engine.should == engine - end - - it "can be specified" do - Table.new(:users, engine = Engine.new).engine.should == engine - end - end - end -end \ No newline at end of file diff --git a/spec/active_relation/unit/relations/take_spec.rb b/spec/active_relation/unit/relations/take_spec.rb deleted file mode 100644 index 8c13732258..0000000000 --- a/spec/active_relation/unit/relations/take_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Take do - before do - @relation = Table.new(:users) - @taken = 4 - end - - describe '#to_sql' do - it "manufactures sql with limit and offset" do - Take.new(@relation, @taken).to_s.should be_like(" - SELECT `users`.`id`, `users`.`name` - FROM `users` - LIMIT #{@taken} - ") - 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 deleted file mode 100644 index 29dd4e12dd..0000000000 --- a/spec/active_relation/unit/relations/update_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Update do - before do - @relation = Table.new(:users) - end - - describe '#to_sql' do - it "manufactures sql updating attributes when given multiple attributes" do - Update.new(@relation, @relation[:id] => 1, @relation[:name] => "nick").to_sql.should be_like(" - UPDATE `users` - SET `users`.`id` = 1, `users`.`name` = 'nick' - ") - end - - it "manufactures sql updating attributes when given a ranged relation" do - Update.new(@relation.take(1), @relation[:name] => "nick").to_sql.should be_like(" - UPDATE `users` - SET `users`.`name` = 'nick' - LIMIT 1 - ") - end - - describe 'when given values whose types correspond to the types of the attributes' do - before do - @update = Update.new(@relation, @relation[:name] => "nick") - end - - it 'manufactures sql updating attributes' do - @update.to_sql.should be_like(" - UPDATE `users` - SET `users`.`name` = 'nick' - ") - end - end - - describe 'when given values whose types differ from from the types of the attributes' do - before do - @update = Update.new(@relation, @relation[:id] => '1-asdf') - end - - it 'manufactures sql updating attributes' do - @update.to_sql.should be_like(" - UPDATE `users` - SET `users`.`id` = 1 - ") - end - end - - describe 'when the relation is a selection' do - before do - @update = Update.new( - @relation.select(@relation[:id].eq(1)), - @relation[:name] => "nick" - ) - end - - it 'manufactures sql updating a selection relation' do - @update.to_sql.should be_like(" - UPDATE `users` - SET `users`.`name` = 'nick' - WHERE `users`.`id` = 1 - ") - end - end - end - - describe '#call' do - before do - @update = Update.new(@relation, @relation[:name] => "nick") - end - - it 'executes an update on the connection' do - 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 deleted file mode 100644 index 5c3b668e1f..0000000000 --- a/spec/active_relation/unit/session/session_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') - -module ActiveRelation - describe Session do - before do - @relation = Table.new(:users) - @session = Session.new - end - - describe '::start' do - describe '::instance' do - it "it is a singleton within the started session" do - Session.start do - Session.new.should == Session.new - end - end - - it "is a singleton across nested sessions" do - Session.start do - outside = Session.new - Session.start do - Session.new.should == outside - end - end - end - - it "manufactures new sessions outside of the started session" do - Session.new.should_not == Session.new - end - end - end - - describe Session::CRUD do - before do - @insert = Insertion.new(@relation, @relation[:name] => 'nick') - @update = Update.new(@relation, @relation[:name] => 'nick') - @delete = Deletion.new(@relation) - @read = @relation - end - - describe '#create' do - it "executes an insertion on the connection" do - mock(@insert).call(@insert.engine.connection) - @session.create(@insert) - end - end - - describe '#read' do - it "executes an selection on the connection" do - mock(@read).call(@read.engine.connection) - @session.read(@read) - end - - it "is memoized" do - 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).call(@update.engine.connection) - @session.update(@update) - end - end - - describe '#delete' do - it "executes a delete on the connection" do - mock(@delete).call(@delete.engine.connection) - @session.delete(@delete) - end - end - end - - describe Session::Transactions do - describe '#begin' do - end - - describe '#end' do - end - end - - describe Session::UnitOfWork do - describe '#flush' do - end - - describe '#clear' do - end - end - end -end \ No newline at end of file diff --git a/spec/arel/unit/predicates/binary_spec.rb b/spec/arel/unit/predicates/binary_spec.rb new file mode 100644 index 0000000000..f39b37d913 --- /dev/null +++ b/spec/arel/unit/predicates/binary_spec.rb @@ -0,0 +1,71 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Binary do + before do + @relation = Table.new(:users) + @attribute1 = @relation[:id] + @attribute2 = @relation[:name] + 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 + before do + @value = "1-asdf" + end + + 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 + end + + describe '==' do + it "obtains if attribute1 and attribute2 are identical" do + Binary.new(@attribute1, @attribute2).should == Binary.new(@attribute1, @attribute2) + Binary.new(@attribute1, @attribute2).should_not == Binary.new(@attribute1, @attribute1) + end + + it "obtains if the concrete type of the predicates are identical" do + Binary.new(@attribute1, @attribute2).should == Binary.new(@attribute1, @attribute2) + Binary.new(@attribute1, @attribute2).should_not == ConcreteBinary.new(@attribute1, @attribute2) + end + end + + describe '#bind' do + before do + @another_relation = Table.new(:photos) + end + + it "descends" do + ConcreteBinary.new(@attribute1, @attribute2).bind(@another_relation). \ + should == ConcreteBinary.new(@attribute1.bind(@another_relation), @attribute2.bind(@another_relation)) + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/predicates/equality_spec.rb b/spec/arel/unit/predicates/equality_spec.rb new file mode 100644 index 0000000000..8a58ba3096 --- /dev/null +++ b/spec/arel/unit/predicates/equality_spec.rb @@ -0,0 +1,49 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Equality do + before do + @relation1 = Table.new(:users) + @relation2 = Table.new(:photos) + @attribute1 = @relation1[:id] + @attribute2 = @relation2[:user_id] + end + + describe '==' do + it "obtains if attribute1 and attribute2 are identical" do + Equality.new(@attribute1, @attribute2).should == Equality.new(@attribute1, @attribute2) + Equality.new(@attribute1, @attribute2).should_not == Equality.new(@attribute1, @attribute1) + end + + it "obtains if the concrete type of the predicates are identical" do + Equality.new(@attribute1, @attribute2).should_not == Binary.new(@attribute1, @attribute2) + end + + it "is commutative on the attributes" do + Equality.new(@attribute1, @attribute2).should == Equality.new(@attribute2, @attribute1) + end + end + + describe '#to_sql' do + describe 'when relating to a non-nil value' do + it "manufactures an equality predicate" do + Equality.new(@attribute1, @attribute2).to_sql.should be_like(" + `users`.`id` = `photos`.`user_id` + ") + end + end + + describe 'when relation to a nil value' do + before do + @nil = nil + end + + it "manufactures an is null predicate" do + Equality.new(@attribute1, @nil).to_sql.should be_like(" + `users`.`id` IS NULL + ") + end + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/predicates/in_spec.rb b/spec/arel/unit/predicates/in_spec.rb new file mode 100644 index 0000000000..797798a77f --- /dev/null +++ b/spec/arel/unit/predicates/in_spec.rb @@ -0,0 +1,58 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe In do + before do + @relation = Table.new(:users) + @attribute = @relation[:id] + end + + describe '#to_sql' do + describe 'when relating to an array' do + describe 'when the array\'s elements are the same type as the attribute' do + before do + @array = [1, 2, 3] + end + + it 'manufactures sql with a comma separated list' do + In.new(@attribute, @array).to_sql.should be_like(" + `users`.`id` IN (1, 2, 3) + ") + end + end + + describe 'when the array\'s elements are not same type as the attribute' do + before do + @array = ['1-asdf', 2, 3] + end + + it 'formats values in the array as the type of the attribute' do + In.new(@attribute, @array).to_sql.should be_like(" + `users`.`id` IN (1, 2, 3) + ") + end + end + end + + describe 'when relating to a range' do + before do + @range = 1..2 + end + + it 'manufactures sql with a between' do + In.new(@attribute, @range).to_sql.should be_like(" + `users`.`id` BETWEEN 1 AND 2 + ") + end + end + + describe 'when relating to a relation' do + it 'manufactures sql with a subselect' do + In.new(@attribute, @relation).to_sql.should be_like(" + `users`.`id` IN (SELECT `users`.`id`, `users`.`name` FROM `users`) + ") + end + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/primitives/attribute_spec.rb b/spec/arel/unit/primitives/attribute_spec.rb new file mode 100644 index 0000000000..ac9afa85c7 --- /dev/null +++ b/spec/arel/unit/primitives/attribute_spec.rb @@ -0,0 +1,177 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Attribute do + before do + @relation = Table.new(:users) + @attribute = Attribute.new(@relation, :id) + end + + describe Attribute::Transformations do + describe '#as' do + it "manufactures an aliased attributed" do + @attribute.as(:alias).should == Attribute.new(@relation, @attribute.name, :alias => :alias, :ancestor => @attribute) + end + end + + describe '#bind' do + it "manufactures an attribute with the relation bound and self as an ancestor" do + derived_relation = @relation.select(@relation[:id].eq(1)) + @attribute.bind(derived_relation).should == Attribute.new(derived_relation, @attribute.name, :ancestor => @attribute) + end + + it "returns self if the substituting to the same relation" do + @attribute.bind(@relation).should == @attribute + end + end + + describe '#to_attribute' do + it "returns self" do + @attribute.to_attribute.should == @attribute + end + end + end + + describe '#column' do + it "returns the corresponding column in the relation" do + @attribute.column.should == @relation.column_for(@attribute) + end + end + + describe '#qualified_name' do + it "manufactures an attribute name prefixed with the relation's name" do + @attribute.qualified_name.should == "#{@relation.prefix_for(@attribute)}.id" + end + end + + describe '#engine' do + it "delegates to its relation" do + Attribute.new(@relation, :id).engine.should == @relation.engine + end + end + + describe Attribute::Congruence do + describe '=~' do + + it "obtains if the attributes are identical" do + Attribute.new(@relation, :name).should =~ Attribute.new(@relation, :name) + end + + it "obtains if the attributes have an overlapping history" do + Attribute.new(@relation, :name, :ancestor => Attribute.new(@relation, :name)).should =~ Attribute.new(@relation, :name) + 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 + describe 'for a simple attribute' do + it "manufactures sql with an alias" do + @attribute.to_sql.should be_like("`users`.`id`") + end + end + + describe 'for an attribute in a join relation where the source relation is aliased' do + before do + another_relation = Table.new(:photos) + @join_with_alias = @relation.as(:alias).join(another_relation).on(@relation[:id].eq(another_relation[:user_id])) + end + + it "manufactures sql with an alias" do + @join_with_alias[@attribute].to_sql.should be_like("`alias`.`id`") + end + end + end + + describe Attribute::Predications do + before do + @attribute = Attribute.new(@relation, :name) + end + + describe '#eq' do + it "manufactures an equality predicate" do + @attribute.eq('name').should == Equality.new(@attribute, 'name') + end + end + + describe '#lt' do + it "manufactures a less-than predicate" do + @attribute.lt(10).should == LessThan.new(@attribute, 10) + end + end + + describe '#lteq' do + it "manufactures a less-than or equal-to predicate" do + @attribute.lteq(10).should == LessThanOrEqualTo.new(@attribute, 10) + end + end + + describe '#gt' do + it "manufactures a greater-than predicate" do + @attribute.gt(10).should == GreaterThan.new(@attribute, 10) + end + end + + describe '#gteq' do + it "manufactures a greater-than or equal-to predicate" do + @attribute.gteq(10).should == GreaterThanOrEqualTo.new(@attribute, 10) + end + end + + describe '#matches' do + it "manufactures a match predicate" do + @attribute.matches(/.*/).should == Match.new(@attribute, /.*/) + end + end + + describe '#in' do + it "manufactures an in predicate" do + @attribute.in(1..30).should == In.new(@attribute, (1..30)) + end + end + end + + describe Attribute::Expressions do + before do + @attribute = Attribute.new(@relation, :name) + end + + describe '#count' do + it "manufactures a count Expression" do + @attribute.count.should == Expression.new(@attribute, "COUNT") + end + end + + describe '#sum' do + it "manufactures a sum Expression" do + @attribute.sum.should == Expression.new(@attribute, "SUM") + end + end + + describe '#maximum' do + it "manufactures a maximum Expression" do + @attribute.maximum.should == Expression.new(@attribute, "MAX") + end + end + + describe '#minimum' do + it "manufactures a minimum Expression" do + @attribute.minimum.should == Expression.new(@attribute, "MIN") + end + end + + describe '#average' do + it "manufactures an average Expression" do + @attribute.average.should == Expression.new(@attribute, "AVG") + end + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/primitives/expression_spec.rb b/spec/arel/unit/primitives/expression_spec.rb new file mode 100644 index 0000000000..8a990231f6 --- /dev/null +++ b/spec/arel/unit/primitives/expression_spec.rb @@ -0,0 +1,45 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Expression do + before do + @relation = Table.new(:users) + @attribute = @relation[:id] + end + + describe Expression::Transformations do + before do + @expression = Expression.new(@attribute, "COUNT") + end + + describe '#bind' do + it "manufactures an attribute with a rebound relation and self as the ancestor" do + derived_relation = @relation.select(@relation[:id].eq(1)) + @expression.bind(derived_relation).should == Expression.new(@attribute.bind(derived_relation), "COUNT", nil, @expression) + end + + it "returns self if the substituting to the same relation" do + @expression.bind(@relation).should == @expression + end + end + + describe '#as' do + it "manufactures an aliased expression" do + @expression.as(:alias).should == Expression.new(@attribute, "COUNT", :alias, @expression) + end + end + + describe '#to_attribute' do + it "manufactures an attribute with the expression as an ancestor" do + @expression.to_attribute.should == Attribute.new(@expression.relation, @expression.alias, :ancestor => @expression) + end + end + end + + describe '#to_sql' do + it "manufactures sql with the expression and alias" do + Expression.new(@attribute, "COUNT", :alias).to_sql.should == "COUNT(`users`.`id`) AS `alias`" + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/primitives/value_spec.rb b/spec/arel/unit/primitives/value_spec.rb new file mode 100644 index 0000000000..87425c780d --- /dev/null +++ b/spec/arel/unit/primitives/value_spec.rb @@ -0,0 +1,28 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Value do + before do + @relation = Table.new(:users) + end + + describe '#to_sql' do + it "appropriately quotes the value" do + Value.new(1, @relation).to_sql.should be_like('1') + Value.new('asdf', @relation).to_sql.should be_like("'asdf'") + end + end + + describe '#format' do + it "returns the sql of the provided object" do + Value.new(1, @relation).format(@relation[:id]).should == @relation[:id].to_sql + end + end + + describe '#bind' do + it "manufactures a new value whose relation is the provided relation" do + Value.new(1, @relation).bind(another_relation = Table.new(:photos)).should == Value.new(1, another_relation) + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/alias_spec.rb b/spec/arel/unit/relations/alias_spec.rb new file mode 100644 index 0000000000..420a91c806 --- /dev/null +++ b/spec/arel/unit/relations/alias_spec.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Alias do + before do + @relation = Table.new(:users) + @alias_relation = Alias.new(@relation, :foo) + end + + describe '#alias' do + it "returns the alias" do + @alias_relation.alias.should == :foo + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/compound_spec.rb b/spec/arel/unit/relations/compound_spec.rb new file mode 100644 index 0000000000..763e447db3 --- /dev/null +++ b/spec/arel/unit/relations/compound_spec.rb @@ -0,0 +1,31 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Compound do + before do + class ConcreteCompound < Compound + def initialize(relation) + @relation = relation + end + + def ==(other) + true + end + end + @relation = Table.new(:users) + @compound_relation = ConcreteCompound.new(@relation) + end + + describe '#attributes' do + it 'manufactures attributes associated with the compound relation' do + @compound_relation.attributes.should == @relation.attributes.collect { |a| a.bind(@compound_relation) } + end + end + + describe 'hashing' do + it 'implements hash equality' do + ConcreteCompound.new(@relation).should hash_the_same_as(ConcreteCompound.new(@relation)) + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/deletion_spec.rb b/spec/arel/unit/relations/deletion_spec.rb new file mode 100644 index 0000000000..f975720d83 --- /dev/null +++ b/spec/arel/unit/relations/deletion_spec.rb @@ -0,0 +1,42 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Deletion do + before do + @relation = Table.new(:users) + end + + describe '#to_sql' do + it 'manufactures sql deleting a table relation' do + Deletion.new(@relation).to_sql.should be_like(" + DELETE + FROM `users` + ") + end + + it 'manufactures sql deleting a selection relation' do + Deletion.new(@relation.select(@relation[:id].eq(1))).to_sql.should be_like(" + DELETE + FROM `users` + WHERE `users`.`id` = 1 + ") + end + + it "manufactures sql deleting a ranged relation" do + Deletion.new(@relation.take(1)).to_sql.should be_like(" + DELETE + FROM `users` + LIMIT 1 + ") + 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/arel/unit/relations/grouping_spec.rb b/spec/arel/unit/relations/grouping_spec.rb new file mode 100644 index 0000000000..bef3566896 --- /dev/null +++ b/spec/arel/unit/relations/grouping_spec.rb @@ -0,0 +1,33 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Grouping do + before do + @relation = Table.new(:users) + @attribute = @relation[:id] + end + + describe '#to_sql' do + describe 'when given a predicate' do + it "manufactures sql with where clause conditions" do + Grouping.new(@relation, @attribute).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + GROUP BY `users`.`id` + ") + end + end + + describe 'when given a string' do + it "passes the string through to the where clause" do + pending 'it should not quote asdf' + Grouping.new(@relation, 'asdf').to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + GROUP BY asdf + ") + end + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/insertion_spec.rb b/spec/arel/unit/relations/insertion_spec.rb new file mode 100644 index 0000000000..10b70a2036 --- /dev/null +++ b/spec/arel/unit/relations/insertion_spec.rb @@ -0,0 +1,71 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Insertion do + before do + @relation = Table.new(:users) + end + + describe '#to_sql' do + it 'manufactures sql inserting data when given multiple rows' do + pending 'it should insert multiple rows' + @insertion = Insertion.new(@relation, [@relation[:name] => "nick", @relation[:name] => "bryan"]) + + @insertion.to_sql.should be_like(" + INSERT + INTO `users` + (`users`.`name`) VALUES ('nick'), ('bryan') + ") + end + + it 'manufactures sql inserting data when given multiple values' do + @insertion = Insertion.new(@relation, @relation[:id] => "1", @relation[:name] => "nick") + + @insertion.to_sql.should be_like(" + INSERT + INTO `users` + (`users`.`id`, `users`.`name`) VALUES (1, 'nick') + ") + end + + describe 'when given values whose types correspond to the types of the attributes' do + before do + @insertion = Insertion.new(@relation, @relation[:name] => "nick") + end + + it 'manufactures sql inserting data' do + @insertion.to_sql.should be_like(" + INSERT + INTO `users` + (`users`.`name`) VALUES ('nick') + ") + end + end + + describe 'when given values whose types differ from from the types of the attributes' do + before do + @insertion = Insertion.new(@relation, @relation[:id] => '1-asdf') + end + + it 'manufactures sql inserting data' do + @insertion.to_sql.should be_like(" + INSERT + INTO `users` + (`users`.`id`) VALUES (1) + ") + end + end + end + + describe '#call' do + before do + @insertion = Insertion.new(@relation, @relation[:name] => "nick") + end + + 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/arel/unit/relations/join_spec.rb b/spec/arel/unit/relations/join_spec.rb new file mode 100644 index 0000000000..f9d0d9561e --- /dev/null +++ b/spec/arel/unit/relations/join_spec.rb @@ -0,0 +1,169 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Join do + before do + @relation1 = Table.new(:users) + @relation2 = Table.new(:photos) + @predicate = @relation1[:id].eq(@relation2[:user_id]) + end + + describe '==' do + before do + @another_predicate = @relation1[:id].eq(1) + @another_relation = Table.new(:cameras) + end + + it 'obtains if the two relations and the predicate are identical' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).should == Join.new("INNER JOIN", @relation1, @relation2, @predicate) + Join.new("INNER JOIN", @relation1, @relation2, @predicate).should_not == Join.new("INNER JOIN", @relation1, @another_relation, @predicate) + Join.new("INNER JOIN", @relation1, @relation2, @predicate).should_not == Join.new("INNER JOIN", @relation1, @relation2, @another_predicate) + end + + it 'is commutative on the relations' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).should == Join.new("INNER JOIN", @relation2, @relation1, @predicate) + end + end + + describe 'hashing' do + it 'implements hash equality' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate) \ + .should hash_the_same_as(Join.new("INNER JOIN", @relation1, @relation2, @predicate)) + end + end + + describe '#prefix_for' do + 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 + + describe '#engine' do + it "delegates to a relation's engine" do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).engine.should == @relation1.engine + end + end + + describe 'when joining simple relations' do + describe '#attributes' do + it 'combines the attributes of the two relations' do + join = Join.new("INNER JOIN", @relation1, @relation2, @predicate) + join.attributes.should == + (@relation1.attributes + @relation2.attributes).collect { |a| a.bind(join) } + end + end + + describe '#to_sql' do + it 'manufactures sql joining the two tables on the predicate' do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` + FROM `users` + INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` + ") + end + + it 'manufactures sql joining the two tables, merging any selects' do + Join.new("INNER JOIN", @relation1.select(@relation1[:id].eq(1)), + @relation2.select(@relation2[:id].eq(2)), @predicate).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` + FROM `users` + INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` + WHERE `users`.`id` = 1 + AND `photos`.`id` = 2 + ") + end + end + end + + describe 'when joining aggregated relations' do + before do + @aggregation = @relation2 \ + .group(@relation2[:user_id]) \ + .project(@relation2[:user_id], @relation2[:id].count.as(:cnt)) \ + .as('photo_count') + end + + describe '#attributes' do + it 'it transforms aggregate expressions into attributes' do + join_with_aggregation = Join.new("INNER JOIN", @relation1, @aggregation, @predicate) + join_with_aggregation.attributes.should == + (@relation1.attributes + @aggregation.attributes).collect(&:to_attribute).collect { |a| a.bind(join_with_aggregation) } + end + end + + describe '#to_sql' do + describe 'with the aggregation on the right' do + it 'manufactures sql joining the left table to a derived table' do + Join.new("INNER JOIN", @relation1, @aggregation, @predicate).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` + FROM `users` + INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` + ON `users`.`id` = `photo_count`.`user_id` + ") + end + end + + describe 'with the aggregation on the left' do + it 'manufactures sql joining the right table to a derived table' do + Join.new("INNER JOIN", @aggregation, @relation1, @predicate).to_sql.should be_like(" + SELECT `photo_count`.`user_id`, `photo_count`.`cnt`, `users`.`id`, `users`.`name` + FROM (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` + INNER JOIN `users` + ON `users`.`id` = `photo_count`.`user_id` + ") + end + end + + it "keeps selects on the aggregation within the derived table" do + Join.new("INNER JOIN", @relation1, @aggregation.select(@aggregation[:user_id].eq(1)), @predicate).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` + FROM `users` + INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` WHERE `photos`.`user_id` = 1 GROUP BY `photos`.`user_id`) AS `photo_count` + ON `users`.`id` = `photo_count`.`user_id` + ") + end + end + end + + describe 'when joining aliased relations' do + it 'aliases the table and attributes properly' do + aliased_relation = @relation1.as(:alias) + @relation1.join(aliased_relation).on(@relation1[:id].eq(aliased_relation[:id])).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `alias`.`id`, `alias`.`name` + FROM `users` + INNER JOIN `users` AS `alias` + ON `users`.`id` = `alias`.`id` + ") + end + end + + describe 'when joining with a string' do + it "passes the string through to the where clause" do + Join.new("INNER JOIN asdf ON fdsa", @relation1).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + INNER JOIN asdf ON fdsa + ") + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/order_spec.rb b/spec/arel/unit/relations/order_spec.rb new file mode 100644 index 0000000000..838a2f141e --- /dev/null +++ b/spec/arel/unit/relations/order_spec.rb @@ -0,0 +1,77 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Order do + before do + @relation = Table.new(:users) + @attribute = @relation[:id] + end + + describe '#initialize' do + before do + @another_attribtue = @relation[:name] + end + + it "manufactures nested Order relations if multiple predicates are provided" do + Order.new(@relation, @predicate, @another_attribute). \ + should == Order.new(Order.new(@relation, @another_attribute), @predicate) + end + end + + describe '#to_sql' do + describe "when given an attribute" do + it "manufactures sql with an order clause populated by the attribute" do + Order.new(@relation, @attribute).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + ORDER BY `users`.`id` + ") + end + end + + describe "when given multiple attributes" do + before do + @another_attribute = @relation[:name] + end + + it "manufactures sql with an order clause populated by comma-separated attributes" do + Order.new(@relation, @attribute, @another_attribute).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + ORDER BY `users`.`id`, `users`.`name` + ") + end + end + + describe "when given a string" do + before do + @string = "asdf" + end + + it "passes the string through to the order clause" do + Order.new(@relation, @string).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + ORDER BY asdf + ") + end + end + + describe "when ordering an ordered relation" do + before do + @ordered_relation = Order.new(@relation, @attribute) + @another_attribute = @relation[:name] + end + + it "manufactures sql with an order clause populated by comma-separated attributes" do + Order.new(@ordered_relation, @another_attribute).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + ORDER BY `users`.`id`, `users`.`name` + ") + end + end + end + end +end + \ No newline at end of file diff --git a/spec/arel/unit/relations/projection_spec.rb b/spec/arel/unit/relations/projection_spec.rb new file mode 100644 index 0000000000..cd59d6c383 --- /dev/null +++ b/spec/arel/unit/relations/projection_spec.rb @@ -0,0 +1,81 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Projection do + before do + @relation = Table.new(:users) + @attribute = @relation[:id] + end + + describe '#attributes' do + before do + @projection = Projection.new(@relation, @attribute) + end + + it "manufactures attributes associated with the projection relation" do + @projection.attributes.should == [@attribute].collect { |a| a.bind(@projection) } + end + end + + describe '==' do + before do + @another_relation = Table.new(:photos) + @another_attribute = @relation[:name] + end + + it "obtains if the relations and attributes are identical" do + Projection.new(@relation, @attribute).should == Projection.new(@relation, @attribute) + Projection.new(@relation, @attribute).should_not == Projection.new(@another_relation, @attribute) + Projection.new(@relation, @attribute).should_not == Projection.new(@relation, @another_attribute) + end + end + + describe '#to_sql' do + describe 'when given an attribute' do + it "manufactures sql with a limited select clause" do + Projection.new(@relation, @attribute).to_sql.should be_like(" + SELECT `users`.`id` + FROM `users` + ") + end + end + + describe 'when given a relation' do + before do + @scalar_relation = Projection.new(@relation, @relation[:name]) + end + + it "manufactures sql with scalar selects" do + Projection.new(@relation, @scalar_relation).to_sql.should be_like(" + SELECT (SELECT `users`.`name` FROM `users`) FROM `users` + ") + end + end + + describe 'when given a string' do + it "passes the string through to the select clause" do + Projection.new(@relation, 'asdf').to_sql.should be_like(" + SELECT asdf FROM `users` + ") + end + end + end + + describe Projection::Externalizable do + describe '#aggregation?' do + describe 'when the projections are attributes' do + it 'returns false' do + Projection.new(@relation, @attribute).should_not be_aggregation + end + end + + describe 'when the projections include an aggregation' do + it "obtains" do + Projection.new(@relation, @attribute.sum).should be_aggregation + end + end + end + + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/relation_spec.rb b/spec/arel/unit/relations/relation_spec.rb new file mode 100644 index 0000000000..46c3ee250d --- /dev/null +++ b/spec/arel/unit/relations/relation_spec.rb @@ -0,0 +1,215 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Relation do + before do + @relation = Table.new(:users) + @attribute1 = @relation[:id] + @attribute2 = @relation[:name] + end + + describe '[]' do + describe 'when given an', Attribute do + it "return the attribute congruent to the provided attribute" do + @relation[@attribute1].should == @attribute1 + end + end + + describe 'when given a', Symbol, String do + it "returns the attribute with the same name, if it exists" do + @relation[:id].should == @attribute1 + @relation['id'].should == @attribute1 + @relation[:does_not_exist].should be_nil + end + end + end + + describe Relation::Externalizable do + describe '#aggregation?' do + it "returns false" do + @relation.should_not be_aggregation + end + end + + describe '#alias?' do + it "returns false" do + @relation.should_not be_alias + end + end + end + + describe Relation::Operations do + describe 'joins' do + before do + @predicate = @relation[:id].eq(@relation[:id]) + end + + describe '#join' do + describe 'when given a relation' do + it "manufactures an inner join operation between those two relations" do + @relation.join(@relation).on(@predicate). \ + should == Join.new("INNER JOIN", @relation, @relation, @predicate) + end + end + + describe "when given a string" do + it "manufactures a join operation with the string passed through" do + @relation.join(arbitrary_string = "ASDF").should == Join.new(arbitrary_string, @relation) + end + end + + describe "when given something blank" do + it "returns self" do + @relation.join.should == @relation + end + end + end + + describe '#outer_join' do + it "manufactures a left outer join operation between those two relations" do + @relation.outer_join(@relation).on(@predicate). \ + should == Join.new("LEFT OUTER JOIN", @relation, @relation, @predicate) + end + end + end + + describe '#project' do + it "manufactures a projection relation" do + @relation.project(@attribute1, @attribute2). \ + should == Projection.new(@relation, @attribute1, @attribute2) + end + + describe "when given blank attributes" do + it "returns self" do + @relation.project.should == @relation + end + end + end + + describe '#as' do + it "manufactures an alias relation" do + @relation.as(:paul).should == Alias.new(@relation, :paul) + end + + describe 'when given a blank alias' do + it 'returns self' do + @relation.as.should == @relation + end + end + end + + describe '#select' do + before do + @predicate = Equality.new(@attribute1, @attribute2) + end + + it "manufactures a selection relation" do + @relation.select(@predicate).should == Selection.new(@relation, @predicate) + end + + it "accepts arbitrary strings" do + @relation.select("arbitrary").should == Selection.new(@relation, "arbitrary") + end + + describe 'when given a blank predicate' do + it 'returns self' do + @relation.select.should == @relation + end + end + end + + describe '#order' do + it "manufactures an order relation" do + @relation.order(@attribute1, @attribute2).should == Order.new(@relation, @attribute1, @attribute2) + end + + describe 'when given a blank ordering' do + it 'returns self' do + @relation.order.should == @relation + end + end + end + + describe '#take' do + it "manufactures a take relation" do + @relation.take(5).should == Take.new(@relation, 5) + end + + describe 'when given a blank number of items' do + it 'returns self' do + @relation.take.should == @relation + end + end + end + + describe '#skip' do + it "manufactures a skip relation" do + @relation.skip(4).should == Skip.new(@relation, 4) + end + + describe 'when given a blank number of items' do + it 'returns self' do + @relation.skip.should == @relation + end + end + end + + describe '#group' do + it 'manufactures a group relation' do + @relation.group(@attribute1, @attribute2).should == Grouping.new(@relation, @attribute1, @attribute2) + end + + describe 'when given blank groupings' do + it 'returns self' do + @relation.group.should == @relation + end + end + end + + describe Relation::Operations::Writes do + describe '#delete' do + it 'manufactures a deletion relation' do + Session.start do + mock(Session.new).delete(Deletion.new(@relation)) + @relation.delete.should == @relation + end + end + end + + describe '#insert' do + it 'manufactures an insertion relation' do + Session.start do + record = {@relation[:name] => 'carl'} + mock(Session.new).create(Insertion.new(@relation, record)) + @relation.insert(record).should == @relation + end + end + end + + describe '#update' do + it 'manufactures an update relation' do + Session.start do + assignments = {@relation[:name] => Value.new('bob', @relation)} + mock(Session.new).update(Update.new(@relation, assignments)) + @relation.update(assignments).should == @relation + end + end + end + end + end + + describe Relation::Enumerable do + it "implements enumerable" do + @relation.collect.should == @relation.session.read(@relation) + @relation.first.should == @relation.session.read(@relation).first + 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 + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/selection_spec.rb b/spec/arel/unit/relations/selection_spec.rb new file mode 100644 index 0000000000..61d9787df2 --- /dev/null +++ b/spec/arel/unit/relations/selection_spec.rb @@ -0,0 +1,43 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Selection do + before do + @relation = Table.new(:users) + @predicate = @relation[:id].eq(1) + end + + describe '#initialize' do + before do + @another_predicate = @relation[:name].lt(2) + end + + it "manufactures nested selection relations if multiple predicates are provided" do + Selection.new(@relation, @predicate, @another_predicate). \ + should == Selection.new(Selection.new(@relation, @another_predicate), @predicate) + end + end + + describe '#to_sql' do + describe 'when given a predicate' do + it "manufactures sql with where clause conditions" do + Selection.new(@relation, @predicate).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + WHERE `users`.`id` = 1 + ") + end + end + + describe 'when given a string' do + it "passes the string through to the where clause" do + Selection.new(@relation, 'asdf').to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + WHERE asdf + ") + end + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/skip_spec.rb b/spec/arel/unit/relations/skip_spec.rb new file mode 100644 index 0000000000..d83c969aa8 --- /dev/null +++ b/spec/arel/unit/relations/skip_spec.rb @@ -0,0 +1,20 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Skip do + before do + @relation = Table.new(:users) + @skipped = 4 + end + + describe '#to_sql' do + it "manufactures sql with limit and offset" do + Skip.new(@relation, @skipped).to_s.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + OFFSET #{@skipped} + ") + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/table_spec.rb b/spec/arel/unit/relations/table_spec.rb new file mode 100644 index 0000000000..eb765ce777 --- /dev/null +++ b/spec/arel/unit/relations/table_spec.rb @@ -0,0 +1,95 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Table do + before do + @relation = Table.new(:users) + end + + describe '[]' do + describe 'when given a', Symbol do + it "manufactures an attribute if the symbol names an attribute within the relation" do + @relation[:id].should == Attribute.new(@relation, :id) + @relation[:does_not_exist].should be_nil + end + end + + describe 'when given an', Attribute do + it "returns the attribute if the attribute is within the relation" do + @relation[@relation[:id]].should == @relation[:id] + end + + it "returns nil if the attribtue is not within the relation" do + another_relation = Table.new(:photos) + @relation[another_relation[:id]].should be_nil + end + end + + describe 'when given an', Expression do + before do + @expression = @relation[:id].count + end + + it "returns the Expression if the Expression is within the relation" do + @relation[@expression].should be_nil + end + end + end + + describe '#to_sql' do + it "manufactures a simple select query" do + @relation.to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + ") + end + end + + describe '#column_for' do + it "returns the column corresponding to the attribute" do + @relation.column_for(@relation[:id]).should == @relation.columns.detect { |c| c.name == 'id' } + end + end + + describe '#prefix_for' do + it "returns the table name if the relation contains the attribute" do + @relation.prefix_for(@relation[:id]).should == 'users' + @relation.prefix_for(:does_not_exist).should be_nil + end + end + + describe '#attributes' do + it 'manufactures attributes corresponding to columns in the table' do + @relation.attributes.should == [ + Attribute.new(@relation, :id), + Attribute.new(@relation, :name) + ] + end + + describe '#reset' do + it "reloads columns from the database" do + lambda { stub(@relation.engine).columns { [] } }.should_not change { @relation.attributes } + lambda { @relation.reset }.should change { @relation.attributes } + end + end + end + + describe 'hashing' do + it "implements hash equality" do + 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 + + describe '#engine' do + it "defaults to global engine" do + Table.engine = engine = Engine.new + Table.new(:users).engine.should == engine + end + + it "can be specified" do + Table.new(:users, engine = Engine.new).engine.should == engine + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/take_spec.rb b/spec/arel/unit/relations/take_spec.rb new file mode 100644 index 0000000000..dca7806057 --- /dev/null +++ b/spec/arel/unit/relations/take_spec.rb @@ -0,0 +1,20 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Take do + before do + @relation = Table.new(:users) + @taken = 4 + end + + describe '#to_sql' do + it "manufactures sql with limit and offset" do + Take.new(@relation, @taken).to_s.should be_like(" + SELECT `users`.`id`, `users`.`name` + FROM `users` + LIMIT #{@taken} + ") + end + end + end +end \ No newline at end of file diff --git a/spec/arel/unit/relations/update_spec.rb b/spec/arel/unit/relations/update_spec.rb new file mode 100644 index 0000000000..f411781392 --- /dev/null +++ b/spec/arel/unit/relations/update_spec.rb @@ -0,0 +1,81 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Update do + before do + @relation = Table.new(:users) + end + + describe '#to_sql' do + it "manufactures sql updating attributes when given multiple attributes" do + Update.new(@relation, @relation[:id] => 1, @relation[:name] => "nick").to_sql.should be_like(" + UPDATE `users` + SET `users`.`id` = 1, `users`.`name` = 'nick' + ") + end + + it "manufactures sql updating attributes when given a ranged relation" do + Update.new(@relation.take(1), @relation[:name] => "nick").to_sql.should be_like(" + UPDATE `users` + SET `users`.`name` = 'nick' + LIMIT 1 + ") + end + + describe 'when given values whose types correspond to the types of the attributes' do + before do + @update = Update.new(@relation, @relation[:name] => "nick") + end + + it 'manufactures sql updating attributes' do + @update.to_sql.should be_like(" + UPDATE `users` + SET `users`.`name` = 'nick' + ") + end + end + + describe 'when given values whose types differ from from the types of the attributes' do + before do + @update = Update.new(@relation, @relation[:id] => '1-asdf') + end + + it 'manufactures sql updating attributes' do + @update.to_sql.should be_like(" + UPDATE `users` + SET `users`.`id` = 1 + ") + end + end + + describe 'when the relation is a selection' do + before do + @update = Update.new( + @relation.select(@relation[:id].eq(1)), + @relation[:name] => "nick" + ) + end + + it 'manufactures sql updating a selection relation' do + @update.to_sql.should be_like(" + UPDATE `users` + SET `users`.`name` = 'nick' + WHERE `users`.`id` = 1 + ") + end + end + end + + describe '#call' do + before do + @update = Update.new(@relation, @relation[:name] => "nick") + end + + it 'executes an update on the connection' do + 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/arel/unit/session/session_spec.rb b/spec/arel/unit/session/session_spec.rb new file mode 100644 index 0000000000..c2eb9a4555 --- /dev/null +++ b/spec/arel/unit/session/session_spec.rb @@ -0,0 +1,92 @@ +require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') + +module Arel + describe Session do + before do + @relation = Table.new(:users) + @session = Session.new + end + + describe '::start' do + describe '::instance' do + it "it is a singleton within the started session" do + Session.start do + Session.new.should == Session.new + end + end + + it "is a singleton across nested sessions" do + Session.start do + outside = Session.new + Session.start do + Session.new.should == outside + end + end + end + + it "manufactures new sessions outside of the started session" do + Session.new.should_not == Session.new + end + end + end + + describe Session::CRUD do + before do + @insert = Insertion.new(@relation, @relation[:name] => 'nick') + @update = Update.new(@relation, @relation[:name] => 'nick') + @delete = Deletion.new(@relation) + @read = @relation + end + + describe '#create' do + it "executes an insertion on the connection" do + mock(@insert).call(@insert.engine.connection) + @session.create(@insert) + end + end + + describe '#read' do + it "executes an selection on the connection" do + mock(@read).call(@read.engine.connection) + @session.read(@read) + end + + it "is memoized" do + 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).call(@update.engine.connection) + @session.update(@update) + end + end + + describe '#delete' do + it "executes a delete on the connection" do + mock(@delete).call(@delete.engine.connection) + @session.delete(@delete) + end + end + end + + describe Session::Transactions do + describe '#begin' do + end + + describe '#end' do + end + end + + describe Session::UnitOfWork do + describe '#flush' do + end + + describe '#clear' do + end + end + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6180f822bd..bc117ec47d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,7 +5,7 @@ require 'rubygems' require 'spec' require 'pp' require 'fileutils' -require 'active_relation' +require 'arel' [:matchers, :doubles].each do |helper| Dir["#{dir}/#{helper}/*"].each { |m| require "#{dir}/#{helper}/#{File.basename(m)}" } @@ -15,6 +15,6 @@ Spec::Runner.configure do |config| config.include(BeLikeMatcher, HashTheSameAsMatcher) config.mock_with :rr config.before do - ActiveRelation::Table.engine = ActiveRelation::Engine.new(Fake::Engine.new) + Arel::Table.engine = Arel::Engine.new(Fake::Engine.new) end end \ No newline at end of file -- cgit v1.2.3