Summary:
I need to validate uniqueness of two columns given four other columns as scope:
validates :preference, uniqueness: { scope: [:voter_id, :term_id, :in_transaction, :transaction_destroy], message: 'must not have the same preference as another vote' }
validates :candidate, uniqueness: { scope: [:voter_id, :term_id, :in_transaction, :transaction_destroy], message: 'can only be voted for once' }
This is to ensure only unique preferences and candidates for the same voter, in the same term, and within the same transaction states.
The problem is, in_transaction and transaction_destroy are booleans, meaning rails validations do not work.
How can I write a workaround?
Background:
I'm working on an STV Election backend.
The entirety of the project is already finished -- frontend site, database, results generation, fancy animated results diff, etc. The only thing I've been unable to do is the uniqueness validations.
With how STV works, each voter may enter a preference (int) for any number of candidates. If their first preference gets eliminated, their vote transfers to their second preference, and so on. All of these are stored in the council_votes table, with columns voter_id, candidate_id, and preference.
Users need to be able to swap preferences, too. However, given the preference uniqueness constraint, individual updates break validation. To solve this, and to prevent data loss on network timeouts, I added transactions.
The client app sends a begin transaction message, sends its preference changes, and finally sends a commit message. During a transaction, all changes create a record with in_transaction: true; destroys create a record with in_transcation: true, transaction_destroy: true. Committing the transaction destroys records first, then recreates the records with the correct preferences. In the event of an error, it rolls back the changes and notifies the client.
Given how this works, there are essentially three sets of 'votes':
- normal vote
in_transactionvotein_transaction && transaction_destroyvote
To prevent duplicate candidates/preferences, I must ensure they are unique across these three sets. but given both state columns are boolean, how can I do this?
Or would it be easier to alter the schema and replace in_transaction and transaction_destroy with transaction_state (null|create|destroy) and scope that instead? That seems to be a more sane option.