我需要在给定其他四列作为范围的情况下验证两列的唯一性:
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' }
这是为了确保同一选民,同一任期和相同交易状态中的唯一偏好和候选人。
问题是,in_transaction
和transaction_destroy
是布尔值,这意味着rails验证不起作用。
如何编写解决方法?
我正在研究STV Election后端。
整个项目已经完成 - 前端站点,数据库,结果生成,花哨的动画结果差异等。我唯一无法做的就是唯一性验证。
对于STV如何运作,每个选民可以为任意数量的候选人输入偏好(int)。如果他们的第一个偏好被消除,他们的投票将转移到他们的第二个偏好,依此类推。所有这些都存储在council_votes
表格中,其中包含voter_id
,candidate_id
和preference
列。
用户也需要能够交换偏好。但是,给定首选项唯一性约束,单个更新会中断验证。为了解决这个问题,并防止网络超时数据丢失,我添加了交易。
客户端应用发送begin transaction
消息,发送其偏好更改,最后发送commit
消息。在交易期间,所有更改都会创建in_transaction: true
的记录; destroys使用in_transcation: true, transaction_destroy: true
创建记录。提交事务首先销毁记录,然后使用正确的首选项重新创建记录。如果发生错误,它会回滚更改并通知客户端。
鉴于这是如何运作的,基本上有三组'投票':
in_transaction
投票in_transaction && transaction_destroy
投票为了防止重复的候选人/偏好,我必须确保他们在这三组中是唯一的。但是如果两个状态列都是布尔值,我该怎么做呢?
或者是否更容易更改架构并将in_transaction
和transaction_destroy
替换为transaction_state (null|create|destroy)
并替换范围?这似乎是一个更合理的选择。
答案 0 :(得分:3)
使用自定义验证来检查是否存在现有记录:
validate :is_new_preference
def is_new_preference
!Item.exists?(preference: preference, voter_id: voter_id, term_id: term_id, in_transaction: in_transaction, transaction_destroy: transaction_destroy)
end
答案 1 :(得分:3)
你可能是一个稍微迟到的国家来解决这个问题,但这似乎非常脆弱,容易出现竞争条件。
class Voter < ActiveRecord::Base
has_many :council_votes
end
class Candidate < ActiveRecord::Base
has_many :candidacies
has_many :terms, through: :candidacies
end
class Candidacy < ActiveRecord::Base
enum status: [:running, :dropped]
belongs_to :candidate
belongs_to :term
validates_uniqueness_of :candidate_id, scope: :term_id
end
class Term < ActiveRecord::Base
has_many :candidacies,
has_many :candidates, through: :candidacies
end
class CouncilVote < ActiveRecord::Base
belongs_to :voter
belongs_to :candidacy
has_one :candidate, through: :candidacy
has_one :term, through: :candidacy
validates_uniqueness_of :voter_id, scope: :candidacy_id
end
我们在candidacy
和Candidate
之间添加Term
m-2-m联接表。使用枚举位掩码列,我们可以设置状态。这意味着我们只需要确保两列的唯一性。
让我们添加一些数据库约束来防止竞争条件并提高性能:
class AddUniqenessToCandidacy < ActiveRecord::Migration
def change
add_index :candidacies, [:candidate_id, :term_id], unique: true
end
end
class AddUniqenessToCouncilVote < ActiveRecord::Migration
def change
add_index :council_votes, [:candidacy_id, :voter_id], unique: true
end
end
这里的最大区别是我们只是在候选人被淘汰时更新candidacies
表。
@canditate = Canditate.find_by(name: 'Berny')
@canditate.candidacies.last.dropped! # sorry Berny
这可以作为一种软删除。而不是拉动和重新插入以及整个交易 Schrödingers Cat dilemma我们将所有内容保留在原位并使用rating
列来排序结果。
@term = Term.find_by(year: 2016)
@votes = CouncilVote.joins(:candidacy, :candidate, :term)
.where(candidacy: { status: :running })
.where(voter: @voter)
.where(term: @term)
.order(rating: :desc)