Hook after_commit, Active Job, adres zapisany w rekordzie.
Rails nie ma odpowiednika bibliotek do obrazków na edge, z których korzysta świat JavaScriptu, więc zwykle kończy się na Chrome uruchomionym obok aplikacji przez Grover albo Ferrum — i na pilnowaniu go. Job z jednym żądaniem HTTP trzyma Chrome z dala od Twoich serwerów i sam ponawia próbę, gdy sieć się zająknie.
app/jobs/generate_og_image_job.rb
require "net/http"
class GenerateOgImageJob < ApplicationJob
queue_as :default
retry_on Net::OpenTimeout, Net::ReadTimeout, wait: :polynomially_longer, attempts: 3
ENDPOINT = URI("https://imageapi.dev/v1/images?wait=true")
def perform(post)
body = {
template: "blog-og",
modifications: {
title: post.title,
author: post.author.name,
date: post.published_at.to_date.iso8601,
site: "yourblog.com"
}
}
response = Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: true, read_timeout: 20) do |http|
request = Net::HTTP::Post.new(ENDPOINT)
request["Authorization"] = "Bearer #{Rails.application.credentials.image_api_key}"
request["Content-Type"] = "application/json"
request.body = body.to_json
http.request(request)
end
response.value # raises on anything that is not 2xx
post.update!(og_image_url: JSON.parse(response.body).fetch("url"))
end
end
# app/models/post.rb
# after_commit -> { GenerateOgImageJob.perform_later(self) },
# on: [:create, :update], if: -> { saved_change_to_title? && published? }
Używaj `after_commit`, nie `after_save`. Przy `after_save` job może wystartować przed zatwierdzeniem transakcji i worker wczyta wpis, którego nowego tytułu nie ma jeszcze w bazie — obrazek pokaże stary tytuł.
Z ?wait=true czekasz na gotowy obrazek w tym samym połączeniu — zwykle około sekundy.
{
"id": "img_01m08s3ez1zcv28h335g9923bw",
"status": "completed",
"url": "https://cdn.imageapi.dev/renders/img_01m08s3ez1zcv28h335g9923bw.png",
"duration_ms": 912
}
Sto obrazków miesięcznie za darmo, potem $9 za dwa tysiące. Ten sam wpis wyrenderowany drugi raz zwraca istniejący adres i nie zużywa limitu — więc przebudowanie strony nic nie kosztuje.