From b9599502c9e738a5a1513e75d08f8d40ed408265 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 15:22:09 +0530 Subject: Add Relation#construct_relation_for_association_calculations for calculations with includes --- activerecord/lib/active_record/relation/finder_methods.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 980c5796f3..c48c8fe828 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -53,6 +53,12 @@ module ActiveRecord [] end + def construct_relation_for_association_calculations + including = (@eager_load_values + @includes_values).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel)) + construct_relation_for_association_find(join_dependency) + end + def construct_relation_for_association_find(join_dependency) relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) -- cgit v1.2.3 From 73b179eb689611ac0518584b21f2304756a7e981 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 20:27:14 +0530 Subject: Delegate count to Relation --- activerecord/lib/active_record/relation/finder_methods.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c48c8fe828..85ae3d11bb 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -56,12 +56,17 @@ module ActiveRecord def construct_relation_for_association_calculations including = (@eager_load_values + @includes_values).uniq join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel)) - construct_relation_for_association_find(join_dependency) + + relation = except(:includes, :eager_load, :preload) + apply_join_dependency(relation, join_dependency) end def construct_relation_for_association_find(join_dependency) relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) + apply_join_dependency(relation, join_dependency) + end + def apply_join_dependency(relation, join_dependency) for association in join_dependency.join_associations relation = association.join_relation(relation) end -- cgit v1.2.3 From 9acf0af544f2f5dcaf257bdc25047017c972ffce Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 23:11:54 +0530 Subject: Remove Relation#where_clause --- activerecord/lib/active_record/relation/finder_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 85ae3d11bb..90ca002c76 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -128,7 +128,7 @@ module ActiveRecord record = where(primary_key.eq(id)).first unless record - conditions = where_clause(', ') + conditions = arel.send(:where_clauses).join(', ') conditions = " [WHERE #{conditions}]" if conditions.present? raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" end @@ -154,7 +154,7 @@ module ActiveRecord if result.size == expected_size result else - conditions = where_clause(', ') + conditions = arel.send(:where_clauses).join(', ') conditions = " [WHERE #{conditions}]" if conditions.present? error = "Couldn't find all #{@klass.name.pluralize} with IDs " -- cgit v1.2.3 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 --- .../lib/active_record/relation/finder_methods.rb | 176 +++++++++++++++++---- 1 file changed, 147 insertions(+), 29 deletions(-) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') 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 -- cgit v1.2.3 From 2493229674ba2e8736901d44abe0c82e6ac82993 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 18:17:37 +0530 Subject: Delegate exists? to Relation --- .../lib/active_record/relation/finder_methods.rb | 36 ++++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 999309d2bd..2e451e380b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -119,10 +119,40 @@ module ActiveRecord args.any? ? apply_finder_options(args.first).to_a : to_a end + # Returns true if a record exists in the table that matches the +id+ or + # conditions given, or false otherwise. The argument can take five forms: + # + # * Integer - Finds the record with this primary key. + # * String - Finds the record with a primary key corresponding to this + # string (such as '5'). + # * Array - Finds the record that matches these +find+-style conditions + # (such as ['color = ?', 'red']). + # * Hash - Finds the record that matches these +find+-style conditions + # (such as {:color => 'red'}). + # * No args - Returns false if the table is empty, true otherwise. + # + # For more information about specifying conditions as a Hash or Array, + # see the Conditions section in the introduction to ActiveRecord::Base. + # + # Note: You can't pass in a condition as a string (like name = + # 'Jamie'), since it would be sanitized and then queried against + # the primary key column, like id = 'name = \'Jamie\''. + # + # ==== Examples + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(:name => "David") + # Person.exists?(['name LIKE ?', "%#{query}%"]) + # Person.exists? def exists?(id = nil) - relation = select(primary_key).limit(1) - relation = relation.where(primary_key.eq(id)) if id - relation.first ? true : false + case id + when Array, Hash + where(id).exists? + else + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id + relation.first ? true : false + end end protected -- cgit v1.2.3 From 459e9b29d4b9922d5ce3f87dd62fd43e752ac3da Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 20 Jan 2010 22:20:33 +0530 Subject: Use @limit_value and @offset_value instead of calling arel --- activerecord/lib/active_record/relation/finder_methods.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/relation/finder_methods.rb') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2e451e380b..d6d3d66642 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -272,15 +272,15 @@ module ActiveRecord result = where(primary_key.in(ids)).all expected_size = - if arel.taken && ids.size > arel.taken - arel.taken + if @limit_value && ids.size > @limit_value + @limit_value else ids.size end # 11 ids with limit 3, offset 9 should give 2 results. - if arel.skipped && (ids.size - arel.skipped < expected_size) - expected_size = ids.size - arel.skipped + if @offset_value && (ids.size - @offset_value < expected_size) + expected_size = ids.size - @offset_value end if result.size == expected_size -- cgit v1.2.3