唯一性验证未按预期运行



我有一个Call模型,具有以下验证:

class Call < ActiveRecord::Base
validates_uniqueness_of :external_id, scope: :source
end

我通过调用以下服务的webhook生成新的调用:

class AircallWebhookService
include HubspotExtension
def initialize(params)
@event = params["event"]
@params = params["data"]
@call = nil
@aircall_number = nil
@employee_email = nil
end
def process
@call = Call.find_by(source: :aircall, external_id: @params["id"])

if @call.present?
p "Found existing call!"
else
p "Could not locate existing call."
@call = Call.new(source: :aircall, external_id: @params["id"])
end
@call.source = 1
@call.external_id = @params["id"]
@call.url = @params["direct_link"]
@call.direction = @params["direction"]
@call.status = @params["status"]
@call.missed_call_reason = @params["missed_call_reason"]
@call.started_at = Time.at(@params["started_at"]) if @params["started_at"].present?
@call.answered_at = Time.at(@params["answered_at"]) if @params["answered_at"].present?
@call.ended_at = Time.at(@params["ended_at"]) if @params["ended_at"].present?
@call.duration = @params["duration"]
@call.raw_digits = @params["raw_digits"]
@call.aircall_user_id = @params.dig("user", "id")
@call.contact_id = @params.dig("contact", "id")
@aircall_number = @params.dig("number", "digits").try{|n| n.gsub(/s|-|(|)|+/, "")}
@call.aircall_user_id = @params.dig("user", "id")
@employee_email = @params.dig("user", "email")
if !@params["tags"].empty?
mapTagToReferrer
end

@call.comments = mapComments
if @call.save
linkTagToCall
linkCallToEmployee
updateHubspotEngagement
end
end
...
end

由于某种原因,尽管uniqueness验证,我仍然看到具有相同external_idsource的调用。例如,在我的数据库中有2条记录:

[
[0] #<Call:0x000055d780f639b8> {
:id => 8149,
:location_id => nil,
:referrer => nil,
:consultation => nil,
:created_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:updated_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:worldwide => nil,
:external_id => 582402916,
:source => "aircall",
:direction => "inbound",
:started_at => Tue, 07 Sep 2021 15:41:03 EDT -04:00,
:answered_at => Tue, 07 Sep 2021 15:41:10 EDT -04:00,
:ended_at => Tue, 07 Sep 2021 15:41:57 EDT -04:00,
:duration => 54,
:status => "done",
:missed_call_reason => nil,
:aircall_user_id => 567754,
:contact_id => nil,
:comments => nil,
:lead_status => nil,
:call_type => "unknown"
},
[1] #<Call:0x000055d780f636e8> {
:id => 8150,
:location_id => nil,
:referrer => nil,
:consultation => nil,
:created_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:updated_at => Tue, 07 Sep 2021 15:42:01 EDT -04:00,
:worldwide => nil,
:external_id => 582402916,
:source => "aircall",
:direction => "inbound",
:started_at => Tue, 07 Sep 2021 15:41:03 EDT -04:00,
:answered_at => Tue, 07 Sep 2021 15:41:10 EDT -04:00,
:ended_at => Tue, 07 Sep 2021 15:41:57 EDT -04:00,
:duration => 54,
:status => "done",
:missed_call_reason => nil,
:aircall_user_id => 567754,
:contact_id => nil,
:comments => nil,
:lead_status => nil,
:call_type => "unknown"
}
]

它们是相同的,甚至created_at也是相同的,精确到毫秒。这怎么可能呢?

如果需要,这里是控制器:

class API::WebhooksController < ApplicationController
def aircall_webhook
ac = AircallWebhookService.new(params)
ac.process
head :ok
end
end

validates_uniqueness_of实际上并不能保证不能插入重复的值。它只是捕获用户输入重复数据的大多数情况,并提供用户反馈。它非常容易出现竞态条件,并且可以通过双击奶奶这样简单的操作来阻止。

如果惟一性确实很重要,你需要在数据库层上使用惟一索引来强制它。

add_index :calls, [:external_id, :source], unique: true