Add Validation of Uniqueness for the Existing Attribute

Let’s say we have a City model and corresponding UI enabling user to manage the list of cities, eventually we discovered that forgot to add validation of uniqueness for the city names and some users was able to create duplicate entries.

No panic! Let’s fix it now!

Validation Helper

The first step is obvious - uniqueness validation helper:

1
2
3
4
class City < ActiveRecord::Base
  attr_accessible :name
  validates :name, uniqueness: true
end

So far so good, but, as documentation says, it does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique.

Let’s move on and create that constraint.

Database Constraint

This is unique index we’ll add to the name attribute on the cities with the following migration:

1
2
3
4
5
class AddUniqueIndexToCity < ActiveRecord::Migration
  def change
    add_index :cities, :name, unique: true
  end
end

That’s was easy, right? And usually enough, but, as you remember, at the beginning of the post, I mentioned that some users was able to create duplicate records, so, how would we solve that problem?

Reduce Duplications

We’ll need to enrich our migration with corresponding behavior and there are many solutions we can easily find with google to achieve this.

For this post I’ll use dedupe method from the following answer on StackOverflow: http://stackoverflow.com/a/14124391/275980

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AddUniqueIndexToCity < ActiveRecord::Migration
  class City < ActiveRecord::Base
    def self.dedupe
      grouped = all.group_by{ |model| [model.name] }
      grouped.values.each do |duplicates|
        duplicates.shift # will keep the first one
        duplicates.each{|double| double.destroy}
      end
    end
  end

  def change
    City.dedupe
    add_index :cities, :name, unique: true
  end
end

You might be wondering why we’re using faux model here? That’s a common practice for using models in migrations, the rails guide describes it well.

Caution!!

If there are any of associations referencing cities we’ve to fix those, i.e. update city_id to reference remaining and unique cities.

Comments