From d5a4d5abb41c50f96b554374b937ffe49d472d7f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 12 Mar 2008 21:26:02 +0000 Subject: Added ActiveRecord::Base.find(:last) (closes #11338) [miloops] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9012 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/base.rb | 39 +++++++++++++++++++++++++++++++++- activerecord/test/cases/base_test.rb | 27 +++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d852a6d3d0..3f7eb54a21 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Added ActiveRecord::Base.find(:last) #11338 [miloops] + * test_native_types expects DateTime.local_offset instead of DateTime.now.offset; fixes test breakage due to dst transition [Geoff Buesing] * Add :readonly option to HasManyThrough associations. #11156 [miloops] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d5da9408f3..a30036f561 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -430,12 +430,14 @@ module ActiveRecord #:nodoc: @@schema_format = :ruby class << self # Class methods - # Find operates with three different retrieval approaches: + # Find operates with four different retrieval approaches: # # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). # If no record can be found for all of the listed ids, then RecordNotFound will be raised. # * Find first: This will return the first record matched by the options used. These options can either be specific # conditions or merely an order. If no record can be matched, nil is returned. + # * Find last: This will return the last record matched by the options used. These options can either be specific + # conditions or merely an order. If no record can be matched, nil is returned. # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned. # # All approaches accept an options hash as their last parameter. The options are: @@ -475,6 +477,11 @@ module ActiveRecord #:nodoc: # Person.find(:first, :conditions => [ "user_name = ?", user_name]) # Person.find(:first, :order => "created_on DESC", :offset => 5) # + # Examples for find last: + # Person.find(:last) # returns the last object fetched by SELECT * FROM people + # Person.find(:last, :conditions => [ "user_name = ?", user_name]) + # Person.find(:last, :order => "created_on DESC", :offset => 5) + # # Examples for find all: # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50) @@ -499,6 +506,7 @@ module ActiveRecord #:nodoc: case args.first when :first then find_initial(options) + when :last then find_last(options) when :all then find_every(options) else find_from_ids(args, options) end @@ -1236,6 +1244,35 @@ module ActiveRecord #:nodoc: find_every(options).first end + def find_last(options) + order = options[:order] + + if order + order = reverse_sql_order(order) + elsif !scoped?(:find, :order) + order = "#{table_name}.#{primary_key} DESC" + end + + if scoped?(:find, :order) + scoped_order = reverse_sql_order(scope(:find, :order)) + scoped_methods.select { |s| s[:find].update(:order => scoped_order) } + end + + find_initial(options.merge({ :order => order })) + end + + def reverse_sql_order(order_query) + reversed_query = order_query.split(/,/).each { |s| + if s.match(/\s(asc|ASC)$/) + s.gsub!(/\s(asc|ASC)$/, ' DESC') + elsif s.match(/\s(desc|DESC)$/) + s.gsub!(/\s(desc|DESC)$/, ' ASC') + elsif !s.match(/\s(asc|ASC|desc|DESC)$/) + s.concat(' DESC') + end + }.join(',') + end + def find_every(options) include_associations = merge_includes(scope(:find, :include), options[:include]) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f2720130e2..91cfdee0cf 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1616,6 +1616,33 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_find_last + last = Developer.find :last + assert_equal last, Developer.find(:first, :order => 'id desc') + end + + def test_find_ordered_last + last = Developer.find :last, :order => 'developers.salary ASC' + assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last + end + + def test_find_reverse_ordered_last + last = Developer.find :last, :order => 'developers.salary DESC' + assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last + end + + def test_find_multiple_ordered_last + last = Developer.find :last, :order => 'developers.name, developers.salary DESC' + assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last + end + + def test_find_scoped_ordered_last + last_developer = Developer.with_scope(:find => { :order => 'developers.salary ASC' }) do + Developer.find(:last) + end + assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last + end + def test_abstract_class assert !ActiveRecord::Base.abstract_class? assert LoosePerson.abstract_class? -- cgit v1.2.3