Rails & postgresql, notify/listen 到创建新记录时

Rails & postgresql, notify/listen to when a new record is created

我正在试验和学习如何使用 PostgreSQL,即它的 Notify/Listen 功能,在根据此 tutorial 制作服务器发送事件的上下文中。

每当保存 user 并更改属性 authy_status 时,教程都会将 NOTIFY 发布到 user 频道(通过其 id) . LISTEN 方法然后产生新的 authy_status 代码:

class Order < ActiveRecord::Base
after_commit :notify_creation

def notify_creation
  if created?
    ActiveRecord::Base.connection_pool.with_connection do |connection|
        execute_query(connection, ["NOTIFY user_?, ?", id, authy_status])
    end
  end
end

def on_creation
  ActiveRecord::Base.connection_pool.with_connection do |connection|
    begin
      execute_query(connection, ["LISTEN user_?", id])
      connection.raw_connection.wait_for_notify do |event, pid, status|
        yield status
      end
    ensure
      execute_query(connection, ["UNLISTEN user_?", id])
    end
  end
end

end

我想做一些不同的事情,但一直无法找到有关如何执行此操作的信息。我想 NOTIFY 首先创建用户(即插入数据库),然后在 LISTEN 中,我想 yield 建立新的创建用户本身(或者更确切地说是它的 id)。

我将如何修改代码来实现这一点?我对编写 SQL 很陌生,所以例如,我不太确定如何将 ["NOTIFY user_?, ?", id, authy_status] 更改为不针对特定用户的语句,而是针对整个 USER table,监听新记录(类似... ["NOTIFY USER on INSERT", id] ?? )

澄清

抱歉没说清楚。 after_save 是一个复制错误,已更正为上面的 after_commit。但这不是问题。问题是侦听器侦听特定现有用户的更改,通知程序通知特定用户的更改。

我反而想监听任何新用户的创建,因此通知它。 Notify 和 Listen 代码需要如何更改才能满足此要求?

我想,与我对代码的猜测不同,通知代码可能不需要更改,因为在创建 id 时通知它似乎仍然有意义(但同样,我不知道,随意纠正我)。但是,你怎么听整个 table,而不是特定的记录,因为我又没有现有的记录可以听?

对于更广泛的上下文,这是原始教程中控制器中 SSE 中监听器的使用方式:

  def one_touch_status_live
    response.headers['Content-Type'] = 'text/event-stream'
    @user = User.find(session[:pre_2fa_auth_user_id])
    sse = SSE.new(response.stream, event: "authy_status")
    begin
      @user.on_creation do |status|
        if status == "approved"
          session[:user_id] = @user.id
          session[:pre_2fa_auth_user_id] = nil
        end
        sse.write({status: status})
      end
    rescue ClientDisconnected
    ensure
      sse.close
    end
  end

但同样,在我的情况下,这不起作用,我没有特定的 @user 我正在听,我希望 SSE 在创建任何用户时触发.. . 也许是这个控制器代码也需要修改?但这是我非常不清楚的地方。如果我有类似...

User.on_creation do |u|

一个class方法是有道理的,但是我又如何获得监听代码来监听整个table?

请使用 after_commit 而不是 after_save。这样,用户记录肯定会提交到数据库中

There are two additional callbacks that are triggered by the completion of a database transaction: after_commit and after_rollback. These callbacks are very similar to the after_save callback except that they don't execute until after database changes have either been committed or rolled back.

https://guides.rubyonrails.org/active_record_callbacks.html#transaction-callbacks

其实与你的问题无关,你可以使用任何一个。

以下是我处理您的用例的方式:您希望在创建用户时收到通知:

#app/models/user.rb
class User < ActiveRecord::Base
  after_commit :notify_creation

  def notify_creation
    if id_previously_changed?
      ActiveRecord::Base.connection_pool.with_connection do |connection|
        self.class.execute_query(connection, ["NOTIFY user_created, '?'", id])
      end
    end
  end

  def self.on_creation
    ActiveRecord::Base.connection_pool.with_connection do |connection|
      begin
        execute_query(connection, ["LISTEN user_created"])
        connection.raw_connection.wait_for_notify do |event, pid, id|
          yield self.find id
        end
      ensure
        execute_query(connection, ["UNLISTEN user_created"])
      end
    end
  end

  def self.clean_sql(query)
    sanitize_sql(query)
  end

  def self.execute_query(connection, query)
    sql = self.clean_sql(query)
    connection.execute(sql)
  end
end

所以如果你使用

User.on_creation do |user|
  #do something with the user
  #check user.authy_status or whatever attribute you want.
end

有一件事我不确定你为什么要这样做,因为它可能会出现竞争条件情况,即创建了 2 个用户而不需要的用户先完成。