Reputation: 5
So I have a table called votes.
There are three columns. VoterID, VoteforID and Vote.
The uniqueness of each individual column is not important however, the same row cant repeat twice
For instance. This is correct
Voter_id | Votefor_id | Vote
1 | 2 | 1
1 | 3 | 1
1 | 4 | 1
2 | 1 | 2
This is wrong.
Voter_id | Votefor_id | Vote
1 | 2 | 1
1 | 2 | 1
1 | 4 | 1
2 | 1 | 2
Currently, I am doing a select statement before adding records just to make sure that an identical row does not exist. Surely, there is a better solution.
Thank you.
Upvotes: 0
Views: 1629
Reputation: 35359
If data integrity is critical, you should not use a validation to guarantee uniqueness. It can fail. The only way to guarantee uniqueness is to use a database constraint. This is because the Rails validates_uniqueness
can have race conditions.
Create a migration to add the index, or change your existing one to reflect this change:
For a new table:
class CreateVotes < ActiveRecord::Migration
def change
create_table :votes do |t|
t.belongs_to :voter
t.belongs_to :votefor
t.string :vote # Choose the correct column type
t.timestamps
end
add_index :votes, [:voter_id, :votefor_id, :vote], unique: true
end
end
For an existing table:
class AddUniqueIndexToVotes < ActiveRecord::Migration
def change
add_index :votes, [voter_id, votefor_id, vote], unique: true
end
end
Now you can go ahead and add a validation, as suggested by others, if you want to give your user feedback that they've already voted:
validates :voter_id, uniqueness: { scope: [:votefor_id, :vote] }
Upvotes: 2
Reputation: 5839
Use the scope
parameter on uniqueness_validation, something like:
validates :voter_id, uniqueness: { scope: :votefor_id }
scope parameters could be more than one:
validates :voter_id, uniqueness: { scope: [:votefor_id, :vote] }
It really depends on the details but for the case in hand my personal opinion that db constraint is a bit overkill here because chances for a race condition (documented in rails uniqueness validation) to happen are low, since we need
two records with the exact same voter_id, votefor_id and vote to be submitted at the exact same time (we are talking about milliseconds), if i may assume something about the OP business logic that voter
would normally be a logged in user meaning duplicate entries should only come from him !!
I had quite a number of rails apps running in production using uniqueness_validation
just fine and only one time it failed me in a situation of a uniqueness check on a single column with many simultaneous entries (obviously i needed db constraint there).
If data integrity is critical you should for sure go with db constraint.
Or if you don't mind the overhead & specially if you could benefit from the new index on the (voter_id, votefor_id and vote) to increase search speed i would say go with db constraint.
otherwise i would say: keep it simple and stay with uniquess_valdation
Upvotes: 2
Reputation: 32364
In your table definition, add a UNIQUE
table constraint:
CREATE TABLE votes (
voter_id int NOT NULL,
votefor_id int NOT NULL,
vote int NOT NULL,
UNIQUE (voter_id, votefor_id, vote)
);
This way PostgreSQL checks for duplicates. If you try to INSERT
a record whose values are the same for all three columns as in an existing record, then an error will be thrown.
Upvotes: 0