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