Skip to content

Commit

Permalink
Add dynamic find_or_create_by_{attribute}! method.
Browse files Browse the repository at this point in the history
(cherry picked from commit 5282485d310d1a6ffcf55e4e7f56ab234e16880d)

Conflicts:

	activerecord/CHANGELOG.md
	activerecord/lib/active_record/dynamic_finder_match.rb
  • Loading branch information
pixeltrix committed Mar 12, 2012
1 parent 2b9041b commit 90d9635
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 1 deletion.
2 changes: 2 additions & 0 deletions 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*
Expand Down
12 changes: 12 additions & 0 deletions activerecord/lib/active_record/dynamic_finder_match.rb
Expand Up @@ -18,6 +18,10 @@ def self.match(method)
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
Expand Down Expand Up @@ -52,5 +56,13 @@ def creator?
def bang?
@bang
end

def save_record?
@instantiator == :create
end

def save_method
bang? ? :save! : :save
end
end
end
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/relation/finder_methods.rb
Expand Up @@ -290,7 +290,7 @@ def find_or_instantiator_by_attributes(match, attributes, *args)
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
Expand Down
8 changes: 8 additions & 0 deletions activerecord/test/cases/dynamic_finder_match_test.rb
Expand Up @@ -83,6 +83,14 @@ def test_find_or_create
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
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/cases/finder_respond_to_test.rb
Expand Up @@ -56,6 +56,16 @@ def test_should_respond_to_find_or_create_from_two_attributes
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
Expand Down
22 changes: 22 additions & 0 deletions activerecord/test/cases/finder_test.rb
Expand Up @@ -862,6 +862,28 @@ def test_find_or_create_from_two_attributes
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")
Expand Down
12 changes: 12 additions & 0 deletions activerecord/test/cases/relations_test.rb
Expand Up @@ -449,6 +449,18 @@ def test_dynamic_find_or_create_by_attributes
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

Expand Down

0 comments on commit 90d9635

Please sign in to comment.