From 90d96353e6dcd962b182e03f53f2214acde00907 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Mon, 12 Mar 2012 11:39:19 +0000 Subject: Add dynamic find_or_create_by_{attribute}! method. (cherry picked from commit 5282485d310d1a6ffcf55e4e7f56ab234e16880d) Conflicts: activerecord/CHANGELOG.md activerecord/lib/active_record/dynamic_finder_match.rb --- activerecord/CHANGELOG.md | 2 ++ .../lib/active_record/dynamic_finder_match.rb | 12 ++++++++++++ .../lib/active_record/relation/finder_methods.rb | 2 +- .../test/cases/dynamic_finder_match_test.rb | 8 ++++++++ activerecord/test/cases/finder_respond_to_test.rb | 10 ++++++++++ activerecord/test/cases/finder_test.rb | 22 ++++++++++++++++++++++ activerecord/test/cases/relations_test.rb | 12 ++++++++++++ 7 files changed, 67 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5fe57712d4..16c525d211 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 3.2.3 (unreleased) ## +* Added find_or_create_by_{attribute}! dynamic method. *Andrew White* + * Whitelist all attribute assignment by default. Change the default for newly generated applications to whitelist all attribute assignment. Also update the generated model classes so users are reminded of the importance of attr_accessible. *NZKoz* * Update ActiveRecord::AttributeMethods#attribute_present? to return false for empty strings. *Jacobkg* diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index b309df9b1b..80b17a27df 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -18,6 +18,10 @@ module ActiveRecord when /^find_by_([_a-zA-Z]\w*)\!$/ bang = true names = $1 + when /^find_or_create_by_([_a-zA-Z]\w*)\!$/ + bang = true + instantiator = :create + names = $1 when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/ instantiator = $1 == 'initialize' ? :new : :create names = $2 @@ -52,5 +56,13 @@ module ActiveRecord def bang? @bang end + + def save_record? + @instantiator == :create + end + + def save_method + bang? ? :save! : :save + end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 0305e561c8..9a34927857 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -290,7 +290,7 @@ module ActiveRecord r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? - record.save if match.instantiator == :create + record.send(match.save_method) if match.save_record? end record diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb index e576870317..db619faa83 100644 --- a/activerecord/test/cases/dynamic_finder_match_test.rb +++ b/activerecord/test/cases/dynamic_finder_match_test.rb @@ -83,6 +83,14 @@ module ActiveRecord assert_equal :create, m.instantiator end + def test_find_or_create! + m = DynamicFinderMatch.match(:find_or_create_by_foo!) + assert_equal :first, m.finder + assert m.bang?, 'should be banging' + assert_equal %w{ foo }, m.attribute_names + assert_equal :create, m.instantiator + end + def test_find_or_initialize m = DynamicFinderMatch.match(:find_or_initialize_by_foo) assert_equal :first, m.finder diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 235805a67c..810c1500cc 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -56,6 +56,16 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_or_create_by_title_and_author_name end + def test_should_respond_to_find_or_create_from_one_attribute_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title!) + assert_respond_to Topic, :find_or_create_by_title! + end + + def test_should_respond_to_find_or_create_from_two_attributes_bang + ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name!) + assert_respond_to Topic, :find_or_create_by_title_and_author_name! + end + def test_should_not_respond_to_find_by_one_missing_attribute assert !Topic.respond_to?(:find_by_undertitle) end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 5a67fcdc0a..173044fc94 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -862,6 +862,28 @@ class FinderTest < ActiveRecord::TestCase assert another.persisted? end + def test_find_or_create_from_one_attribute_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name!("38signals") + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name!("38signals") + assert sig38.persisted? + end + + def test_find_or_create_from_two_attributes_bang + number_of_companies = Company.count + assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) } + assert_equal number_of_companies, Company.count + sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + end + def test_find_or_create_from_two_attributes_with_one_being_an_aggregate number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf1eb6386a..5e0caf5fce 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -449,6 +449,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') end + def test_dynamic_find_or_create_by_attributes_bang + authors = Author.scoped + + assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') } + + lifo = authors.find_or_create_by_name!('Lifo') + assert_equal "Lifo", lifo.name + assert lifo.persisted? + + assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David') + end + def test_find_id authors = Author.scoped -- cgit v1.2.3