module ThoughtBot::Shoulda::ActiveRecord::Macros
def should_require_unique_attributes(*attributes)
should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
should_require_unique_attributes :email, :scoped_to => :name
should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
should_require_unique_attributes :keyword, :username
Examples:
* :scoped_to - field(s) to scope the uniqueness to.
Regexp or string. Default = I18n.translate('activerecord.errors.messages.taken')
* :message - value the test expects to find in errors.on(:attribute).
Options:
Requires an existing record
Ensures that the model cannot be saved if one of the attributes listed is not unique.
def should_require_unique_attributes(*attributes) message, scope = get_options!(attributes, :message, :scoped_to) scope = [*scope].compact message ||= default_error_message(:taken) klass = model_class attributes.each do |attribute| attribute = attribute.to_sym should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do assert existing = klass.find(:first), "Can't find first #{klass}" object = klass.new existing_value = existing.send(attribute) 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_bad_value(object, attribute, existing_value, message) # Now test that the object is valid when changing the scoped attribute # TODO: There is a chance that we could change the scoped field # to a value that's already taken. An alternative implementation # could actually find all values for scope and create a unique # one. if !scope.blank? scope.each do |s| # Assume the scope is a foreign key if the field is nil object.send("#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next) assert_good_value(object, attribute, existing_value, message) end end end end end