Vlatko Ristovski
Vlatko Ristovski

Reputation: 111

Rails uniqueness validation with unless condition unwanted behavior

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

Answers (2)

Sergio Tulentsev
Sergio Tulentsev

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

Shadwell
Shadwell

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

Related Questions