Rails & postgresql, уведомлять / слушать, когда создается новая запись - PullRequest
4 голосов
/ 02 марта 2020

Я экспериментирую и изучаю, как работать с PostgreSQL, а именно с его функцией уведомления / прослушивания, в контексте создания событий, отправляемых сервером, в соответствии с этим учебным пособием .

Учебник публикует NOTIFY на канале user (через его id) каждый раз, когда сохраняется user и изменяется атрибут authy_status. Затем метод 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] на оператор, который смотрит не на конкретного c пользователя, а на всю таблицу USER, слушая для новых записей (что-то вроде ... ["NOTIFY USER on INSERT", id] ??)

РАЗЪЯСНЕНИЯ

Извините, что неясно. after_save была ошибка копирования, исправленная до after_commit выше. Это не проблема, хотя. Проблема заключается в том, что слушатель прослушивает изменения в существующем пользователе SPECIFI C, а уведомитель уведомляет об изменениях пользователя SPECIFI C.

Вместо этого я хочу прослушать любое создание нового пользователя, и поэтому сообщите об этом. Как необходимо изменить код уведомлений и прослушиваний, чтобы соответствовать этому требованию?

Полагаю, в отличие от моего предположения о коде, код уведомлений может не нуждаться в изменении, так как уведомление об идентификаторе при его создании кажется все еще имеет смысл (но опять же, я не знаю, не стесняйтесь поправлять меня). Однако как вы слушаете всю таблицу, а не конкретную запись, потому что опять же у меня нет существующей записи для прослушивания?

Для более широкого контекста, это то, как слушатель используется в 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

Но опять же, в моем случае, это не работает, у меня нет спецификаций c @user Я слушаю, Я хочу, чтобы SSE срабатывал при создании любого пользователя ... Возможно, именно этот код контроллера также необходимо изменить? Но здесь мне очень непонятно. Если у меня есть что-то вроде ...

User.on_creation do |u|

Метод класса имеет смысл, но опять же, как мне получить код прослушивания для прослушивания всей таблицы?

1 Ответ

0 голосов
/ 06 марта 2020

Пожалуйста, используйте after_commit вместо after_save. Таким образом, запись пользователя обязательно фиксируется в базе данных

. Существуют два дополнительных обратных вызова, которые запускаются при завершении транзакции базы данных: after_commit и after_rollback. Эти обратные вызовы очень похожи на обратный вызов after_save за исключением того, что они не выполняются до тех пор, пока изменения базы данных не будут зафиксированы или отменены.

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 пользователя создаются, а нежелательный завершается первым.

...