diff options
-rw-r--r-- | activerecord/CHANGELOG.md | 10 | ||||
-rw-r--r-- | activerecord/lib/active_record/querying.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/finder_methods.rb | 19 | ||||
-rw-r--r-- | activerecord/test/cases/base_test.rb | 14 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 34 | ||||
-rw-r--r-- | guides/source/active_record_querying.textile | 36 |
6 files changed, 114 insertions, 0 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1d5b8841e2..26f6093bc2 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* Added `#find_by` and `#find_by!` to mirror the functionality + provided by dynamic finders in a way that allows dynamic input more + easily: + + Post.find_by name: 'Spartacus', rating: 4 + Post.find_by "published_at < ?", 2.weeks.ago + Post.find_by! name: 'Spartacus' + + *Jon Leighton* + * Added ActiveRecord::Base#slice to return a hash of the given methods with their names as keys and returned values as values. diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 0e6fecbc4b..95565b503a 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -5,6 +5,7 @@ module ActiveRecord module Querying delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped + delegate :find_by, :find_by!, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2c74f4011d..74f8e30404 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -109,6 +109,25 @@ module ActiveRecord end end + # Finds the first record matching the specified conditions. There + # is no implied ording so if order matters, you should specify it + # yourself. + # + # If no record is found, returns <tt>nil</tt>. + # + # Post.find_by name: 'Spartacus', rating: 4 + # Post.find_by "published_at < ?", 2.weeks.ago + # + def find_by(*args) + where(*args).first + end + + # Like <tt>find_by</tt>, except that if no record is found, raises + # an <tt>ActiveRecord::RecordNotFound</tt> error. + def find_by!(*args) + where(*args).first! + end + # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. def first(*args) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f0fbca190c..5fb49d540f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -2069,4 +2069,18 @@ class BasicsTest < ActiveRecord::TestCase assert_nil hash[:firm_name] assert_nil hash['firm_name'] end + + ["find_by", "find_by!"].each do |meth| + test "#{meth} delegates to scoped" do + record = stub + + scope = mock + scope.expects(meth).with(:foo, :bar).returns(record) + + klass = Class.new(ActiveRecord::Base) + klass.stubs(:scoped => scope) + + assert_equal record, klass.public_send(meth, :foo, :bar) + end + end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 3edc237c44..25eb7c1672 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1256,4 +1256,38 @@ class RelationTest < ActiveRecord::TestCase assert topics.loaded? end + + test "find_by with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2) + end + + test "find_by with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = 2") + end + + test "find_by with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2) + end + + test "find_by returns nil if the record is missing" do + assert_equal nil, Post.scoped.find_by("1 = 0") + end + + test "find_by! with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2) + end + + test "find_by! with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = 2") + end + + test "find_by! with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2) + end + + test "find_by! raises RecordNotFound if the record is missing" do + assert_raises(ActiveRecord::RecordNotFound) do + Post.scoped.find_by!("1 = 0") + end + end end diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index 8e23a577e2..8471d67def 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -133,6 +133,24 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 <tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised. +h5. +find_by+ + +<tt>Model.find_by</tt> finds the first record matching some conditions. For example: + +<ruby> +Client.find_by first_name: 'Lifo' +# => #<Client id: 1, first_name: "Lifo"> + +Client.find_by first_name: 'Jon' +# => nil +</ruby> + +It is equivalent to writing: + +<ruby> +Client.where(first_name: 'Lifo').first +</ruby> + h5(#first_1). +first!+ <tt>Model.first!</tt> finds the first record. For example: @@ -167,6 +185,24 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 <tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found. +h5(#find_by_1). +find_by!+ + +<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +RecordNotFound+ if no matching record is found. For example: + +<ruby> +Client.find_by! first_name: 'Lifo' +# => #<Client id: 1, first_name: "Lifo"> + +Client.find_by! first_name: 'Jon' +# => RecordNotFound +</ruby> + +It is equivalent to writing: + +<ruby> +Client.where(first_name: 'Lifo').first! +</ruby> + h4. Retrieving Multiple Objects h5. Using Multiple Primary Keys |