public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Add first/last methods to associations/named_scope. [#226 state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
ryanb (author)
Tue May 20 04:11:25 -0700 2008
lifo (committer)
Tue May 20 04:27:14 -0700 2008
commit  73c59638549686fccc749ffd3ac53cb533c5fd61
tree    44aebe6752869fdb13240d0bf8422388cca81767
parent  ebb642fa3a2b1a4e31abf9610ca634e6bb5d57d3
...
 
 
1
2
3
...
1
2
3
4
5
0
@@ -1,3 +1,5 @@
0
+* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
0
+
0
 *2.1.0 RC1 (May 11th, 2008)*
0
 
0
 * Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
...
48
49
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
52
53
...
330
331
332
333
 
 
 
 
334
335
336
...
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
...
350
351
352
 
353
354
355
356
357
358
359
0
@@ -48,6 +48,26 @@ module ActiveRecord
0
         end
0
       end
0
       
0
+      # fetch first using SQL if possible
0
+      def first(*args)
0
+        if fetch_first_or_last_using_find? args
0
+          find(:first, *args)
0
+        else
0
+          load_target unless loaded?
0
+          @target.first(*args)
0
+        end
0
+      end
0
+
0
+      # fetch last using SQL if possible
0
+      def last(*args)
0
+        if fetch_first_or_last_using_find? args
0
+          find(:last, *args)
0
+        else
0
+          load_target unless loaded?
0
+          @target.last(*args)
0
+        end
0
+      end
0
+
0
       def to_ary
0
         load_target
0
         @target.to_ary
0
@@ -330,7 +350,10 @@ module ActiveRecord
0
             raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
0
           end
0
         end
0
-               
0
+
0
+        def fetch_first_or_last_using_find?(args)
0
+          args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
0
+        end
0
     end
0
   end
0
 end
...
102
103
104
105
 
 
 
 
 
 
 
106
107
108
...
115
116
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
119
120
...
102
103
104
 
105
106
107
108
109
110
111
112
113
114
...
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
0
@@ -102,7 +102,13 @@ module ActiveRecord
0
     
0
     class Scope
0
       attr_reader :proxy_scope, :proxy_options
0
-      [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
0
+
0
+      [].methods.each do |m|
0
+        unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last)/
0
+          delegate m, :to => :proxy_found
0
+        end
0
+      end
0
+
0
       delegate :scopes, :with_scope, :to => :proxy_scope
0
 
0
       def initialize(proxy_scope, options, &block)
0
@@ -115,6 +121,22 @@ module ActiveRecord
0
         load_found; self
0
       end
0
 
0
+      def first(*args)
0
+        if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
0
+          proxy_found.first(*args)
0
+        else
0
+          find(:first, *args)
0
+        end
0
+      end
0
+
0
+      def last(*args)
0
+        if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
0
+          proxy_found.last(*args)
0
+        else
0
+          find(:last, *args)
0
+        end
0
+      end
0
+
0
       protected
0
       def proxy_found
0
         @found || load_found
...
401
402
403
 
 
404
405
406
...
401
402
403
404
405
406
407
408
0
@@ -401,6 +401,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
0
 
0
   def test_include_uses_array_include_after_loaded
0
     project = projects(:active_record)
0
+    project.developers.class # force load target
0
+
0
     developer = project.developers.first
0
     
0
     assert_no_queries do
...
818
819
820
 
 
821
822
823
...
857
858
859
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
...
818
819
820
821
822
823
824
825
...
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
0
@@ -818,6 +818,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
0
 
0
   def test_include_uses_array_include_after_loaded
0
     firm = companies(:first_firm)
0
+    firm.clients.class # force load target
0
+
0
     client = firm.clients.first
0
 
0
     assert_no_queries do
0
@@ -857,4 +859,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
0
     assert ! firm.clients.include?(client)
0
   end
0
 
0
+  def test_calling_first_or_last_on_association_should_not_load_association
0
+    firm = companies(:first_firm)
0
+    firm.clients.first
0
+    firm.clients.last
0
+    assert !firm.clients.loaded?
0
+  end
0
+
0
+  def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
0
+    firm = companies(:first_firm)
0
+    firm.clients.class # force load target
0
+    assert firm.clients.loaded?
0
+
0
+    assert_no_queries do
0
+      firm.clients.first
0
+      assert_equal 2, firm.clients.first(2).size
0
+      firm.clients.last
0
+      assert_equal 2, firm.clients.last(2).size
0
+    end
0
+  end
0
+
0
+  def test_calling_first_or_last_on_existing_record_with_build_should_load_association
0
+    firm = companies(:first_firm)
0
+    firm.clients.build(:name => 'Foo')
0
+    assert !firm.clients.loaded?
0
+
0
+    assert_queries 1 do
0
+      firm.clients.first
0
+      firm.clients.last
0
+    end
0
+
0
+    assert firm.clients.loaded?
0
+  end
0
+
0
+  def test_calling_first_or_last_on_new_record_should_not_run_queries
0
+    firm = Firm.new
0
+
0
+    assert_no_queries do
0
+      firm.clients.first
0
+      firm.clients.last
0
+    end
0
+  end
0
+
0
+  def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
0
+    firm = companies(:first_firm)
0
+    firm.clients.class # force load target
0
+
0
+    assert_queries 2 do
0
+      assert firm.clients.loaded?
0
+      firm.clients.first(:order => 'name')
0
+      firm.clients.last(:order => 'name')
0
+    end
0
+  end
0
+
0
+  def test_calling_first_or_last_with_integer_on_association_should_load_association
0
+    firm = companies(:first_firm)
0
+
0
+    assert_queries 1 do
0
+      firm.clients.first(2)
0
+      firm.clients.last(2)
0
+    end
0
+
0
+    assert firm.clients.loaded?
0
+  end
0
+
0
 end
...
664
665
666
 
 
667
668
669
...
664
665
666
667
668
669
670
671
0
@@ -664,6 +664,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
0
 
0
   def test_has_many_through_include_uses_array_include_after_loaded
0
     david = authors(:david)
0
+    david.categories.class # force load target
0
+
0
     category = david.categories.first
0
 
0
     assert_no_queries do
...
99
100
101
102
 
103
104
105
106
107
 
108
109
110
...
99
100
101
 
102
103
104
105
106
 
107
108
109
110
0
@@ -99,12 +99,12 @@ class AssociationProxyTest < ActiveRecord::TestCase
0
     david = authors(:david)
0
     assert_equal  david, david.posts.proxy_owner
0
     assert_equal  david.class.reflect_on_association(:posts), david.posts.proxy_reflection
0
-    david.posts.first   # force load target
0
+    david.posts.class   # force load target
0
     assert_equal  david.posts, david.posts.proxy_target
0
 
0
     assert_equal  david, david.posts_with_extension.testing_proxy_owner
0
     assert_equal  david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection
0
-    david.posts_with_extension.first   # force load target
0
+    david.posts_with_extension.class   # force load target
0
     assert_equal  david.posts_with_extension, david.posts_with_extension.testing_proxy_target
0
   end
0
 
...
118
119
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
...
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
0
@@ -118,4 +118,32 @@ class NamedScopeTest < ActiveRecord::TestCase
0
     assert_equal expected_proxy_options, Topic.approved.proxy_options
0
   end
0
 
0
+  def test_first_and_last_should_support_find_options
0
+    assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
0
+    assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
0
+  end
0
+
0
+  def test_first_and_last_should_allow_integers_for_limit
0
+    assert_equal Topic.base.first(2), Topic.base.to_a.first(2)
0
+    assert_equal Topic.base.last(2), Topic.base.to_a.last(2)
0
+  end
0
+
0
+  def test_first_and_last_should_not_use_query_when_results_are_loaded
0
+    topics = Topic.base
0
+    topics.reload # force load
0
+    assert_no_queries do
0
+      topics.first
0
+      topics.last
0
+    end
0
+  end
0
+
0
+  def test_first_and_last_find_options_should_use_query_when_results_are_loaded
0
+    topics = Topic.base
0
+    topics.reload # force load
0
+    assert_queries(2) do
0
+      topics.first(:order => 'title')
0
+      topics.last(:order => 'title')
0
+    end
0
+  end
0
+
0
 end

Comments