我有两个Rails环境。一个运行Postgres和Rails 5.0.6的开发环境和一个几乎完全相同的Heroku环境。
我有一个Administrator
课程,可根据用户的Administrator
和{{1}为before_save
回调生成forename
的用户名} fields。
surname
验证用户后,将以class Administrator < ApplicationRecord
validates :username, uniqueness: true
validates :forename, presence: true
validates :surname, presence: true
before_save :generate_username
def generate_username
return if username.present?
proposed = "#{forename}#{surname}".downcase
existing_count = Administrator.where("username ILIKE ?", "#{proposed}%").size
self.username = existing_count.zero? ? proposed : "#{proposed}#{existing_count}"
end
end
格式生成用户名,其中X是递增数字(或无)。
这是我在开发机器上的Rails控制台中运行的命令。
FORENAMESURNAMEX
如您所见,执行回调并生成用户的用户名并按预期持久保存到数据库。
但是,当我在Heroku(和Heroku Postgres)上运行的测试环境中运行相同的代码时,会发生以下情况:
irb(main):012:0> Administrator.create(email: 'edward@test.net', forename: 'Edward', surname: 'Scissorhands')
D, [2017-10-13T10:00:18.985765 #280] DEBUG -- : (0.2ms) BEGIN
D, [2017-10-13T10:00:18.987554 #280] DEBUG -- : Administrator Exists (0.5ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."email" = $1 LIMIT $2 [["email", "edward@test.net"], ["LIMIT", 1]]
D, [2017-10-13T10:00:18.988923 #280] DEBUG -- : Administrator Exists (0.4ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."username" IS NULL LIMIT $1 [["LIMIT", 1]]
D, [2017-10-13T10:00:18.990155 #280] DEBUG -- : (0.4ms) SELECT COUNT(*) FROM "administrators" WHERE (username ILIKE 'edwardscissorhands%')
D, [2017-10-13T10:00:18.992000 #280] DEBUG -- : SQL (0.5ms) INSERT INTO "administrators" ("email", "created_at", "updated_at", "username", "forename", "surname") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["email", "edward@test.net"], ["created_at", "2017-10-13 10:00:18.990421"], ["updated_at", "2017-10-13 10:00:18.990421"], ["username", "edwardscissorhands"], ["forename", "Edward"], ["surname", "Scissorhands"]]
D, [2017-10-13T10:00:18.995845 #280] DEBUG -- : (1.8ms) COMMIT
=> #<Administrator id: 10, email: "edward@test.net", created_at: "2017-10-13 10:00:18", updated_at: "2017-10-13 10:00:18", role: nil, otp_public_key: nil, username: "edwardscissorhands", forename: "Edward", surname: "Scissorhands">
(我在这里使用irb(main):005:0> Administrator.create!(email: 'edward@test.net', forename: 'Edward', surname: 'Scissorhands')
(1.9ms) BEGIN
Administrator Exists (1.1ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."email" = $1 LIMIT $2 [["email", "edward@test.net"], ["LIMIT", 1]]
Administrator Exists (0.9ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."username" IS NULL LIMIT $1 [["LIMIT", 1]]
(0.9ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Username has already been taken
代替create!
来显示开发中未发生的验证错误。)
我不明白为什么环境之间的行为会有所不同。两者都运行相同版本的Rails(5.0.6)并运行相同的代码库。
答案 0 :(得分:1)
代码中的逻辑存在缺陷。这是一个合法的错误;你需要重新设计用户名生成单词的方式。
例如,假设系统中有一个名为edwardscissorhands1
的用户。没有edwardscissorhands
,也没有edwardscissorhands2/3/4
等。
行:Administrator.where("username ILIKE ?", "edwardscissorhands%").size
返回1
,然后您的逻辑尝试创建已存在的新用户。
...我不能确定生产服务器上发生了什么而没有看到实际数据,但我打赌它是这样的。它可能稍微复杂一些,例如用户:tom
,tom3
和tomlord
存在;因此,您的逻辑会尝试创建第二个tom3
用户。
例如,如果您生成了一些edwardscissorhards
个用户,然后已删除一个或多个用户,则可能会发生这种情况。
作为示例,您可以通过以下方式重新设计逻辑:
def generate_username
return if username.present?
proposed = "#{forename}#{surname}".downcase
return proposed unless Administrator.exists?("username ILIKE ?", proposed)
counter = 1
while(Administrator.exists?("username ILIKE ?", "#{proposed}#{counter}"))
counter += 1
end
"#{proposed}#{counter}"
end
这可能会在性能方面得到改善,尽管这里的多个数据库查询不太可能成为实际应用程序中的主要问题(假设您没有得到批次的管理员)同名!)。
答案 1 :(得分:1)
验证后调用before_save,因此出错。
尝试使用before_validation。
这里的参考是创建对象时调用的顺序回调: