diff options
Diffstat (limited to 'activerecord/lib/active_record/relation/finder_methods.rb')
-rw-r--r-- | activerecord/lib/active_record/relation/finder_methods.rb | 101 |
1 files changed, 73 insertions, 28 deletions
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index ba222aac93..3ea3c33fcc 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -11,9 +11,11 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # Note that returned records may not be in the same order as the ids you - # provide since database rows are unordered. Give an explicit <tt>order</tt> - # to ensure the results are sorted. + # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found. + # + # NOTE: The returned records may not be in the same order as the ids you + # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt> + # option if you want the results are sorted. # # ==== Find with lock # @@ -28,6 +30,34 @@ module ActiveRecord # person.visits += 1 # person.save! # end + # + # ==== Variations of +find+ + # + # Person.where(name: 'Spartacus', rating: 4) + # # returns a chainable list (which can be empty). + # + # Person.find_by(name: 'Spartacus', rating: 4) + # # returns the first item or nil. + # + # Person.where(name: 'Spartacus', rating: 4).first_or_initialize + # # returns the first item or returns a new instance (requires you call .save to persist against the database). + # + # Person.where(name: 'Spartacus', rating: 4).first_or_create + # # returns the first item or creates it and returns it, available since Rails 3.2.1. + # + # ==== Alternatives for +find+ + # + # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) + # # returns a boolean indicating if any record with the given conditions exist. + # + # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") + # # returns a chainable list of instances with only the mentioned fields. + # + # Person.where(name: 'Spartacus', rating: 4).ids + # # returns an Array of ids, available since Rails 3.2.1. + # + # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) + # # returns an Array of the required fields, available since Rails 3.1. def find(*args) if block_given? to_a.find { |*block_args| yield(*block_args) } @@ -79,13 +109,22 @@ module ActiveRecord # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3 + # + # ==== Rails 3 + # + # Person.first # SELECT "people".* FROM "people" LIMIT 1 + # + # NOTE: Rails 3 may not order this query by the primary key and the order + # will depend on the database implementation. In order to ensure that behavior, + # use <tt>User.order(:id).first</tt> instead. + # + # ==== Rails 4 + # + # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1 + # def first(limit = nil) if limit - if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).to_a - else - limit(limit).to_a - end + find_first_with_limit(order_values, limit) else find_first end @@ -160,8 +199,9 @@ module ActiveRecord conditions = conditions.id if Base === conditions return false if !conditions - join_dependency = construct_join_dependency - relation = construct_relation_for_association_find(join_dependency) + relation = construct_relation_for_association_find(construct_join_dependency) + return false if ActiveRecord::NullRelation === relation + relation = relation.except(:select, :order).select("1 AS one").limit(1) case conditions @@ -171,9 +211,8 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none end - connection.select_value(relation, "#{name} Exists", relation.bind_values) - rescue ThrowResult - false + relation = relation.with_default_scope + connection.select_value(relation.arel, "#{name} Exists", relation.bind_values) end # This method is called whenever no records are found with either a single @@ -203,10 +242,12 @@ module ActiveRecord def find_with_associations join_dependency = construct_join_dependency relation = construct_relation_for_association_find(join_dependency) - rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) - join_dependency.instantiate(rows) - rescue ThrowResult - [] + if ActiveRecord::NullRelation === relation + [] + else + rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) + join_dependency.instantiate(rows) + end end def construct_join_dependency(joins = []) @@ -230,21 +271,22 @@ module ActiveRecord if using_limitable_reflections?(join_dependency.reflections) relation else - relation.where!(construct_limited_ids_condition(relation)) if relation.limit_value + if relation.limit_value + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids)) + end relation.except(:limit, :offset) end end - def construct_limited_ids_condition(relation) + def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) relation = relation.except(:select).select(values).distinct! id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values) - ids_array = id_rows.map {|row| row[primary_key]} - - ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array) + id_rows.map {|row| row[primary_key]} end def find_with_ids(*ids) @@ -312,12 +354,15 @@ module ActiveRecord if loaded? @records.first else - @first ||= - if with_default_scope.order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(1).to_a.first - else - limit(1).to_a.first - end + @first ||= find_first_with_limit(with_default_scope.order_values, 1).first + end + end + + def find_first_with_limit(order_values, limit) + if order_values.empty? && primary_key + order(arel_table[primary_key].asc).limit(limit).to_a + else + limit(limit).to_a end end |