Реал-тайм сайти на Ruby on Rails без жодного рядка JS

Інтегруємось з моно

Йдемо на https://api.monobank.ua/, скануємо QR код апкою, отримуємо ключ.

Далі встановлюємо вебхук, щоб отримувати транзакції.

Вебхук веде на ендпоїнт який дістає параметри транзакції та зберігає її:

def respond(data)
  t = data.require(:statementItem).permit(:description, :amount, :comment, :balance, :time)
  t[:account] = data[:account]
  t[:transaction_id] = data[:statementItem][:id]
  MonoTransaction.create(t)
end

Показуємо транзакції

Робимо сторінку, передаємо параметром account номер картки/банки, витягуємо по ньому всі транзакції та малюємо їх. Переглянути демо тут.

Додаємо інтерактивності

При створенні нової транзакції потрібно негайно якимось чином оновити сторінку. В Rails для цього вже все передбачено, нам знадобляться: ActionCable та Hotwire. За замовчуванням при створенні нового Rails-проєкту вони будуть додані та сконфігуровані.

ActionCable це клієнт-серверна ліба яка робить pub/sub у вашій апці. Під капотом там знаходяться Redis для сервера та вебсокети для клієнта. Коли ви додаєте собі це в проєкт, то ваша вебсторінка буде відкривати вебсокет з'єднання, та слухати що туди приходить.

Таким чином ми можемо одразу після створення транзакції записати щось у «канал», клієнт отримає нову інфу по вебсокету та за допомогою JS оновить HTML.

Але ж ми хочемо все робити без JS! На допомогу приходить Hotwire. Це бібліотека яка оперує поняттями фреймів та стримів.

Фрейм визначає на HTML сторінці будь-який шматочок та присвоює йому ідентифікатор.

Ось я загортаю список всіх транзакцій у фрейм:

<%= turbo_frame_tag "mono_transactions" do %>
  <% @transactions.each do |transaction| %>
    <%= render partial: "mono_transaction", locals: { transaction: transaction } %>
  <% end %>
<% end %>

За допомогою стриму можна передати на клієнт спеціальні інструкції, які потім будуть виконані в рамках фрейму. Ці інструкції включають (але не обмежені) у себе: додати HTML елемент в початок або кінець фрейму, замінити, видалити, додати після такого-то елементу і так далі.

Нам потрібно додавати нотифікашку з транзакцією у початок списку.

Тут починається найцікавіше. Ми можемо згенерувати відповідний івент на сервері, а ActionCable подбає, щоб доставити його у браузер та викликати необхідний JS, щоб виконати необхідну нам дію.

Для того, щоб наша сторінка була підписана на стрим, нам необхідно додати відповідну інструкцію:

<%= turbo_stream_from "mono_transactions_#{@account}" %>

Цим ми кажемо «обробляти всі події які будуть приходити у канал під назвою mono_transactions.

Тепер, на бекенді, кажемо, щоб після створення транзакції відправили новий івент у відповідний канал:

after_create_commit do |transaction|
  broadcast_prepend_to "mono_transactions_#{transaction.account}",
                       partial: "mono_dashboards/mono_transaction",
                       locals: {transaction: transaction},
                       target: "mono_transactions"
end

broadcast_prepend_to означає що буде відправлене повідомлення з типом prepend у канал mono_transactions_#{transaction.account} та переданий маленький шматочок HTML відрендерений на сервері для обробки фреймом mono_transactions. prepend означає що HTML буде доданий першим до списку дочірних елементів фрейму.

Таким чином, ми з сервера посилаємо шматочок HTML який Hotwire обробить як треба. В нашому випадку — додасть нотифікашку.

Так само оновлюється значення суми на банці.

Висновки

Ось так можна без жодного рядка JS робити інтерактивні сайти. Уважні та освічені читачі вже знають, що бібліотек та фреймворків, що працюють за таким принципом є декілька: найвідоміший це Phoenix LiveView, менш відомий це HTMX, поки що маловідомий TwinSpark він пана Соловйова.


Сподобалось? Долучайтеся до мого телеграм каналу: https://t.me/full_of_hatred