From ac8fd7dfb91eb0e554537e671eaf1615a1d19757 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 2 Jan 2005 13:31:00 +0000 Subject: Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@307 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 10 ++++++++++ activerecord/lib/active_record/base.rb | 25 +++++++++++++++++++++++++ activerecord/test/finder_test.rb | 34 ++++++++++++++++++++++------------ 3 files changed, 57 insertions(+), 12 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index f354430b68..eee602faf2 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *SVN* +* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL. + They work by appending the name of an attribute to find_by_, so you get finders like Person.find_by_user_name, + Payment.find_by_transaction_id. So instead of writing Person.find_first(["user_name = ?", user_name]), you just do + Person.find_by_user_name(user_name). + + It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like + Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing + Person.find_first(["user_name = ? AND password = ?", user_name, password]), you just do + Person.find_by_user_name_and_password(user_name, password). + * Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [bitsweat] * Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 63810d3c41..cc61498a94 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -97,6 +97,17 @@ module ActiveRecord #:nodoc: # end # end # + # == Dynamic attribute-based finders + # + # Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by + # appending the name of an attribute to find_by_, so you get finders like Person.find_by_user_name, Payment.find_by_transaction_id. + # So instead of writing Person.find_first(["user_name = ?", user_name]), you just do Person.find_by_user_name(user_name). + # + # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like + # Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing + # Person.find_first(["user_name = ? AND password = ?", user_name, password]), you just do + # Person.find_by_user_name_and_password(user_name, password). + # # == Saving arrays, hashes, and other non-mappeable objects in text columns # # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+. @@ -636,6 +647,20 @@ module ActiveRecord #:nodoc: return table_name end + # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into + # find_first(["user_name = ?", user_name]) and find_first(["user_name = ? AND password = ?", user_name, password]) respectively. + def method_missing(method_id, *arguments) + method_name = method_id.id2name + + if method_name =~ /find_by_([_a-z]+)/ + attributes = $1.split("_and_") + attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] } + conditions = attributes.collect { |attr_name| "#{attr_name} = ? "}.join(" AND ") + find_first([conditions, *arguments]) + else + super + end + end protected def subclasses diff --git a/activerecord/test/finder_test.rb b/activerecord/test/finder_test.rb index 74ea11c944..fdbbc68e98 100755 --- a/activerecord/test/finder_test.rb +++ b/activerecord/test/finder_test.rb @@ -4,14 +4,10 @@ require 'fixtures/topic' require 'fixtures/entrant' class FinderTest < Test::Unit::TestCase - def setup - @company_fixtures = create_fixtures("companies") - @topic_fixtures = create_fixtures("topics") - @entrant_fixtures = create_fixtures("entrants") - end + fixtures :companies, :topics, :entrants def test_find - assert_equal(@topic_fixtures["first"]["title"], Topic.find(1).title) + assert_equal(@topics["first"]["title"], Topic.find(1).title) end def test_find_by_array_of_one_id @@ -21,7 +17,7 @@ class FinderTest < Test::Unit::TestCase def test_find_by_ids assert_equal(2, Topic.find(1, 2).length) - assert_equal(@topic_fixtures["second"]["title"], Topic.find([ 2 ]).first.title) + assert_equal(@topics["second"]["title"], Topic.find([ 2 ]).first.title) end def test_find_by_ids_missing_one @@ -34,33 +30,33 @@ class FinderTest < Test::Unit::TestCase entrants = Entrant.find_all nil, "id ASC", 2 assert_equal(2, entrants.size) - assert_equal(@entrant_fixtures["first"]["name"], entrants.first.name) + assert_equal(@entrants["first"]["name"], entrants.first.name) end def test_find_all_with_prepared_limit_and_offset entrants = Entrant.find_all nil, "id ASC", ["? OFFSET ?", 2, 1] assert_equal(2, entrants.size) - assert_equal(@entrant_fixtures["second"]["name"], entrants.first.name) + assert_equal(@entrants["second"]["name"], entrants.first.name) end def test_find_with_entire_select_statement topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'" assert_equal(1, topics.size) - assert_equal(@topic_fixtures["second"]["title"], topics.first.title) + assert_equal(@topics["second"]["title"], topics.first.title) end def test_find_with_prepared_select_statement topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"] assert_equal(1, topics.size) - assert_equal(@topic_fixtures["second"]["title"], topics.first.title) + assert_equal(@topics["second"]["title"], topics.first.title) end def test_find_first first = Topic.find_first "title = 'The First Topic'" - assert_equal(@topic_fixtures["first"]["title"], first.title) + assert_equal(@topics["first"]["title"], first.title) end def test_find_first_failing @@ -168,6 +164,20 @@ class FinderTest < Test::Unit::TestCase assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) end + def test_find_by_one_attribute + assert_equal @topics["first"].find, Topic.find_by_title("The First Topic") + assert_nil Topic.find_by_title("The First Topic!") + end + + def test_find_by_one_missing_attribute + assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") } + end + + def test_find_by_two_attributes + assert_equal @topics["first"].find, Topic.find_by_title_and_author_name("The First Topic", "David") + assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary") + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) -- cgit v1.2.3