Reputation: 111
I have this following validation in Playlist model with attributes title and is_deleted. The point of the 'is_deleted' column(bool type column with default value 'false') is cause I have to archive the playlists not delete them.
validates :title, presence: true, uniqueness: true, unless: :is_deleted?
Now if I have a record for example with title 'Playlist 1' and is_deleted false and try to create a record with is_deleted: true it won't validate him, which is good. If I try to update the is_deleted column from the first record to true it won't validate, which is good as well.
But here is the thing now I have two playlists with title: "Playlist 1" and is_deleted: true. If I try to update any of them to is_deleted: false, the validation won't let me. It gives a "Title has already been taken" error. I really don't understand why, cause I don't have any other record that is is_deleted: false with the title: "Playlist 1".
Upvotes: 3
Views: 2331
Reputation: 230551
This unless
runs in ruby-land. And it does what it says on the tin: "validate uniqueness of title if post is not deleted". Note that it doesn't say "validate uniqueness of title among only non-deleted records".
You should be using database tools to enforce this kind of restrictions. Postgres has partial unique indexes that can do this.
Upvotes: 3
Reputation: 34784
This is because your validation is checking for the uniqueness of :title
only. When it validates your model after changing is_deleted
to false
it is checking that the title is unique. It'll do a query something like:
Playlist.where(title: 'Playlist 1').where.not(id: self.id).exists?
and obviously there is a playlist with that title already.
The key thing is that it doesn't check the is_deleted
flag on other playlists. It is only using it on the current playlist to decide whether it should validate or not.
You probably need to us validates_uniqueness_of
rather than just validates
which means you can add conditions to the validation:
validates_uniqueness_of :title,
unless: :is_deleted?,
conditions: -> { where(is_deleted: false) }
This will ensure that your uniqueness check only checks against other playlists that are also not deleted.
You need to keep your validates :title, presence: true, unless: :is_deleted?
to validate that the title is present (or you may have some success with the allow_nil
or allow_blank
options on validates_uniqueness_of
).
Upvotes: 5