2017-10-13 72 views
1

我有两个Rails环境。一个运行Postgres和Rails 5.0.6的开发环境和Heroku上几乎相同的环境。Rails回调在不同环境下表现不同

我有一个Administrator类,它生成基于用户的forenamesurname字段before_save回调的Administrator一个用户名。

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 

用户被验证后,在形式FORENAMESURNAMEX,其中X是一个递增数目(或没有)生成的用户名。

下面是我在开发机器上的Rails控制台中运行的命令。

irb(main):012:0> Administrator.create(email: '[email protected]', 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", "[email protected]"], ["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", "[email protected]"], ["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: "[email protected]", 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"> 

正如您所看到的,执行回调并生成用户的用户名并将其持久保存到数据库中。

然而,当我运行在Heroku(和Heroku的Postgres的)运行我们的测试环境相同的代码,这是发生了什么:

irb(main):005:0> Administrator.create!(email: '[email protected]', forename: 'Edward', surname: 'Scissorhands') 
    (1.9ms) BEGIN 
    Administrator Exists (1.1ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."email" = $1 LIMIT $2 [["email", "[email protected]"], ["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!这里,而不是create展示验证错误不会在开发过程中发生。)

我不明白为什么行为应该在不同环境之间有所不同。两者都运行相同版本的Rails(5.0.6)并运行相同的代码库。

+0

这是在你的代码一个错字或礼物? 'Administrator.where(“username ILIKE?”,“#{proposed}%”)。size'' ILIKE'应该是'LIKE'。 –

+2

ILIKE是LIKE,但不区分大小写。 –

回答

1

before_save在验证之后被调用,因此出现错误。

改为尝试before_validation。

仅供参考以下是创建对象时,回调调用的顺序:

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_create
  • around_create
  • after_create
  • after_save的
  • after_commit/after_rollback
+0

是的,这是对的。事实证明,我们有肮脏的测试数据导致验证失败,因为我们选择了错误的回调来处理我们的情况。谢谢。 –

1

你的代码中的逻辑是有缺陷的。这是一个合法的错误;你需要重新设计用户名生成的单词。

例如,假设系统中有一个用户名为:edwardscissorhands1。没有edwardscissorhands,并没有edwardscissorhands2/3/4

行:Administrator.where("username ILIKE ?", "edwardscissorhands%").size回报1,然后你的逻辑试图创建一个已经存在的新用户。

......我无法确定在生产服务器上发生了什么事,却没有看到实际的数据,但我敢打赌这是这样的。它可能稍微更复杂一些,例如用户:tom,tom3tomlord存在;因此您的逻辑尝试创建第二个用户。

例如,如果您生成了一些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 

这也许可以提高性能,明智的,尽管这里的多个数据库查询不太可能在一个重大问题真正的应用程序(假设你没有得到批次管理员具有相同的名称!)。

相关问题