Zum Inhalt springen

Ruby

Starte im Assistenten mit einer Ruby-Vorlage oder wähl Ruby-Anwendung für ein leeres Projekt oder ein Repository, oder nimm Ruby im Bereich Laufzeit zu einem bestehenden Projekt dazu. Der Container envoryx-<projekt>-ruby läuft mit ghcr.io/envoryx/envoryx-ruby:<version>. Das ist das offizielle Image ruby:<version>-slim-bookworm, dazu alles, was die gängigen nativen Gems zum Kompilieren brauchen (pg, mysql2, sqlite3, psych), libvips für die Bildvarianten des Standard-Prozessors :vips von Active Storage und das debug-Gem. Er läuft als PUID:PGID, mit deinem Projekt unter /var/www/html und einem Home-Verzeichnis unter /home/envoryx.

Gems werden nach GEM_HOME=/home/envoryx/.gem/ruby/<X.Y.0> installiert, bei Ruby 3.4 also nach ~/.gem/ruby/3.4.0. Das ist das Verzeichnis, das RubyGems selbst für User-Installationen nimmt. Deshalb findet Ruby Gems, die eine IDE dort installiert (etwa die Debugger-Gems von RubyMine), ohne Tricks mit GEM_PATH, und Gems, die für eine Ruby-Version gebaut sind, begegnen nie einer anderen. Sie überstehen es, wenn der Container neu erstellt wird, und in deinem Projektordner liegt kein vendor/bundle. Der Download-Cache von Bundler liegt im Paket-Cache des Projekts, ein neues Installieren des Bundles lädt die Gems also nicht noch einmal. Schaltest du dort das Teilen ein, nutzen alle Projekte einen Cache, und die zweite Rails-App installiert sich deutlich schneller als die erste.

Projekte von vor Envoryx 0.17.0 haben ~/.gem/ruby ohne Version benutzt. Sie installieren ihr Bundle beim ersten Neustart nach dem Update noch einmal, und den alten Inhalt von ~/.gem/ruby im Projekt-Home kannst du löschen.

Node.js steckt nicht im Image. Nutzt du jsbundling-rails oder cssbundling-rails, nimm für diese Builds einen Node.js-Dienst neben Ruby dazu. Das Rails-Template arbeitet mit importmap und braucht Node überhaupt nicht.

Standard ist 4.0, außerdem gibt es 3.4 und 3.3.

Schalte Ruby-Server ausführen ein und wähl eine Vorlage. Der Server wird zum Hauptprozess des Containers, startet automatisch neu und bekommt einen eigenen Host-Port. Schaltest du den Server aus und wieder ein, behält er diesen Port, ein Lesezeichen darauf bleibt also gültig.

Die Karte Ruby im Bereich Laufzeit mit der Version, dem eingeschalteten Rails-Server, dem Modus, dem Port und der Option für rdbgDie Karte Ruby im Bereich Laufzeit mit der Version, dem eingeschalteten Rails-Server, dem Modus, dem Port und der Option für rdbg
Vorlage Entwicklung Produktion Port
Rails bin/rails server - Rails lädt geänderten Code selbst neu bundle exec puma 3000
Rack Puma mit config.ru (Sinatra, Roda, Hanami …) genauso 9292

Der Server lauscht auf $PORT, HOST und BINDING stehen auf 0.0.0.0. RAILS_ENV, RACK_ENV, APP_ENV und HANAMI_ENV folgen dem Modus, und deine Worker bekommen dieselben Werte. Mit der Rack-Vorlage ist das Neuladen von geändertem Code Sache des Frameworks - bei Sinatra etwa sinatra/reloader.

Rails lehnt Anfragen für Hostnamen ab, die es nicht kennt. In der Entwicklung setzt Envoryx RAILS_DEVELOPMENT_HOSTS=.<basis>, damit die Namen deines Projekts durchkommen, ohne dass du config.hosts anfassen musst.

Der Produktionsmodus ist wirklich die Production-Umgebung von Rails, mit allem, was dazugehört. Er braucht config/master.key (oder SECRET_KEY_BASE als Projektvariable), vorkompilierte Assets - dafür rails assets:precompile ausführen - und die Datenbanken, die deine database.yml für Production nennt. Rails liefert public/ dann selbst aus, und steht in deiner Production-Konfiguration force_ssl, leitet es auf https um.

Bevor der Server startet, wartet er auf das Gemfile (und bin/rails oder config.ru). Dann führt er bundle check aus und, falls etwas fehlt - nach einem frischen Klon, einem geänderten Gemfile oder einem Ruby-Upgrade -, bundle install. Eine geklonte Anwendung kommt also einfach hoch, ohne dass du von Hand etwas ausführst. Schlägt die Installation fehl, steht das im Log, und es gibt alle 30 Sekunden einen neuen Versuch. Worker machen dasselbe, bevor sie starten.

Diese Prüfung passiert allerdings beim Start. Ein Server, der schon läuft, behält das Bundle, mit dem er gestartet ist. Nach einem bundle add oder einer Änderung am Gemfile startest du das Projekt also neu, dann kommen Server und Worker mit den neuen Gems hoch; bis dahin gibt es für das neue Gem einen LoadError.

Envoryx speist DATABASE_URL ein, und Rails übernimmt das von selbst in config/database.yml. Bei PostgreSQL bekommen die Ruby-Container den Wert als postgresql://…, in der Form, die Active Record versteht. Eine zusätzliche Datenbank namens analytics taucht als ANALYTICS_DATABASE_URL auf, und das Multi-Database-Setup von Rails greift sie für eine Datenbank analytics in der database.yml auf.

Solid Queue bekommt eine eigene Datenbank, so wie Rails das für die Produktion einrichtet. Die Ruby-Container erhalten QUEUE_DATABASE_URL, das auf <database>_queue auf dem Datenbankserver des Projekts zeigt, und Rails übernimmt es in den Eintrag queue der database.yml. Rails 8 schreibt diesen Eintrag für die Produktion, die Rails-Templates legen auch einen für die Entwicklung an, und einen cable-Eintrag daneben. Sobald die database.yml einen queue-Eintrag hat, legt Envoryx die Queue-Datenbank an (als Administrator, wie die Testdatenbank), und rails db:prepare spielt die Tabellen von Solid Queue dort ein.

Solid Cache und Solid Cable funktionieren genauso. CACHE_DATABASE_URL zeigt auf <database>_cache und CABLE_DATABASE_URL auf <database>_cable, passend zu den Einträgen cache und cable, die Rails 8 für die Produktion schreibt, und Envoryx legt diese Datenbanken an, wenn die database.yml die Einträge hat. Ruby-Projekte von vor 0.18 melden wegen der zwei neuen Variablen einmal einen älteren Stand; starte sie neu, wenn es dir passt.

Das Rails-Template lässt den Cache in der Entwicklung auf :memory_store. Willst du auch dort Solid Cache, ergänzt du im Abschnitt development der database.yml einen cache-Eintrag (wie den für die Produktion, mit migrations_paths: db/cache_migrate), lässt config/cache.yml mit database: cache darauf zeigen, setzt config.cache_store = :solid_cache_store in der development.rb und führst rails db:prepare aus.

Diese Datenbanken gehören zum Projekt wie die Hauptdatenbank. Backups und Wiederherstellungen enthalten die Datenbanken _queue, _cache und _cable, ein Duplikat und eine Wiederherstellung in ein neues Projekt bringen sie mit, und beim Umbenennen ziehen sie zum neuen Namen um. Wartende Jobs von Solid Queue überstehen also eine Wiederherstellung, und eine Kopie scheitert nicht an einer fehlenden Queue-Datenbank.

Ohne PHP und ohne Python- oder Go-Server ist der Ruby-Server die Anwendung: https://<projekt>.<basis> und jede zusätzliche Domain landen direkt bei ihm, der Port des Webserver-Containers bleibt zu, und Direktzugriff ist der Ruby-Host-Port. Neben PHP, einem Python- oder einem Go-Server hat er nur seinen eigenen Host-Port. Ein Node.js-Dev-Server neben Ruby behält <projekt>-dev.<basis>.

Rails führt rails new mit Hotwire und importmap aus, Rails (API only) führt rails new --api aus. Beide werden nach deinem Projekt benannt und für seine Datenbank eingerichtet - PostgreSQL, MySQL oder MariaDB, und SQLite, wenn das Projekt keine hat. Beim ersten Mal dauert das ein paar Minuten, weil die nativen Gems kompiliert werden. Scheitert unterwegs bundle install, etwa ohne Netz oder an einem Gem, das sich nicht bauen lässt, schlägt das Template mit dem Grund fehl, statt eine halbe Anwendung zu hinterlassen. Sobald das Projekt läuft, führt Envoryx rails db:prepare für dich aus; hast du es gestoppt angelegt, startest du das im Bereich Aktionen, bevor du die Seite öffnest.

Außerdem richten die Templates die Entwicklungsumgebung für die Arbeit unter Envoryx ein:

  • Solid Queue bekommt eine eigene Queue-Datenbank und Solid Cable eine eigene Cable-Datenbank (siehe Datenbank), und Action Cable nutzt in der Entwicklung Solid Cable statt des async-Adapters. Mit async verlässt ein Turbo-Broadcast aus einem Job nie den Worker-Prozess und kommt also nie im Browser an. Für Hintergrundjobs fügst du danach den Worker Solid Queue hinzu.
  • lib/tasks/envoryx.rake sorgt dafür, dass rails db:prepare die Schemas für Queue und Cable in eine Datenbank lädt, die nur die Verwaltungstabellen von Rails hat. Wer die Seite vor dem ersten db:prepare öffnet, legt genau diese Tabellen an, und db:prepare hielte die Queue-Datenbank sonst für fertig.
  • Hat das Projekt Mailpit, liefert Action Mailer dorthin. Ohne diese Einstellung versucht Rails es auf localhost:25 und verwirft jede Mail stillschweigend. Links in Mails (post_url in einer Mailer-View) zeigen auf die Projektadresse aus ENVORYX_URL, nicht auf Rails’ Standard localhost:3000.
  • Server und Worker loggen zusätzlich nach stdout, damit der Bereich Logs sie zeigt; log/development.log bleibt.
  • web-console akzeptiert die Netze des Containers. Auf einer Fehlerseite, die du über die Projekt-URL erreichst, öffnet sich ihre Konsole trotzdem nicht: web-console prüft die Adresse des Browsers aus X-Forwarded-For, und das ist dein Rechner, nicht der Proxy. Envoryx lässt das mit Absicht so, denn die Konsole führt beliebigen Ruby-Code aus, und sie für dein ganzes privates Netz zu öffnen, gäbe das jedem dort in die Hand. Nimm stattdessen rails console im Ruby-Terminal, oder trag, wenn du das Risiko eingehst, deine eigene Adresse in config.web_console.allowed_ips in der development.rb ein.

Sinatra schreibt eine app.rb, eine config.ru und ein Gemfile mit Puma, sinatra-contrib für den Reloader und dem debug-Gem.

Hat das Projekt einen Objektspeicher, kann Active Storage ihn als S3-Dienst nutzen. Nimm aws-sdk-s3 ins Gemfile auf und trag einen Dienst in config/storage.yml ein:

envoryx:
service: S3
endpoint: <%= ENV["S3_ENDPOINT"] %>
access_key_id: <%= ENV["S3_ACCESS_KEY"] %>
secret_access_key: <%= ENV["S3_SECRET_KEY"] %>
region: <%= ENV["S3_REGION"] %>
bucket: <%= ENV["S3_BUCKET"] %>
force_path_style: true

Dann setzt du config.active_storage.service = :envoryx. Von sich aus schickt Rails den Browser zu einer URL, die für S3_ENDPOINT signiert ist, also http://s3:9000, und die gibt es nur im Projektnetz. Am einfachsten kommst du mit config.active_storage.resolve_model_to_route = :rails_storage_proxy heraus: Dann liefert Rails die Dateien selbst unter der Projekt-URL aus.

Der Bereich Tests führt bundle exec rspec aus, wenn es einen Ordner spec/ und rspec in der Gemfile.lock gibt, und bin/rails test. Eine Rails-App mit rspec-rails bekommt beide Suites angeboten, du nimmst die, die du benutzt. Beides läuft in der Test-Umgebung gegen <datenbank>_test auf dem Datenbankserver deines Projekts.

Der letzte Punkt ist wichtiger, als er klingt. Active Record übernimmt DATABASE_URL in jede Umgebung, die gerade läuft. Ein Testlauf würde seine Fixtures sonst in deine Entwicklungsdatenbank laden und deren Tabellen leeren. Envoryx lenkt den Lauf deshalb auf die Testdatenbank um und legt sie vorher an. Das macht es als Administrator, weil der Projekt-Login bei MySQL und MariaDB keine Datenbanken anlegen darf. Das Schema lädt dann Rails. Ist rspec_junit_formatter in deinem Bundle, erscheinen Fehler pro Test. rails test schreibt keinen Report, deshalb liest Envoryx seine Zusammenfassung und die Fehler, die Rails ausgibt (Test, Meldung, Datei und Zeile), aus der Ausgabe - Zahlen und fehlgeschlagene Tests bekommst du also auch hier.

Mit rdbg debuggen startet den Server unter rdbg --open --nonstop aus dem debug-Gem, auf Port 12345, solange du nichts anderes einstellst, und legt diesen Port auf einen Host-Port. Sperrt die Gemfile.lock das debug-Gem - Rails und das Sinatra-Template tun das -, wird das rdbg aus deinem Bundle genommen. So ist nur eine Version des Gems im Spiel.

In VS Code installierst du die Erweiterung VSCode rdbg Ruby Debugger und trägst das hier ein, mit Host und Port aus dem Bereich IDE:

.vscode/launch.json
{
"type": "rdbg",
"name": "Attach to Envoryx",
"request": "attach",
"debugPort": "<host>:<port>",
"localfsMap": "/var/www/html:${workspaceFolder}"
}

Im Terminal erledigt rdbg -A <host> <port> dasselbe.

rdbg nimmt immer nur einen Client an. Kommt ein Attach nicht zurück, hängt vermutlich noch eine frühere Sitzung dran, etwa ein rdbg -A im Ruby-Terminal, das du geschlossen hast, ohne es zu beenden. Beende diesen Prozess (pkill -f 'rdbg -A' im Ruby-Terminal) und verbinde dich neu.

Bei RubyMine ist es anders. Dessen Konfiguration Ruby remote debug spricht nur ruby-debug-ide, nicht das debug-Gem, und kann sich deshalb nicht an rdbg hängen. Richte den Ruby-Container stattdessen als SSH-Remote-Interpreter ein (Benutzer <projekt>.ruby, siehe IDE-Integration), dann debuggt RubyMine mit seinem eigenen Debugger. Den rdbg-Schalter brauchst du dafür nicht, wohl aber JetBrains Gateway für dieses Projekt erlauben im Bereich IDE, denn der Debugger von RubyMine leitet einen Port über SSH weiter.

Ohne Server wird nur der Port veröffentlicht - für ein rdbg, das du selbst im Ruby-Terminal startest, etwa rdbg --open --host=0.0.0.0 --port=12345 -c -- bin/rails test.

Im Bereich Aktionen gibt es ruby --version, bundle install, bundle update, bundle outdated, rubocop (wenn eine .rubocop.yml da ist) und für Rails db:prepare, db:migrate, db:rollback, db:seed, routes, assets:precompile, tmp:clear und about.

Für Hintergrundjobs gibt es Worker für Solid Queue (bin/jobs start), GoodJob (good_job start, auf Wunsch auf bestimmte Queues beschränkt), Sidekiq (auf Wunsch mit Queues - nimm Redis zum Projekt dazu, dann wird REDIS_URL eingespeist), einen Rake-Task und ein Ruby-Skript. Ein Solid Queue-Worker, dessen Tabellen es noch nicht gibt, wartet mit einem Hinweis im Log, bis rails db:prepare gelaufen ist. Die wiederkehrenden Aufgaben aus config/recurring.yml laufen in demselben Worker mit, einen zweiten brauchst du dafür nicht. Mehr dazu unter Worker und Cron-Jobs.

Die Ruby-Karte im Bereich Laufzeit hat einen Schalter Ruby aktivieren. Schaltest du ihn aus, verschwinden der Ruby-Container und die Container der Ruby-Worker; deine Dateien und die Worker-Definitionen bleiben. Auf der Kommandozeile kennt envoryx dafür --ruby <version>, --ruby-server und --ruby-preset rails|rack.