Ruby on Rails source walkthrough
The runnable source below is included from the same files packaged in the notes download. Lockfiles, configuration, and the shared browser UI are included in the download.
Database
A notes request reaches this adapter after input validation. DATABASE_ENGINE chooses
PostgreSQL, MariaDB, or MongoDB; DATABASE_URL comes from the app’s attachment. SQL uses
parameters rather than interpolating note text, and MongoDB uses driver operations.
The initialization process creates a table or unique index. Records live in the database,
so replacing a web instance leaves them available to its replacement.
def database
DB_MUTEX.synchronize do
return @database if @database
url = ENV.fetch("DATABASE_URL")
@database = case ENGINE
when "mongodb" then Mongo::Client.new(url, server_selection_timeout: 5)[:notes]
when "postgresql", "mariadb"
# Active Record uses the Trilogy adapter for MariaDB.
url = url.sub(/\Amysql:/, "trilogy:") if ENGINE == "mariadb"
ActiveRecord::Base.establish_connection(url)
else raise "Unsupported DATABASE_ENGINE"
end
end
end
def migrate
db = database
if ENGINE == "mongodb"
db.indexes.create_one({ id: 1 }, unique: true)
else
ActiveRecord::Base.connection_pool.with_connection do |c|
c.execute("CREATE TABLE IF NOT EXISTS notes (id VARCHAR(36) PRIMARY KEY, body TEXT NOT NULL)")
end
end
puts "Schema ready"
end
def notes
db = database
if ENGINE == "mongodb"
db.find.projection(_id: 0).sort(id: 1).to_a
else
Note.order(:id).pluck(:id, :body).map { |id, body| { id: id, body: body } }
end
end
def add_note(body)
db = database
note = { id: SecureRandom.uuid, body: body }
ENGINE == "mongodb" ? db.insert_one(note.dup) : Note.create!(note)
note
end
Worker
Creating an export enqueues an ID and returns immediately. This loop reads the notes, writes an export under that ID, records its status, and acknowledges the stream entry. It also reclaims jobs abandoned before acknowledgement. Retrying writes the same export key, so interruption does not create an additional export file; the snapshot can reflect newer notes if they changed before the retry. See worker recovery.
stopping = false
Signal.trap("TERM") { stopping = true }
Signal.trap("INT") { stopping = true }
consumer = SecureRandom.uuid
NotesStore.queue do |q|
begin
q.call("XGROUP", "CREATE", "exports", "workers", "0", "MKSTREAM")
rescue Redis::CommandError => error
raise unless error.message.include?("BUSYGROUP")
end
puts "Worker ready"
until stopping
begin
messages = q.call("XAUTOCLAIM", "exports", "workers", consumer, "30000", "0-0", "COUNT", "1")[1]
if messages.empty?
result = q.call("XREADGROUP", "GROUP", "workers", consumer, "COUNT", "1", "BLOCK", "5000", "STREAMS", "exports", ">")
messages = result ? result[0][1] : []
end
messages.each do |message_id, values|
id = Hash[*values]["id"]
unless id&.match?(/\A[0-9a-f-]{36}\z/)
q.call("XACK", "exports", "workers", message_id)
next
end
unless q.get("export:#{id}") == "ready"
NotesStore.save_export(q, id, JSON.pretty_generate(NotesStore.notes))
q.set("export:#{id}", "ready")
end
q.call("XACK", "exports", "workers", message_id)
q.call("XDEL", "exports", message_id)
puts "Export ready: #{id}"
end
rescue StandardError
warn "Export failed; unacknowledged jobs retry after 30 seconds. Check attachments and schema."
sleep 1
end
end
end
Object storage
Without S3 configuration, exports are stored in Redis for the worker checkpoint. With
an attached bucket, the worker writes JSON to exports/<id>.json and the web process
reads that object for downloads. Both processes need their own bucket attachment.
The client uses the supplied endpoint and region with path-style requests. Credentials
stay on the server; a private bucket does not add authentication to the example’s public download route.
def storage
Aws::S3::Client.new(endpoint: ENV.fetch("S3_ENDPOINT"), region: ENV.fetch("S3_REGION"), force_path_style: true)
end
def save_export(q, id, data)
if ENV["S3_BUCKET"]
storage.put_object(bucket: ENV.fetch("S3_BUCKET"), key: "exports/#{id}.json", body: data, content_type: "application/json")
else
q.set("export:#{id}:data", data)
end
end
def load_export(q, id)
if ENV["S3_BUCKET"]
storage.get_object(bucket: ENV.fetch("S3_BUCKET"), key: "exports/#{id}.json").body.read
else
q.get("export:#{id}:data")
end
end
Routes and controller
Rails.application.routes.draw do
root "notes#index"
get "/health", to: "notes#health"
get "/notes", to: "notes#list"
post "/notes", to: "notes#create"
post "/exports", to: "notes#export"
get "/exports/:id", to: "notes#status"
get "/exports/:id/download", to: "notes#download"
end
class NotesController < ActionController::API
rescue_from StandardError do
render json: { error: "Check attachments, schema, and service availability" }, status: 503
end
def index
render html: File.read(Rails.root.join("public/index.html")).html_safe
end
def health
render plain: "ok\n"
end
def list
render json: NotesStore.notes
end
def create
body = params[:body]
unless body.is_a?(String) && !body.strip.empty? && body.length <= 2000
return render json: { error: "Enter a note of 1–2000 characters" }, status: 400
end
render json: NotesStore.add_note(body), status: 201
end
def export
id = SecureRandom.uuid
NotesStore.queue do |q|
q.set("export:#{id}", "queued")
q.call("XADD", "exports", "*", "id", id)
end
render json: { id: id }, status: 202
end
def status
state = NotesStore.queue { |q| q.get("export:#{params[:id]}") }
render json: { status: state || "unknown" }, status: state ? 200 : 404
end
def download
id = params[:id]
state = NotesStore.queue { |q| q.get("export:#{id}") }
unless id.match?(/\A[0-9a-f-]{36}\z/) && state == "ready"
return render json: { error: "Export not ready" }, status: 404
end
data = NotesStore.queue { |q| NotesStore.load_export(q, id) }
send_data data, type: "application/json", filename: "notes.json"
end
end
Review production preparation before adapting these handlers to real users.