1
0
Fork 0
mirror of https://github.com/thoughtbot/shoulda-matchers.git synced 2022-11-09 12:01:38 -05:00

Scoping of uniqueness validations on multiple attributes

Previously the :scoped_to option on the 'should_require_unique_attributes'
only allowed a single attribute to scope uniqueness.  This adds the ability to
scope to multiple attributes.  This also improves the documentation for the
method.
This commit is contained in:
Nathan Sutton 2008-05-29 16:48:39 -07:00
parent 9c78fe05a1
commit 911b2f7cb4
7 changed files with 29 additions and 9 deletions

View file

@ -51,25 +51,32 @@ module ThoughtBot # :nodoc:
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>/taken/</tt>
# * <tt>:scoped_to</tt> - 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

3
test/fixtures/addresses.yml vendored Normal file
View file

@ -0,0 +1,3 @@
first:
title: Home
addressable: first (User)

View file

@ -1,3 +1,4 @@
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
validates_uniqueness_of :title, :scope => [:addressable_type, :addressable_id]
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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