Rails使用多个布尔值进行范围验证

时间:2016-05-24 21:35:43

标签: ruby-on-rails validation

要点:

我需要在给定其他四列作为范围的情况下验证两列的唯一性:

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_transactiontransaction_destroy是布尔值,这意味着rails验证不起作用。

如何编写解决方法?

背景:

我正在研究STV Election后端。

整个项目已经完成 - 前端站点,数据库,结果生成,花哨的动画结果差异等。我唯一无法做的就是唯一性验证。

对于STV如何运作,每个选民可以为任意数量的候选人输入偏好(int)。如果他们的第一个偏好被消除,他们的投票将转移到他们的第二个偏好,依此类推。所有这些都存储在council_votes表格中,其中包含voter_idcandidate_idpreference列。

用户也需要能够交换偏好。但是,给定首选项唯一性约束,单个更新会中断验证。为了解决这个问题,并防止网络超时数据丢失,我添加了交易。

客户端应用发送begin transaction消息,发送其偏好更改,最后发送commit消息。在交易期间,所有更改都会创建in_transaction: true的记录; destroys使用in_transcation: true, transaction_destroy: true创建记录。提交事务首先销毁记录,然后使用正确的首选项重新创建记录。如果发生错误,它会回滚更改并通知客户端。

鉴于这是如何运作的,基本上有三组'投票':

  • 正常投票
  • in_transaction投票
  • in_transaction && transaction_destroy投票

为了防止重复的候选人/偏好,我必须确保他们在这三组中是唯一的。但是如果两个状态列都是布尔值,我该怎么做呢?

或者是否更容易更改架构并将in_transactiontransaction_destroy替换为transaction_state (null|create|destroy)并替换范围?这似乎是一个更合理的选择。

2 个答案:

答案 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 

我们在candidacyCandidate之间添加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)