From 52ec4311f5bf8b596612f297da0b3be8e284b038 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 03:35:25 +0530 Subject: Delegate all finders to Relation --- activerecord/lib/active_record/base.rb | 116 +------------- activerecord/lib/active_record/relation.rb | 2 - .../lib/active_record/relation/finder_methods.rb | 176 +++++++++++++++++---- .../lib/active_record/relation/spawn_methods.rb | 19 +-- 4 files changed, 154 insertions(+), 159 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c20a14551e..6063c9789f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -556,124 +556,10 @@ module ActiveRecord #:nodoc: end alias :colorize_logging= :colorize_logging - # 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. Use - # Model.find(:first, *args) or its shortcut Model.first(*args). - # * 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. Use - # Model.find(:last, *args) or its shortcut Model.last(*args). - # * Find all - This will return all the records matched by the options used. - # If no records are found, an empty array is returned. Use - # Model.find(:all, *args) or its shortcut Model.all(*args). - # - # All approaches accept an options hash as their last parameter. - # - # ==== Parameters - # - # * :conditions - An SQL fragment like "administrator = 1", [ "user_name = ?", username ], or ["user_name = :user_name", { :user_name => user_name }]. See conditions in the intro. - # * :order - An SQL fragment like "created_at DESC, name". - # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :having - Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. - # * :limit - An integer determining the limit on the number of rows that should be returned. - # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. - # * :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed), - # named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s), - # or an array containing a mixture of both strings and named associations. - # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. - # Pass :readonly => false to override. - # * :include - Names associations that should be loaded alongside. The symbols named refer - # to already defined associations. See eager loading under Associations. - # * :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not - # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name"). - # * :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name - # of a database view). - # * :readonly - Mark the returned records read-only so they cannot be saved or updated. - # * :lock - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". - # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE". - # - # ==== Examples - # - # # find by id - # Person.find(1) # returns the object for ID = 1 - # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) - # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) - # Person.find([1]) # returns an array for the object with ID = 1 - # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC") - # - # Note that returned records may not be in the same order as the ids you - # provide since database rows are unordered. Give an explicit :order - # to ensure the results are sorted. - # - # ==== Examples - # - # # find first - # Person.find(:first) # returns the first object fetched by SELECT * FROM people - # Person.find(:first, :conditions => [ "user_name = ?", user_name]) - # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }]) - # Person.find(:first, :order => "created_on DESC", :offset => 5) - # - # # 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) - # - # # 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) - # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] } - # Person.find(:all, :offset => 10, :limit => 10) - # Person.find(:all, :include => [ :account, :friends ]) - # Person.find(:all, :group => "category") - # - # Example for find with a lock: Imagine two concurrent transactions: - # each will read person.visits == 2, add 1 to it, and save, resulting - # in two saves of person.visits = 3. By locking the row, the second - # transaction has to wait until the first is finished; we get the - # expected person.visits == 4. - # - # Person.transaction do - # person = Person.find(1, :lock => true) - # person.visits += 1 - # person.save! - # end - def find(*args) - options = args.extract_options! - - relation = construct_finder_arel(options, current_scoped_methods) - - case args.first - when :first, :last, :all - relation.send(args.first) - else - relation.find(*args) - end - end - + delegate :find, :first, :last, :all, :to => :scoped delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped - # A convenience wrapper for find(:first, *args). You can pass in all the - # same arguments to this method as you can to find(:first). - def first(*args) - find(:first, *args) - end - - # A convenience wrapper for find(:last, *args). You can pass in all the - # same arguments to this method as you can to find(:last). - def last(*args) - find(:last, *args) - end - - # A convenience wrapper for find(:all, *args). You can pass in all the - # same arguments to this method as you can to find(:all). - def all(*args) - find(:all, *args) - end - # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call # this method from. If you call Product.find_by_sql then the results will be returned in diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f86677aa51..decde50427 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -60,8 +60,6 @@ module ActiveRecord @records end - alias all to_a - def size loaded? ? @records.length : count end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 90ca002c76..999309d2bd 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,45 +1,128 @@ module ActiveRecord module FinderMethods - - def find(*ids, &block) + # 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. Use + # Model.find(:first, *args) or its shortcut Model.first(*args). + # * 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. Use + # Model.find(:last, *args) or its shortcut Model.last(*args). + # * Find all - This will return all the records matched by the options used. + # If no records are found, an empty array is returned. Use + # Model.find(:all, *args) or its shortcut Model.all(*args). + # + # All approaches accept an options hash as their last parameter. + # + # ==== Parameters + # + # * :conditions - An SQL fragment like "administrator = 1", [ "user_name = ?", username ], or ["user_name = :user_name", { :user_name => user_name }]. See conditions in the intro. + # * :order - An SQL fragment like "created_at DESC, name". + # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :having - Combined with +:group+ this can be used to filter the records that a GROUP BY returns. Uses the HAVING SQL-clause. + # * :limit - An integer determining the limit on the number of rows that should be returned. + # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. + # * :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed), + # named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s), + # or an array containing a mixture of both strings and named associations. + # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns. + # Pass :readonly => false to override. + # * :include - Names associations that should be loaded alongside. The symbols named refer + # to already defined associations. See eager loading under Associations. + # * :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not + # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name"). + # * :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name + # of a database view). + # * :readonly - Mark the returned records read-only so they cannot be saved or updated. + # * :lock - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". + # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE". + # + # ==== Examples + # + # # find by id + # Person.find(1) # returns the object for ID = 1 + # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) + # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) + # Person.find([1]) # returns an array for the object with ID = 1 + # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC") + # + # Note that returned records may not be in the same order as the ids you + # provide since database rows are unordered. Give an explicit :order + # to ensure the results are sorted. + # + # ==== Examples + # + # # find first + # Person.find(:first) # returns the first object fetched by SELECT * FROM people + # Person.find(:first, :conditions => [ "user_name = ?", user_name]) + # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }]) + # Person.find(:first, :order => "created_on DESC", :offset => 5) + # + # # 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) + # + # # 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) + # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] } + # Person.find(:all, :offset => 10, :limit => 10) + # Person.find(:all, :include => [ :account, :friends ]) + # Person.find(:all, :group => "category") + # + # Example for find with a lock: Imagine two concurrent transactions: + # each will read person.visits == 2, add 1 to it, and save, resulting + # in two saves of person.visits = 3. By locking the row, the second + # transaction has to wait until the first is finished; we get the + # expected person.visits == 4. + # + # Person.transaction do + # person = Person.find(1, :lock => true) + # person.visits += 1 + # person.save! + # end + def find(*args, &block) return to_a.find(&block) if block_given? - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? - - ids = ids.flatten.compact.uniq + options = args.extract_options! - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" - when 1 - result = find_one(ids.first) - expects_array ? [ result ] : result + if options.present? + apply_finder_options(options).find(*args) else - find_some(ids) + case args.first + when :first, :last, :all + send(args.first) + else + find_with_ids(*args) + end end end - def exists?(id = nil) - relation = select(primary_key).limit(1) - relation = relation.where(primary_key.eq(id)) if id - relation.first ? true : false + # A convenience wrapper for find(:first, *args). You can pass in all the + # same arguments to this method as you can to find(:first). + def first(*args) + args.any? ? apply_finder_options(args.first).first : find_first end - def first - if loaded? - @records.first - else - @first ||= limit(1).to_a[0] - end + # A convenience wrapper for find(:last, *args). You can pass in all the + # same arguments to this method as you can to find(:last). + def last(*args) + args.any? ? apply_finder_options(args.first).last : find_last end - def last - if loaded? - @records.last - else - @last ||= reverse_order.limit(1).to_a[0] - end + # A convenience wrapper for find(:all, *args). You can pass in all the + # same arguments to this method as you can to find(:all). + def all(*args) + args.any? ? apply_finder_options(args.first).to_a : to_a + end + + def exists?(id = nil) + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id + relation.first ? true : false end protected @@ -124,6 +207,25 @@ module ActiveRecord record end + def find_with_ids(*ids, &block) + return to_a.find(&block) if block_given? + + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + end + def find_one(id) record = where(primary_key.eq(id)).first @@ -163,5 +265,21 @@ module ActiveRecord end end + def find_first + if loaded? + @records.first + else + @first ||= limit(1).to_a[0] + end + end + + def find_last + if loaded? + @records.last + else + @last ||= reverse_order.limit(1).to_a[0] + end + end + end end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index d5b13c6100..1577a9b116 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -98,19 +98,12 @@ module ActiveRecord options.assert_valid_keys(VALID_FIND_OPTIONS) - relation = relation.joins(options[:joins]). - where(options[:conditions]). - select(options[:select]). - group(options[:group]). - having(options[:having]). - order(options[:order]). - limit(options[:limit]). - offset(options[:offset]). - from(options[:from]). - includes(options[:include]) - - relation = relation.lock(options[:lock]) if options[:lock].present? - relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly) + [:joins, :select, :group, :having, :order, :limit, :offset, :from, :lock, :readonly].each do |finder| + relation = relation.send(finder, options[finder]) if options.has_key?(finder) + end + + relation = relation.where(options[:conditions]) if options.has_key?(:conditions) + relation = relation.includes(options[:include]) if options.has_key?(:include) relation end -- cgit v1.2.3