Пре-альфаSpinel 2026.09.12MIT

Веб-приложения в духе Rails, скомпилированные в один нативный бинарный файл.

CyberTrain — фреймворк для веб-приложений в духе Rails, написанный специально для Spinel, AOT-компилятора Ruby от matz. Привычный словарь остаётся с вами (resources, before_action, validates, form_with, скаффолды), а cybertrain build компилирует контроллеры, модели, представления и сам фреймворк в один нативный бинарный файл — без Rack, без гемов во время выполнения и без интерпретатора Ruby в артефакте.

Быстрый старт

терминал — любой каталог, затем blog/shell
gem install cybertrain
cybertrain new blog          # при первом запуске устанавливает Spinel 2026.09.12 (несколько минут)
cd blog
cybertrain g scaffold article title:string body:text
cybertrain db migrate
cybertrain server            # http://127.0.0.1:3000
cybertrain build             # продакшен: dist/blog, dist/public/, dist/storage/, dist/tmp/

Нужен Ruby 3.2+ — только для команды cybertrain (приложения никогда не запускаются на CRuby), а также git, make, curl, компилятор C и заголовки SQLite 3: см. перед началом. Запускайте сборку командой cd dist && ./blog migrate && ./blog с заданной переменной CYBERTRAIN_SECRET_KEY_BASE.

  • СерверHTTP/1.1, без Rack
  • ВыполнениеБез гемов и интерпретатора
  • База данныхSQLite через FFI Spinel
  • CILinux и macOS

Для кого это и чем это не является

Для Rails-разработчиков, которым нужен один нативный бинарный файл и которых не смущают молодой проект и подмножество Ruby.

  • Не Roundhouse

    Roundhouse транспилирует существующие Rails-приложения в Spinel. CyberTrain — нативный фреймворк, под который вы пишете напрямую, а не слой совместимости.

  • Не совместим с Rails и Rack

    Нет совместимости с Rack и нет гемов во время выполнения: только собственные пакеты Spinel из spin-index, которые компилируются в рамках его подмножества Ruby.

  • Не готов к продакшену

    CyberTrain находится в стадии пре-альфа. Пример блога проверяется в CI на Linux и macOS.

Код, который вы пишете

Словарь Rails и записанные правила Spinel.

Контроллеры, модели, маршруты и представления читаются как Rails-приложение. Там, где AOT-компилятор вынуждает к отличиям, они небольшие, явные и задокументированные.

  • Маршруты

    Вложенные блоки resources получают маппер явно: articles.resources :comments.

  • Контроллеры

    new пишется как new_action: метод с именем new затенил бы Klass.new(ctx).

  • Представления

    В режиме разработки шаблоны читаются с диска, поэтому правка представления не требует пересборки; cybertrain build встраивает их в бинарный файл. Это замкнутая грамматика, а не произвольный Ruby.

  • Модели

    class Article объявляет валидации; столбцы, finder-методы и ассоциации генерируются из db/schema.rb.

config/routes.rbruby
Cybertrain::Routes.draw do
  root "articles#index"

  # A nested block takes the mapper explicitly (Spinel's instance_eval
  # trampoline only rewires `self` for the outermost block): `articles.resources`.
  resources :articles do |articles|
    articles.resources :comments, only: [:create, :destroy]
  end
end
app/controllers/articles_controller.rbruby
# app/controllers/articles_controller.rb (index/show/edit are as plain as
# Rails' own scaffold; update mirrors create)
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  def new_action   # a `new` method would shadow ArticlesController.new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      flash[:notice] = "Article was successfully created."
      redirect_to article_path(@article), status: :see_other
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body)
  end
end
app/views/comments/_comment.html.erberb
<%# locals: (comment:) %>
<div class="comment">
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>

  <%= button_to "Destroy comment", [@article, comment], method: :delete %>
</div>
app/models/article.rbruby
# app/models/article.rb -- only the first string field gets a default
# presence validation; add anything past that yourself.
class Article
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

Что работает сегодня

Та часть Rails, которая нужна в первую очередь, написанная специально для Spinel.

  • Нативный HTTP-сервер

    HTTP/1.1-сервер на обычном Ruby: один зелёный поток на соединение, типизированные запросы и ответы. Слоя Rack нет; для TLS поставьте перед ним nginx или Caddy.

    TCPServer#accept

  • Маршрутизация и контроллеры

    resources, вложенные маршруты, before_action, after_action и rescue_from на этапе сборки генерируются в обычный Ruby. Strong params и связка «flash, затем редирект» работают как в Rails.

    config/routes.rb

  • Сессии, flash, CSRF

    Сессии в cookie с подписью HMAC, flash-сообщения и защита от CSRF включены по умолчанию. Формы передают токен автоматически; на поддельные POST-запросы приходит 403.

    _cybertrain_session

  • Шаблоны в стиле ERB

    Представления выглядят как Rails ERB: макеты, партиалы, form_with и link_to. В режиме разработки они читаются с диска, поэтому правки не требуют пересборки. cybertrain build встраивает их в бинарный файл.

    app/views/**/*.html.erb

  • Модели на SQLite

    Валидации, колбэки и ассоциации, выведенные из внешних ключей. Работа с SQLite идёт через FFI Spinel с пулом соединений; все запросы используют связанные параметры.

    ffi_func · ffi_lib

  • Миграции и скаффолды

    cybertrain generate scaffold создаёт миграцию, модель, контроллер и пять представлений. cybertrain db migrate выполняет генерацию, применяет миграции, перезаписывает db/schema.rb и генерирует снова.

    db/migrate/*.rb

  • Живой цикл разработки

    cybertrain server следит за вашим кодом, пересобирает его в фоне, а затем перезапускает новый бинарный файл через exec на том же порту и с тем же PID. При неудачной сборке продолжает работать старый сервер.

    rebuild → stop → execv

  • Встроенное тестирование

    Пишите тесты с Cybertrain::Test и прогоняйте всё приложение внутри процесса с помощью клиента с хранилищем cookie. cybertrain spin test компилирует каждый тест и сравнивает его вывод с эталоном.

    test/<name>.rb.expected

Фреймворк и CLI

Одно имя, две части.

gem install cybertrain устанавливает команду, а не фреймворк. Фреймворк — это пакет Spinel, от которого зависит ваше приложение и который компилируется в него. Гем даёт вам команду cybertrain: она создаёт приложение, устанавливает Spinel и запускает инструменты Spinel за вас.

Пакет Spinel

Фреймворк

Библиотека, на которой построено ваше приложение.

Что внутри

HTTP-сервер, маршрутизация и контроллеры, сессии и защита от CSRF, интерпретатор шаблонов, модели и миграции SQLite, а также генератор кода, стоящий за spin run gen.

Как получить

Устанавливать его не нужно. В spin.toml вашего приложения указан релизный тег v0.2.1 этого репозитория, а spin lock загружает его в кэш spin и фиксирует коммит в spin.lock. В ваше приложение ничего не копируется.

Где работает

Внутри вашего приложения. Spinel заранее компилирует его вместе с вашим кодом; на CRuby он никогда не запускается.

В dist/blog

ВкомпилированЭто та часть CyberTrain, которая попадает в поставку.

RubyGem

Команда cybertrain

Инструмент, который создаёт, собирает и запускает ваше приложение.

Что внутри

Только команда cybertrain: new, generate scaffold, db migrate, server, build, spin, setup и doctor. Ни фреймворка, ни Spinel в геме нет.

Как получить

gem install cybertrain. При первом использовании она собирает зафиксированный релиз Spinel 2026.09.12 в ~/.cybertrain, если этот релиз ещё не доступен в вашем PATH.

Где работает

На вашей машине, под CRuby 3.2 или новее. Она вызывает инструмент Spinel spin за вас, так что вам не нужно делать это самим.

В dist/blog

Не поставляетсяБинарный файл работает без Ruby, гема и Spinel.

Обе части выпускаются из одного тега, поэтому их версии совпадают: гем 0.2.1 создаёт приложения, зависящие от фреймворка v0.2.1. Чтобы перевести приложение на новый релиз, обновите гем, укажите в spin.toml новый тег, затем выполните cybertrain spin lock и cybertrain spin run gen.

Как это работает

Метапрограммирование происходит один раз — при сборке.

Spinel компилирует заранее, и eval в нём нет. Поэтому там, где Rails во время выполнения прибегает к method_missing и define_method, spin run gen пишет обычный Ruby, который можно прочитать и закоммитить, а интерпретируются только представления.

Конвейер CyberTrainНа вход сборки поступают db/schema.rb, config/routes.rb, app/ и app/views/. spin run gen записывает в gen/ обычный Ruby, который коммитится в репозиторий. cybertrain build встраивает app/views/ и компилирует gen/ вместе с фреймворком в один нативный бинарный файл dist/blog, который обслуживает HTTP/1.1 и работает с SQLite через FFI Spinel. В режиме разработки бинарный файл вместо этого читает app/views/ с диска, а обратный путь отслеживает исходники: слежение, пересборка, остановка, execv.ВЫ ПИШЕТЕСБОРКА · spinЗАПУСКdb/schema.rbtablesconfig/routes.rbroutesapp/контроллеры, моделиspin run gen1gen/обычный Ruby, в gitмоделиroutes · URL helpersview_assignsrun_callbackmigrations · viewsmanifest2cybertrain builddist/blogone native binaryфреймворкконтроллеры · моделиtemplate interpreterembedded viewsHTTP/1.1-серверSQLite via Spinel FFI4app/views/dev: чтение с дискавстроены в dist/blog35dev: watch → rebuild → stop → execvтолько в разработке · при сбое сборки служит старый сервер Конвейер CyberTrainНа вход сборки поступают db/schema.rb, config/routes.rb, app/ и app/views/. spin run gen записывает в gen/ обычный Ruby, который коммитится в репозиторий. cybertrain build встраивает app/views/ и компилирует gen/ вместе с фреймворком в один нативный бинарный файл dist/blog, который обслуживает HTTP/1.1 и работает с SQLite через FFI Spinel. В режиме разработки бинарный файл вместо этого читает app/views/ с диска, а обратный путь отслеживает исходники: слежение, пересборка, остановка, execv.ВЫ ПИШЕТЕdb/schema.rbtablesconfig/routes.rbroutesapp/контроллеры, моделиСБОРКАspin run gen1gen/обычный Ruby, в gitмоделиroutes · URL helpersview_assignsrun_callbackmigrations · viewsmanifest2cybertrain buildЗАПУСКdist/blogone native binaryфреймворкконтроллеры · моделиtemplate interpreterembedded viewsHTTP/1.1-серверSQLite via Spinel FFI4app/views/dev: чтение с дискавстроены в dist/blog3dev: watch → rebuild → stop → execv5только в разработке · при сбоесборки служит старый сервер
шаг сборкитолько в разработке: чтение с диска / цикл разработкибинарный файлНомера соответствуют шагам ниже.
  1. Генерация при сборке

    spin run gen выполняет db/schema.rb и config/routes.rb, чтобы получить данные о таблицах и маршрутах, и лексически сканирует (регулярными выражениями, а не парсером) app/controllers и app/models в поисках присваиваний @ivar =, имён колбэков и def без аргументов. Он записывает обычный, читаемый Ruby в gen/, включая gen/views.rb — таблицу встроенных представлений (пустую, если сборка не для развёртывания). cybertrain db migrate, server и build запускают его за вас.

  2. Коммитьте gen/

    Каталог хранится в репозитории, а не в gitignore, потому что у spin build и spin test нет хука, чтобы сначала выполнить генерацию. CI заново запускает spin run gen и падает при наличии диффа, поэтому устаревший сгенерированный код не может попасть в ветку. cybertrain spin run gen -- --check сообщает об устаревших файлах.

  3. Интерпретация представлений

    app/views/**/*.html.erb разбираются во время запроса и исполняются интерпретатором, обходящим дерево. В режиме разработки движок читает app/views/ с диска и заново разбирает шаблон при его изменении, поэтому правки представлений не требуют пересборки. cybertrain build встраивает каждый шаблон в бинарный файл (spin run gen -- --embed-views); продакшен-бинарник рендерит только из этой таблицы и разбирает каждый шаблон один раз. В Spinel нет eval, поэтому это замкнутая грамматика, а не настоящий Ruby.

  4. Обслуживание зелёными потоками, хранение в SQLite

    HTTP/1.1-сервер на обычном Ruby запускает по одному зелёному потоку Spinel на соединение. Модели работают с SQLite через ffi_func/ffi_lib с пулом из 4 соединений (WAL, обработчик busy на 5 секунд, внешние ключи включены). SPINEL_WORKERS по умолчанию равен 1.

  5. Пересборка в режиме разработки

    cybertrain server запускает spin run gen, затем spin run blog (имя вашего приложения). В режиме разработки бинарный файл каждые полсекунды опрашивает app/**/*.rb, config/**/*.rb, db/schema.rb и gen/**/*.rb (представления и gen/views.rb исключены). Изменение запускает в фоне spin run gen && spin build blog; при успехе выставляется флаг перезапуска (тот же, что выставляет внешний kill -HUP), и сервер перестаёт принимать соединения, дожидается завершения открытых запросов и через execv запускает новый бинарный файл на том же порту и с тем же PID. При неудачной сборке продолжает работать старый бинарный файл, а вывод компилятора показывается в виде баннера. Пока cybertrain build удерживает tmp/cybertrain-build.lock, пересборки пропускаются. В продакшене ничего из этого не загружается.

терминал — blog/shell
cybertrain spin run gen   # читает db/schema.rb + config/routes.rb, сканирует app/, пишет gen/
cybertrain build          # dist/blog со встроенными app/views, а также dist/public/, dist/storage/, dist/tmp/

Rails и CyberTrain

Тот же словарь. Другие правила — и они записаны.

AOT-компилятор меняет несколько привычек. Вот те, с которыми вы столкнётесь первыми.

Различия между Rails и CyberTrain (кратко)
ТемаRailsCyberTrain
01Консольrails consoleКонсоли нет — в Spinel нет eval
02ПерезагрузкаПравите код — запущенное приложение подхватывает измененияRuby требует пересборки; в режиме разработки cybertrain server делает это за вас (пересборка, остановка, execv)
03Действие newdef newdef new_action (new затенил бы Klass.new(ctx))
04Колбэкиbefore_action { do_thing } (неявный self)before_action { |c| c.do_thing } — для сохранённого блока нет instance_exec, поэтому колбэки получают контроллер/запись явно
05Вложенные маршрутыresources :posts do resources :comments endresources :posts do |posts| posts.resources :comments end — вложенные блоки получают маппер явно
06СессииСессия хранит любой сериализуемый через Marshal объектЗначения сессии — только строки (String), cookie с подписью HMAC
07База данныхМножество адаптеров баз данныхТолько SQLite, через FFI
08Тестыminitest / RSpecCybertrain::Test — раннеры на основе рефлексии не могут работать при AOT-компиляции

Список не исчерпывающий.

Полная таблица в README

Производительность

На одном ядре — в 2,6–12,2 раза больше запросов, чем у Rails.

examples/blog против того же блога на Rails 8.1: те же представления, маршруты, модели и схема SQLite. Оба работают в production-режиме на одних и тех же ядрах CPU одной машины, под одинаковой нагрузкой от wrk. Второе ядро больше помогает Rails: Puma добавляет целый процесс, а второй рабочий поток CyberTrain добавляет 9–58%, поэтому на двух ядрах разрыв — 2,0–5,5 раза.

Запросов в секунду при 16 соединенияхCyberTrainRails 8.1
  • GET /articlesсписок, 30 статей

    Одно ядро

    CyberTrain: 2 641

    Rails: 468

    5,6×к Rails

    Два ядра

    CyberTrain: 3 289

    Rails: 1 036

    3,2×к Rails

  • GET /articles/1статья с 10 комментариями и 12 формами

    Одно ядро

    CyberTrain: 2 450

    Rails: 201

    12,2×к Rails

    Два ядра

    CyberTrain: 2 662

    Rails: 486

    5,5×к Rails

  • POST /articles/2/commentsвставка с валидацией и редирект

    Одно ядро

    CyberTrain: 1 507

    Rails: 390

    3,9×к Rails

    Два ядра

    CyberTrain: 2 379

    Rails: 870

    2,7×к Rails

  • GET /style.cssстатический файл из public/

    Одно ядро

    CyberTrain: 13 300

    Rails: 5 127

    2,6×к Rails

    Два ядра

    CyberTrain: 20 589

    Rails: 10 311

    2,0×к Rails

Память после нагрузки (меньше — лучше)CyberTrainRails 8.1
  • Системный mallocаллокатор по умолчанию

    Одно ядро

    CyberTrain: 10,6 МБ

    Rails: 125,9 МБ

    11,9×меньше

    Два ядра

    CyberTrain: 12,0 МБ

    Rails: 331,4 МБ

    27,6×меньше

  • jemallocподгружен в оба

    Одно ядро

    CyberTrain: 13,3 МБ

    Rails: 119,9 МБ

    9,0×меньше

    Два ядра

    CyberTrain: 15,1 МБ

    Rails: 313,2 МБ

    20,7×меньше

Резидентная память (RSS из /proc) всех процессов сервера в сумме, считанная после всех прогонов нагрузки. Rails работает как один процесс Puma с 3 потоками на одном ядре и как два воркера на двух. RSS считает страницу, общую для воркеров Puma, по разу на каждый воркер; по PSS, который делит общие страницы между процессами, Rails на двух ядрах занимает 249,1 МБ (222,5 МБ с jemalloc) против 10,2 МБ (12,3 МБ) у CyberTrain.

  • Память под нагрузкой

    10,6 МБ

    Rails: 126 МБ, один процесс Puma с 3 потоками.

  • От запуска до первого ответа

    0,02 с

    Rails: 1,3 с.

  • Что нужно на сервере

    2,5 МБ

    Один бинарник. Rails: 109 МБ — Ruby, 69 gem-пакетов и приложение.

  • Цена: сборка

    3 мин

    на компиляцию бинарника командой cybertrain build. Rails запускает исходники как есть.

Медианы трёх 10-секундных прогонов после разогрева на облачной ВМ с 4 vCPU; wrk работает на двух ядрах, которые не занимает сервер. CyberTrain 0.2.1, собранный Spinel 2026.09.12, против Rails 8.1.4 на Ruby 3.4.6 с YJIT и Puma 8.0 (на одном ядре — один процесс с 3 потоками, на двух — два). У полос в одной строке общий масштаб. При одном запросе за раз страница статьи отвечает за 0,4 мс против 4,7 мс. С jemalloc в обоих (образ Rails для production подгружает его сам, у CyberTrain это одна строка в spin.toml) на одном ядре разрыв — 2,8–14,6 раза. Часть разрыва — работа, которую Rails делает по умолчанию, а CyberTrain нет: шифрует cookie сессии и подписывает отдельный CSRF-токен для каждой формы. Методика, все числа и как повторить замер: docs/benchmark.md.

Перед началом

Что нужно на вашей машине.

Ruby
3.2+ с RubyGems — только для команды cybertrain. Приложения никогда не запускаются на CRuby.
Spinel
2026.09.12; с другими версиями не тестировался. Первая команда, которой он нужен, собирает его в ~/.cybertrain (несколько минут, один раз); если spinel этого релиза уже есть в PATH, используется он.
Инструменты
cc и make (Spinel компилирует каждую программу в C), а также git и curl для сборки Spinel. Заголовки и библиотека SQLite 3: libsqlite3-dev в Debian/Ubuntu, на macOS входят в Xcode command line tools. Заголовки OpenSSL необязательны.
Статус
Пре-альфа, версия 0.2.1, лицензия MIT. Пример блога проверяется в CI на Linux и macOS.

cybertrain doctor проверяет эти инструменты и заголовки, а также установку Spinel и, если чего-то не хватает, выводит команду установки для вашей платформы. cybertrain setup устанавливает Spinel по запросу и выводит строку для PATH, чтобы запускать spin самостоятельно; cybertrain spin ARGS... запускает его и без неё.