Reputation: 197
Given that a User can have_many Addresses, I'm trying to validate that a given user can only have one address for a given address_type
. For example, a user can have a primary address and a billing address, but the user cannot have two primary addresses. How do I enforce that rule on my model, and how to I test it? My current best guess is that I need to validate address_type
's uniqueness scoped to user_id
, but this code is preventing two addresses from existing of the same type. I've seen other people write code very similar to this, but checking on strings instead of on enums.
<!-- language: lang-ruby -->
# user.rb
class User < ApplicationRecord
has_many :addresses
end
# address.rb
class Address < ApplicationRecord
belongs_to :user
enum :address_type => { :primary, :mailing, :billing }
validates :address_type, :uniqueness => { :scope => :user_id }
end
Upvotes: 0
Views: 712
Reputation: 101811
The Rails uniqueness validation works perfectly fine with integer columns. However your enum definition is not valid Ruby syntax.
class Address < ApplicationRecord
belongs_to :user
enum :address_type => [ :primary, :mailing, :billing ]
# or preferably with Ruby 2.0+ hash syntax
enum address_type: [ :primary, :mailing, :billing ]
# ...
end
You can test validations by calling .valid?
on a model instance and checking the errors object:
require 'rails_helper'
RSpec.describe Address, type: :model do
let(:user) { create(:user) }
it "should require the user id to be unique" do
Address.create(user: user, address_type: :primary)
duplicate = Address.new(user: user, address_type: :primary)
expect(duplicate.valid?).to be_falsy
expect(duplicate.errors.full_messages_for(:address_type)).to include "Address type has already been taken"
end
end
Beware of just testing expect(duplicate.valid?).to be_falsy
and expect(duplicate.valid?)
as it can lead to false positives/negatives. Instead test for the specific error message or key. Shoulda-matchers is pretty nice for this purpose but not strictly necessary.
require 'rails_helper'
RSpec.describe Address, type: :model do
# shoulda-matchers takes care of the boilerplate
it { should validate_uniqueness_of(:address_type).scoped_to(:user_id) }
end
You should also consider adding a compound unique index on addresses.address_type
and addresses.user_id
as this will prevent race issues.
Upvotes: 2