首页 > 解决方案 > 唯一性验证未按预期运行

问题描述

我有一个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

标签: ruby-on-railsvalidation

解决方案


validates_uniqueness_of实际上并不能保证不能插入重复值。它仅捕获用户输入重复数据并提供用户反馈的大多数情况。它非常容易出现竞争条件,并且被像双击阿妈这样简单的东西所挫败。

如果唯一性确实很重要,则需要在具有唯一索引的数据库层上强制执行它。

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

推荐阅读