From 0e0866e0565bd530ee265b9298accff4185f7022 Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Tue, 21 Jul 2009 20:21:03 -0300 Subject: Introduced ActiveRecord::Relation, a layer between an ARel relation and an AR relation --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/associations.rb | 17 ++++---- activerecord/lib/active_record/base.rb | 56 +++++++++----------------- activerecord/lib/active_record/relation.rb | 39 ++++++++++++++++++ activerecord/test/cases/base_test.rb | 2 +- activerecord/test/cases/finder_test.rb | 4 +- activerecord/test/cases/method_scoping_test.rb | 4 +- activerecord/test/cases/named_scope_test.rb | 8 ++-- 8 files changed, 77 insertions(+), 54 deletions(-) create mode 100644 activerecord/lib/active_record/relation.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 3b8b9826fe..009071e1d4 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -52,6 +52,7 @@ module ActiveRecord autoload :Associations, 'active_record/associations' autoload :AttributeMethods, 'active_record/attribute_methods' autoload :AutosaveAssociation, 'active_record/autosave_association' + autoload :Relation, 'active_record/relation' autoload :Base, 'active_record/base' autoload :Batches, 'active_record/batches' autoload :Calculations, 'active_record/calculations' diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 47f97718eb..a0aeff68b6 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1602,16 +1602,17 @@ module ActiveRecord conditions = construct_conditions(options[:conditions], scope) || '' conditions << construct_limited_ids_condition(conditions, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) - arel_table((scope && scope[:from]) || options[:from]) - join(joins) - where(conditions) - project(column_aliases(join_dependency)) - group(construct_group(options[:group], options[:having], scope)) - order(construct_order(options[:order], scope)) + relation = arel_table((scope && scope[:from]) || options[:from]). + join(joins). + where(conditions). + project(column_aliases(join_dependency)). + group(construct_group(options[:group], options[:having], scope)). + order(construct_order(options[:order], scope) + ) - take(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) + relation = relation.take(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) - return sanitize_sql(arel_relation.to_sql) + return sanitize_sql(relation.to_sql) end def construct_limited_ids_condition(where, options, join_dependency) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 85f7cbfad2..718f7ea37b 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -663,7 +663,8 @@ module ActiveRecord #:nodoc: # This is an alias for find(:all). You can pass in all the same arguments to this method as you can # to find(:all) def all(*args) - find(:all, *args) + relation = arel_table + construct_finder_arel(*args) end # Executes a custom SQL query against your database and returns all the results. The results will @@ -861,22 +862,21 @@ module ActiveRecord #:nodoc: def update_all(updates, conditions = nil, options = {}) scope = scope(:find) - arel_table + relation = arel_table.relation if conditions = construct_conditions(conditions, scope) - where(Arel::SqlLiteral.new(conditions)) + relation = relation.where(Arel::SqlLiteral.new(conditions)) end if options.has_key?(:limit) || (scope && scope[:limit]) # Only take order from scope if limit is also provided by scope, this # is useful for updating a has_many association with a limit. - order(construct_order(options[:order], scope)) - take(construct_limit(options[:limit], scope)) + relation = relation.order(construct_order(options[:order], scope)).take(construct_limit(options[:limit], scope)) else - order(construct_order(options[:order], nil)) + relation = relation.order(construct_order(options[:order], nil)) end - arel_relation.update(sanitize_sql_for_assignment(updates)) + relation.update(sanitize_sql_for_assignment(updates)) end # Destroys the records matching +conditions+ by instantiating each @@ -1536,25 +1536,7 @@ module ActiveRecord #:nodoc: def arel_table(table = nil) table = table_name if table.blank? - self.arel_relation = Arel::Table.new(table) - end - - def arel_relation - Thread.current[:"#{self}_arel_relation"] ||= Arel::Table.new(table_name) - end - - def arel_relation=(relation) - Thread.current[:"#{self}_arel_relation"] = relation - end - - CLAUSES_METHODS = ["where", "join", "project", "group", "order", "take", "skip"].freeze - - for clause in CLAUSES_METHODS - class_eval %{ - def #{clause}(_#{clause}) - self.arel_relation = self.arel_relation.#{clause}(_#{clause}) if _#{clause} - end - } + Relation.new(self, table) end private @@ -1736,21 +1718,21 @@ module ActiveRecord #:nodoc: end end - def construct_finder_arel(options, scope = scope(:find)) + def construct_finder_arel(options = {}, scope = scope(:find)) # TODO add lock to Arel - arel_table(options[:from]) - join(construct_join(options[:joins], scope)) - where(construct_conditions(options[:conditions], scope)) - project(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))) - group(construct_group(options[:group], options[:having], scope)) - order(construct_order(options[:order], scope)) - take(construct_limit(options[:limit], scope)) - skip(construct_offset(options[:offset], scope)) - arel_relation + arel_table(options[:from]). + join(construct_join(options[:joins], scope)). + where(construct_conditions(options[:conditions], scope)). + project(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). + group(construct_group(options[:group], options[:having], scope)). + order(construct_order(options[:order], scope)). + take(construct_limit(options[:limit], scope)). + skip(construct_offset(options[:offset], scope) + ) end def construct_finder_sql(options, scope = scope(:find)) - construct_finder_arel(options, scope).to_sql + construct_finder_arel(options, scope).relation.to_sql end def construct_join(joins, scope) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb new file mode 100644 index 0000000000..36e8d98298 --- /dev/null +++ b/activerecord/lib/active_record/relation.rb @@ -0,0 +1,39 @@ +module ActiveRecord + class Relation + delegate :delete, :to_sql, :to => :relation + CLAUSES_METHODS = ["where", "join", "project", "group", "order", "take", "skip"].freeze + attr_reader :relation, :klass + + def initialize(klass, table = nil) + @klass = klass + @relation = Arel::Table.new(table || @klass.table_name) + end + + def to_a + @klass.find_by_sql(@relation.to_sql) + end + + def first + @relation = @relation.take(1) + to_a.first + end + + for clause in CLAUSES_METHODS + class_eval %{ + def #{clause}(_#{clause}) + @relation = @relation.#{clause}(_#{clause}) if _#{clause} + self + end + } + end + + private + def method_missing(method, *args, &block) + if @relation.respond_to?(method) + @relation.send(method, *args, &block) + elsif Array.instance_methods.include?(method.to_s) + to_a.send(method, *args, &block) + end + end + end +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e47f898485..3143ec2850 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1784,7 +1784,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_all_with_conditions - assert_equal Developer.find(:all, :order => 'id desc'), Developer.all(:order => 'id desc') + assert_equal Developer.find(:all, :order => 'id desc'), Developer.all.order('id desc').to_a end def test_find_ordered_last diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d8f5695a0f..f8c8d3648a 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1072,10 +1072,10 @@ class FinderTest < ActiveRecord::TestCase end def test_finder_with_scoped_from - all_topics = Topic.all + all_topics = Topic.find(:all) Topic.with_scope(:find => { :from => 'fake_topics' }) do - assert_equal all_topics, Topic.all(:from => 'topics') + assert_equal all_topics, Topic.all(:from => 'topics').to_a end end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index bea5c5fb76..d4e63ce2fd 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -592,12 +592,12 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.to_a.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.to_a.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 92fe48cb5a..10daff5d65 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -344,14 +344,14 @@ class NamedScopeTest < ActiveRecord::TestCase def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions - assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all - assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all + assert_equal Topic.all(:conditions => {:approved => true}).to_a, Topic.rejected.approved.all.to_a + assert_equal Topic.all(:conditions => {:approved => false}).to_a, Topic.approved.rejected.all.to_a # Nested hash conditions with same keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a # Nested hash conditions with different keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.to_a.uniq end def test_methods_invoked_within_scopes_should_respect_scope -- cgit v1.2.3