From 311f5f8eb588d4cde051762ace87a61425300bec Mon Sep 17 00:00:00 2001 From: Nick Kallen Date: Mon, 7 Jan 2008 18:37:20 -0800 Subject: minor --- eager include.txt | 49 ---- lib/active_relation.rb | 56 +++++ lib/active_relation/extensions/array.rb | 5 + lib/active_relation/extensions/base.rb | 47 ++++ lib/active_relation/extensions/hash.rb | 13 + lib/active_relation/extensions/object.rb | 12 + lib/active_relation/extensions/range.rb | 0 lib/active_relation/predicates/binary_predicate.rb | 25 ++ .../predicates/equality_predicate.rb | 12 + .../greater_than_or_equal_to_predicate.rb | 2 + .../predicates/greater_than_predicate.rb | 2 + .../predicates/less_than_or_equal_to_predicate.rb | 2 + .../predicates/less_than_predicate.rb | 2 + lib/active_relation/predicates/match_predicate.rb | 7 + lib/active_relation/predicates/predicate.rb | 5 + .../predicates/range_inclusion_predicate.rb | 0 .../predicates/relation_inclusion_predicate.rb | 11 + lib/active_relation/relations/attribute.rb | 56 +++++ lib/active_relation/relations/compound_relation.rb | 3 + lib/active_relation/relations/deletion_relation.rb | 22 ++ .../relations/inner_join_operation.rb | 6 + .../relations/inner_join_relation.rb | 6 + .../relations/insertion_relation.rb | 29 +++ lib/active_relation/relations/join.rb | 15 ++ lib/active_relation/relations/join_operation.rb | 16 ++ lib/active_relation/relations/join_relation.rb | 36 +++ .../relations/left_outer_join_operation.rb | 6 + .../relations/left_outer_join_relation.rb | 6 + lib/active_relation/relations/order_relation.rb | 25 ++ .../relations/projection_relation.rb | 23 ++ lib/active_relation/relations/range_relation.rb | 18 ++ lib/active_relation/relations/relation.rb | 91 +++++++ lib/active_relation/relations/rename_relation.rb | 34 +++ .../relations/selection_relation.rb | 21 ++ lib/active_relation/relations/table_relation.rb | 31 +++ lib/active_relation/sql_builder/columns_builder.rb | 16 ++ .../sql_builder/conditions_builder.rb | 20 ++ lib/active_relation/sql_builder/delete_builder.rb | 32 +++ .../sql_builder/equals_condition_builder.rb | 18 ++ .../sql_builder/inner_join_builder.rb | 5 + lib/active_relation/sql_builder/insert_builder.rb | 41 ++++ lib/active_relation/sql_builder/join_builder.rb | 13 + lib/active_relation/sql_builder/joins_builder.rb | 18 ++ .../sql_builder/left_outer_join_builder.rb | 5 + lib/active_relation/sql_builder/order_builder.rb | 16 ++ lib/active_relation/sql_builder/select_builder.rb | 61 +++++ lib/active_relation/sql_builder/selects_builder.rb | 9 + lib/active_relation/sql_builder/sql_builder.rb | 35 +++ lib/active_relation/sql_builder/values_builder.rb | 16 ++ lib/sql_algebra.rb | 49 ---- lib/sql_algebra/extensions/array.rb | 5 - lib/sql_algebra/extensions/base.rb | 47 ---- lib/sql_algebra/extensions/hash.rb | 7 - lib/sql_algebra/extensions/object.rb | 12 - lib/sql_algebra/extensions/range.rb | 0 lib/sql_algebra/predicates/binary_predicate.rb | 25 -- lib/sql_algebra/predicates/equality_predicate.rb | 12 - .../greater_than_or_equal_to_predicate.rb | 2 - .../predicates/greater_than_predicate.rb | 2 - .../predicates/less_than_or_equal_to_predicate.rb | 2 - lib/sql_algebra/predicates/less_than_predicate.rb | 2 - lib/sql_algebra/predicates/match_predicate.rb | 7 - lib/sql_algebra/predicates/predicate.rb | 5 - .../predicates/range_inclusion_predicate.rb | 0 .../predicates/relation_inclusion_predicate.rb | 11 - lib/sql_algebra/relations/attribute.rb | 56 ----- lib/sql_algebra/relations/compound_relation.rb | 3 - lib/sql_algebra/relations/inner_join_operation.rb | 6 - lib/sql_algebra/relations/inner_join_relation.rb | 6 - lib/sql_algebra/relations/join.rb | 15 -- lib/sql_algebra/relations/join_operation.rb | 16 -- lib/sql_algebra/relations/join_relation.rb | 36 --- .../relations/left_outer_join_operation.rb | 6 - .../relations/left_outer_join_relation.rb | 6 - lib/sql_algebra/relations/order_relation.rb | 25 -- lib/sql_algebra/relations/projection_relation.rb | 23 -- lib/sql_algebra/relations/range_relation.rb | 18 -- lib/sql_algebra/relations/relation.rb | 83 ------- lib/sql_algebra/relations/rename_relation.rb | 34 --- lib/sql_algebra/relations/selection_relation.rb | 21 -- lib/sql_algebra/relations/table_relation.rb | 31 --- lib/sql_algebra/sql_builder/conditions_builder.rb | 16 -- .../sql_builder/equals_condition_builder.rb | 18 -- lib/sql_algebra/sql_builder/inner_join_builder.rb | 5 - lib/sql_algebra/sql_builder/join_builder.rb | 13 - lib/sql_algebra/sql_builder/joins_builder.rb | 18 -- .../sql_builder/left_outer_join_builder.rb | 5 - lib/sql_algebra/sql_builder/order_builder.rb | 16 -- lib/sql_algebra/sql_builder/select_builder.rb | 62 ----- lib/sql_algebra/sql_builder/selects_builder.rb | 20 -- lib/sql_algebra/sql_builder/sql_builder.rb | 35 --- spec/active_relation/integration/scratch_spec.rb | 271 +++++++++++++++++++++ .../predicates/binary_predicate_spec.rb | 51 ++++ .../predicates/equality_predicate_spec.rb | 25 ++ .../relation_inclusion_predicate_spec.rb | 16 ++ spec/active_relation/relations/attribute_spec.rb | 90 +++++++ .../relations/deletion_relation_spec.rb | 22 ++ .../relations/insertion_relation_spec.rb | 37 +++ .../relations/join_operation_spec.rb | 39 +++ .../relations/join_relation_spec.rb | 59 +++++ .../relations/order_relation_spec.rb | 41 ++++ .../relations/projection_relation_spec.rb | 36 +++ .../relations/range_relation_spec.rb | 41 ++++ spec/active_relation/relations/relation_spec.rb | 92 +++++++ .../relations/rename_relation_spec.rb | 64 +++++ .../relations/selection_relation_spec.rb | 53 ++++ .../relations/table_relation_spec.rb | 33 +++ .../active_relation/sql_builder/conditions_spec.rb | 18 ++ .../sql_builder/delete_builder_spec.rb | 22 ++ .../sql_builder/insert_builder_spec.rb | 24 ++ .../sql_builder/select_builder_spec.rb | 148 +++++++++++ spec/debug.log | 1 - spec/extensions/range_spec.rb | 1 - spec/integration/debug.log | 0 spec/integration/scratch_spec.rb | 226 ----------------- spec/matchers/be_like.rb | 24 ++ spec/predicates/binary_predicate_spec.rb | 51 ---- spec/predicates/equality_predicate_spec.rb | 25 -- .../relation_inclusion_predicate_spec.rb | 16 -- spec/relations/attribute_spec.rb | 90 ------- spec/relations/join_operation_spec.rb | 35 --- spec/relations/join_relation_spec.rb | 59 ----- spec/relations/order_relation_spec.rb | 41 ---- spec/relations/projection_relation_spec.rb | 36 --- spec/relations/range_relation_spec.rb | 41 ---- spec/relations/relation_spec.rb | 72 ------ spec/relations/rename_relation_spec.rb | 64 ----- spec/relations/selection_relation_spec.rb | 53 ---- spec/relations/table_relation_spec.rb | 33 --- spec/spec_helper.rb | 7 +- spec/spec_helpers/be_like.rb | 24 -- spec/sql_builder/conditions_spec.rb | 18 -- spec/sql_builder/select_builder_spec.rb | 113 --------- 133 files changed, 2160 insertions(+), 1832 deletions(-) delete mode 100644 eager include.txt create mode 100644 lib/active_relation.rb create mode 100644 lib/active_relation/extensions/array.rb create mode 100644 lib/active_relation/extensions/base.rb create mode 100644 lib/active_relation/extensions/hash.rb create mode 100644 lib/active_relation/extensions/object.rb create mode 100644 lib/active_relation/extensions/range.rb create mode 100644 lib/active_relation/predicates/binary_predicate.rb create mode 100644 lib/active_relation/predicates/equality_predicate.rb create mode 100644 lib/active_relation/predicates/greater_than_or_equal_to_predicate.rb create mode 100644 lib/active_relation/predicates/greater_than_predicate.rb create mode 100644 lib/active_relation/predicates/less_than_or_equal_to_predicate.rb create mode 100644 lib/active_relation/predicates/less_than_predicate.rb create mode 100644 lib/active_relation/predicates/match_predicate.rb create mode 100644 lib/active_relation/predicates/predicate.rb create mode 100644 lib/active_relation/predicates/range_inclusion_predicate.rb create mode 100644 lib/active_relation/predicates/relation_inclusion_predicate.rb create mode 100644 lib/active_relation/relations/attribute.rb create mode 100644 lib/active_relation/relations/compound_relation.rb create mode 100644 lib/active_relation/relations/deletion_relation.rb create mode 100644 lib/active_relation/relations/inner_join_operation.rb create mode 100644 lib/active_relation/relations/inner_join_relation.rb create mode 100644 lib/active_relation/relations/insertion_relation.rb create mode 100644 lib/active_relation/relations/join.rb create mode 100644 lib/active_relation/relations/join_operation.rb create mode 100644 lib/active_relation/relations/join_relation.rb create mode 100644 lib/active_relation/relations/left_outer_join_operation.rb create mode 100644 lib/active_relation/relations/left_outer_join_relation.rb create mode 100644 lib/active_relation/relations/order_relation.rb create mode 100644 lib/active_relation/relations/projection_relation.rb create mode 100644 lib/active_relation/relations/range_relation.rb create mode 100644 lib/active_relation/relations/relation.rb create mode 100644 lib/active_relation/relations/rename_relation.rb create mode 100644 lib/active_relation/relations/selection_relation.rb create mode 100644 lib/active_relation/relations/table_relation.rb create mode 100644 lib/active_relation/sql_builder/columns_builder.rb create mode 100644 lib/active_relation/sql_builder/conditions_builder.rb create mode 100644 lib/active_relation/sql_builder/delete_builder.rb create mode 100644 lib/active_relation/sql_builder/equals_condition_builder.rb create mode 100644 lib/active_relation/sql_builder/inner_join_builder.rb create mode 100644 lib/active_relation/sql_builder/insert_builder.rb create mode 100644 lib/active_relation/sql_builder/join_builder.rb create mode 100644 lib/active_relation/sql_builder/joins_builder.rb create mode 100644 lib/active_relation/sql_builder/left_outer_join_builder.rb create mode 100644 lib/active_relation/sql_builder/order_builder.rb create mode 100644 lib/active_relation/sql_builder/select_builder.rb create mode 100644 lib/active_relation/sql_builder/selects_builder.rb create mode 100644 lib/active_relation/sql_builder/sql_builder.rb create mode 100644 lib/active_relation/sql_builder/values_builder.rb delete mode 100644 lib/sql_algebra.rb delete mode 100644 lib/sql_algebra/extensions/array.rb delete mode 100644 lib/sql_algebra/extensions/base.rb delete mode 100644 lib/sql_algebra/extensions/hash.rb delete mode 100644 lib/sql_algebra/extensions/object.rb delete mode 100644 lib/sql_algebra/extensions/range.rb delete mode 100644 lib/sql_algebra/predicates/binary_predicate.rb delete mode 100644 lib/sql_algebra/predicates/equality_predicate.rb delete mode 100644 lib/sql_algebra/predicates/greater_than_or_equal_to_predicate.rb delete mode 100644 lib/sql_algebra/predicates/greater_than_predicate.rb delete mode 100644 lib/sql_algebra/predicates/less_than_or_equal_to_predicate.rb delete mode 100644 lib/sql_algebra/predicates/less_than_predicate.rb delete mode 100644 lib/sql_algebra/predicates/match_predicate.rb delete mode 100644 lib/sql_algebra/predicates/predicate.rb delete mode 100644 lib/sql_algebra/predicates/range_inclusion_predicate.rb delete mode 100644 lib/sql_algebra/predicates/relation_inclusion_predicate.rb delete mode 100644 lib/sql_algebra/relations/attribute.rb delete mode 100644 lib/sql_algebra/relations/compound_relation.rb delete mode 100644 lib/sql_algebra/relations/inner_join_operation.rb delete mode 100644 lib/sql_algebra/relations/inner_join_relation.rb delete mode 100644 lib/sql_algebra/relations/join.rb delete mode 100644 lib/sql_algebra/relations/join_operation.rb delete mode 100644 lib/sql_algebra/relations/join_relation.rb delete mode 100644 lib/sql_algebra/relations/left_outer_join_operation.rb delete mode 100644 lib/sql_algebra/relations/left_outer_join_relation.rb delete mode 100644 lib/sql_algebra/relations/order_relation.rb delete mode 100644 lib/sql_algebra/relations/projection_relation.rb delete mode 100644 lib/sql_algebra/relations/range_relation.rb delete mode 100644 lib/sql_algebra/relations/relation.rb delete mode 100644 lib/sql_algebra/relations/rename_relation.rb delete mode 100644 lib/sql_algebra/relations/selection_relation.rb delete mode 100644 lib/sql_algebra/relations/table_relation.rb delete mode 100644 lib/sql_algebra/sql_builder/conditions_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/equals_condition_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/inner_join_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/join_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/joins_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/left_outer_join_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/order_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/select_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/selects_builder.rb delete mode 100644 lib/sql_algebra/sql_builder/sql_builder.rb create mode 100644 spec/active_relation/integration/scratch_spec.rb create mode 100644 spec/active_relation/predicates/binary_predicate_spec.rb create mode 100644 spec/active_relation/predicates/equality_predicate_spec.rb create mode 100644 spec/active_relation/predicates/relation_inclusion_predicate_spec.rb create mode 100644 spec/active_relation/relations/attribute_spec.rb create mode 100644 spec/active_relation/relations/deletion_relation_spec.rb create mode 100644 spec/active_relation/relations/insertion_relation_spec.rb create mode 100644 spec/active_relation/relations/join_operation_spec.rb create mode 100644 spec/active_relation/relations/join_relation_spec.rb create mode 100644 spec/active_relation/relations/order_relation_spec.rb create mode 100644 spec/active_relation/relations/projection_relation_spec.rb create mode 100644 spec/active_relation/relations/range_relation_spec.rb create mode 100644 spec/active_relation/relations/relation_spec.rb create mode 100644 spec/active_relation/relations/rename_relation_spec.rb create mode 100644 spec/active_relation/relations/selection_relation_spec.rb create mode 100644 spec/active_relation/relations/table_relation_spec.rb create mode 100644 spec/active_relation/sql_builder/conditions_spec.rb create mode 100644 spec/active_relation/sql_builder/delete_builder_spec.rb create mode 100644 spec/active_relation/sql_builder/insert_builder_spec.rb create mode 100644 spec/active_relation/sql_builder/select_builder_spec.rb delete mode 100644 spec/debug.log delete mode 100644 spec/extensions/range_spec.rb delete mode 100644 spec/integration/debug.log delete mode 100644 spec/integration/scratch_spec.rb create mode 100644 spec/matchers/be_like.rb delete mode 100644 spec/predicates/binary_predicate_spec.rb delete mode 100644 spec/predicates/equality_predicate_spec.rb delete mode 100644 spec/predicates/relation_inclusion_predicate_spec.rb delete mode 100644 spec/relations/attribute_spec.rb delete mode 100644 spec/relations/join_operation_spec.rb delete mode 100644 spec/relations/join_relation_spec.rb delete mode 100644 spec/relations/order_relation_spec.rb delete mode 100644 spec/relations/projection_relation_spec.rb delete mode 100644 spec/relations/range_relation_spec.rb delete mode 100644 spec/relations/relation_spec.rb delete mode 100644 spec/relations/rename_relation_spec.rb delete mode 100644 spec/relations/selection_relation_spec.rb delete mode 100644 spec/relations/table_relation_spec.rb delete mode 100644 spec/spec_helpers/be_like.rb delete mode 100644 spec/sql_builder/conditions_spec.rb delete mode 100644 spec/sql_builder/select_builder_spec.rb diff --git a/eager include.txt b/eager include.txt deleted file mode 100644 index 0c56323b40..0000000000 --- a/eager include.txt +++ /dev/null @@ -1,49 +0,0 @@ -User.find( :all, :include => { :photos => :camera } ) - -User.reflection[:photos].klass.reflection[:camera] - -users_photos_camera = User.relation << User.reflections[:photos].relation << Photo.reflections[:camera].relation - -users_photos_camera.each do |record| - User.bring_forth(record, :photos => :camera) -end - -def User.bring_forth(record, included = { :photos => :camera }) - user = @cache[ record % 'users.id' ] || User.instantiate(record % User.attributes) - user.photos.bring_forth(record, :camera) -end - -def User.photos.bring_forth(record, included = :camera) - photo = @cache[ record % 'photos.id' ] || Photo.instantiate(record % Photo.attributes) - photo.camera.bring_forth(record) -end - -def User.photos.camera.bring_forth(record, included = nil) - camera = @cache [ record % 'cameras.id' ] || Camera.instantiate(record % Camera.attributes) -end - -########################### - -# first, rename the attributes to remove ambiguity (analogous to c0_t0 stuff) -eager_loaded_user_cameras = @user_cameras.rename( - @user.attributes => @user.attributes.prefixed, - @photos.attributes => ..., - @cameras.attributes => ..., -) - -# second, bring forth!! -class Repository - def bring_forth(record, includes = []) - object = cache.get(record % klass.primary_key) { Klass.instantiate(record % Klass.attributes) } - includes.each do |include| - case include - when Symbol - object.send(association = include).bring_forth(record) - when Hash - include.each do |association, nested_associations| - object.send(association).bring_forth(record, nested_associations) - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_relation.rb b/lib/active_relation.rb new file mode 100644 index 0000000000..0d7af7bfb8 --- /dev/null +++ b/lib/active_relation.rb @@ -0,0 +1,56 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) + +require 'rubygems' +require 'activesupport' +require 'activerecord' + +require 'active_relation/relations/relation' +require 'active_relation/relations/compound_relation' +require 'active_relation/relations/table_relation' +require 'active_relation/relations/join_operation' +require 'active_relation/relations/inner_join_operation' +require 'active_relation/relations/left_outer_join_operation' +require 'active_relation/relations/join_relation' +require 'active_relation/relations/inner_join_relation' +require 'active_relation/relations/left_outer_join_relation' +require 'active_relation/relations/attribute' +require 'active_relation/relations/projection_relation' +require 'active_relation/relations/selection_relation' +require 'active_relation/relations/order_relation' +require 'active_relation/relations/range_relation' +require 'active_relation/relations/rename_relation' +require 'active_relation/relations/join' +require 'active_relation/relations/deletion_relation' +require 'active_relation/relations/insertion_relation' + +require 'active_relation/predicates/predicate' +require 'active_relation/predicates/binary_predicate' +require 'active_relation/predicates/equality_predicate' +require 'active_relation/predicates/less_than_predicate' +require 'active_relation/predicates/less_than_or_equal_to_predicate' +require 'active_relation/predicates/greater_than_predicate' +require 'active_relation/predicates/greater_than_or_equal_to_predicate' +require 'active_relation/predicates/range_inclusion_predicate' +require 'active_relation/predicates/relation_inclusion_predicate' +require 'active_relation/predicates/match_predicate' + +require 'active_relation/extensions/range' +require 'active_relation/extensions/object' +require 'active_relation/extensions/array' +require 'active_relation/extensions/base' +require 'active_relation/extensions/hash' + +require 'active_relation/sql_builder/sql_builder' +require 'active_relation/sql_builder/select_builder' +require 'active_relation/sql_builder/delete_builder' +require 'active_relation/sql_builder/insert_builder' +require 'active_relation/sql_builder/joins_builder' +require 'active_relation/sql_builder/join_builder' +require 'active_relation/sql_builder/inner_join_builder' +require 'active_relation/sql_builder/left_outer_join_builder' +require 'active_relation/sql_builder/equals_condition_builder' +require 'active_relation/sql_builder/conditions_builder' +require 'active_relation/sql_builder/order_builder' +require 'active_relation/sql_builder/columns_builder' +require 'active_relation/sql_builder/selects_builder' +require 'active_relation/sql_builder/values_builder' \ No newline at end of file diff --git a/lib/active_relation/extensions/array.rb b/lib/active_relation/extensions/array.rb new file mode 100644 index 0000000000..5b6d6d6abd --- /dev/null +++ b/lib/active_relation/extensions/array.rb @@ -0,0 +1,5 @@ +class Array + def to_hash + Hash[*flatten] + end +end \ No newline at end of file diff --git a/lib/active_relation/extensions/base.rb b/lib/active_relation/extensions/base.rb new file mode 100644 index 0000000000..0dbdef703f --- /dev/null +++ b/lib/active_relation/extensions/base.rb @@ -0,0 +1,47 @@ +class ActiveRecord::Base + class << self + def cache + @identity_map ||= IdentityMap.new + end + + def relation + @relation ||= TableRelation.new(table_name) + end + end + + class IdentityMap + def initialize + @map = {} + end + + def get(record, &block) + @map[record] ||= yield + end + end +end + +class ActiveRecord::Associations::BelongsToAssociation + def instantiate(record, joins = []) + @target = proxy_reflection.klass.instantiate(record, joins) + loaded + end + + # this basically disables belongs_to from loading themselves + def reload + @target = 'hack' + end +end + +class ActiveRecord::Associations::AssociationCollection + def instantiate(record, joins = []) + @target << proxy_reflection.klass.instantiate(record, joins) + loaded # technically, this isn't true. doesn't matter though + end +end + +class ActiveRecord::Associations::HasManyThroughAssociation + def instantiate(record, joins = []) + @target << proxy_reflection.klass.instantiate(record, joins) + loaded # again, not really true. + end +end \ No newline at end of file diff --git a/lib/active_relation/extensions/hash.rb b/lib/active_relation/extensions/hash.rb new file mode 100644 index 0000000000..f643ac17ab --- /dev/null +++ b/lib/active_relation/extensions/hash.rb @@ -0,0 +1,13 @@ +class Hash + def alias(&block) + inject({}) do |aliased, (key, value)| + aliased.merge(yield(key) => value) + end + end + + def to_sql(builder = ValuesBuilder.new) + builder.call do + row *values + end + end +end \ No newline at end of file diff --git a/lib/active_relation/extensions/object.rb b/lib/active_relation/extensions/object.rb new file mode 100644 index 0000000000..c241581f86 --- /dev/null +++ b/lib/active_relation/extensions/object.rb @@ -0,0 +1,12 @@ +class Object + def qualify + self + end + + def to_sql(builder = EqualsConditionBuilder.new) + me = self + builder.call do + value me.to_s + 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 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/active_relation/predicates/binary_predicate.rb b/lib/active_relation/predicates/binary_predicate.rb new file mode 100644 index 0000000000..f5c420c833 --- /dev/null +++ b/lib/active_relation/predicates/binary_predicate.rb @@ -0,0 +1,25 @@ +class BinaryPredicate < Predicate + attr_reader :attribute1, :attribute2 + + def initialize(attribute1, attribute2) + @attribute1, @attribute2 = attribute1, attribute2 + end + + def ==(other) + super and + (attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) + end + + def qualify + self.class.new(attribute1.qualify, attribute2.qualify) + end + + def to_sql(builder = ConditionsBuilder.new) + builder.call do + send(predicate_name) do + attribute1.to_sql(self) + attribute2.to_sql(self) + end + end + end +end \ No newline at end of file diff --git a/lib/active_relation/predicates/equality_predicate.rb b/lib/active_relation/predicates/equality_predicate.rb new file mode 100644 index 0000000000..7040c45f67 --- /dev/null +++ b/lib/active_relation/predicates/equality_predicate.rb @@ -0,0 +1,12 @@ +class EqualityPredicate < BinaryPredicate + def ==(other) + self.class == other.class and + ((attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) or + (attribute1.eql?(other.attribute2) and attribute2.eql?(other.attribute1))) + end + + protected + def predicate_name + :equals + end +end \ No newline at end of file diff --git a/lib/active_relation/predicates/greater_than_or_equal_to_predicate.rb b/lib/active_relation/predicates/greater_than_or_equal_to_predicate.rb new file mode 100644 index 0000000000..49127c312c --- /dev/null +++ b/lib/active_relation/predicates/greater_than_or_equal_to_predicate.rb @@ -0,0 +1,2 @@ +class GreaterThanOrEqualToPredicate < BinaryPredicate +end \ No newline at end of file diff --git a/lib/active_relation/predicates/greater_than_predicate.rb b/lib/active_relation/predicates/greater_than_predicate.rb new file mode 100644 index 0000000000..03aecaed62 --- /dev/null +++ b/lib/active_relation/predicates/greater_than_predicate.rb @@ -0,0 +1,2 @@ +class GreaterThanPredicate < BinaryPredicate +end \ No newline at end of file diff --git a/lib/active_relation/predicates/less_than_or_equal_to_predicate.rb b/lib/active_relation/predicates/less_than_or_equal_to_predicate.rb new file mode 100644 index 0000000000..fee6ea7f35 --- /dev/null +++ b/lib/active_relation/predicates/less_than_or_equal_to_predicate.rb @@ -0,0 +1,2 @@ +class LessThanOrEqualToPredicate < BinaryPredicate +end \ No newline at end of file diff --git a/lib/active_relation/predicates/less_than_predicate.rb b/lib/active_relation/predicates/less_than_predicate.rb new file mode 100644 index 0000000000..03cbdcf000 --- /dev/null +++ b/lib/active_relation/predicates/less_than_predicate.rb @@ -0,0 +1,2 @@ +class LessThanPredicate < BinaryPredicate +end \ No newline at end of file diff --git a/lib/active_relation/predicates/match_predicate.rb b/lib/active_relation/predicates/match_predicate.rb new file mode 100644 index 0000000000..90a13090d4 --- /dev/null +++ b/lib/active_relation/predicates/match_predicate.rb @@ -0,0 +1,7 @@ +class MatchPredicate < Predicate + attr_reader :attribute, :regexp + + def initialize(attribute, regexp) + @attribute, @regexp = attribute, regexp + end +end \ No newline at end of file diff --git a/lib/active_relation/predicates/predicate.rb b/lib/active_relation/predicates/predicate.rb new file mode 100644 index 0000000000..4c395a3fdc --- /dev/null +++ b/lib/active_relation/predicates/predicate.rb @@ -0,0 +1,5 @@ +class Predicate + def ==(other) + self.class == other.class + end +end \ No newline at end of file diff --git a/lib/active_relation/predicates/range_inclusion_predicate.rb b/lib/active_relation/predicates/range_inclusion_predicate.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/active_relation/predicates/relation_inclusion_predicate.rb b/lib/active_relation/predicates/relation_inclusion_predicate.rb new file mode 100644 index 0000000000..5881a85d99 --- /dev/null +++ b/lib/active_relation/predicates/relation_inclusion_predicate.rb @@ -0,0 +1,11 @@ +class RelationInclusionPredicate < Predicate + attr_reader :attribute, :relation + + def initialize(attribute, relation) + @attribute, @relation = attribute, relation + end + + def ==(other) + super and attribute == other.attribute and relation == other.relation + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/attribute.rb b/lib/active_relation/relations/attribute.rb new file mode 100644 index 0000000000..7583553b80 --- /dev/null +++ b/lib/active_relation/relations/attribute.rb @@ -0,0 +1,56 @@ +class Attribute + attr_reader :relation, :name, :aliaz + + def initialize(relation, name, aliaz = nil) + @relation, @name, @aliaz = relation, name, aliaz + end + + def aliazz(aliaz) + Attribute.new(relation, name, aliaz) + end + + def qualified_name + "#{relation.table}.#{name}" + end + + def qualify + aliazz(qualified_name) + end + + module Predications + def eql?(other) + relation == other.relation and name == other.name and aliaz == other.aliaz + end + + def ==(other) + EqualityPredicate.new(self, other) + end + + def <(other) + LessThanPredicate.new(self, other) + end + + def <=(other) + LessThanOrEqualToPredicate.new(self, other) + end + + def >(other) + GreaterThanPredicate.new(self, other) + end + + def >=(other) + GreaterThanOrEqualToPredicate.new(self, other) + end + + def =~(regexp) + MatchPredicate.new(self, regexp) + end + end + include Predications + + def to_sql(builder = SelectsBuilder.new) + builder.call do + column relation.table, name, aliaz + end + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/compound_relation.rb b/lib/active_relation/relations/compound_relation.rb new file mode 100644 index 0000000000..fe92905d92 --- /dev/null +++ b/lib/active_relation/relations/compound_relation.rb @@ -0,0 +1,3 @@ +class CompoundRelation < Relation + delegate :attributes, :attribute, :joins, :selects, :orders, :table, :inserts, :to => :relation +end \ No newline at end of file diff --git a/lib/active_relation/relations/deletion_relation.rb b/lib/active_relation/relations/deletion_relation.rb new file mode 100644 index 0000000000..e060efd5f9 --- /dev/null +++ b/lib/active_relation/relations/deletion_relation.rb @@ -0,0 +1,22 @@ +class DeletionRelation < CompoundRelation + attr_reader :relation + + def ==(other) + relation == other.relation + end + + def initialize(relation) + @relation = relation + end + + def to_sql(builder = DeleteBuilder.new) + builder.call do + delete + from table + where do + selects.each { |s| s.to_sql(self) } + end + end + end + +end \ No newline at end of file diff --git a/lib/active_relation/relations/inner_join_operation.rb b/lib/active_relation/relations/inner_join_operation.rb new file mode 100644 index 0000000000..6b5c5ce8d0 --- /dev/null +++ b/lib/active_relation/relations/inner_join_operation.rb @@ -0,0 +1,6 @@ +class InnerJoinOperation < JoinOperation + protected + def relation_class + InnerJoinRelation + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/inner_join_relation.rb b/lib/active_relation/relations/inner_join_relation.rb new file mode 100644 index 0000000000..5e58f241f8 --- /dev/null +++ b/lib/active_relation/relations/inner_join_relation.rb @@ -0,0 +1,6 @@ +class InnerJoinRelation < JoinRelation + protected + def join_type + :inner_join + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/insertion_relation.rb b/lib/active_relation/relations/insertion_relation.rb new file mode 100644 index 0000000000..84752d13f9 --- /dev/null +++ b/lib/active_relation/relations/insertion_relation.rb @@ -0,0 +1,29 @@ +class InsertionRelation < CompoundRelation + attr_reader :relation, :tuple + + def initialize(relation, tuple) + @relation, @tuple = relation, tuple + end + + def to_sql(builder = InsertBuilder.new) + builder.call do + insert + into table + columns do + tuple.keys.each { |attribute| attribute.to_sql(self) } + end + values do + inserts.each { |insert| insert.to_sql(self) } + end + end + end + + def ==(other) + relation == other.relation and tuple == other.tuple + end + + protected + def inserts + relation.inserts + [tuple] + 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..9a6196deac --- /dev/null +++ b/lib/active_relation/relations/join.rb @@ -0,0 +1,15 @@ +class Join + attr_reader :relation1, :relation2, :predicates, :join_type + + def initialize(relation1, relation2, predicates, join_type) + @relation1, @relation2, @predicates, @join_type = relation1, relation2, predicates, join_type + end + + def to_sql(builder = JoinsBuilder.new) + builder.call do + send(join_type, relation2.table) do + predicates.each { |p| p.to_sql(self) } + end + end + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/join_operation.rb b/lib/active_relation/relations/join_operation.rb new file mode 100644 index 0000000000..2b4548a041 --- /dev/null +++ b/lib/active_relation/relations/join_operation.rb @@ -0,0 +1,16 @@ +class JoinOperation + attr_reader :relation1, :relation2 + + def initialize(relation1, relation2) + @relation1, @relation2 = relation1, relation2 + end + + def on(*predicates) + relation_class.new(relation1, relation2, *predicates) + end + + def ==(other) + (relation1 == other.relation1 and relation2 == other.relation2) or + (relation1 == other.relation2 and relation2 == other.relation1) + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/join_relation.rb b/lib/active_relation/relations/join_relation.rb new file mode 100644 index 0000000000..79c8a915b8 --- /dev/null +++ b/lib/active_relation/relations/join_relation.rb @@ -0,0 +1,36 @@ +class JoinRelation < Relation + attr_reader :relation1, :relation2, :predicates + + def initialize(relation1, relation2, *predicates) + @relation1, @relation2, @predicates = 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 + self.class.new(relation1.qualify, relation2.qualify, *predicates.collect(&:qualify)) + end + + protected + def joins + relation1.joins + relation2.joins + [Join.new(relation1, relation2, predicates, join_type)] + 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 +end \ No newline at end of file diff --git a/lib/active_relation/relations/left_outer_join_operation.rb b/lib/active_relation/relations/left_outer_join_operation.rb new file mode 100644 index 0000000000..fbb2a4e2ed --- /dev/null +++ b/lib/active_relation/relations/left_outer_join_operation.rb @@ -0,0 +1,6 @@ +class LeftOuterJoinOperation < JoinOperation + protected + def relation_class + LeftOuterJoinRelation + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/left_outer_join_relation.rb b/lib/active_relation/relations/left_outer_join_relation.rb new file mode 100644 index 0000000000..6d13d8da07 --- /dev/null +++ b/lib/active_relation/relations/left_outer_join_relation.rb @@ -0,0 +1,6 @@ +class LeftOuterJoinRelation < JoinRelation + protected + def join_type + :left_outer_join + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/order_relation.rb b/lib/active_relation/relations/order_relation.rb new file mode 100644 index 0000000000..b39dc45c3f --- /dev/null +++ b/lib/active_relation/relations/order_relation.rb @@ -0,0 +1,25 @@ +class OrderRelation < CompoundRelation + 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 + OrderRelation.new(relation.qualify, *attributes.collect { |a| a.qualify }) + end + + def to_sql(builder = SelectBuilder.new) + relation.to_sql(builder).call do + attributes.each do |attribute| + order_by do + attribute.to_sql(self) + end + end + end + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/projection_relation.rb b/lib/active_relation/relations/projection_relation.rb new file mode 100644 index 0000000000..1a0e8dbfe4 --- /dev/null +++ b/lib/active_relation/relations/projection_relation.rb @@ -0,0 +1,23 @@ +class ProjectionRelation < Relation + 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 + ProjectionRelation.new(relation.qualify, *attributes.collect(&:qualify)) + end + + def to_sql(builder = SelectBuilder.new) + relation.to_sql(builder).call do + select do + attributes.collect { |a| a.to_sql(self) } + end + end + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/range_relation.rb b/lib/active_relation/relations/range_relation.rb new file mode 100644 index 0000000000..9225d5615b --- /dev/null +++ b/lib/active_relation/relations/range_relation.rb @@ -0,0 +1,18 @@ +class RangeRelation < Relation + attr_reader :relation, :range + + def initialize(relation, range) + @relation, @range = relation, range + end + + def ==(other) + relation == other.relation and range == other.range + end + + def to_sql(builder = SelectBuilder.new) + relation.to_sql(builder).call do + limit range.last - range.first + 1 + offset range.first + 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 new file mode 100644 index 0000000000..be6ee760a5 --- /dev/null +++ b/lib/active_relation/relations/relation.rb @@ -0,0 +1,91 @@ +class Relation + 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) + InnerJoinOperation.new(self, other) + end + + def <<(other) + LeftOuterJoinOperation.new(self, other) + end + + def [](index) + case index + when Symbol + attribute(index) + when Range + RangeRelation.new(self, index) + end + end + + def include?(attribute) + RelationInclusionPredicate.new(attribute, self) + end + + def select(*predicates) + SelectionRelation.new(self, *predicates) + end + + def project(*attributes) + ProjectionRelation.new(self, *attributes) + end + + def order(*attributes) + OrderRelation.new(self, *attributes) + end + + def rename(attribute, aliaz) + RenameRelation.new(self, attribute => aliaz) + end + + def insert(tuple) + InsertionRelation.new(self, tuple) + end + + def delete + DeletionRelation.new(self) + end + end + include Operations + + def connection + ActiveRecord::Base.connection + end + + def to_sql(builder = SelectBuilder.new) + builder.call do + select do + attributes.each { |a| a.to_sql(self) } + end + from table do + joins.each { |j| j.to_sql(self) } + end + where do + selects.each { |s| s.to_sql(self) } + end + order_by do + orders.each { |o| o.to_sql(self) } + end + end + end + delegate :to_s, :to => :to_sql + + protected + def attributes; [] end + def joins; [] end + def selects; [] end + def orders; [] end + def inserts; [] end +end \ No newline at end of file diff --git a/lib/active_relation/relations/rename_relation.rb b/lib/active_relation/relations/rename_relation.rb new file mode 100644 index 0000000000..8acf5091b2 --- /dev/null +++ b/lib/active_relation/relations/rename_relation.rb @@ -0,0 +1,34 @@ +class RenameRelation < CompoundRelation + attr_reader :relation, :schmattribute, :aliaz + + def initialize(relation, renames) + @schmattribute, @aliaz = renames.shift + @relation = renames.empty?? relation : RenameRelation.new(relation, renames) + end + + def ==(other) + relation == other.relation and schmattribute.eql?(other.schmattribute) and aliaz == other.aliaz + end + + def attributes + relation.attributes.collect { |a| substitute(a) } + end + + def qualify + RenameRelation.new(relation.qualify, schmattribute.qualify => aliaz) + end + + protected + def attribute(name) + case + when name == aliaz then schmattribute.aliazz(aliaz) + when relation[name].eql?(schmattribute) then nil + else relation[name] + end + end + + private + def substitute(a) + a.eql?(schmattribute) ? a.aliazz(aliaz) : a + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/selection_relation.rb b/lib/active_relation/relations/selection_relation.rb new file mode 100644 index 0000000000..77864efb28 --- /dev/null +++ b/lib/active_relation/relations/selection_relation.rb @@ -0,0 +1,21 @@ +class SelectionRelation < CompoundRelation + attr_reader :relation, :predicate + + def initialize(relation, *predicates) + @predicate = predicates.shift + @relation = predicates.empty?? relation : SelectionRelation.new(relation, *predicates) + end + + def ==(other) + relation == other.relation and predicate == other.predicate + end + + def qualify + SelectionRelation.new(relation.qualify, predicate.qualify) + end + + protected + def selects + relation.send(:selects) + [predicate] + end +end \ No newline at end of file diff --git a/lib/active_relation/relations/table_relation.rb b/lib/active_relation/relations/table_relation.rb new file mode 100644 index 0000000000..5a47ae7a34 --- /dev/null +++ b/lib/active_relation/relations/table_relation.rb @@ -0,0 +1,31 @@ +class TableRelation < Relation + attr_reader :table + + def initialize(table) + @table = table + end + + def attributes + attributes_by_name.values + end + + def qualify + RenameRelation.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 => Attribute.new(self, column.name.to_sym)) + end + end + + def qualifications + attributes.zip(attributes.collect(&:qualified_name)).to_hash + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/columns_builder.rb b/lib/active_relation/sql_builder/columns_builder.rb new file mode 100644 index 0000000000..a8a5d0e4ca --- /dev/null +++ b/lib/active_relation/sql_builder/columns_builder.rb @@ -0,0 +1,16 @@ +class ColumnsBuilder < SqlBuilder + def initialize(&block) + @columns = [] + super(&block) + end + + def to_s + @columns.join(', ') + end + + def column(table, column, aliaz = nil) + @columns << "#{quote_table_name(table)}.#{quote_column_name(column)}" + end + + delegate :blank?, :to => :@columns +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/conditions_builder.rb b/lib/active_relation/sql_builder/conditions_builder.rb new file mode 100644 index 0000000000..60430f65d8 --- /dev/null +++ b/lib/active_relation/sql_builder/conditions_builder.rb @@ -0,0 +1,20 @@ +class ConditionsBuilder < SqlBuilder + def initialize(&block) + @conditions = [] + super(&block) + end + + def equals(&block) + @conditions << EqualsConditionBuilder.new(&block) + end + + def value(value) + @conditions << value + end + + def to_s + @conditions.join(' AND ') + end + + delegate :blank?, :to => :@conditions +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/delete_builder.rb b/lib/active_relation/sql_builder/delete_builder.rb new file mode 100644 index 0000000000..2e8be94dfe --- /dev/null +++ b/lib/active_relation/sql_builder/delete_builder.rb @@ -0,0 +1,32 @@ +class DeleteBuilder < SqlBuilder + def delete + end + + def from(table) + @table = table + end + + def where(&block) + @conditions = ConditionsBuilder.new(&block) + end + + def to_s + [delete_clause, + from_clause, + where_clause + ].compact.join("\n") + end + + private + def delete_clause + "DELETE" + end + + def from_clause + "FROM #{quote_table_name(@table)}" + end + + def where_clause + "WHERE #{@conditions}" unless @conditions.blank? + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/equals_condition_builder.rb b/lib/active_relation/sql_builder/equals_condition_builder.rb new file mode 100644 index 0000000000..cfa919c34c --- /dev/null +++ b/lib/active_relation/sql_builder/equals_condition_builder.rb @@ -0,0 +1,18 @@ +class EqualsConditionBuilder < SqlBuilder + def initialize(&block) + @operands = [] + super(&block) + end + + def column(table, column, aliaz = nil) + @operands << "#{quote_table_name(table)}.#{quote_column_name(column)}" + end + + def value(value) + @operands << value + end + + def to_s + "#{@operands[0]} = #{@operands[1]}" + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/inner_join_builder.rb b/lib/active_relation/sql_builder/inner_join_builder.rb new file mode 100644 index 0000000000..6aec703325 --- /dev/null +++ b/lib/active_relation/sql_builder/inner_join_builder.rb @@ -0,0 +1,5 @@ +class InnerJoinBuilder < JoinBuilder + def join_type + "INNER JOIN" + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/insert_builder.rb b/lib/active_relation/sql_builder/insert_builder.rb new file mode 100644 index 0000000000..09deefad10 --- /dev/null +++ b/lib/active_relation/sql_builder/insert_builder.rb @@ -0,0 +1,41 @@ +class InsertBuilder < SqlBuilder + def insert + end + + def into(table) + @table = table + end + + def columns(&block) + @columns = ColumnsBuilder.new(&block) + end + + def values(&block) + @values = ValuesBuilder.new(&block) + end + + def to_s + [insert_clause, + into_clause, + columns_clause, + values_clause + ].compact.join("\n") + end + + private + def insert_clause + "INSERT" + end + + def into_clause + "INTO #{quote_table_name(@table)}" + end + + def values_clause + "VALUES #{@values}" unless @values.blank? + end + + def columns_clause + "(#{@columns})" unless @columns.blank? + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/join_builder.rb b/lib/active_relation/sql_builder/join_builder.rb new file mode 100644 index 0000000000..ef63d1fcb1 --- /dev/null +++ b/lib/active_relation/sql_builder/join_builder.rb @@ -0,0 +1,13 @@ +class JoinBuilder < SqlBuilder + def initialize(table, &block) + @table = table + @conditions = ConditionsBuilder.new + super(&block) + end + + delegate :call, :to => :@conditions + + def to_s + "#{join_type} #{quote_table_name(@table)} ON #{@conditions}" + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/joins_builder.rb b/lib/active_relation/sql_builder/joins_builder.rb new file mode 100644 index 0000000000..36a92e9922 --- /dev/null +++ b/lib/active_relation/sql_builder/joins_builder.rb @@ -0,0 +1,18 @@ +class JoinsBuilder < SqlBuilder + def initialize(&block) + @joins = [] + super(&block) + end + + def inner_join(table, &block) + @joins << InnerJoinBuilder.new(table, &block) + end + + def left_outer_join(table, &block) + @joins << LeftOuterJoinBuilder.new(table, &block) + end + + def to_s + @joins.join(' ') + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/left_outer_join_builder.rb b/lib/active_relation/sql_builder/left_outer_join_builder.rb new file mode 100644 index 0000000000..dad3f85810 --- /dev/null +++ b/lib/active_relation/sql_builder/left_outer_join_builder.rb @@ -0,0 +1,5 @@ +class LeftOuterJoinBuilder < JoinBuilder + def join_type + "LEFT OUTER JOIN" + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/order_builder.rb b/lib/active_relation/sql_builder/order_builder.rb new file mode 100644 index 0000000000..66a8cfdba9 --- /dev/null +++ b/lib/active_relation/sql_builder/order_builder.rb @@ -0,0 +1,16 @@ +class OrderBuilder < SqlBuilder + def initialize(&block) + @orders = [] + super(&block) + end + + def column(table, column, aliaz = nil) + @orders << "#{quote_table_name(table)}.#{quote_column_name(column)}" + end + + def to_s + @orders.join(', ') + end + + delegate :blank?, :to => :@orders +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/select_builder.rb b/lib/active_relation/sql_builder/select_builder.rb new file mode 100644 index 0000000000..57116cb64b --- /dev/null +++ b/lib/active_relation/sql_builder/select_builder.rb @@ -0,0 +1,61 @@ +class SelectBuilder < SqlBuilder + def select(&block) + @selects = SelectsBuilder.new(&block) + end + + def from(table, &block) + @table = table + @joins = JoinsBuilder.new(&block) + end + + def where(&block) + @conditions = ConditionsBuilder.new(&block) + end + + def order_by(&block) + @orders = OrderBuilder.new(&block) + end + + def limit(i, offset = nil) + @limit = i + offset(offset) if offset + end + + def offset(i) + @offset = i + end + + def to_s + [select_clause, + from_clause, + where_clause, + order_by_clause, + limit_clause, + offset_clause].compact.join("\n") + end + + private + def select_clause + "SELECT #{@selects}" unless @selects.blank? + end + + def from_clause + "FROM #{quote_table_name(@table)} #{@joins}" unless @table.blank? + end + + def where_clause + "WHERE #{@conditions}" unless @conditions.blank? + end + + def order_by_clause + "ORDER BY #{@orders}" unless @orders.blank? + end + + def limit_clause + "LIMIT #{@limit}" unless @limit.blank? + end + + def offset_clause + "OFFSET #{@offset}" unless @offset.blank? + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/selects_builder.rb b/lib/active_relation/sql_builder/selects_builder.rb new file mode 100644 index 0000000000..6ad06d0ae4 --- /dev/null +++ b/lib/active_relation/sql_builder/selects_builder.rb @@ -0,0 +1,9 @@ +class SelectsBuilder < ColumnsBuilder + def all + @columns << :* + end + + def column(table, column, aliaz = nil) + @columns << "#{quote_table_name(table)}.#{quote_column_name(column)}" + (aliaz ? " AS #{quote(aliaz)}" : '') + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/sql_builder.rb b/lib/active_relation/sql_builder/sql_builder.rb new file mode 100644 index 0000000000..c984444e41 --- /dev/null +++ b/lib/active_relation/sql_builder/sql_builder.rb @@ -0,0 +1,35 @@ +class SqlBuilder + def initialize(&block) + @callers = [] + call(&block) if block + end + + def method_missing(method, *args) + @callers.last.send(method, *args) + end + + def ==(other) + to_s == other.to_s + end + + def to_s + end + + def call(&block) + returning self do |builder| + @callers << eval("self", block.binding) + begin + instance_eval &block + ensure + @callers.pop + end + end + end + + private + delegate :quote_table_name, :quote_column_name, :quote, :to => :connection + + def connection + ActiveRecord::Base.connection + end +end \ No newline at end of file diff --git a/lib/active_relation/sql_builder/values_builder.rb b/lib/active_relation/sql_builder/values_builder.rb new file mode 100644 index 0000000000..f22b1e507e --- /dev/null +++ b/lib/active_relation/sql_builder/values_builder.rb @@ -0,0 +1,16 @@ +class ValuesBuilder < SqlBuilder + def initialize(&block) + @values = [] + super(&block) + end + + def row(*values) + @values << "(#{values.collect { |v| quote(v) }.join(', ')})" + end + + def to_s + @values.join(', ') + end + + delegate :blank?, :to => :@values +end \ No newline at end of file diff --git a/lib/sql_algebra.rb b/lib/sql_algebra.rb deleted file mode 100644 index fbd053541c..0000000000 --- a/lib/sql_algebra.rb +++ /dev/null @@ -1,49 +0,0 @@ -$LOAD_PATH.unshift(File.dirname(__FILE__)) -require 'rubygems' -require 'activesupport' -require 'activerecord' - -require 'sql_algebra/relations/relation' -require 'sql_algebra/relations/compound_relation' -require 'sql_algebra/relations/table_relation' -require 'sql_algebra/relations/join_operation' -require 'sql_algebra/relations/inner_join_operation' -require 'sql_algebra/relations/left_outer_join_operation' -require 'sql_algebra/relations/join_relation' -require 'sql_algebra/relations/inner_join_relation' -require 'sql_algebra/relations/left_outer_join_relation' -require 'sql_algebra/relations/attribute' -require 'sql_algebra/relations/projection_relation' -require 'sql_algebra/relations/selection_relation' -require 'sql_algebra/relations/order_relation' -require 'sql_algebra/relations/range_relation' -require 'sql_algebra/relations/rename_relation' -require 'sql_algebra/relations/join' - -require 'sql_algebra/predicates/predicate' -require 'sql_algebra/predicates/binary_predicate' -require 'sql_algebra/predicates/equality_predicate' -require 'sql_algebra/predicates/less_than_predicate' -require 'sql_algebra/predicates/less_than_or_equal_to_predicate' -require 'sql_algebra/predicates/greater_than_predicate' -require 'sql_algebra/predicates/greater_than_or_equal_to_predicate' -require 'sql_algebra/predicates/range_inclusion_predicate' -require 'sql_algebra/predicates/relation_inclusion_predicate' -require 'sql_algebra/predicates/match_predicate' - -require 'sql_algebra/extensions/range' -require 'sql_algebra/extensions/object' -require 'sql_algebra/extensions/array' -require 'sql_algebra/extensions/base' -require 'sql_algebra/extensions/hash' - -require 'sql_algebra/sql_builder/sql_builder' -require 'sql_algebra/sql_builder/select_builder' -require 'sql_algebra/sql_builder/joins_builder' -require 'sql_algebra/sql_builder/join_builder' -require 'sql_algebra/sql_builder/inner_join_builder' -require 'sql_algebra/sql_builder/left_outer_join_builder' -require 'sql_algebra/sql_builder/equals_condition_builder' -require 'sql_algebra/sql_builder/conditions_builder' -require 'sql_algebra/sql_builder/order_builder' -require 'sql_algebra/sql_builder/selects_builder' \ No newline at end of file diff --git a/lib/sql_algebra/extensions/array.rb b/lib/sql_algebra/extensions/array.rb deleted file mode 100644 index 5b6d6d6abd..0000000000 --- a/lib/sql_algebra/extensions/array.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Array - def to_hash - Hash[*flatten] - end -end \ No newline at end of file diff --git a/lib/sql_algebra/extensions/base.rb b/lib/sql_algebra/extensions/base.rb deleted file mode 100644 index 0dbdef703f..0000000000 --- a/lib/sql_algebra/extensions/base.rb +++ /dev/null @@ -1,47 +0,0 @@ -class ActiveRecord::Base - class << self - def cache - @identity_map ||= IdentityMap.new - end - - def relation - @relation ||= TableRelation.new(table_name) - end - end - - class IdentityMap - def initialize - @map = {} - end - - def get(record, &block) - @map[record] ||= yield - end - end -end - -class ActiveRecord::Associations::BelongsToAssociation - def instantiate(record, joins = []) - @target = proxy_reflection.klass.instantiate(record, joins) - loaded - end - - # this basically disables belongs_to from loading themselves - def reload - @target = 'hack' - end -end - -class ActiveRecord::Associations::AssociationCollection - def instantiate(record, joins = []) - @target << proxy_reflection.klass.instantiate(record, joins) - loaded # technically, this isn't true. doesn't matter though - end -end - -class ActiveRecord::Associations::HasManyThroughAssociation - def instantiate(record, joins = []) - @target << proxy_reflection.klass.instantiate(record, joins) - loaded # again, not really true. - end -end \ No newline at end of file diff --git a/lib/sql_algebra/extensions/hash.rb b/lib/sql_algebra/extensions/hash.rb deleted file mode 100644 index c83ee0d04f..0000000000 --- a/lib/sql_algebra/extensions/hash.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Hash - def alias(&block) - inject({}) do |aliased, (key, value)| - aliased.merge(yield(key) => value) - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/extensions/object.rb b/lib/sql_algebra/extensions/object.rb deleted file mode 100644 index c241581f86..0000000000 --- a/lib/sql_algebra/extensions/object.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Object - def qualify - self - end - - def to_sql(builder = EqualsConditionBuilder.new) - me = self - builder.call do - value me.to_s - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/extensions/range.rb b/lib/sql_algebra/extensions/range.rb deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/sql_algebra/predicates/binary_predicate.rb b/lib/sql_algebra/predicates/binary_predicate.rb deleted file mode 100644 index f5c420c833..0000000000 --- a/lib/sql_algebra/predicates/binary_predicate.rb +++ /dev/null @@ -1,25 +0,0 @@ -class BinaryPredicate < Predicate - attr_reader :attribute1, :attribute2 - - def initialize(attribute1, attribute2) - @attribute1, @attribute2 = attribute1, attribute2 - end - - def ==(other) - super and - (attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) - end - - def qualify - self.class.new(attribute1.qualify, attribute2.qualify) - end - - def to_sql(builder = ConditionsBuilder.new) - builder.call do - send(predicate_name) do - attribute1.to_sql(self) - attribute2.to_sql(self) - end - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/equality_predicate.rb b/lib/sql_algebra/predicates/equality_predicate.rb deleted file mode 100644 index 7040c45f67..0000000000 --- a/lib/sql_algebra/predicates/equality_predicate.rb +++ /dev/null @@ -1,12 +0,0 @@ -class EqualityPredicate < BinaryPredicate - def ==(other) - self.class == other.class and - ((attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) or - (attribute1.eql?(other.attribute2) and attribute2.eql?(other.attribute1))) - end - - protected - def predicate_name - :equals - end -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/greater_than_or_equal_to_predicate.rb b/lib/sql_algebra/predicates/greater_than_or_equal_to_predicate.rb deleted file mode 100644 index 49127c312c..0000000000 --- a/lib/sql_algebra/predicates/greater_than_or_equal_to_predicate.rb +++ /dev/null @@ -1,2 +0,0 @@ -class GreaterThanOrEqualToPredicate < BinaryPredicate -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/greater_than_predicate.rb b/lib/sql_algebra/predicates/greater_than_predicate.rb deleted file mode 100644 index 03aecaed62..0000000000 --- a/lib/sql_algebra/predicates/greater_than_predicate.rb +++ /dev/null @@ -1,2 +0,0 @@ -class GreaterThanPredicate < BinaryPredicate -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/less_than_or_equal_to_predicate.rb b/lib/sql_algebra/predicates/less_than_or_equal_to_predicate.rb deleted file mode 100644 index fee6ea7f35..0000000000 --- a/lib/sql_algebra/predicates/less_than_or_equal_to_predicate.rb +++ /dev/null @@ -1,2 +0,0 @@ -class LessThanOrEqualToPredicate < BinaryPredicate -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/less_than_predicate.rb b/lib/sql_algebra/predicates/less_than_predicate.rb deleted file mode 100644 index 03cbdcf000..0000000000 --- a/lib/sql_algebra/predicates/less_than_predicate.rb +++ /dev/null @@ -1,2 +0,0 @@ -class LessThanPredicate < BinaryPredicate -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/match_predicate.rb b/lib/sql_algebra/predicates/match_predicate.rb deleted file mode 100644 index 90a13090d4..0000000000 --- a/lib/sql_algebra/predicates/match_predicate.rb +++ /dev/null @@ -1,7 +0,0 @@ -class MatchPredicate < Predicate - attr_reader :attribute, :regexp - - def initialize(attribute, regexp) - @attribute, @regexp = attribute, regexp - end -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/predicate.rb b/lib/sql_algebra/predicates/predicate.rb deleted file mode 100644 index 4c395a3fdc..0000000000 --- a/lib/sql_algebra/predicates/predicate.rb +++ /dev/null @@ -1,5 +0,0 @@ -class Predicate - def ==(other) - self.class == other.class - end -end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/range_inclusion_predicate.rb b/lib/sql_algebra/predicates/range_inclusion_predicate.rb deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/sql_algebra/predicates/relation_inclusion_predicate.rb b/lib/sql_algebra/predicates/relation_inclusion_predicate.rb deleted file mode 100644 index 5881a85d99..0000000000 --- a/lib/sql_algebra/predicates/relation_inclusion_predicate.rb +++ /dev/null @@ -1,11 +0,0 @@ -class RelationInclusionPredicate < Predicate - attr_reader :attribute, :relation - - def initialize(attribute, relation) - @attribute, @relation = attribute, relation - end - - def ==(other) - super and attribute == other.attribute and relation == other.relation - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/attribute.rb b/lib/sql_algebra/relations/attribute.rb deleted file mode 100644 index 7583553b80..0000000000 --- a/lib/sql_algebra/relations/attribute.rb +++ /dev/null @@ -1,56 +0,0 @@ -class Attribute - attr_reader :relation, :name, :aliaz - - def initialize(relation, name, aliaz = nil) - @relation, @name, @aliaz = relation, name, aliaz - end - - def aliazz(aliaz) - Attribute.new(relation, name, aliaz) - end - - def qualified_name - "#{relation.table}.#{name}" - end - - def qualify - aliazz(qualified_name) - end - - module Predications - def eql?(other) - relation == other.relation and name == other.name and aliaz == other.aliaz - end - - def ==(other) - EqualityPredicate.new(self, other) - end - - def <(other) - LessThanPredicate.new(self, other) - end - - def <=(other) - LessThanOrEqualToPredicate.new(self, other) - end - - def >(other) - GreaterThanPredicate.new(self, other) - end - - def >=(other) - GreaterThanOrEqualToPredicate.new(self, other) - end - - def =~(regexp) - MatchPredicate.new(self, regexp) - end - end - include Predications - - def to_sql(builder = SelectsBuilder.new) - builder.call do - column relation.table, name, aliaz - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/compound_relation.rb b/lib/sql_algebra/relations/compound_relation.rb deleted file mode 100644 index a8e9a41b5e..0000000000 --- a/lib/sql_algebra/relations/compound_relation.rb +++ /dev/null @@ -1,3 +0,0 @@ -class CompoundRelation < Relation - delegate :attributes, :attribute, :joins, :select, :orders, :table, :to => :relation -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/inner_join_operation.rb b/lib/sql_algebra/relations/inner_join_operation.rb deleted file mode 100644 index 6b5c5ce8d0..0000000000 --- a/lib/sql_algebra/relations/inner_join_operation.rb +++ /dev/null @@ -1,6 +0,0 @@ -class InnerJoinOperation < JoinOperation - protected - def relation_class - InnerJoinRelation - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/inner_join_relation.rb b/lib/sql_algebra/relations/inner_join_relation.rb deleted file mode 100644 index 5e58f241f8..0000000000 --- a/lib/sql_algebra/relations/inner_join_relation.rb +++ /dev/null @@ -1,6 +0,0 @@ -class InnerJoinRelation < JoinRelation - protected - def join_type - :inner_join - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/join.rb b/lib/sql_algebra/relations/join.rb deleted file mode 100644 index 9a6196deac..0000000000 --- a/lib/sql_algebra/relations/join.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Join - attr_reader :relation1, :relation2, :predicates, :join_type - - def initialize(relation1, relation2, predicates, join_type) - @relation1, @relation2, @predicates, @join_type = relation1, relation2, predicates, join_type - end - - def to_sql(builder = JoinsBuilder.new) - builder.call do - send(join_type, relation2.table) do - predicates.each { |p| p.to_sql(self) } - end - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/join_operation.rb b/lib/sql_algebra/relations/join_operation.rb deleted file mode 100644 index 2b4548a041..0000000000 --- a/lib/sql_algebra/relations/join_operation.rb +++ /dev/null @@ -1,16 +0,0 @@ -class JoinOperation - attr_reader :relation1, :relation2 - - def initialize(relation1, relation2) - @relation1, @relation2 = relation1, relation2 - end - - def on(*predicates) - relation_class.new(relation1, relation2, *predicates) - end - - def ==(other) - (relation1 == other.relation1 and relation2 == other.relation2) or - (relation1 == other.relation2 and relation2 == other.relation1) - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/join_relation.rb b/lib/sql_algebra/relations/join_relation.rb deleted file mode 100644 index 79c8a915b8..0000000000 --- a/lib/sql_algebra/relations/join_relation.rb +++ /dev/null @@ -1,36 +0,0 @@ -class JoinRelation < Relation - attr_reader :relation1, :relation2, :predicates - - def initialize(relation1, relation2, *predicates) - @relation1, @relation2, @predicates = 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 - self.class.new(relation1.qualify, relation2.qualify, *predicates.collect(&:qualify)) - end - - protected - def joins - relation1.joins + relation2.joins + [Join.new(relation1, relation2, predicates, join_type)] - 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 -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/left_outer_join_operation.rb b/lib/sql_algebra/relations/left_outer_join_operation.rb deleted file mode 100644 index fbb2a4e2ed..0000000000 --- a/lib/sql_algebra/relations/left_outer_join_operation.rb +++ /dev/null @@ -1,6 +0,0 @@ -class LeftOuterJoinOperation < JoinOperation - protected - def relation_class - LeftOuterJoinRelation - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/left_outer_join_relation.rb b/lib/sql_algebra/relations/left_outer_join_relation.rb deleted file mode 100644 index 6d13d8da07..0000000000 --- a/lib/sql_algebra/relations/left_outer_join_relation.rb +++ /dev/null @@ -1,6 +0,0 @@ -class LeftOuterJoinRelation < JoinRelation - protected - def join_type - :left_outer_join - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/order_relation.rb b/lib/sql_algebra/relations/order_relation.rb deleted file mode 100644 index b39dc45c3f..0000000000 --- a/lib/sql_algebra/relations/order_relation.rb +++ /dev/null @@ -1,25 +0,0 @@ -class OrderRelation < CompoundRelation - 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 - OrderRelation.new(relation.qualify, *attributes.collect { |a| a.qualify }) - end - - def to_sql(builder = SelectBuilder.new) - relation.to_sql(builder).call do - attributes.each do |attribute| - order_by do - attribute.to_sql(self) - end - end - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/projection_relation.rb b/lib/sql_algebra/relations/projection_relation.rb deleted file mode 100644 index 1a0e8dbfe4..0000000000 --- a/lib/sql_algebra/relations/projection_relation.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ProjectionRelation < Relation - 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 - ProjectionRelation.new(relation.qualify, *attributes.collect(&:qualify)) - end - - def to_sql(builder = SelectBuilder.new) - relation.to_sql(builder).call do - select do - attributes.collect { |a| a.to_sql(self) } - end - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/range_relation.rb b/lib/sql_algebra/relations/range_relation.rb deleted file mode 100644 index 9225d5615b..0000000000 --- a/lib/sql_algebra/relations/range_relation.rb +++ /dev/null @@ -1,18 +0,0 @@ -class RangeRelation < Relation - attr_reader :relation, :range - - def initialize(relation, range) - @relation, @range = relation, range - end - - def ==(other) - relation == other.relation and range == other.range - end - - def to_sql(builder = SelectBuilder.new) - relation.to_sql(builder).call do - limit range.last - range.first + 1 - offset range.first - end - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/relation.rb b/lib/sql_algebra/relations/relation.rb deleted file mode 100644 index 8efe0c7d9f..0000000000 --- a/lib/sql_algebra/relations/relation.rb +++ /dev/null @@ -1,83 +0,0 @@ -class Relation - 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) - InnerJoinOperation.new(self, other) - end - - def <<(other) - LeftOuterJoinOperation.new(self, other) - end - - def [](index) - case index - when Symbol - attribute(index) - when Range - RangeRelation.new(self, index) - end - end - - def include?(attribute) - RelationInclusionPredicate.new(attribute, self) - end - - def select(*predicates) - SelectionRelation.new(self, *predicates) - end - - def project(*attributes) - ProjectionRelation.new(self, *attributes) - end - - def order(*attributes) - OrderRelation.new(self, *attributes) - end - - def rename(attribute, aliaz) - RenameRelation.new(self, attribute => aliaz) - end - end - include Operations - - def connection - ActiveRecord::Base.connection - end - - def to_sql(builder = SelectBuilder.new) - builder.call do - select do - attributes.each { |a| a.to_sql(self) } - end - from table do - joins.each { |j| j.to_sql(self) } - end - where do - selects.each { |s| s.to_sql(self) } - end - order_by do - orders.each { |o| o.to_sql(self) } - end - end - end - delegate :to_s, :to => :to_sql - - protected - def attributes; [] end - def joins; [] end - def selects; [] end - def orders; [] end - -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/rename_relation.rb b/lib/sql_algebra/relations/rename_relation.rb deleted file mode 100644 index 8acf5091b2..0000000000 --- a/lib/sql_algebra/relations/rename_relation.rb +++ /dev/null @@ -1,34 +0,0 @@ -class RenameRelation < CompoundRelation - attr_reader :relation, :schmattribute, :aliaz - - def initialize(relation, renames) - @schmattribute, @aliaz = renames.shift - @relation = renames.empty?? relation : RenameRelation.new(relation, renames) - end - - def ==(other) - relation == other.relation and schmattribute.eql?(other.schmattribute) and aliaz == other.aliaz - end - - def attributes - relation.attributes.collect { |a| substitute(a) } - end - - def qualify - RenameRelation.new(relation.qualify, schmattribute.qualify => aliaz) - end - - protected - def attribute(name) - case - when name == aliaz then schmattribute.aliazz(aliaz) - when relation[name].eql?(schmattribute) then nil - else relation[name] - end - end - - private - def substitute(a) - a.eql?(schmattribute) ? a.aliazz(aliaz) : a - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/selection_relation.rb b/lib/sql_algebra/relations/selection_relation.rb deleted file mode 100644 index dcf5f4745f..0000000000 --- a/lib/sql_algebra/relations/selection_relation.rb +++ /dev/null @@ -1,21 +0,0 @@ -class SelectionRelation < CompoundRelation - attr_reader :relation, :predicate - - def initialize(relation, *predicates) - @predicate = predicates.shift - @relation = predicates.empty?? relation : SelectionRelation.new(relation, *predicates) - end - - def ==(other) - relation == other.relation and predicate == other.predicate - end - - def qualify - SelectionRelation.new(relation.qualify, predicate.qualify) - end - - protected - def selects - [predicate] - end -end \ No newline at end of file diff --git a/lib/sql_algebra/relations/table_relation.rb b/lib/sql_algebra/relations/table_relation.rb deleted file mode 100644 index 5a47ae7a34..0000000000 --- a/lib/sql_algebra/relations/table_relation.rb +++ /dev/null @@ -1,31 +0,0 @@ -class TableRelation < Relation - attr_reader :table - - def initialize(table) - @table = table - end - - def attributes - attributes_by_name.values - end - - def qualify - RenameRelation.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 => Attribute.new(self, column.name.to_sym)) - end - end - - def qualifications - attributes.zip(attributes.collect(&:qualified_name)).to_hash - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/conditions_builder.rb b/lib/sql_algebra/sql_builder/conditions_builder.rb deleted file mode 100644 index 5d42a36cec..0000000000 --- a/lib/sql_algebra/sql_builder/conditions_builder.rb +++ /dev/null @@ -1,16 +0,0 @@ -class ConditionsBuilder < SqlBuilder - def initialize(&block) - @conditions = [] - super(&block) - end - - def equals(&block) - @conditions << EqualsConditionBuilder.new(&block) - end - - def to_s - @conditions.join(' AND ') - end - - delegate :blank?, :to => :@conditions -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/equals_condition_builder.rb b/lib/sql_algebra/sql_builder/equals_condition_builder.rb deleted file mode 100644 index cfa919c34c..0000000000 --- a/lib/sql_algebra/sql_builder/equals_condition_builder.rb +++ /dev/null @@ -1,18 +0,0 @@ -class EqualsConditionBuilder < SqlBuilder - def initialize(&block) - @operands = [] - super(&block) - end - - def column(table, column, aliaz = nil) - @operands << "#{quote_table_name(table)}.#{quote_column_name(column)}" - end - - def value(value) - @operands << value - end - - def to_s - "#{@operands[0]} = #{@operands[1]}" - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/inner_join_builder.rb b/lib/sql_algebra/sql_builder/inner_join_builder.rb deleted file mode 100644 index 6aec703325..0000000000 --- a/lib/sql_algebra/sql_builder/inner_join_builder.rb +++ /dev/null @@ -1,5 +0,0 @@ -class InnerJoinBuilder < JoinBuilder - def join_type - "INNER JOIN" - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/join_builder.rb b/lib/sql_algebra/sql_builder/join_builder.rb deleted file mode 100644 index ef63d1fcb1..0000000000 --- a/lib/sql_algebra/sql_builder/join_builder.rb +++ /dev/null @@ -1,13 +0,0 @@ -class JoinBuilder < SqlBuilder - def initialize(table, &block) - @table = table - @conditions = ConditionsBuilder.new - super(&block) - end - - delegate :call, :to => :@conditions - - def to_s - "#{join_type} #{quote_table_name(@table)} ON #{@conditions}" - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/joins_builder.rb b/lib/sql_algebra/sql_builder/joins_builder.rb deleted file mode 100644 index 36a92e9922..0000000000 --- a/lib/sql_algebra/sql_builder/joins_builder.rb +++ /dev/null @@ -1,18 +0,0 @@ -class JoinsBuilder < SqlBuilder - def initialize(&block) - @joins = [] - super(&block) - end - - def inner_join(table, &block) - @joins << InnerJoinBuilder.new(table, &block) - end - - def left_outer_join(table, &block) - @joins << LeftOuterJoinBuilder.new(table, &block) - end - - def to_s - @joins.join(' ') - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/left_outer_join_builder.rb b/lib/sql_algebra/sql_builder/left_outer_join_builder.rb deleted file mode 100644 index dad3f85810..0000000000 --- a/lib/sql_algebra/sql_builder/left_outer_join_builder.rb +++ /dev/null @@ -1,5 +0,0 @@ -class LeftOuterJoinBuilder < JoinBuilder - def join_type - "LEFT OUTER JOIN" - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/order_builder.rb b/lib/sql_algebra/sql_builder/order_builder.rb deleted file mode 100644 index 66a8cfdba9..0000000000 --- a/lib/sql_algebra/sql_builder/order_builder.rb +++ /dev/null @@ -1,16 +0,0 @@ -class OrderBuilder < SqlBuilder - def initialize(&block) - @orders = [] - super(&block) - end - - def column(table, column, aliaz = nil) - @orders << "#{quote_table_name(table)}.#{quote_column_name(column)}" - end - - def to_s - @orders.join(', ') - end - - delegate :blank?, :to => :@orders -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/select_builder.rb b/lib/sql_algebra/sql_builder/select_builder.rb deleted file mode 100644 index 9a85ad7eec..0000000000 --- a/lib/sql_algebra/sql_builder/select_builder.rb +++ /dev/null @@ -1,62 +0,0 @@ -class SelectBuilder < SqlBuilder - def select(&block) - @selects = SelectsBuilder.new(&block) - end - - def from(table, &block) - @table = table - @joins = JoinsBuilder.new(&block) - end - - def where(&block) - @conditions ||= ConditionsBuilder.new - @conditions.call(&block) - end - - def order_by(&block) - @orders = OrderBuilder.new(&block) - end - - def limit(i, offset = nil) - @limit = i - offset(offset) if offset - end - - def offset(i) - @offset = i - end - - def to_s - [select_clause, - from_clause, - where_clause, - order_by_clause, - limit_clause, - offset_clause].compact.join("\n") - end - - private - def select_clause - "SELECT #{@selects}" unless @selects.blank? - end - - def from_clause - "FROM #{quote_table_name(@table)} #{@joins}" unless @table.blank? - end - - def where_clause - "WHERE #{@conditions}" unless @conditions.blank? - end - - def order_by_clause - "ORDER BY #{@orders}" unless @orders.blank? - end - - def limit_clause - "LIMIT #{@limit}" unless @limit.blank? - end - - def offset_clause - "OFFSET #{@offset}" unless @offset.blank? - end -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/selects_builder.rb b/lib/sql_algebra/sql_builder/selects_builder.rb deleted file mode 100644 index ce6ee1eb67..0000000000 --- a/lib/sql_algebra/sql_builder/selects_builder.rb +++ /dev/null @@ -1,20 +0,0 @@ -class SelectsBuilder < SqlBuilder - def initialize(&block) - @selects = [] - super(&block) - end - - def to_s - @selects.join(', ') - end - - def all - @selects << :* - end - - def column(table, column, aliaz = nil) - @selects << "#{quote_table_name(table)}.#{quote_column_name(column)}" + (aliaz ? " AS #{quote(aliaz)}" : '') - end - - delegate :blank?, :to => :@selects -end \ No newline at end of file diff --git a/lib/sql_algebra/sql_builder/sql_builder.rb b/lib/sql_algebra/sql_builder/sql_builder.rb deleted file mode 100644 index c984444e41..0000000000 --- a/lib/sql_algebra/sql_builder/sql_builder.rb +++ /dev/null @@ -1,35 +0,0 @@ -class SqlBuilder - def initialize(&block) - @callers = [] - call(&block) if block - end - - def method_missing(method, *args) - @callers.last.send(method, *args) - end - - def ==(other) - to_s == other.to_s - end - - def to_s - end - - def call(&block) - returning self do |builder| - @callers << eval("self", block.binding) - begin - instance_eval &block - ensure - @callers.pop - end - end - end - - private - delegate :quote_table_name, :quote_column_name, :quote, :to => :connection - - def connection - ActiveRecord::Base.connection - end -end \ No newline at end of file diff --git a/spec/active_relation/integration/scratch_spec.rb b/spec/active_relation/integration/scratch_spec.rb new file mode 100644 index 0000000000..fba587e4ba --- /dev/null +++ b/spec/active_relation/integration/scratch_spec.rb @@ -0,0 +1,271 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe 'ActiveRelation', 'A proposed refactoring to ActiveRecord, introducing both a SQL + Builder and a Relational Algebra to mediate between + ActiveRecord and the database. The goal of the refactoring is + to remove code duplication concerning AR associations; remove + complexity surrounding eager loading; comprehensively solve + quoting issues; remove the with_scope merging logic; minimize + the need for with_scope in general; simplify the + implementation of plugins like HasFinder and ActsAsParanoid; + introduce an identity map; and allow for query optimization. + All this while remaining backwards-compatible with the + existing ActiveRecord interface. + The Relational Algebra makes these ambitious goals + possible. There\'s no need to be scared by the math, it\'s + actually quite simple. Relational Algebras have some nice + advantages over flexible SQL builders like Sequel and and + SqlAlchemy (a beautiful Python library). Principally, a + relation is writable as well as readable. This obviates the + :create with_scope, and perhaps also + #set_belongs_to_association_for. + With so much complexity removed from ActiveRecord, I + propose a mild reconsideration of the architecture of Base, + AssocationProxy, AssociationCollection, and so forth. These + should all be understood as \'Repositories\': a factory that + given a relation can manufacture objects, and given an object + can manipulate a relation. This may sound trivial, but I + think it has the potential to make the code smaller and + more consistent.' do + before do + class User < ActiveRecord::Base; has_many :photos end + class Photo < ActiveRecord::Base; belongs_to :camera end + class Camera < ActiveRecord::Base; end + end + + before do + # Rather than being associated with a table, an ActiveRecord is now associated with + # a relation. + @users = User.relation + @photos = Photo.relation + @cameras = Camera.relation + # A first taste of a Relational Algebra: User.find(1) + @user = @users.select(@users[:id] == 1) + # == is overridden on attributes to return a predicate, not true or false + end + + # In a Relational Algebra, the various ActiveRecord associations become a simple + # mapping from one relation to another. The Reflection object parameterizes the + # mapping. + def user_has_many_photos(user_relation) + primary_key = User.reflections[:photos].klass.primary_key.to_sym + foreign_key = User.reflections[:photos].primary_key_name.to_sym + + # << is the left outer join operator + (user_relation << @photos).on(user_relation[primary_key] == @photos[foreign_key]) + end + + def photo_belongs_to_camera(photo_relation) + primary_key = Photo.reflections[:camera].klass.primary_key.to_sym + foreign_key = Photo.reflections[:camera].primary_key_name.to_sym + + (photo_relation << @cameras).on(photo_relation[foreign_key] == @cameras[primary_key]) + end + + describe 'Relational Algebra', 'a relational algebra allows the implementation of + associations like has_many to be specified once, + regardless of eager-joins, has_many :through, and so + forth' do + it 'generates the query for User.has_many :photos' do + user_photos = user_has_many_photos(@user) + # the 'project' operator limits the columns that come back from the query. + # Note how all the operators are compositional: 'project' is applied to a query + # that previously had been joined and selected. + user_photos.project(*@photos.attributes).to_s.should be_like(""" + SELECT `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` + FROM `users` + LEFT OUTER JOIN `photos` + ON `users`.`id` = `photos`.`user_id` + WHERE + `users`.`id` = 1 + """) + # Also note the correctly quoted columns and tables. In this instance the + # MysqlAdapter from ActiveRecord is used to do the escaping. + end + + it 'generates the query for User.has_many :cameras :through => :photos' do + # note, again, the compositionality of the operators: + user_cameras = photo_belongs_to_camera(user_has_many_photos(@user)) + user_cameras.project(*@cameras.attributes).to_s.should be_like(""" + SELECT `cameras`.`id` + FROM `users` + LEFT OUTER JOIN `photos` + ON `users`.`id` = `photos`.`user_id` + LEFT OUTER JOIN `cameras` + ON `photos`.`camera_id` = `cameras`.`id` + WHERE + `users`.`id` = 1 + """) + end + + it 'generates the query for an eager join for a collection using the same logic as + for an association on an individual row' do + users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)) + users_cameras.to_s.should be_like(""" + SELECT `users`.`name`, `users`.`id`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id`, `cameras`.`id` + FROM `users` + LEFT OUTER JOIN `photos` + ON `users`.`id` = `photos`.`user_id` + LEFT OUTER JOIN `cameras` + ON `photos`.`camera_id` = `cameras`.`id` + """) + end + + it 'is trivial to disambiguate columns' do + users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)).qualify + users_cameras.to_s.should be_like(""" + SELECT `users`.`name` AS 'users.name', `users`.`id` AS 'users.id', `photos`.`id` AS 'photos.id', `photos`.`user_id` AS 'photos.user_id', `photos`.`camera_id` AS 'photos.camera_id', `cameras`.`id` AS 'cameras.id' + FROM `users` + LEFT OUTER JOIN `photos` + ON `users`.`id` = `photos`.`user_id` + LEFT OUTER JOIN `cameras` + ON `photos`.`camera_id` = `cameras`.`id` + """) + end + + it 'allows arbitrary sql to be passed through' do + (@users << @photos).on("asdf").to_s.should be_like(""" + SELECT `users`.`name`, `users`.`id`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` + FROM `users` + LEFT OUTER JOIN `photos` + ON asdf + """) + @users.select("asdf").to_s.should be_like(""" + SELECT `users`.`name`, `users`.`id` + FROM `users` + WHERE asdf + """) + end + + describe 'write operations' do + it 'generates the query for user.destroy' do + @user.delete.to_s.should be_like(""" + DELETE + FROM `users` + WHERE `users`.`id` = 1 + """) + end + + it 'generates an efficient query for two User.creates -- UnitOfWork is within reach!' do + @users.insert(@users[:name] => "humpty").insert(@users[:name] => "dumpty").to_s.should be_like(""" + INSERT + INTO `users` + (`users`.`name`) VALUES ('humpty'), ('dumpty') + """) + end + end + + describe 'with_scope' do + it 'obviates the need for with_scope merging logic since, e.g., + `with_scope :conditions => ...` is just a #select operation on the relation' do + end + + it 'may eliminate the need for with_scope altogether since the associations no longer + need it: the relation underlying the association fully encapsulates the scope' do + end + end + end + + describe 'Repository', 'ActiveRecord::Base, HasManyAssociation, and so forth are + all repositories: given a relation, they manufacture objects' do + before do + class << ActiveRecord::Base; public :instantiate end + end + + it 'manufactures objects' do + User.instantiate(@users.first).attributes.should == {"name" => "hai", "id" => 1} + end + + it 'frees ActiveRecords from being tied to tables' do + pending # pending, but trivial to implement: + + class User < ActiveRecord::Base + # acts_as_paranoid without alias_method_chain: + set_relation @users.select(@users[:deleted_at] != nil) + end + + class Person < ActiveRecord::Base + set_relation @accounts.join(@profiles).on(@accounts[:id] == @profiles[:account_id]) + end + # I know this sounds crazy, but even writes are possible in the last example. + # calling #save on a person can write to two tables! + end + + describe 'the n+1 problem' do + describe 'the eager join algorithm is vastly simpler' do + it 'loads three active records with only one query' do + # using 'rr' mocking framework: the real #select_all is called, but we assert + # that it only happens once: + mock.proxy(ActiveRecord::Base.connection).select_all.with_any_args.once + + users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)).qualify + user = User.instantiate(users_cameras.first, [:photos => [:camera]]) + user.photos.first.camera.attributes.should == {"id" => 1} + end + + before do + class << ActiveRecord::Base + # An identity map makes this algorithm efficient. + def instantiate_with_cache(record) + cache.get(record) { instantiate_without_cache(record) } + end + alias_method_chain :instantiate, :cache + + # for each row in the result set, which may contain data from n tables, + # - instantiate that slice of the data corresponding to the current class + # - recusively walk the dependency chain and repeat. + def instantiate_with_joins(data, joins = []) + record = unqualify(data) + returning instantiate_without_joins(record) do |object| + joins.each do |join| + case join + when Symbol + object.send(association = join).instantiate(data) + when Hash + join.each do |association, nested_associations| + object.send(association).instantiate(data, nested_associations) + end + end + end + end + end + alias_method_chain :instantiate, :joins + + private + # Sometimes, attributes are qualified to remove ambiguity. Here, bring back + # ambiguity by translating 'users.id' to 'id' so we can call #attributes=. + # This code should work correctly if the attributes are qualified or not. + def unqualify(qualified_attributes) + qualified_attributes_for_this_class = qualified_attributes. \ + slice(*relation.attributes.collect(&:qualified_name)) + qualified_attributes_for_this_class.alias do |qualified_name| + qualified_name.split('.')[1] || qualified_name # the latter means it must not really be qualified + end + end + end + end + + it "is possible to be smarter about eager loading. DataMapper is smart enough + to notice when you do users.each { |u| u.photos } and make this two queries + rather than n+1: the first invocation of #photos is lazy but it preloads + photos for all subsequent users. This is substantially easier with the + Algebra since we can do @user.join(@photos).on(...) and transform that to + @users.join(@photos).on(...), relying on the IdentityMap to eliminate + the n+1 problem." do + pending + end + end + end + end + + describe 'The Architecture', 'I propose to produce a new gem, ActiveRelation, which encaplulates + the existing ActiveRecord Connection Adapter, the new SQL Builder, + and the Relational Algebra. ActiveRecord, then, should no longer + interact with the connection object directly.' do + end + + describe 'Miscellaneous Ideas' do + it 'may be easy to write a SQL parser that can take arbitrary SQL and produce a relation. + This has the advantage of permitting e.g., pagination with custom finder_sql' + end +end \ No newline at end of file diff --git a/spec/active_relation/predicates/binary_predicate_spec.rb b/spec/active_relation/predicates/binary_predicate_spec.rb new file mode 100644 index 0000000000..5de559df41 --- /dev/null +++ b/spec/active_relation/predicates/binary_predicate_spec.rb @@ -0,0 +1,51 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe BinaryPredicate do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute1 = Attribute.new(@relation1, :name1) + @attribute2 = Attribute.new(@relation2, :name2) + class ConcreteBinaryPredicate < BinaryPredicate + def predicate_name + :equals + end + end + end + + describe '#initialize' do + it "requires that both columns come from the same relation" do + pending + end + end + + describe '==' do + it "obtains if attribute1 and attribute2 are identical" do + BinaryPredicate.new(@attribute1, @attribute2).should == BinaryPredicate.new(@attribute1, @attribute2) + BinaryPredicate.new(@attribute1, @attribute2).should_not == BinaryPredicate.new(@attribute1, @attribute1) + end + + it "obtains if the concrete type of the BinaryPredicates are identical" do + ConcreteBinaryPredicate.new(@attribute1, @attribute2).should == ConcreteBinaryPredicate.new(@attribute1, @attribute2) + BinaryPredicate.new(@attribute1, @attribute2).should_not == ConcreteBinaryPredicate.new(@attribute1, @attribute2) + end + end + + describe '#qualify' do + it "distributes over the predicates and attributes" do + ConcreteBinaryPredicate.new(@attribute1, @attribute2).qualify. \ + should == ConcreteBinaryPredicate.new(@attribute1.qualify, @attribute2.qualify) + end + end + + describe '#to_sql' do + it 'manufactures correct sql' do + ConcreteBinaryPredicate.new(@attribute1, @attribute2).to_sql.should == ConditionsBuilder.new do + equals do + column :foo, :name1 + column :bar, :name2 + end + end + end + end +end \ No newline at end of file diff --git a/spec/active_relation/predicates/equality_predicate_spec.rb b/spec/active_relation/predicates/equality_predicate_spec.rb new file mode 100644 index 0000000000..af43b754e0 --- /dev/null +++ b/spec/active_relation/predicates/equality_predicate_spec.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe EqualityPredicate do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute1 = Attribute.new(@relation1, :name) + @attribute2 = Attribute.new(@relation2, :name) + end + + describe '==' do + it "obtains if attribute1 and attribute2 are identical" do + EqualityPredicate.new(@attribute1, @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) + EqualityPredicate.new(@attribute1, @attribute2).should_not == EqualityPredicate.new(@attribute1, @attribute1) + end + + it "obtains if the concrete type of the predicates are identical" do + EqualityPredicate.new(@attribute1, @attribute2).should_not == BinaryPredicate.new(@attribute1, @attribute2) + end + + it "is commutative on the attributes" do + EqualityPredicate.new(@attribute1, @attribute2).should == EqualityPredicate.new(@attribute2, @attribute1) + end + end +end \ No newline at end of file diff --git a/spec/active_relation/predicates/relation_inclusion_predicate_spec.rb b/spec/active_relation/predicates/relation_inclusion_predicate_spec.rb new file mode 100644 index 0000000000..f8c911429b --- /dev/null +++ b/spec/active_relation/predicates/relation_inclusion_predicate_spec.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe RelationInclusionPredicate do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute = @relation1[:baz] + end + + describe RelationInclusionPredicate, '==' do + it "obtains if attribute1 and attribute2 are identical" do + RelationInclusionPredicate.new(@attribute, @relation1).should == RelationInclusionPredicate.new(@attribute, @relation1) + RelationInclusionPredicate.new(@attribute, @relation1).should_not == RelationInclusionPredicate.new(@attribute, @relation2) + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/attribute_spec.rb b/spec/active_relation/relations/attribute_spec.rb new file mode 100644 index 0000000000..ddfc22fe28 --- /dev/null +++ b/spec/active_relation/relations/attribute_spec.rb @@ -0,0 +1,90 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe Attribute do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + end + + describe '#aliazz' do + it "manufactures an aliased attributed" do + pending + end + + it "should be renamed to #alias!" do + pending + @relation1.alias + end + end + + describe '#qualified_name' do + it "manufactures an attribute name prefixed with the relation's name" do + @relation1[:id].qualified_name.should == 'foo.id' + end + end + + describe '#qualify' do + it "manufactures an attribute aliased with that attributes qualified name" do + @relation1[:id].qualify == @relation1[:id].qualify + end + end + + describe '#eql?' do + it "obtains if the relation and attribute name are identical" do + Attribute.new(@relation1, :name).should be_eql(Attribute.new(@relation1, :name)) + Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation1, :another_name)) + Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation2, :name)) + end + end + + describe 'predications' do + before do + @attribute1 = Attribute.new(@relation1, :name) + @attribute2 = Attribute.new(@relation2, :name) + end + + describe '==' do + it "manufactures an equality predicate" do + (@attribute1 == @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) + end + end + + describe '<' do + it "manufactures a less-than predicate" do + (@attribute1 < @attribute2).should == LessThanPredicate.new(@attribute1, @attribute2) + end + end + + describe '<=' do + it "manufactures a less-than or equal-to predicate" do + (@attribute1 <= @attribute2).should == LessThanOrEqualToPredicate.new(@attribute1, @attribute2) + end + end + + describe '>' do + it "manufactures a greater-than predicate" do + (@attribute1 > @attribute2).should == GreaterThanPredicate.new(@attribute1, @attribute2) + end + end + + describe '>=' do + it "manufactures a greater-than or equal to predicate" do + (@attribute1 >= @attribute2).should == GreaterThanOrEqualToPredicate.new(@attribute1, @attribute2) + end + end + + describe '=~' do + it "manufactures a match predicate" do + (@attribute1 =~ /.*/).should == MatchPredicate.new(@attribute1, @attribute2) + end + end + end + + describe '#to_sql' do + it "manufactures a column" do + Attribute.new(@relation1, :name, :alias).to_sql.should == SelectsBuilder.new do + column :foo, :name, :alias + end + end + end +end diff --git a/spec/active_relation/relations/deletion_relation_spec.rb b/spec/active_relation/relations/deletion_relation_spec.rb new file mode 100644 index 0000000000..4f75a261f4 --- /dev/null +++ b/spec/active_relation/relations/deletion_relation_spec.rb @@ -0,0 +1,22 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe DeletionRelation do + before do + @relation = TableRelation.new(:users) + end + + describe '#to_sql' do + it 'manufactures sql deleting the relation' do + DeletionRelation.new(@relation.select(@relation[:id] == 1)).to_sql.to_s.should == DeleteBuilder.new do + delete + from :users + where do + equals do + column :users, :id + value 1 + end + end + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/insertion_relation_spec.rb b/spec/active_relation/relations/insertion_relation_spec.rb new file mode 100644 index 0000000000..6bafabb473 --- /dev/null +++ b/spec/active_relation/relations/insertion_relation_spec.rb @@ -0,0 +1,37 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe InsertionRelation do + before do + @relation = TableRelation.new(:users) + end + + describe '#to_sql' do + it 'manufactures sql inserting the data for one item' do + InsertionRelation.new(@relation, @relation[:name] => "nick").to_sql.should == InsertBuilder.new do + insert + into :users + columns do + column :users, :name + end + values do + row "nick" + end + end + end + + it 'manufactures sql inserting the data for multiple items' do + nested_insertion = InsertionRelation.new(@relation, @relation[:name] => "cobra") + InsertionRelation.new(nested_insertion, nested_insertion[:name] => "commander").to_sql.to_s.should == InsertBuilder.new do + insert + into :users + columns do + column :users, :name + end + values do + row "cobra" + row "commander" + end + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/join_operation_spec.rb b/spec/active_relation/relations/join_operation_spec.rb new file mode 100644 index 0000000000..a8ab85123b --- /dev/null +++ b/spec/active_relation/relations/join_operation_spec.rb @@ -0,0 +1,39 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe 'between two relations' do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + end + + describe '==' do + it "obtains if the relations of both joins are identical" do + JoinOperation.new(@relation1, @relation2).should == JoinOperation.new(@relation1, @relation2) + JoinOperation.new(@relation1, @relation2).should_not == JoinOperation.new(@relation1, @relation1) + end + + it "is commutative on the relations" do + JoinOperation.new(@relation1, @relation2).should == JoinOperation.new(@relation2, @relation1) + end + end + + describe 'on' do + before do + @predicate = Predicate.new + @join_operation = JoinOperation.new(@relation1, @relation2) + class << @join_operation + def relation_class + JoinRelation + end + end + end + + it "manufactures a join relation of the appropriate type" do + @join_operation.on(@predicate).should == JoinRelation.new(@relation1, @relation2, @predicate) + end + + it "accepts arbitrary strings" do + @join_operation.on("arbitrary").should == JoinRelation.new(@relation1, @relation2, "arbitrary") + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/join_relation_spec.rb b/spec/active_relation/relations/join_relation_spec.rb new file mode 100644 index 0000000000..d0be270837 --- /dev/null +++ b/spec/active_relation/relations/join_relation_spec.rb @@ -0,0 +1,59 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe JoinRelation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @predicate = EqualityPredicate.new(@relation1[:id], @relation2[:id]) + end + + describe '==' do + it 'obtains if the two relations and the predicate are identical' do + JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation1, @relation2, @predicate) + JoinRelation.new(@relation1, @relation2, @predicate).should_not == JoinRelation.new(@relation1, @relation1, @predicate) + end + + it 'is commutative on the relations' do + JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation2, @relation1, @predicate) + end + end + + describe '#qualify' do + it 'distributes over the relations and predicates' do + InnerJoinRelation.new(@relation1, @relation2, @predicate).qualify. \ + should == InnerJoinRelation.new(@relation1.qualify, @relation2.qualify, @predicate.qualify) + end + end + + describe '#to_sql' do + before do + @relation1 = @relation1.select(@relation1[:id] == @relation2[:foo_id]) + end + + it 'manufactures sql joining the two tables on the predicate, merging the selects' do + InnerJoinRelation.new(@relation1, @relation2, @predicate).to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id + column :bar, :name + column :bar, :foo_id + column :bar, :id + end + from :foo do + inner_join :bar do + equals do + column :foo, :id + column :bar, :id + end + end + end + where do + equals do + column :foo, :id + column :bar, :foo_id + end + end + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/order_relation_spec.rb b/spec/active_relation/relations/order_relation_spec.rb new file mode 100644 index 0000000000..17f730b564 --- /dev/null +++ b/spec/active_relation/relations/order_relation_spec.rb @@ -0,0 +1,41 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe OrderRelation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute1 = @relation1[:id] + @attribute2 = @relation2[:id] + end + + describe '==' do + it "obtains if the relation and attributes are identical" do + OrderRelation.new(@relation1, @attribute1, @attribute2).should == OrderRelation.new(@relation1, @attribute1, @attribute2) + OrderRelation.new(@relation1, @attribute1).should_not == OrderRelation.new(@relation2, @attribute1) + OrderRelation.new(@relation1, @attribute1, @attribute2).should_not == OrderRelation.new(@relation1, @attribute2, @attribute1) + end + end + + describe '#qualify' do + it "distributes over the relation and attributes" do + OrderRelation.new(@relation1, @attribute1).qualify. \ + should == OrderRelation.new(@relation1.qualify, @attribute1.qualify) + end + end + + describe '#to_sql' do + it "manufactures sql with an order clause" do + OrderRelation.new(@relation1, @attribute1).to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id + end + from :foo + order_by do + column :foo, :id + end + end.to_s + end + end + +end \ No newline at end of file diff --git a/spec/active_relation/relations/projection_relation_spec.rb b/spec/active_relation/relations/projection_relation_spec.rb new file mode 100644 index 0000000000..164c485761 --- /dev/null +++ b/spec/active_relation/relations/projection_relation_spec.rb @@ -0,0 +1,36 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe ProjectionRelation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute1 = @relation1[:id] + @attribute2 = @relation2[:id] + end + + describe '==' do + it "obtains if the relations and attributes are identical" do + ProjectionRelation.new(@relation1, @attribute1, @attribute2).should == ProjectionRelation.new(@relation1, @attribute1, @attribute2) + ProjectionRelation.new(@relation1, @attribute1).should_not == ProjectionRelation.new(@relation2, @attribute1) + ProjectionRelation.new(@relation1, @attribute1).should_not == ProjectionRelation.new(@relation1, @attribute2) + end + end + + describe '#qualify' do + it "distributes over teh relation and attributes" do + ProjectionRelation.new(@relation1, @attribute1).qualify. \ + should == ProjectionRelation.new(@relation1.qualify, @attribute1.qualify) + end + end + + describe '#to_sql' do + it "manufactures sql with a limited select clause" do + ProjectionRelation.new(@relation1, @attribute1).to_s.should == SelectBuilder.new do + select do + column :foo, :id + end + from :foo + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/range_relation_spec.rb b/spec/active_relation/relations/range_relation_spec.rb new file mode 100644 index 0000000000..ac9f887d9b --- /dev/null +++ b/spec/active_relation/relations/range_relation_spec.rb @@ -0,0 +1,41 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe RangeRelation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @range1 = 1..2 + @range2 = 4..9 + end + + describe '==' do + it "obtains if the relation and range are identical" do + RangeRelation.new(@relation1, @range1).should == RangeRelation.new(@relation1, @range1) + RangeRelation.new(@relation1, @range1).should_not == RangeRelation.new(@relation2, @range1) + RangeRelation.new(@relation1, @range1).should_not == RangeRelation.new(@relation1, @range2) + end + end + + describe '#qualify' do + it "distributes over the relation and attributes" do + pending + end + end + + describe '#to_sql' do + it "manufactures sql with limit and offset" do + range_size = @range2.last - @range2.first + 1 + range_start = @range2.first + RangeRelation.new(@relation1, @range2).to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id + end + from :foo + limit range_size + offset range_start + end.to_s + end + end + +end \ No newline at end of file diff --git a/spec/active_relation/relations/relation_spec.rb b/spec/active_relation/relations/relation_spec.rb new file mode 100644 index 0000000000..9ed42a98cb --- /dev/null +++ b/spec/active_relation/relations/relation_spec.rb @@ -0,0 +1,92 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe Relation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @attribute1 = Attribute.new(@relation1, :id) + @attribute2 = Attribute.new(@relation1, :name) + end + + describe '[]' do + it "manufactures an attribute when given a symbol" do + @relation1[:id].should be_eql(Attribute.new(@relation1, :id)) + end + + it "manufactures a range relation when given a range" do + @relation1[1..2].should == RangeRelation.new(@relation1, 1..2) + end + end + + describe '#include?' do + it "manufactures an inclusion predicate" do + @relation1.include?(@attribute1).should == RelationInclusionPredicate.new(@attribute1, @relation1) + end + end + + describe 'read operations' do + describe 'joins' do + describe '<=>' do + it "manufactures an inner join operation between those two relations" do + (@relation1 <=> @relation2).should == InnerJoinOperation.new(@relation1, @relation2) + end + end + + describe '<<' do + it "manufactures a left outer join operation between those two relations" do + (@relation1 << @relation2).should == LeftOuterJoinOperation.new(@relation1, @relation2) + end + end + end + + describe '#project' do + it "collapses identical projections" do + pending + end + + it "manufactures a projection relation" do + @relation1.project(@attribute1, @attribute2).should == ProjectionRelation.new(@relation1, @attribute1, @attribute2) + end + end + + describe '#rename' do + it "manufactures a rename relation" do + @relation1.rename(@attribute1, :foo).should == RenameRelation.new(@relation1, @attribute1 => :foo) + end + end + + describe '#select' do + before do + @predicate = EqualityPredicate.new(@attribute1, @attribute2) + end + + it "manufactures a selection relation" do + @relation1.select(@predicate).should == SelectionRelation.new(@relation1, @predicate) + end + + it "accepts arbitrary strings" do + @relation1.select("arbitrary").should == SelectionRelation.new(@relation1, "arbitrary") + end + end + + describe '#order' do + it "manufactures an order relation" do + @relation1.order(@attribute1, @attribute2).should == OrderRelation.new(@relation1, @attribute1, @attribute2) + end + end + end + + describe 'write operations' do + describe '#delete' do + it 'manufactures a deletion relation' do + @relation1.delete.should == DeletionRelation.new(@relation1) + end + end + + describe '#insert' do + it 'manufactures an insertion relation' do + @relation1.insert(tuple = {:id => 1}).should == InsertionRelation.new(@relation1, tuple) + end + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/rename_relation_spec.rb b/spec/active_relation/relations/rename_relation_spec.rb new file mode 100644 index 0000000000..9b1d2d5cc8 --- /dev/null +++ b/spec/active_relation/relations/rename_relation_spec.rb @@ -0,0 +1,64 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe RenameRelation do + before do + @relation = TableRelation.new(:foo) + @renamed_relation = RenameRelation.new(@relation, @relation[:id] => :schmid) + end + + describe '#initialize' do + it "manufactures nested rename relations if multiple renames are provided" do + RenameRelation.new(@relation, @relation[:id] => :humpty, @relation[:name] => :dumpty). \ + should == RenameRelation.new(RenameRelation.new(@relation, @relation[:id] => :humpty), @relation[:name] => :dumpty) + end + + it "raises an exception if the alias provided is already used" do + pending + end + end + + describe '==' do + it "obtains if the relation, attribute, and alias are identical" do + pending + end + end + + describe '#attributes' do + it "manufactures a list of attributes with the renamed attribute aliased" do + RenameRelation.new(@relation, @relation[:id] => :schmid).attributes.should == + (@relation.attributes - [@relation[:id]]) + [@relation[:id].aliazz(:schmid)] + end + end + + describe '[]' do + it 'indexes attributes by alias' do + @renamed_relation[:id].should be_nil + @renamed_relation[:schmid].should == @relation[:id] + end + end + + describe '#schmattribute' do + it "should be renamed" do + pending + end + end + + describe '#qualify' do + it "distributes over the relation and renames" do + RenameRelation.new(@relation, @relation[:id] => :schmid).qualify. \ + should == RenameRelation.new(@relation.qualify, @relation[:id].qualify => :schmid) + end + end + + describe '#to_sql' do + it 'manufactures sql aliasing the attribute' do + @renamed_relation.to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id, :schmid + end + from :foo + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/selection_relation_spec.rb b/spec/active_relation/relations/selection_relation_spec.rb new file mode 100644 index 0000000000..c4aadc807b --- /dev/null +++ b/spec/active_relation/relations/selection_relation_spec.rb @@ -0,0 +1,53 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe SelectionRelation do + before do + @relation1 = TableRelation.new(:foo) + @relation2 = TableRelation.new(:bar) + @predicate1 = EqualityPredicate.new(@relation1[:id], @relation2[:foo_id]) + @predicate2 = LessThanPredicate.new(@relation1[:age], 2) + end + + describe '==' do + it "obtains if both the predicate and the relation are identical" do + SelectionRelation.new(@relation1, @predicate1). \ + should == SelectionRelation.new(@relation1, @predicate1) + SelectionRelation.new(@relation1, @predicate1). \ + should_not == SelectionRelation.new(@relation2, @predicate1) + SelectionRelation.new(@relation1, @predicate1). \ + should_not == SelectionRelation.new(@relation1, @predicate2) + end + end + + describe '#initialize' do + it "manufactures nested selection relations if multiple predicates are provided" do + SelectionRelation.new(@relation1, @predicate1, @predicate2). \ + should == SelectionRelation.new(SelectionRelation.new(@relation1, @predicate2), @predicate1) + end + end + + describe '#qualify' do + it "distributes over the relation and predicates" do + SelectionRelation.new(@relation1, @predicate1).qualify. \ + should == SelectionRelation.new(@relation1.qualify, @predicate1.qualify) + end + end + + describe '#to_sql' do + it "manufactures sql with where clause conditions" do + SelectionRelation.new(@relation1, @predicate1).to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id + end + from :foo + where do + equals do + column :foo, :id + column :bar, :foo_id + end + end + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/active_relation/relations/table_relation_spec.rb b/spec/active_relation/relations/table_relation_spec.rb new file mode 100644 index 0000000000..c943fe6c92 --- /dev/null +++ b/spec/active_relation/relations/table_relation_spec.rb @@ -0,0 +1,33 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe TableRelation do + before do + @relation = TableRelation.new(:users) + end + + describe '#to_sql' do + it "returns a simple SELECT query" do + @relation.to_sql.should == SelectBuilder.new do |s| + select do + column :users, :name + column :users, :id + end + from :users + end + end + end + + describe '#attributes' do + it 'manufactures attributes corresponding to columns in the table' do + pending + end + end + + describe '#qualify' do + it 'manufactures a rename relation with all attribute names qualified' do + @relation.qualify.should == RenameRelation.new( + RenameRelation.new(@relation, @relation[:id] => 'users.id'), @relation[:name] => 'users.name' + ) + end + end +end \ No newline at end of file diff --git a/spec/active_relation/sql_builder/conditions_spec.rb b/spec/active_relation/sql_builder/conditions_spec.rb new file mode 100644 index 0000000000..dc2d10a2f6 --- /dev/null +++ b/spec/active_relation/sql_builder/conditions_spec.rb @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe ConditionsBuilder do + describe '#to_s' do + describe 'with aliased columns' do + it 'manufactures correct sql' do + ConditionsBuilder.new do + equals do + column(:a, :b) + column(:c, :d, 'e') + end + end.to_s.should be_like(""" + `a`.`b` = `c`.`d` + """) + end + end + end +end \ No newline at end of file diff --git a/spec/active_relation/sql_builder/delete_builder_spec.rb b/spec/active_relation/sql_builder/delete_builder_spec.rb new file mode 100644 index 0000000000..fd62fde155 --- /dev/null +++ b/spec/active_relation/sql_builder/delete_builder_spec.rb @@ -0,0 +1,22 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe DeleteBuilder do + describe '#to_s' do + it 'manufactures correct sql' do + DeleteBuilder.new do + delete + from :users + where do + equals do + column :users, :id + value 1 + end + end + end.to_s.should be_like(""" + DELETE + FROM `users` + WHERE `users`.`id` = 1 + """) + end + end +end \ No newline at end of file diff --git a/spec/active_relation/sql_builder/insert_builder_spec.rb b/spec/active_relation/sql_builder/insert_builder_spec.rb new file mode 100644 index 0000000000..dddc971986 --- /dev/null +++ b/spec/active_relation/sql_builder/insert_builder_spec.rb @@ -0,0 +1,24 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe InsertBuilder do + describe '#to_s' do + it 'manufactures correct sql' do + InsertBuilder.new do + insert + into :users + columns do + column :users, :id + column :users, :name + end + values do + row 1, 'bob' + row 2, 'moe' + end + end.to_s.should be_like(""" + INSERT + INTO `users` + (`users`.`id`, `users`.`name`) VALUES (1, 'bob'), (2, 'moe') + """) + end + end +end \ No newline at end of file diff --git a/spec/active_relation/sql_builder/select_builder_spec.rb b/spec/active_relation/sql_builder/select_builder_spec.rb new file mode 100644 index 0000000000..6539afe0c4 --- /dev/null +++ b/spec/active_relation/sql_builder/select_builder_spec.rb @@ -0,0 +1,148 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') + +describe SelectBuilder do + describe '#to_s' do + describe 'with select and from clauses' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + all + end + from :users + end.to_s.should be_like(""" + SELECT * + FROM `users` + """) + end + end + + describe 'with specified columns and column aliases' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + column :a, :b, 'c' + column :e, :f + end + from :users + end.to_s.should be_like(""" + SELECT `a`.`b` AS 'c', `e`.`f` + FROM `users` + """) + end + end + + describe 'with where clause' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + all + end + from :users + where do + equals do + value 1 + column :b, :c + end + end + end.to_s.should be_like(""" + SELECT * + FROM `users` + WHERE 1 = `b`.`c` + """) + end + + it 'accepts arbitrary strings' do + SelectBuilder.new do + select do + all + end + from :users + where do + value "'a' = 'a'" + end + end.to_s.should be_like(""" + SELECT * + FROM `users` + WHERE 'a' = 'a' + """) + end + end + + describe 'with inner join' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + all + end + from :users do + inner_join(:friendships) do + equals do + column :users, :id + column :friendships, :user_id + end + end + end + end.to_s.should be_like(""" + SELECT * + FROM `users` + INNER JOIN `friendships` + ON `users`.`id` = `friendships`.`user_id` + """) + end + + it 'accepts arbitrary on strings' do + SelectBuilder.new do + select do + all + end + from :users do + inner_join :friendships do + value "arbitrary" + end + end + end.to_s.should be_like(""" + SELECT * + FROM `users` + INNER JOIN `friendships` ON arbitrary + """) + end + end + + describe 'with order' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + all + end + from :users + order_by do + column :users, :id + column :users, :created_at, 'alias' + end + end.to_s.should be_like(""" + SELECT * + FROM `users` + ORDER BY `users`.`id`, `users`.`created_at` + """) + end + end + + describe 'with limit and/or offset' do + it 'manufactures correct sql' do + SelectBuilder.new do + select do + all + end + from :users + limit 10 + offset 10 + end.to_s.should be_like(""" + SELECT * + FROM `users` + LIMIT 10 + OFFSET 10 + """) + end + end + end +end \ No newline at end of file diff --git a/spec/debug.log b/spec/debug.log deleted file mode 100644 index d38ed11d82..0000000000 --- a/spec/debug.log +++ /dev/null @@ -1 +0,0 @@ -# Logfile created on Tue Jan 01 17:49:28 -0800 2008 by logger.rb/1.5.2.9 diff --git a/spec/extensions/range_spec.rb b/spec/extensions/range_spec.rb deleted file mode 100644 index 26ca8978ca..0000000000 --- a/spec/extensions/range_spec.rb +++ /dev/null @@ -1 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') diff --git a/spec/integration/debug.log b/spec/integration/debug.log deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/spec/integration/scratch_spec.rb b/spec/integration/scratch_spec.rb deleted file mode 100644 index b5d27f1dc2..0000000000 --- a/spec/integration/scratch_spec.rb +++ /dev/null @@ -1,226 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe 'ActiveRelation', 'Proposed refactoring to ActiveRecord, introducing both a SQL - builder and a Relational Algebra to mediate connections - between ActiveRecord and the database. The goal of the - refactoring is to remove code duplication concerning AR - associations; remove complexity surrounding eager loading; - comprehensively solve quoting issues; remove the with_scope - merging logic; minimize the need for with_scope in general; - simplify the implementation of plugins like HasFinder and - ActsAsParanoid; introduce an identity map; and allow for - query optimization. All this while effectively not changing - the public interface of ActiveRecord. - The Relational Algebra makes these ambitious goals - possible. There\'s no need to be scared by the math, it\'s - actually quite simple. Relational Algebras have some nice - advantages over flexible SQL builders like Sequel and and - SqlAlchemy (a beautiful Python library). Principally, a - relation is writable as well as readable. This obviates the - :create with_scope, and perhaps also - #set_belongs_to_association_for. - With so much complexity removed from ActiveRecord, I - propose a mild reconsideration of the architecture of Base, - AssocationProxy, AssociationCollection, and so forth. These - should all be understood as \'Repositories\': a factory that - given a relation can manufacture objects, and given an object - can manipulate a relation. This may sound trivial, but I - think it has the potential to make the code much smaller and - more consistent.' do - before do - class User < ActiveRecord::Base; has_many :photos end - class Photo < ActiveRecord::Base; belongs_to :camera end - class Camera < ActiveRecord::Base; end - end - - before do - # Rather than being associated with a table, an ActiveRecord is now associated with - # a relation. - @users = User.relation - @photos = Photo.relation - @cameras = Camera.relation - # A first taste of a Relational Algebra: User.find(1) - # == is overridden on attributes to return a predicate, not true or false - @user = @users.select(@users[:id] == 1) - end - - # In a Relational Algebra, the various ActiveRecord associations become a simple - # mapping from one relation to another. The Reflection object parameterizes the - # mapping. - def user_has_many_photos(user_relation) - primary_key = User.reflections[:photos].klass.primary_key.to_sym - foreign_key = User.reflections[:photos].primary_key_name.to_sym - - # << is the left outer join operator - (user_relation << @photos).on(user_relation[primary_key] == @photos[foreign_key]) - end - - def photo_belongs_to_camera(photo_relation) - primary_key = Photo.reflections[:camera].klass.primary_key.to_sym - foreign_key = Photo.reflections[:camera].primary_key_name.to_sym - - (photo_relation << @cameras).on(photo_relation[foreign_key] == @cameras[primary_key]) - end - - describe 'Relational Algebra', 'a relational algebra allows the implementation of - associations like has_many to be specified once, - regardless of eager-joins, has_many :through, and so - forth' do - it 'generates the query for User.has_many :photos' do - user_photos = user_has_many_photos(@user) - # the 'project' operator limits the columns that come back from the query. - # Note how all the operators are compositional: 'project' is applied to a query - # that previously had been joined and selected. - user_photos.project(*@photos.attributes).to_s.should be_like(""" - SELECT `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id` - FROM `users` - LEFT OUTER JOIN `photos` - ON `users`.`id` = `photos`.`user_id` - WHERE - `users`.`id` = 1 - """) - # Also note the correctly quoted columns and tables. In this instance the - # MysqlAdapter from ActiveRecord is used to do the escaping. - end - - it 'generates the query for User.has_many :cameras :through => :photos' do - # note, again, the compositionality of the operators: - user_cameras = photo_belongs_to_camera(user_has_many_photos(@user)) - user_cameras.project(*@cameras.attributes).to_s.should be_like(""" - SELECT `cameras`.`id` - FROM `users` - LEFT OUTER JOIN `photos` - ON `users`.`id` = `photos`.`user_id` - LEFT OUTER JOIN `cameras` - ON `photos`.`camera_id` = `cameras`.`id` - WHERE - `users`.`id` = 1 - """) - end - - it 'generates the query for an eager join for a collection using the same logic as - for an association on an individual row' do - users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)) - users_cameras.to_s.should be_like(""" - SELECT `users`.`name`, `users`.`id`, `photos`.`id`, `photos`.`user_id`, `photos`.`camera_id`, `cameras`.`id` - FROM `users` - LEFT OUTER JOIN `photos` - ON `users`.`id` = `photos`.`user_id` - LEFT OUTER JOIN `cameras` - ON `photos`.`camera_id` = `cameras`.`id` - """) - end - - it 'is trivial to disambiguate columns' do - users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)).qualify - users_cameras.to_s.should be_like(""" - SELECT `users`.`name` AS 'users.name', `users`.`id` AS 'users.id', `photos`.`id` AS 'photos.id', `photos`.`user_id` AS 'photos.user_id', `photos`.`camera_id` AS 'photos.camera_id', `cameras`.`id` AS 'cameras.id' - FROM `users` - LEFT OUTER JOIN `photos` - ON `users`.`id` = `photos`.`user_id` - LEFT OUTER JOIN `cameras` - ON `photos`.`camera_id` = `cameras`.`id` - """) - end - - it 'obviates the need for with_scope merging logic since, e.g., - `with_scope :conditions => ...` is just a #select operation on the relation' do - end - - it 'may eliminate the need for with_scope altogether since the associations no longer - need it: the relation underlying the association fully encapsulates the scope' do - end - end - - describe 'Repository', 'ActiveRecord::Base, HasManyAssociation, and so forth are - all repositories: given a relation, they manufacture objects' do - before do - class << ActiveRecord::Base; public :instantiate end - end - - it 'manufactures objects' do - User.instantiate(@users.first).attributes.should == {"name" => "hai", "id" => 1} - end - - it 'frees ActiveRecords from being tied to tables' do - pending # pending, but trivial to implement: - - class User < ActiveRecord::Base - # acts_as_paranoid without alias_method_chain: - set_relation @users.select(@users[:deleted_at] != nil) - end - - class Person < ActiveRecord::Base - set_relation @accounts.join(@profiles).on(@accounts[:id] == @profiles[:account_id]) - end - # I know this sounds crazy, but even writes are possible in the last example. - # calling #save on a person can write to two tables! - end - - describe 'the n+1 problem' do - describe 'the eager join algorithm is vastly simpler' do - it 'three active records are loaded with only one query' do - # using 'rr' mocking framework: the real #select_all is called, but we assert - # that it only happens once: - mock.proxy(ActiveRecord::Base.connection).select_all.with_any_args.once - users_cameras = photo_belongs_to_camera(user_has_many_photos(@users)).qualify - user = User.instantiate(users_cameras.first, [:photos => [:camera]]) - user.photos.first.camera.attributes.should == {"id" => 1} - end - - before do - class << ActiveRecord::Base - # An identity map makes this algorithm efficient. - def instantiate_with_cache(record) - cache.get(record) { instantiate_without_cache(record) } - end - alias_method_chain :instantiate, :cache - - # for each row in the result set, which may contain data from n tables, - # - instantiate that slice of the data corresponding to the current class - # - recusively walk the dependency chain and repeat. - def instantiate_with_joins(data, joins = []) - record = unqualify(data) - returning instantiate_without_joins(record) do |object| - joins.each do |join| - case join - when Symbol - object.send(association = join).instantiate(data) - when Hash - join.each do |association, nested_associations| - object.send(association).instantiate(data, nested_associations) - end - end - end - end - end - alias_method_chain :instantiate, :joins - - private - # Sometimes, attributes are qualified to remove ambiguity. Here, bring back - # ambiguity by translating 'users.id' to 'id' so we can call #attributes=. - # This code should work correctly if the attributes are qualified or not. - def unqualify(qualified_attributes) - qualified_attributes_for_this_class = qualified_attributes. \ - slice(*relation.attributes.collect(&:qualified_name)) - qualified_attributes_for_this_class.alias do |qualified_name| - qualified_name.split('.')[1] || qualified_name # the latter means it must not really be qualified - end - end - end - end - - it "is possible to be smarter about eager loading. DataMapper is smart enough - to notice when you do users.each { |u| u.photos } and make this two queries - rather than n+1: the first invocation of #photos is lazy but it preloads - photos for all subsequent users. This is substantially easier with the - Algebra since we can do @user.join(@photos).on(...) and transform that to - @users.join(@photos).on(...), relying on the IdentityMap to eliminate - the n+1 problem. This is somewhat similar to ActiveRecordContext but it - works with every association type, not just belongs_to." do - pending - end - end - end - end -end \ No newline at end of file diff --git a/spec/matchers/be_like.rb b/spec/matchers/be_like.rb new file mode 100644 index 0000000000..cea3f3027b --- /dev/null +++ b/spec/matchers/be_like.rb @@ -0,0 +1,24 @@ +module BeLikeMatcher + class BeLike + def initialize(expected) + @expected = expected + end + + def matches?(target) + @target = target + @expected.gsub(/\s+/, ' ').strip == @target.gsub(/\s+/, ' ').strip + end + + def failure_message + "expected #{@target} to be like #{@expected}" + end + + def negative_failure_message + "expected #{@target} to be unlike #{@expected}" + end + end + + def be_like(expected) + BeLike.new(expected) + end +end \ No newline at end of file diff --git a/spec/predicates/binary_predicate_spec.rb b/spec/predicates/binary_predicate_spec.rb deleted file mode 100644 index ede44e5175..0000000000 --- a/spec/predicates/binary_predicate_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe BinaryPredicate do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute1 = Attribute.new(@relation1, :name1) - @attribute2 = Attribute.new(@relation2, :name2) - class ConcreteBinaryPredicate < BinaryPredicate - def predicate_name - :equals - end - end - end - - describe '#initialize' do - it "requires that both columns come from the same relation" do - pending - end - end - - describe '==' do - it "obtains if attribute1 and attribute2 are identical" do - BinaryPredicate.new(@attribute1, @attribute2).should == BinaryPredicate.new(@attribute1, @attribute2) - BinaryPredicate.new(@attribute1, @attribute2).should_not == BinaryPredicate.new(@attribute1, @attribute1) - end - - it "obtains if the concrete type of the BinaryPredicates are identical" do - ConcreteBinaryPredicate.new(@attribute1, @attribute2).should == ConcreteBinaryPredicate.new(@attribute1, @attribute2) - BinaryPredicate.new(@attribute1, @attribute2).should_not == ConcreteBinaryPredicate.new(@attribute1, @attribute2) - end - end - - describe '#qualify' do - it "distributes over the predicates and attributes" do - ConcreteBinaryPredicate.new(@attribute1, @attribute2).qualify. \ - should == ConcreteBinaryPredicate.new(@attribute1.qualify, @attribute2.qualify) - end - end - - describe '#to_sql' do - it 'manufactures correct sql' do - ConcreteBinaryPredicate.new(@attribute1, @attribute2).to_sql.should == ConditionsBuilder.new do - equals do - column :foo, :name1 - column :bar, :name2 - end - end - end - end -end \ No newline at end of file diff --git a/spec/predicates/equality_predicate_spec.rb b/spec/predicates/equality_predicate_spec.rb deleted file mode 100644 index 75b495b6f7..0000000000 --- a/spec/predicates/equality_predicate_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe EqualityPredicate do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute1 = Attribute.new(@relation1, :name) - @attribute2 = Attribute.new(@relation2, :name) - end - - describe '==' do - it "obtains if attribute1 and attribute2 are identical" do - EqualityPredicate.new(@attribute1, @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) - EqualityPredicate.new(@attribute1, @attribute2).should_not == EqualityPredicate.new(@attribute1, @attribute1) - end - - it "obtains if the concrete type of the predicates are identical" do - EqualityPredicate.new(@attribute1, @attribute2).should_not == BinaryPredicate.new(@attribute1, @attribute2) - end - - it "is commutative on the attributes" do - EqualityPredicate.new(@attribute1, @attribute2).should == EqualityPredicate.new(@attribute2, @attribute1) - end - end -end \ No newline at end of file diff --git a/spec/predicates/relation_inclusion_predicate_spec.rb b/spec/predicates/relation_inclusion_predicate_spec.rb deleted file mode 100644 index 6cd37fafa8..0000000000 --- a/spec/predicates/relation_inclusion_predicate_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe RelationInclusionPredicate do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute = @relation1[:baz] - end - - describe RelationInclusionPredicate, '==' do - it "obtains if attribute1 and attribute2 are identical" do - RelationInclusionPredicate.new(@attribute, @relation1).should == RelationInclusionPredicate.new(@attribute, @relation1) - RelationInclusionPredicate.new(@attribute, @relation1).should_not == RelationInclusionPredicate.new(@attribute, @relation2) - end - end -end \ No newline at end of file diff --git a/spec/relations/attribute_spec.rb b/spec/relations/attribute_spec.rb deleted file mode 100644 index 4887be38d2..0000000000 --- a/spec/relations/attribute_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe Attribute do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - end - - describe '#aliazz' do - it "manufactures an aliased attributed" do - pending - end - - it "should be renamed to #alias!" do - pending - @relation1.alias - end - end - - describe '#qualified_name' do - it "manufactures an attribute name prefixed with the relation's name" do - @relation1[:id].qualified_name.should == 'foo.id' - end - end - - describe '#qualify' do - it "manufactures an attribute aliased with that attributes qualified name" do - @relation1[:id].qualify == @relation1[:id].qualify - end - end - - describe '#eql?' do - it "obtains if the relation and attribute name are identical" do - Attribute.new(@relation1, :name).should be_eql(Attribute.new(@relation1, :name)) - Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation1, :another_name)) - Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation2, :name)) - end - end - - describe 'predications' do - before do - @attribute1 = Attribute.new(@relation1, :name) - @attribute2 = Attribute.new(@relation2, :name) - end - - describe '==' do - it "manufactures an equality predicate" do - (@attribute1 == @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) - end - end - - describe '<' do - it "manufactures a less-than predicate" do - (@attribute1 < @attribute2).should == LessThanPredicate.new(@attribute1, @attribute2) - end - end - - describe '<=' do - it "manufactures a less-than or equal-to predicate" do - (@attribute1 <= @attribute2).should == LessThanOrEqualToPredicate.new(@attribute1, @attribute2) - end - end - - describe '>' do - it "manufactures a greater-than predicate" do - (@attribute1 > @attribute2).should == GreaterThanPredicate.new(@attribute1, @attribute2) - end - end - - describe '>=' do - it "manufactures a greater-than or equal to predicate" do - (@attribute1 >= @attribute2).should == GreaterThanOrEqualToPredicate.new(@attribute1, @attribute2) - end - end - - describe '=~' do - it "manufactures a match predicate" do - (@attribute1 =~ /.*/).should == MatchPredicate.new(@attribute1, @attribute2) - end - end - end - - describe '#to_sql' do - it "manufactures a column" do - Attribute.new(@relation1, :name, :alias).to_sql.should == SelectsBuilder.new do - column :foo, :name, :alias - end - end - end -end diff --git a/spec/relations/join_operation_spec.rb b/spec/relations/join_operation_spec.rb deleted file mode 100644 index db30198f6e..0000000000 --- a/spec/relations/join_operation_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe 'between two relations' do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - end - - describe '==' do - it "obtains if the relations of both joins are identical" do - JoinOperation.new(@relation1, @relation2).should == JoinOperation.new(@relation1, @relation2) - JoinOperation.new(@relation1, @relation2).should_not == JoinOperation.new(@relation1, @relation1) - end - - it "is commutative on the relations" do - JoinOperation.new(@relation1, @relation2).should == JoinOperation.new(@relation2, @relation1) - end - end - - describe 'on' do - before do - @predicate = Predicate.new - @join_operation = JoinOperation.new(@relation1, @relation2) - class << @join_operation - def relation_class - JoinRelation - end - end - end - - it "manufactures a join relation of the appropriate type" do - @join_operation.on(@predicate).should == JoinRelation.new(@relation1, @relation2, @predicate) - end - end -end \ No newline at end of file diff --git a/spec/relations/join_relation_spec.rb b/spec/relations/join_relation_spec.rb deleted file mode 100644 index ece7e61cc1..0000000000 --- a/spec/relations/join_relation_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe JoinRelation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @predicate = EqualityPredicate.new(@relation1[:id], @relation2[:id]) - end - - describe '==' do - it 'obtains if the two relations and the predicate are identical' do - JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation1, @relation2, @predicate) - JoinRelation.new(@relation1, @relation2, @predicate).should_not == JoinRelation.new(@relation1, @relation1, @predicate) - end - - it 'is commutative on the relations' do - JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation2, @relation1, @predicate) - end - end - - describe '#qualify' do - it 'distributes over the relations and predicates' do - InnerJoinRelation.new(@relation1, @relation2, @predicate).qualify. \ - should == InnerJoinRelation.new(@relation1.qualify, @relation2.qualify, @predicate.qualify) - end - end - - describe '#to_sql' do - before do - @relation1 = @relation1.select(@relation1[:id] == @relation2[:foo_id]) - end - - it 'manufactures sql joining the two tables on the predicate, merging the selects' do - InnerJoinRelation.new(@relation1, @relation2, @predicate).to_s.should == SelectBuilder.new do - select do - column :foo, :name - column :foo, :id - column :bar, :name - column :bar, :foo_id - column :bar, :id - end - from :foo do - inner_join :bar do - equals do - column :foo, :id - column :bar, :id - end - end - end - where do - equals do - column :foo, :id - column :bar, :foo_id - end - end - end.to_s - end - end -end \ No newline at end of file diff --git a/spec/relations/order_relation_spec.rb b/spec/relations/order_relation_spec.rb deleted file mode 100644 index a78ac148e2..0000000000 --- a/spec/relations/order_relation_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe OrderRelation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute1 = @relation1[:id] - @attribute2 = @relation2[:id] - end - - describe '==' do - it "obtains if the relation and attributes are identical" do - OrderRelation.new(@relation1, @attribute1, @attribute2).should == OrderRelation.new(@relation1, @attribute1, @attribute2) - OrderRelation.new(@relation1, @attribute1).should_not == OrderRelation.new(@relation2, @attribute1) - OrderRelation.new(@relation1, @attribute1, @attribute2).should_not == OrderRelation.new(@relation1, @attribute2, @attribute1) - end - end - - describe '#qualify' do - it "distributes over the relation and attributes" do - OrderRelation.new(@relation1, @attribute1).qualify. \ - should == OrderRelation.new(@relation1.qualify, @attribute1.qualify) - end - end - - describe '#to_sql' do - it "manufactures sql with an order clause" do - OrderRelation.new(@relation1, @attribute1).to_s.should == SelectBuilder.new do - select do - column :foo, :name - column :foo, :id - end - from :foo - order_by do - column :foo, :id - end - end.to_s - end - end - -end \ No newline at end of file diff --git a/spec/relations/projection_relation_spec.rb b/spec/relations/projection_relation_spec.rb deleted file mode 100644 index 5a33b16bd5..0000000000 --- a/spec/relations/projection_relation_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe ProjectionRelation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute1 = @relation1[:id] - @attribute2 = @relation2[:id] - end - - describe '==' do - it "obtains if the relations and attributes are identical" do - ProjectionRelation.new(@relation1, @attribute1, @attribute2).should == ProjectionRelation.new(@relation1, @attribute1, @attribute2) - ProjectionRelation.new(@relation1, @attribute1).should_not == ProjectionRelation.new(@relation2, @attribute1) - ProjectionRelation.new(@relation1, @attribute1).should_not == ProjectionRelation.new(@relation1, @attribute2) - end - end - - describe '#qualify' do - it "distributes over teh relation and attributes" do - ProjectionRelation.new(@relation1, @attribute1).qualify. \ - should == ProjectionRelation.new(@relation1.qualify, @attribute1.qualify) - end - end - - describe '#to_sql' do - it "manufactures sql with a limited select clause" do - ProjectionRelation.new(@relation1, @attribute1).to_s.should == SelectBuilder.new do - select do - column :foo, :id - end - from :foo - end.to_s - end - end -end \ No newline at end of file diff --git a/spec/relations/range_relation_spec.rb b/spec/relations/range_relation_spec.rb deleted file mode 100644 index 926cc0929f..0000000000 --- a/spec/relations/range_relation_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe RangeRelation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @range1 = 1..2 - @range2 = 4..9 - end - - describe '==' do - it "obtains if the relation and range are identical" do - RangeRelation.new(@relation1, @range1).should == RangeRelation.new(@relation1, @range1) - RangeRelation.new(@relation1, @range1).should_not == RangeRelation.new(@relation2, @range1) - RangeRelation.new(@relation1, @range1).should_not == RangeRelation.new(@relation1, @range2) - end - end - - describe '#qualify' do - it "distributes over the relation and attributes" do - pending - end - end - - describe '#to_sql' do - it "manufactures sql with limit and offset" do - range_size = @range2.last - @range2.first + 1 - range_start = @range2.first - RangeRelation.new(@relation1, @range2).to_s.should == SelectBuilder.new do - select do - column :foo, :name - column :foo, :id - end - from :foo - limit range_size - offset range_start - end.to_s - end - end - -end \ No newline at end of file diff --git a/spec/relations/relation_spec.rb b/spec/relations/relation_spec.rb deleted file mode 100644 index d029827f21..0000000000 --- a/spec/relations/relation_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe Relation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @attribute1 = Attribute.new(@relation1, :id) - @attribute2 = Attribute.new(@relation1, :name) - end - - describe 'joins' do - describe '<=>' do - it "manufactures an inner join operation between those two relations" do - (@relation1 <=> @relation2).should == InnerJoinOperation.new(@relation1, @relation2) - end - end - - describe '<<' do - it "manufactures a left outer join operation between those two relations" do - (@relation1 << @relation2).should == LeftOuterJoinOperation.new(@relation1, @relation2) - end - end - end - - describe '[]' do - it "manufactures an attribute when given a symbol" do - @relation1[:id].should be_eql(Attribute.new(@relation1, :id)) - end - - it "manufactures a range relation when given a range" do - @relation1[1..2].should == RangeRelation.new(@relation1, 1..2) - end - end - - describe '#include?' do - it "manufactures an inclusion predicate" do - @relation1.include?(@attribute1).should == RelationInclusionPredicate.new(@attribute1, @relation1) - end - end - - describe '#project' do - it "collapses identical projections" do - pending - end - - it "manufactures a projection relation" do - @relation1.project(@attribute1, @attribute2).should == ProjectionRelation.new(@relation1, @attribute1, @attribute2) - end - end - - describe '#rename' do - it "manufactures a rename relation" do - @relation1.rename(@attribute1, :foo).should == RenameRelation.new(@relation1, @attribute1 => :foo) - end - end - - describe '#select' do - before do - @predicate = EqualityPredicate.new(@attribute1, @attribute2) - end - - it "manufactures a selection relation" do - @relation1.select(@attribute1, @attribute2).should == SelectionRelation.new(@relation1, @attribute1, @attribute2) - end - end - - describe 'order' do - it "manufactures an order relation" do - @relation1.order(@attribute1, @attribute2).should == OrderRelation.new(@relation1, @attribute1, @attribute2) - end - end -end \ No newline at end of file diff --git a/spec/relations/rename_relation_spec.rb b/spec/relations/rename_relation_spec.rb deleted file mode 100644 index 301ee6db9e..0000000000 --- a/spec/relations/rename_relation_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe RenameRelation do - before do - @relation = TableRelation.new(:foo) - @renamed_relation = RenameRelation.new(@relation, @relation[:id] => :schmid) - end - - describe '#initialize' do - it "manufactures nested rename relations if multiple renames are provided" do - RenameRelation.new(@relation, @relation[:id] => :humpty, @relation[:name] => :dumpty). \ - should == RenameRelation.new(RenameRelation.new(@relation, @relation[:id] => :humpty), @relation[:name] => :dumpty) - end - - it "raises an exception if the alias provided is already used" do - pending - end - end - - describe '==' do - it "obtains if the relation, attribute, and alias are identical" do - pending - end - end - - describe '#attributes' do - it "manufactures a list of attributes with the renamed attribute aliased" do - RenameRelation.new(@relation, @relation[:id] => :schmid).attributes.should == - (@relation.attributes - [@relation[:id]]) + [@relation[:id].aliazz(:schmid)] - end - end - - describe '[]' do - it 'indexes attributes by alias' do - @renamed_relation[:id].should be_nil - @renamed_relation[:schmid].should == @relation[:id] - end - end - - describe '#schmattribute' do - it "should be renamed" do - pending - end - end - - describe '#qualify' do - it "distributes over the relation and renames" do - RenameRelation.new(@relation, @relation[:id] => :schmid).qualify. \ - should == RenameRelation.new(@relation.qualify, @relation[:id].qualify => :schmid) - end - end - - describe '#to_sql' do - it 'manufactures sql aliasing the attribute' do - @renamed_relation.to_s.should == SelectBuilder.new do - select do - column :foo, :name - column :foo, :id, :schmid - end - from :foo - end.to_s - end - end -end \ No newline at end of file diff --git a/spec/relations/selection_relation_spec.rb b/spec/relations/selection_relation_spec.rb deleted file mode 100644 index 656a386fd6..0000000000 --- a/spec/relations/selection_relation_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe SelectionRelation do - before do - @relation1 = TableRelation.new(:foo) - @relation2 = TableRelation.new(:bar) - @predicate1 = EqualityPredicate.new(@relation1[:id], @relation2[:foo_id]) - @predicate2 = LessThanPredicate.new(@relation1[:age], 2) - end - - describe '==' do - it "obtains if both the predicate and the relation are identical" do - SelectionRelation.new(@relation1, @predicate1). \ - should == SelectionRelation.new(@relation1, @predicate1) - SelectionRelation.new(@relation1, @predicate1). \ - should_not == SelectionRelation.new(@relation2, @predicate1) - SelectionRelation.new(@relation1, @predicate1). \ - should_not == SelectionRelation.new(@relation1, @predicate2) - end - end - - describe '#initialize' do - it "manufactures nested selection relations if multiple predicates are provided" do - SelectionRelation.new(@relation1, @predicate1, @predicate2). \ - should == SelectionRelation.new(SelectionRelation.new(@relation1, @predicate2), @predicate1) - end - end - - describe '#qualify' do - it "distributes over the relation and predicates" do - SelectionRelation.new(@relation1, @predicate1).qualify. \ - should == SelectionRelation.new(@relation1.qualify, @predicate1.qualify) - end - end - - describe '#to_sql' do - it "manufactures sql with where clause conditions" do - SelectionRelation.new(@relation1, @predicate1).to_s.should == SelectBuilder.new do - select do - column :foo, :name - column :foo, :id - end - from :foo - where do - equals do - column :foo, :id - column :bar, :foo_id - end - end - end.to_s - end - end -end \ No newline at end of file diff --git a/spec/relations/table_relation_spec.rb b/spec/relations/table_relation_spec.rb deleted file mode 100644 index 0380372344..0000000000 --- a/spec/relations/table_relation_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe TableRelation do - before do - @relation = TableRelation.new(:users) - end - - describe '#to_sql' do - it "returns a simple SELECT query" do - @relation.to_sql.should == SelectBuilder.new do |s| - select do - column :users, :name - column :users, :id - end - from :users - end - end - end - - describe '#attributes' do - it 'manufactures attributes corresponding to columns in the table' do - pending - end - end - - describe '#qualify' do - it 'manufactures a rename relation with all attribute names qualified' do - @relation.qualify.should == RenameRelation.new( - RenameRelation.new(@relation, @relation[:id] => 'users.id'), @relation[:name] => 'users.name' - ) - end - end -end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8d90e0dd51..8f5d76370c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,9 @@ require 'rubygems' require 'spec' -require 'rr' -require File.join(File.dirname(__FILE__), '..', 'lib', 'sql_algebra') -require File.join(File.dirname(__FILE__), 'spec_helpers', 'be_like') +dir = File.dirname(__FILE__) +$LOAD_PATH.unshift "#{dir}/../lib" +Dir["#{dir}/matchers/*"].each { |m| require "#{dir}/matchers/#{File.basename(m)}" } +require 'active_relation' ActiveRecord::Base.configurations = { 'sql_algebra_test' => { diff --git a/spec/spec_helpers/be_like.rb b/spec/spec_helpers/be_like.rb deleted file mode 100644 index cea3f3027b..0000000000 --- a/spec/spec_helpers/be_like.rb +++ /dev/null @@ -1,24 +0,0 @@ -module BeLikeMatcher - class BeLike - def initialize(expected) - @expected = expected - end - - def matches?(target) - @target = target - @expected.gsub(/\s+/, ' ').strip == @target.gsub(/\s+/, ' ').strip - end - - def failure_message - "expected #{@target} to be like #{@expected}" - end - - def negative_failure_message - "expected #{@target} to be unlike #{@expected}" - end - end - - def be_like(expected) - BeLike.new(expected) - end -end \ No newline at end of file diff --git a/spec/sql_builder/conditions_spec.rb b/spec/sql_builder/conditions_spec.rb deleted file mode 100644 index dc44cedc85..0000000000 --- a/spec/sql_builder/conditions_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe ConditionsBuilder do - describe '#to_s' do - describe 'with aliased columns' do - it 'manufactures correct sql' do - ConditionsBuilder.new do - equals do - column(:a, :b) - column(:c, :d, 'e') - end - end.to_s.should be_like(""" - `a`.`b` = `c`.`d` - """) - end - end - end -end \ No newline at end of file diff --git a/spec/sql_builder/select_builder_spec.rb b/spec/sql_builder/select_builder_spec.rb deleted file mode 100644 index 122539967e..0000000000 --- a/spec/sql_builder/select_builder_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', 'spec_helper') - -describe SelectBuilder do - describe '#to_s' do - describe 'with select and from clauses' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - all - end - from :users - end.to_s.should be_like(""" - SELECT * - FROM `users` - """) - end - end - - describe 'with specified columns and column aliases' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - column :a, :b, 'c' - column :e, :f - end - from :users - end.to_s.should be_like(""" - SELECT `a`.`b` AS 'c', `e`.`f` - FROM `users` - """) - end - end - - describe 'with where clause' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - all - end - from :users - where do - equals do - value 1 - column :b, :c - end - end - end.to_s.should be_like(""" - SELECT * - FROM `users` - WHERE 1 = `b`.`c` - """) - end - end - - describe 'with inner join' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - all - end - from :users do - inner_join(:friendships) do - equals do - column :users, :id - column :friendships, :user_id - end - end - end - end.to_s.should be_like(""" - SELECT * - FROM `users` INNER JOIN `friendships` ON `users`.`id` = `friendships`.`user_id` - """) - end - end - - describe 'with order' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - all - end - from :users - order_by do - column :users, :id - column :users, :created_at, 'alias' - end - end.to_s.should be_like(""" - SELECT * - FROM `users` - ORDER BY `users`.`id`, `users`.`created_at` - """) - end - end - - describe 'with limit and/or offset' do - it 'manufactures correct sql' do - SelectBuilder.new do - select do - all - end - from :users - limit 10 - offset 10 - end.to_s.should be_like(""" - SELECT * - FROM `users` - LIMIT 10 - OFFSET 10 - """) - end - end - end -end \ No newline at end of file -- cgit v1.2.3