aboutsummaryrefslogtreecommitdiffstats
path: root/lib/active_relation
diff options
context:
space:
mode:
Diffstat (limited to 'lib/active_relation')
-rw-r--r--lib/active_relation/extensions.rb4
-rw-r--r--lib/active_relation/relations.rb12
-rw-r--r--lib/active_relation/relations/base.rb111
-rw-r--r--lib/active_relation/relations/compound.rb9
-rw-r--r--lib/active_relation/relations/deletion.rb17
-rw-r--r--lib/active_relation/relations/insertion.rb25
-rw-r--r--lib/active_relation/relations/join.rb45
-rw-r--r--lib/active_relation/relations/order.rb19
-rw-r--r--lib/active_relation/relations/projection.rb19
-rw-r--r--lib/active_relation/relations/range.rb23
-rw-r--r--lib/active_relation/relations/rename.rb38
-rw-r--r--lib/active_relation/relations/selection.rb25
-rw-r--r--lib/active_relation/relations/table.rb35
13 files changed, 382 insertions, 0 deletions
diff --git a/lib/active_relation/extensions.rb b/lib/active_relation/extensions.rb
new file mode 100644
index 0000000000..3751088483
--- /dev/null
+++ b/lib/active_relation/extensions.rb
@@ -0,0 +1,4 @@
+require 'active_relation/extensions/object'
+require 'active_relation/extensions/array'
+require 'active_relation/extensions/base'
+require 'active_relation/extensions/hash'
diff --git a/lib/active_relation/relations.rb b/lib/active_relation/relations.rb
new file mode 100644
index 0000000000..f7d1bc207a
--- /dev/null
+++ b/lib/active_relation/relations.rb
@@ -0,0 +1,12 @@
+require 'active_relation/relations/base'
+require 'active_relation/relations/compound'
+require 'active_relation/relations/table'
+require 'active_relation/relations/join'
+require 'active_relation/relations/attribute'
+require 'active_relation/relations/projection'
+require 'active_relation/relations/selection'
+require 'active_relation/relations/order'
+require 'active_relation/relations/range'
+require 'active_relation/relations/rename'
+require 'active_relation/relations/deletion'
+require 'active_relation/relations/insertion' \ No newline at end of file
diff --git a/lib/active_relation/relations/base.rb b/lib/active_relation/relations/base.rb
new file mode 100644
index 0000000000..c4a887eecd
--- /dev/null
+++ b/lib/active_relation/relations/base.rb
@@ -0,0 +1,111 @@
+module ActiveRelation
+ module Relations
+ class Base
+ include SqlBuilder
+
+ module Iteration
+ include Enumerable
+
+ def each(&block)
+ connection.select_all(to_s).each(&block)
+ end
+
+ def first
+ connection.select_one(to_s)
+ end
+ end
+ include Iteration
+
+ module Operations
+ def <=>(other)
+ JoinOperation.new("INNER JOIN", self, other)
+ end
+
+ def <<(other)
+ JoinOperation.new("LEFT OUTER JOIN", self, other)
+ end
+
+ def [](index)
+ case index
+ when Symbol
+ attribute(index)
+ when ::Range
+ Range.new(self, index)
+ end
+ end
+
+ def include?(attribute)
+ Predicates::RelationInclusion.new(attribute, self)
+ end
+
+ def select(*s)
+ Selection.new(self, *s)
+ end
+
+ def project(*attributes)
+ Projection.new(self, *attributes)
+ end
+
+ def order(*attributes)
+ Order.new(self, *attributes)
+ end
+
+ def rename(attribute, aliaz)
+ Rename.new(self, attribute => aliaz)
+ end
+
+ def insert(record)
+ Insertion.new(self, record)
+ end
+
+ def delete
+ Deletion.new(self)
+ end
+
+ class JoinOperation
+ attr_reader :join_sql, :relation1, :relation2
+
+ def initialize(join_sql, relation1, relation2)
+ @join_sql, @relation1, @relation2 = join_sql, relation1, relation2
+ end
+
+ def on(*predicates)
+ Join.new(join_sql, relation1, relation2, *predicates)
+ end
+
+ def ==(other)
+ (relation1 == other.relation1 and relation2 == other.relation2) or
+ (relation1 == other.relation2 and relation2 == other.relation1)
+ end
+ end
+ end
+ include Operations
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def to_sql(options = {})
+ [
+ "SELECT #{attributes.collect{ |a| a.to_sql(:use_alias => true) }.join(', ')}",
+ "FROM #{quote_table_name(table)}",
+ (joins.to_sql(:quote => false) unless joins.blank?),
+ ("WHERE #{selects.collect{|s| s.to_sql(:quote => false)}.join("\n\tAND ")}" unless selects.blank?),
+ ("ORDER BY #{orders.collect(&:to_sql)}" unless orders.blank?),
+ ("LIMIT #{limit.to_sql}" unless limit.blank?),
+ ("OFFSET #{offset.to_sql}" unless offset.blank?)
+ ].compact.join("\n")
+ end
+ alias_method :to_s, :to_sql
+
+ protected
+ def attributes; [] end
+ def selects; [] end
+ def orders; [] end
+ def inserts; [] end
+ def joins; nil end
+ def limit; nil end
+ def offset; nil end
+ 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
new file mode 100644
index 0000000000..442224a011
--- /dev/null
+++ b/lib/active_relation/relations/compound.rb
@@ -0,0 +1,9 @@
+module ActiveRelation
+ module Relations
+ class Compound < Base
+ attr_reader :relation
+
+ delegate :attributes, :attribute, :joins, :selects, :orders, :table, :inserts, :limit, :offset, :to => :relation
+ 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
new file mode 100644
index 0000000000..f218d9da6d
--- /dev/null
+++ b/lib/active_relation/relations/deletion.rb
@@ -0,0 +1,17 @@
+module ActiveRelation
+ module Relations
+ class Deletion < Compound
+ def initialize(relation)
+ @relation = relation
+ end
+
+ def to_sql(options = {})
+ [
+ "DELETE",
+ "FROM #{quote_table_name(table)}",
+ ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank?)
+ ].compact.join("\n")
+ end
+ 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
new file mode 100644
index 0000000000..a0042a18a5
--- /dev/null
+++ b/lib/active_relation/relations/insertion.rb
@@ -0,0 +1,25 @@
+module ActiveRelation
+ module Relations
+ class Insertion < Compound
+ attr_reader :record
+
+ def initialize(relation, record)
+ @relation, @record = relation, record
+ end
+
+ def to_sql(options = {})
+ [
+ "INSERT",
+ "INTO #{quote_table_name(table)}",
+ "(#{record.keys.collect(&:to_sql).join(', ')})",
+ "VALUES #{inserts.collect(&:to_sql).join(', ')}"
+ ].join("\n")
+ end
+
+ protected
+ def inserts
+ relation.inserts + [record]
+ end
+ 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
new file mode 100644
index 0000000000..1bd1439dd6
--- /dev/null
+++ b/lib/active_relation/relations/join.rb
@@ -0,0 +1,45 @@
+module ActiveRelation
+ module Relations
+ class Join < Base
+ attr_reader :join_sql, :relation1, :relation2, :predicates
+
+ def initialize(join_sql, relation1, relation2, *predicates)
+ @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates
+ end
+
+ def ==(other)
+ predicates == other.predicates and
+ ((relation1 == other.relation1 and relation2 == other.relation2) or
+ (relation2 == other.relation1 and relation1 == other.relation2))
+ end
+
+ def qualify
+ Join.new(join_sql, relation1.qualify, relation2.qualify, *predicates.collect(&:qualify))
+ end
+
+ protected
+ def joins
+ [relation1.joins, relation2.joins, join].compact.join(" ")
+ end
+
+ def selects
+ relation1.send(:selects) + relation2.send(:selects)
+ end
+
+ def attributes
+ relation1.attributes + relation2.attributes
+ end
+
+ def attribute(name)
+ relation1[name] || relation2[name]
+ end
+
+ delegate :table, :to => :relation1
+
+ private
+ def join
+ "#{join_sql} #{quote_table_name(relation2.table)} ON #{predicates.collect { |p| p.to_sql(:quote => false) }.join(' AND ')}"
+ end
+ 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
new file mode 100644
index 0000000000..99ff939528
--- /dev/null
+++ b/lib/active_relation/relations/order.rb
@@ -0,0 +1,19 @@
+module ActiveRelation
+ module Relations
+ class Order < Compound
+ attr_reader :relation, :orders
+
+ def initialize(relation, *orders)
+ @relation, @orders = relation, orders
+ end
+
+ def ==(other)
+ relation == other.relation and orders.eql?(other.orders)
+ end
+
+ def qualify
+ Order.new(relation.qualify, *orders.collect { |o| o.qualify })
+ end
+ 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
new file mode 100644
index 0000000000..b30c76898d
--- /dev/null
+++ b/lib/active_relation/relations/projection.rb
@@ -0,0 +1,19 @@
+module ActiveRelation
+ module Relations
+ class Projection < Compound
+ attr_reader :relation, :attributes
+
+ def initialize(relation, *attributes)
+ @relation, @attributes = relation, attributes
+ end
+
+ def ==(other)
+ relation == other.relation and attributes.eql?(other.attributes)
+ end
+
+ def qualify
+ Projection.new(relation.qualify, *attributes.collect(&:qualify))
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/active_relation/relations/range.rb b/lib/active_relation/relations/range.rb
new file mode 100644
index 0000000000..d7e08efa06
--- /dev/null
+++ b/lib/active_relation/relations/range.rb
@@ -0,0 +1,23 @@
+module ActiveRelation
+ module Relations
+ class Range < Compound
+ attr_reader :range
+
+ def initialize(relation, range)
+ @relation, @range = relation, range
+ end
+
+ def ==(other)
+ relation == other.relation and range == other.range
+ end
+
+ def limit
+ range.end - range.begin + 1
+ end
+
+ def offset
+ range.begin
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/active_relation/relations/rename.rb b/lib/active_relation/relations/rename.rb
new file mode 100644
index 0000000000..7a1693df57
--- /dev/null
+++ b/lib/active_relation/relations/rename.rb
@@ -0,0 +1,38 @@
+module ActiveRelation
+ module Relations
+ class Rename < Compound
+ attr_reader :relation, :schmattribute, :alias
+
+ def initialize(relation, renames)
+ @schmattribute, @alias = renames.shift
+ @relation = renames.empty?? relation : Rename.new(relation, renames)
+ end
+
+ def ==(other)
+ relation == other.relation and schmattribute.eql?(other.schmattribute) and self.alias == other.alias
+ end
+
+ def attributes
+ relation.attributes.collect { |a| substitute(a) }
+ end
+
+ def qualify
+ Rename.new(relation.qualify, schmattribute.qualify => self.alias)
+ end
+
+ protected
+ def attribute(name)
+ case
+ when name == self.alias then schmattribute.alias(self.alias)
+ when relation[name].eql?(schmattribute) then nil
+ else relation[name]
+ end
+ end
+
+ private
+ def substitute(a)
+ a.eql?(schmattribute) ? a.alias(self.alias) : a
+ end
+ 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
new file mode 100644
index 0000000000..e102d105a0
--- /dev/null
+++ b/lib/active_relation/relations/selection.rb
@@ -0,0 +1,25 @@
+module ActiveRelation
+ module Relations
+ class Selection < Compound
+ attr_reader :relation, :predicate
+
+ def initialize(relation, *predicates)
+ @predicate = predicates.shift
+ @relation = predicates.empty?? relation : Selection.new(relation, *predicates)
+ end
+
+ def ==(other)
+ relation == other.relation and predicate == other.predicate
+ end
+
+ def qualify
+ Selection.new(relation.qualify, predicate.qualify)
+ end
+
+ protected
+ def selects
+ relation.send(:selects) + [predicate]
+ end
+ 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
new file mode 100644
index 0000000000..38f540cc52
--- /dev/null
+++ b/lib/active_relation/relations/table.rb
@@ -0,0 +1,35 @@
+module ActiveRelation
+ module Relations
+ class Table < Base
+ attr_reader :table
+
+ def initialize(table)
+ @table = table
+ end
+
+ def attributes
+ attributes_by_name.values
+ end
+
+ def qualify
+ Rename.new self, qualifications
+ end
+
+ protected
+ def attribute(name)
+ attributes_by_name[name.to_s]
+ end
+
+ private
+ def attributes_by_name
+ @attributes_by_name ||= connection.columns(table, "#{table} Columns").inject({}) do |attributes_by_name, column|
+ attributes_by_name.merge(column.name => ActiveRelation::Primitives::Attribute.new(self, column.name.to_sym))
+ end
+ end
+
+ def qualifications
+ attributes.zip(attributes.collect(&:qualified_name)).to_hash
+ end
+ end
+ end
+end \ No newline at end of file