我有一个使用{remote:true}发送的表单和一个在电子邮件字段中具有唯一性验证器的Adopter模型。现在我在生产环境中有一个奇怪的错误(我无法在开发中重现它):
有时候如果人们正在创建一个采用者,那么发布请求将被发送两次(大部分时间一切正常)。当发生这种情况时,我的Rails应用程序会使用相同的电子邮件创建两个采用者。以下是引发请求的问题摘录(我删除了无聊和私人内容):
Started GET "/" for 123.456.789.012 at 2016-06-25 15:03:37 +0000
Processing by LandingController#index as HTML
request http://ipinfo.io/123.456.789.012
{
"ip": "123.456.789.012",
"hostname": "12345678.dip0.t-ipconnect.de",
"city": "",
"region": "",
"country": "DE",
"loc": "1.0000,1.0000",
"org": "Organistion name"
}
Rendered landing/index.html.slim within layouts/application (1.6ms)
Rendered layouts/application/_head.html.slim (2.0ms)
Rendered layouts/application/_ga.html.slim (0.1ms)
Completed 200 OK in 39ms (Views: 6.8ms | ActiveRecord: 0.0ms)
Started POST "/adopters" for 123.456.789.012 at 2016-06-25 15:05:42 +0000
Processing by AdoptersController#create as JS
Parameters: {"utf8"=>"✓", "name"=>"Mika", "answer"=>{"for"=>"friend", "for_precise"=>"male_friend", "nature"=>"humorous", "interests"=>["technology", "music"]}, "email"=>"nobody@shouldknow.com"}
request http://ipinfo.io/123.456.789.012
{
"ip": "123.456.789.012",
"hostname": "12345678.dip0.t-ipconnect.de",
"city": "",
"region": "",
"country": "DE",
"loc": "1.0000,1.0000",
"org": "Organistion name"
}
Adopter Load (0.7ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "97f32b"]]
CACHE (0.0ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "97f32b"]]
(0.3ms) BEGIN
(0.4ms) SELECT COUNT(*) FROM "adopters" WHERE "adopters"."sign_up_ip" = $1 [["sign_up_ip", "123.456.789.012/32"]]
Adopter Exists (0.9ms) SELECT 1 AS one FROM "adopters" WHERE "adopters"."email" = 'nobody@shouldknow.com' LIMIT 1
Adopter Exists (0.4ms) SELECT 1 AS one FROM "adopters" WHERE "adopters"."referral_code" IS NULL LIMIT 1
Adopter Load (0.4ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "58b7fd"]]
(0.2ms) SELECT COUNT(*) FROM "adopters" WHERE "adopters"."sign_up_ip" = $1 [["sign_up_ip", "89.15.237.123/32"]]
Adopter Exists (0.4ms) SELECT 1 AS one FROM "adopters" WHERE ("adopters"."email" = 'lukas.thorr@web.de' AND "adopters"."id" != 52) LIMIT 1
Adopter Exists (0.3ms) SELECT 1 AS one FROM "adopters" WHERE ("adopters"."referral_code" = '97f32b' AND "adopters"."id" != 52) LIMIT 1
SQL (2.0ms) UPDATE "adopters" SET "referred_count" = $1, "updated_at" = $2 WHERE "adopters"."id" = $3 [["referred_count", 1], ["updated_at", "2016-06-25 15:05:42.492589"], ["id", 52]]
SQL (1.2ms) INSERT INTO "adopters" ("name", "email", "gift_for_person", "gift_for_person_nature", "gift_for_person_interests", "sign_up_ip", "referred_by", "locale", "created_at", "updated_at", "referral_code", "referred_count") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id" [["name", "Mika"], ["email", "nobody@shouldknow.com"], ["gift_for_person", "male_friend"], ["gift_for_person_nature", "humorous"], ["gift_for_person_interests", "[\"technology\",\"music\"]"], ["sign_up_ip", "123.456.789.012/32"], ["referred_by", 52], ["locale", "de"], ["created_at", "2016-06-25 15:05:42.484886"], ["updated_at", "2016-06-25 15:05:42.484886"], ["referral_code", "58b7fd"], ["referred_count", 0]]
[ActiveJob] Adopter Load (0.6ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."id" = $1 LIMIT 1 [["id", 54]]
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f] Performing ActionMailer::DeliveryJob from Inline(mailers) with arguments: "AdopterMailer", "signup_email", "deliver_now", gid://Mycoolsite-prelauncher/Adopter/54
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f] Rendered adopter_mailer/signup_email.html.slim (3.0ms)
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f]
AdopterMailer#signup_email: processed outbound mail in 8.1ms
Started POST "/adopters" for 123.456.789.012 at 2016-06-25 15:05:44 +0000
Processing by AdoptersController#create as JS
Parameters: {"utf8"=>"✓", "name"=>"Mika", "answer"=>{"for"=>"friend", "for_precise"=>"male_friend", "nature"=>"humorous", "interests"=>["technology", "music"]}, "email"=>"nobody@shouldknow.com"}
request http://ipinfo.io/123.456.789.012
{
"ip": "123.456.789.012",
"hostname": "12345678.dip0.t-ipconnect.de",
"city": "",
"region": "",
"country": "DE",
"loc": "1.0000,1.0000",
"org": "Organistion name"
}
Adopter Load (1.2ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "97f32b"]]
CACHE (0.0ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "97f32b"]]
(0.2ms) BEGIN
(0.3ms) SELECT COUNT(*) FROM "adopters" WHERE "adopters"."sign_up_ip" = $1 [["sign_up_ip", "123.456.789.012/32"]]
Adopter Exists (0.5ms) SELECT 1 AS one FROM "adopters" WHERE "adopters"."email" = 'nobody@shouldknow.com' LIMIT 1
Adopter Exists (0.3ms) SELECT 1 AS one FROM "adopters" WHERE "adopters"."referral_code" IS NULL LIMIT 1
Adopter Load (0.2ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "860fc4"]]
(0.2ms) SELECT COUNT(*) FROM "adopters" WHERE "adopters"."sign_up_ip" = $1 [["sign_up_ip", "89.15.237.123/32"]]
Adopter Exists (0.4ms) SELECT 1 AS one FROM "adopters" WHERE ("adopters"."email" = 'lukas.thorr@web.de' AND "adopters"."id" != 52) LIMIT 1
Adopter Exists (0.3ms) SELECT 1 AS one FROM "adopters" WHERE ("adopters"."referral_code" = '97f32b' AND "adopters"."id" != 52) LIMIT 1
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f]
Sent mail to nobody@shouldknow.com (1613.9ms)
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f] Date: Sat, 25 Jun 2016 15:05:42 +0000
From: Mycoolsite <subscribed@mysite.io>
To: nobody@shouldknow.com
Message-ID: <576e9dc67f160_83f98bcce081c5454@1c50fd29020a.mail>
Subject: Mycoolsite - Deine Anmeldung
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
some email content
[ActiveJob] [ActionMailer::DeliveryJob] [3f82f6c1-3f4e-4411-a377-2a667d32559f] Performed ActionMailer::DeliveryJob from Inline(mailers) in 1625.69ms
[ActiveJob] Enqueued ActionMailer::DeliveryJob (Job ID: 3f82f6c1-3f4e-4411-a377-2a667d32559f) to Inline(mailers) with arguments: "AdopterMailer", "signup_email", "deliver_now", gid://Mycoolsite-prelauncher/Adopter/54
(1.3ms) COMMIT
SQL (62.8ms) UPDATE "adopters" SET "referred_count" = $1, "updated_at" = $2 WHERE "adopters"."id" = $3 [["referred_count", 1], ["updated_at", "2016-06-25 15:05:44.074420"], ["id", 52]]
SQL (0.6ms) INSERT INTO "adopters" ("name", "email", "gift_for_person", "gift_for_person_nature", "gift_for_person_interests", "sign_up_ip", "referred_by", "locale", "created_at", "updated_at", "referral_code", "referred_count") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id" [["name", "Mika"], ["email", "nobody@shouldknow.com"], ["gift_for_person", "male_friend"], ["gift_for_person_nature", "humorous"], ["gift_for_person_interests", "[\"technology\",\"music\"]"], ["sign_up_ip", "123.456.789.012/32"], ["referred_by", 52], ["locale", "de"], ["created_at", "2016-06-25 15:05:44.068500"], ["updated_at", "2016-06-25 15:05:44.068500"], ["referral_code", "860fc4"], ["referred_count", 0]]
Completed 200 OK in 1716ms (Views: 0.3ms | ActiveRecord: 9.1ms)
[ActiveJob] Adopter Load (4.3ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."id" = $1 LIMIT 1 [["id", 55]]
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60] Performing ActionMailer::DeliveryJob from Inline(mailers) with arguments: "AdopterMailer", "signup_email", "deliver_now", gid://Mycoolsite-prelauncher/Adopter/55
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60] Rendered adopter_mailer/signup_email.html.slim (1.7ms)
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60]
AdopterMailer#signup_email: processed outbound mail in 4.0ms
Started GET "/rank/58b7fd" for 123.456.789.012 at 2016-06-25 15:05:44 +0000
Processing by AdoptersController#refer as HTML
Parameters: {"code"=>"58b7fd"}
request http://ipinfo.io/123.456.789.012
{
"ip": "123.456.789.012",
"hostname": "12345678.dip0.t-ipconnect.de",
"city": "",
"region": "",
"country": "DE",
"loc": "1.0000,1.0000",
"org": "Organistion name"
}
Adopter Load (0.8ms) SELECT "adopters".* FROM "adopters" WHERE "adopters"."referral_code" = $1 LIMIT 1 [["referral_code", "58b7fd"]]
(0.6ms) SELECT COUNT(*) FROM "adopters" WHERE "adopters"."referred_by" = $1 [["referred_by", 54]]
(0.8ms)
SELECT * FROM (
SELECT adopters.id as id, adopters.referred_count as referred_count, row_number() over(order by adopters.referred_count desc) as rn FROM adopters
) t where id = 54
Adopter Load (0.6ms) SELECT "adopters".* FROM "adopters" ORDER BY "adopters"."referred_count" DESC, "adopters"."created_at" ASC LIMIT 15
Rendered adopters/refer.html.slim within layouts/application (7.1ms)
Rendered layouts/application/_head.html.slim (1.9ms)
Rendered layouts/application/_ga.html.slim (0.1ms)
Completed 200 OK in 29ms (Views: 9.0ms | ActiveRecord: 2.8ms)
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60]
Sent mail to nobody@shouldknow.com (1356.5ms)
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60] Date: Sat, 25 Jun 2016 15:05:44 +0000
From: Mycoolsite <subscribed@mysite.io>
To: nobody@shouldknow.com
Message-ID: <576e9dc8265ef_83f98bc2ae3e4546d8@1c50fd29020a.mail>
Subject: Mycoolsite - Deine Anmeldung
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: quoted-printable
some email content again
[ActiveJob] [ActionMailer::DeliveryJob] [d91eebb4-14ce-43e3-845c-6655c293de60] Performed ActionMailer::DeliveryJob from Inline(mailers) in 1363.09ms
[ActiveJob] Enqueued ActionMailer::DeliveryJob (Job ID: d91eebb4-14ce-43e3-845c-6655c293de60) to Inline(mailers) with arguments: "AdopterMailer", "signup_email", "deliver_now", gid://Mycoolsite-prelauncher/Adopter/55
(1.1ms) COMMIT
Completed 200 OK in 1491ms (Views: 0.2ms | ActiveRecord: 72.7ms)
这是Adopter #create动作(被调用两次):
def create
ref_code = cookies[:h_ref]
@adopter = Adopter.new
@adopter.name = params[:name]
@adopter.email = params[:email]
@adopter.gift_for_person = params[:answer][:for_precise] if !params[:answer][:for_precise].blank?
@adopter.gift_for_person = params[:answer][:for] if params[:answer][:for_precise].blank?
@adopter.gift_for_person_nature = params[:answer][:nature]
@adopter.gift_for_person_interests = params[:answer][:interests].to_json
@adopter.sign_up_ip = request.remote_ip
if ref_code && Adopter.find_by(referral_code: ref_code)
@adopter.referrer = Adopter.find_by(referral_code: ref_code)
end
@adopter.locale = I18n.locale
respond_to do |format|
if @adopter.save
cookies[:h_email] = { value: @adopter.email }
#format.html { redirect_to rank_path(code: @adopter.referral_code) }
format.js {
render :js => "window.location = '#{rank_path(code: @adopter.referral_code)}'"
}
else
logger.info("Error saving user with email, #{@adopter.email}")
# redirect_to root_path, alert: 'Something went wrong!'
# format.js { flash[:notice] = @adopter.errors }
format.js { flash[:notice] = @adopter.errors }
end
end
end
这是我的采用者模型:
require 'adopters_helper'
class Adopter < ActiveRecord::Base
belongs_to :referrer, class_name: 'Adopter', foreign_key: 'referred_by'
has_many :referrals, class_name: 'Adopter', foreign_key: 'referred_by'
validate :not_more_than_two_adopters_per_ip
validates :email, presence: true, uniqueness: true, format: {
with: /\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/i,
message: 'Invalid email format.'
}
validates :referral_code, uniqueness: true
validates :name, :locale, presence: true, allow_blank: false
before_create :create_referral_code
after_create :send_welcome_email
before_create :compute_queue_positions
def rank
row_number_tupel = ActiveRecord::Base.connection.execute("
SELECT * FROM (
SELECT adopters.id as id, adopters.referred_count as referred_count, row_number() over(order by adopters.referred_count desc) as rn FROM adopters
) t where id = #{self.id}
")
row_number_tupel[0]['rn']
end
private
def create_referral_code
self.referral_code = AdoptersHelper.unused_referral_code
end
def not_more_than_two_adopters_per_ip
if !Rails.env.development?
adopter_count_with_current_ip = Adopter.where(sign_up_ip: sign_up_ip).count
if adopter_count_with_current_ip >= 3
errors.add(:sign_up_ip, I18n.t('activerecord.errors.models.adopter.attributes.sign_up_ip.max_ips'))
end
end
end
def send_welcome_email
AdopterMailer.signup_email(self).deliver_later
end
def compute_queue_positions
self.referred_count = 0
if !self.referrer.blank?
self.referrer.update_attributes! referred_count: (self.referrer.referred_count + 1)
end
end
end
所以基本上有两个问题。第一个,有时Post请求被发送和处理两次。第二个,如果第一个发生,唯一性验证器失败。
答案 0 :(得分:2)
第一个:我注意到用户仍然认为他们必须双击喜欢。
秒一:不,它没有失败,这是一种竞争条件。两个请求最终在应用程序的两个不同实例上,两个实例都检查是否已存在类似记录,两者都注意到它没有,因此两者都创建了一个。解决方案是您需要在数据库上使用唯一索引来避免这些问题。
来自Rails Guides about ActiveRecord uniqueness validations:
的引用此帮助程序验证属性的值在保存对象之前是唯一的。它不会在数据库中创建唯一性约束,因此可能会发生两个不同的数据库连接创建两个记录,这些记录对于您想要唯一的列具有相同的值。为避免这种情况,您必须在数据库的两列上创建唯一索引。