diff --git a/lib/shoulda/active_record_helpers.rb b/lib/shoulda/active_record_helpers.rb
index 1ad8a7f4..e066d8d0 100644
--- a/lib/shoulda/active_record_helpers.rb
+++ b/lib/shoulda/active_record_helpers.rb
@@ -51,25 +51,32 @@ module ThoughtBot # :nodoc:
# Options:
# * :message - value the test expects to find in errors.on(:attribute).
# Regexp or string. Default = /taken/
+ # * :scoped_to - field(s) to scope the uniqueness to.
#
- # Example:
+ # Examples:
# should_require_unique_attributes :keyword, :username
+ # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
+ # should_require_unique_attributes :email, :scoped_to => :name
+ # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
#
def should_require_unique_attributes(*attributes)
message, scope = get_options!(attributes, :message, :scoped_to)
+ scope = [*scope].compact
message ||= /taken/
klass = model_class
attributes.each do |attribute|
attribute = attribute.to_sym
- should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do
+ should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" if scope}" do
assert existing = klass.find(:first), "Can't find first #{klass}"
object = klass.new
object.send(:"#{attribute}=", existing.send(attribute))
- if scope
- assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute."
- object.send(:"#{scope}=", existing.send(scope))
+ if !scope.blank?
+ scope.each do |s|
+ assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
+ object.send(:"#{s}=", existing.send(s))
+ end
end
assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
@@ -82,13 +89,18 @@ module ThoughtBot # :nodoc:
# to a value that's already taken. An alternative implementation
# could actually find all values for scope and create a unique
# one.
- if scope
+ if !scope.blank?
+ scope.each do |s|
# Assume the scope is a foreign key if the field is nil
- object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next)
+ object.send(:"#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
+ end
+
object.errors.clear
object.valid?
- assert_does_not_contain(object.errors.on(attribute), message,
- "after :#{scope} set to #{object.send(scope.to_sym)}")
+ scope.each do |s|
+ assert_does_not_contain(object.errors.on(attribute), message,
+ "after :#{s} set to #{object.send(s.to_sym)}")
+ end
end
end
end
diff --git a/test/fixtures/addresses.yml b/test/fixtures/addresses.yml
new file mode 100644
index 00000000..3b468e55
--- /dev/null
+++ b/test/fixtures/addresses.yml
@@ -0,0 +1,3 @@
+first:
+ title: Home
+ addressable: first (User)
diff --git a/test/rails_root/app/models/address.rb b/test/rails_root/app/models/address.rb
index f9443eba..cb3c363b 100644
--- a/test/rails_root/app/models/address.rb
+++ b/test/rails_root/app/models/address.rb
@@ -1,3 +1,4 @@
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
+ validates_uniqueness_of :title, :scope => [:addressable_type, :addressable_id]
end
diff --git a/test/rails_root/app/models/user.rb b/test/rails_root/app/models/user.rb
index 50441826..02a643a7 100644
--- a/test/rails_root/app/models/user.rb
+++ b/test/rails_root/app/models/user.rb
@@ -10,4 +10,5 @@ class User < ActiveRecord::Base
validates_length_of :email, :in => 1..100
validates_inclusion_of :age, :in => 1..100
validates_acceptance_of :eula
+ validates_uniqueness_of :email, :scope => :name
end
diff --git a/test/rails_root/db/migrate/006_create_addresses.rb b/test/rails_root/db/migrate/006_create_addresses.rb
index 5ec7bd54..7d559f32 100644
--- a/test/rails_root/db/migrate/006_create_addresses.rb
+++ b/test/rails_root/db/migrate/006_create_addresses.rb
@@ -1,6 +1,7 @@
class CreateAddresses < ActiveRecord::Migration
def self.up
create_table :addresses do |t|
+ t.column :title, :string
t.column :addressable_id, :integer
t.column :addressable_type, :string
end
diff --git a/test/unit/address_test.rb b/test/unit/address_test.rb
index 18f59240..6a20845a 100644
--- a/test/unit/address_test.rb
+++ b/test/unit/address_test.rb
@@ -3,4 +3,5 @@ require File.dirname(__FILE__) + '/../test_helper'
class AddressTest < Test::Unit::TestCase
load_all_fixtures
should_belong_to :addressable
+ should_require_unique_attributes :title, :scoped_to => [:addressable_id, :addressable_type]
end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index ff1a57e4..af4202f7 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -23,4 +23,5 @@ class UserTest < Test::Unit::TestCase
should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
:null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
should_require_acceptance_of :eula
+ should_require_unique_attributes :email, :scoped_to => :name
end