Вы здесь:Подготовка Ruby, cc, SQLite
Требования
ЗачемCyberTrain компилируется через Spinel в C, поэтому ему нужны инструменты C и заголовки SQLite; команда cybertrain — это гем, поэтому ей нужен Ruby.
- Ruby 3.2+ и RubyGems — только для команды
cybertrain; приложения никогда не запускаются на CRuby. - Spinel
2026.09.12; с другими версиями не тестировался. Вручную его устанавливать не нужно: первая команда, которой он нужен, собирает этот релиз в~/.cybertrain(несколько минут, один раз), еслиspinelэтого релиза ещё нет вPATH. - Инструменты C (
ccили компилятор, указанный вCC) иmake, а такжеgitиcurl— для сборки Spinel, который компилирует каждую программу в C. - Заголовки и библиотека SQLite 3:
libsqlite3-devв Debian/Ubuntu; на macOS входят в Xcode command line tools. - Заголовки OpenSSL (
libssl-dev) необязательны: без них Spinel собирается без своего пакетаopenssl, который CyberTrain не использует.
cybertrain doctor проверяет эти инструменты и заголовки, а также установку Spinel; если чего-то обязательного не хватает, она также выводит команду установки для вашей платформы.
Как CyberTrain устанавливает SpinelЧто происходит, когда команде впервые нужен spin
cybertrain new, db, server, build и spin сначала ищут Spinel 2026.09.12: spinel и spin этого релиза, уже доступные в PATH; иначе — копию в ~/.cybertrain/spinel/2026.09.12/; иначе собирают эту копию из релизного тега Spinel (git clone, make deps, make -j, make install) — один раз, за несколько минут. Лог пишется в ~/.cybertrain/log/spinel-2026.09.12-build.log; если не хватает инструмента, нужного для сборки, cybertrain ещё до её начала сообщает, какого именно, и выводит команду установки для вашей платформы.
cybertrain setup делает то же самое по запросу (--force пересобирает копию в ~/.cybertrain) и, если используется именно её копия, выводит строку, добавляющую её в ваш PATH, чтобы запускать spin самостоятельно:
export PATH="$HOME/.cybertrain/bin:$PATH"
В ~/.cybertrain/bin лежат ссылки spinel и spin на используемый релиз. Если этого каталога нет в PATH, cybertrain spin ARGS... запускает spin этого релиза из любого каталога. CYBERTRAIN_HOME заменяет ~/.cybertrain, а CYBERTRAIN_SPINEL_HOME=PREFIX вместо этого использует Spinel, установленный в PREFIX (это должен быть тот же релиз). spinel другого релиза в PATH никогда не используется и не затрагивается.
Негде устанавливать? Песочница открывает этот блог в GitHub Codespace, где шаги 02, 03, 04 и 07 уже выполнены.
Вы здесь:Подготовка CLI cybertrain
Установка CLI
Зачемcybertrain — команда, которая создаёт приложения, генерирует скаффолды ресурсов, выполняет миграции, запускает сервер и собирает.
Первая строка устанавливает её из RubyGems. Остальное — весь учебник в двух словах; шаги ниже разбирают его по одной команде.
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/
Команда cybertrain написана на обычном Ruby, и гем запускает её под CRuby (3.2 или новее); ни фреймворка, ни Spinel в геме нет. Её команды (их выводит cybertrain help):
cybertrain new NAME [--path DIR | --version V | --git URL [--ref R]] [--skip-spin]создаёт приложение в NAME, затем запускает в нёмspin lockиspin run gen, при необходимости сначала устанавливая Spinel.cybertrain generate scaffold NAME field:type ...(псевдонимg scaffold) добавляет ресурс в приложение в текущем каталоге.cybertrain db migrateвыполняет генерацию, применяет ожидающие миграции и генерирует снова (spin run gen; spin run db -- migrate; spin run gen);cybertrain migrationоставлена как псевдоним.cybertrain db COMMAND...послеspin run genзапускает любую другую команду базы данных:status,rollback [N](после которой генерация выполняется снова),schema:dump,create.cybertrain server [PORT]выполняет генерацию, затем запускает сервер разработки (spin run gen; spin run NAMEилиspin run NAME -- PORT; порт по умолчанию — 3000).cybertrain buildсобирает NAME со встроеннымиapp/views/и формируетdist/(бинарный файл,public/,storage/,tmp/).cybertrain spin ARGS...запускаетspinс зафиксированным Spinel из любого каталога:cybertrain spin test,cybertrain spin run gen -- --check.cybertrain setup [--force]устанавливает Spinel2026.09.12в~/.cybertrain, еслиspinelэтого релиза ещё нет вPATH;--forceпересобирает копию в~/.cybertrain.cybertrain doctorпроверяет инструменты C, заголовки SQLite и установку Spinel.cybertrain versionвыводит версию (cybertrain 0.2.1);cybertrain helpвыводит справку по использованию.
db, server и build запускаются внутри приложения; NAME — это [package] name из его spin.toml.
Вы здесь:Вы пишете blog/
Создание приложения
ЗачемВы получаете привычную для Rails структуру каталогов и три программы в bin/: bin/blog.rb (приложение), bin/gen.rb (генератор) и bin/db.rb (миграции при разработке).
cybertrain new blog
cd blog
cybertrain new создаёт приложение, чей spin.toml указывает на релиз, соответствующий CLI:
[package]
name = "blog"
version = "0.1.0"
[dependencies]
cybertrain = { git = "https://github.com/saeki-mototsune/cybertrain", ref = "v0.2.1" }
Затем она проверяет, что Spinel установлен (в первый раз это означает сборку Spinel 2026.09.12 в ~/.cybertrain: несколько минут, один раз; см. шаг 00), и запускает spin lock (spin загружает фреймворк в свой кэш ~/.cache/spin/packages/ и фиксирует коммит в spin.lock) и spin run gen — примерно так же, как rails new запускает bundle install, — так что cybertrain server сразу работает. В конце она выводит следующие шаги. Ничего из фреймворка в приложение не копируется; закоммитьте spin.toml и spin.lock.
Если не удаётся установить Spinel или выполнить один из двух шагов spin, файлы остаются, new завершается с кодом 1 и выводит, что запустить после устранения причины (для Spinel: cybertrain setup, затем cybertrain spin lock && cybertrain spin run gen в новом каталоге). --skip-spin только записывает файлы; --path DIR вместо этого делает зависимостью локальную копию репозитория, --git URL [--ref R] — фреймворк по адресу URL (на ветке или теге R), а --version V — версию из индекса, когда cybertrain появится в spin-index.
Имена приложений состоят из строчных букв, цифр и _ и начинаются с буквы. Сгенерированное дерево:
blog/
spin.toml config/app.rb config/routes.rb
db/schema.rb db/migrate/
app/controllers/application_controller.rb app/models/ app/views/ app/helpers/
public/ bin/blog.rb bin/gen.rb bin/db.rb
gen/ storage/ tmp/ test/
bin/blog.rb — единственная точка входа приложения, программа, которую компилирует spin build blog: ./blog обслуживает запросы, ./blog migrate и ./blog db status управляют базой данных. bin/gen.rb запускает генератор (spin run gen), а bin/db.rb запускает мигратор во время разработки (его использует cybertrain db migrate, потому что bin/blog.rb не скомпилируется, пока первая миграция не сгенерирует gen/models/). В gen/ уже лежит views.rb — закоммиченная пустая таблица встроенных представлений.
ApplicationController изначально выглядит так, поэтому отсутствующая запись через rescue_from превращается в 404:
class ApplicationController < Cybertrain::Controller
rescue_from Cybertrain::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "Not Found", status: :not_found
end
end
Вы здесь:Вы пишете app/, db/migrate/
Скаффолд статьи
ЗачемОдна команда создаёт весь CRUD-срез, как в Rails.
cybertrain generate scaffold article title:string body:text
Она создаёт миграцию, модель, контроллер и пять представлений (index, show, new, edit, _form) и вставляет resources :articles в config/routes.rb. Типы полей: string, text, integer, float, boolean, date, datetime и references; по умолчанию — string. cybertrain g scaffold из быстрого старта — та же самая команда.
Существующие файлы никогда не перезаписываются: для каждого файла выводится create, identical или exist.
Вы здесь:Вы пишете config/routes.rb
Корневой маршрут
Зачем/ должен показывать список статей.
# config/routes.rb
Cybertrain::Routes.draw do
root "articles#index"
resources :articles
end
Маршруты читаются при сборке: spin run gen превращает их в gen/routes.rb. cybertrain server, cybertrain db migrate и cybertrain build запускают его за вас.
Вы здесь:Вы пишете app/models/
Валидации модели
ЗачемКак и в руководстве Rails, добавьте валидацию длины вручную.
# 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
У class Article суперкласс не указан: суперкласс Cybertrain::Model, столбцы и finder-методы берутся из gen/models/article.rb. Неудачное сохранение выдаёт сообщения вроде «Title can't be blank» и «Body is too short (minimum is 10 characters)».
Вы здесь:Вы пишете app/controllers/
ArticlesController
ЗачемПолный CRUD в духе Rails, вплоть до strong params и связки «flash, затем редирект», — за исключением двух вещей, которые Spinel навязывает везде.
# 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
Скаффолд создаёт полный контроллер, включая также index, show, edit, update и destroy; во фрагменте выше они опущены. В index — @articles = Article.all.to_a: .to_a обязателен.
Переменные экземпляра находятся лексическим сканированием @ivar =, поэтому после добавления новой заново запустите cybertrain spin run gen; сервер разработки делает это за вас.
Вы здесь:Сборка, затем запуск gen/ → build/bin/blog
Генерация, миграции, запуск
ЗачемПревратить схему и маршруты в gen/, создать базу данных SQLite, запустить сервер.
cybertrain db migrate # spin run gen; spin run db -- migrate; spin run gen
cybertrain server # http://127.0.0.1:3000; пересборка при правках Ruby, представления перезагружаются без пересборки
spin run gen читает db/schema.rb и config/routes.rb, сканирует app/ и записывает gen/; spin run db -- migrate применяет db/migrate/*.rb и перезаписывает db/schema.rb. Порядок важен: первый spin run gen помещает новую миграцию в gen/migrations.rb, который подключает bin/db.rb, а второй строит модели по перезаписанному db/schema.rb.
cybertrain server выводит каждую запускаемую команду spin, а затем стартовый баннер приложения. При первом запуске приложение компилируется, так что это займёт некоторое время:
=> Booting cybertrain 0.2.1
=> development environment (1 worker)
=> Watching app/, config/ and db/schema.rb for changes
* Listening on http://127.0.0.1:3000
Use Ctrl-C to stop
- Заново запускайте
cybertrain spin run genи коммитьтеgen/после изменения схемы, маршрутов или колбэков и переменных экземпляра контроллера либо модели. Сервер разработки делает это за вас. - Порт меняется так:
cybertrain server 4000(илиPORT=4000). cybertrain dbдобавляет--, который нуженspin run dbперед аргументами;cybertrain db statusи другие команды базы данных работают так же.
Вы здесь:Вы пишете db/migrate/, app/
Скаффолд комментариев
ЗачемТип references даёт внешний ключ, из которого выводится ассоциация.
cybertrain generate scaffold comment commenter:string body:text article:references
cybertrain db migrate # скаффолд создал миграцию для таблицы comments
article:references добавляет столбец article_id, индекс и внешний ключ — этого достаточно, чтобы Article/Comment получили ассоциацию; писать has_many/belongs_to не нужно, spin run gen выводит её из внешнего ключа. Получившаяся схема:
s.create_table "comments" do |t|
t.string "commenter"
t.text "body"
t.integer "article_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
s.add_index "comments", ["article_id"], name: "index_comments_on_article_id"
s.add_foreign_key "comments", "articles", column: "article_id"
Скаффолд вставляет отдельную строку resources :comments, которую заменит следующий шаг.
Вы здесь:Вы пишете config/routes.rb
Вложенные маршруты
ЗачемВ руководстве Rails комментарии вложены в статьи.
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/
CommentsController
ЗачемДоступны только два действия, а родительская статья берётся из URL (params[:article_id]), а не из :id.
class CommentsController < ApplicationController
before_action :set_article
def create
@comment = Comment.new(comment_params)
@comment.article_id = @article.id
if @comment.save
flash[:notice] = "Comment was successfully created."
else
flash[:alert] = "Comment could not be saved: #{@comment.errors.full_messages.join(", ")}"
end
redirect_to article_path(@article), status: :see_other
end
def destroy
@comment = Comment.where(article_id: @article.id).find(params[:id])
@comment.destroy
flash[:notice] = "Comment was successfully destroyed."
redirect_to article_path(@article), status: :see_other
end
private
def set_article
@article = Article.find(params[:article_id])
end
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
Затем добавьте валидации в модель комментария:
class Comment
validates :commenter, presence: true
validates :body, presence: true
end
Article#comments здесь — это Array, поэтому рельсовый @article.comments.find(params[:id]) превращается в Comment.where(article_id: @article.id).find(params[:id]). Комментарий под чужой статьёй возвращает 404.
Вы здесь:Запуск app/views/, при разработке читаются с диска, встраиваются командой cybertrain build
Представления и партиалы
ЗачемСтраница статьи выводит список её комментариев и заканчивается формой, привязанной к [@article, @comment].
Сначала удалите ставшие недоступными представления комментариев index, show, new и edit и добавьте в ArticlesController#show пустой @comment:
def show
@comment = Comment.new
end
Затем выведите комментарии и форму на странице статьи:
<%# app/views/articles/show.html.erb %>
<% @article.comments.each do |comment| %>
<%= render "comments/comment", comment: comment %>
<% end %>
<%= render "comments/form" %>
<%# app/views/comments/_form.html.erb %>
<%= form_with(model: [@article, @comment]) do |f| %>
<%= f.label :commenter %> <%= f.text_field :commenter %>
<%= f.label :body %> <%= f.text_area :body %>
<%= f.submit %>
<% end %>
Партиал комментария в том виде, в каком он есть в примере:
<%# 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>
И button_to ... [@article, comment], и form_with(model: [@article, @comment]) разрешаются по одному и тому же правилу [parent, child]: новый дочерний объект направляется во вложенную коллекцию (article_comments_path), сохранённый — во вложенный элемент (article_comment_path); эти хелперы генерируются в gen/routes.rb из вложенного блока resources выше.
Вы здесь:Сборка cybertrain spin test
Тестирование
Зачемminitest и RSpec находят тесты через рефлексию, на которую AOT-компилятор не способен, поэтому CyberTrain поставляется с Cybertrain::Test.
Тест модели
require_relative "support/blog_test" # the app, BLOG and Cybertrain::Test
test "title must be present" do
article = Article.new(body: "a body long enough to pass length")
refute article.save
assert_includes article.errors.full_messages, "Title can't be blank"
end
Утверждения: assert, refute, assert_equal, assert_nil, assert_includes, assert_raises("Class") { }, flunk. В примере также используются assert_response и assert_redirected_to.
Полный HTTP-сценарий
require_relative "support/blog_test" # the app, BLOG and Cybertrain::Test
test "POST /articles creates an article and redirects to it" do
client = Cybertrain::Test::Client.new(BLOG)
client.get("/articles/new")
res = client.post("/articles", { "authenticity_token" => BlogTest.form_token(client),
"article[title]" => "Hello Rails",
"article[body]" => "I am on Rails! This is my first article." })
assert_redirected_to res, BlogTest.article_path(Article.last)
assert_response res, :see_other
end
Cybertrain::Test::Client прогоняет приложение внутри процесса (без сокета) с хранилищем cookie, поэтому сессии и CSRF ведут себя так же, как за браузером. Запросы без токена получают 403.
Запуск тестов
cybertrain spin test
cybertrain spin test запускает spin test с зафиксированным Spinel; если инструменты есть в PATH (шаг 00), то же самое делает spin test. Он компилирует каждый test/*.rb в отдельную программу и сравнивает её вывод (stdout и stderr вместе) с закоммиченным снимком test/<name>.rb.expected. Файлы в test/support/ подключаются, а не запускаются. Чтобы записать снимок:
cybertrain spin test --regen test/<name>.rb
rm -f build/test/<name>; cybertrain spin test test/<name>.rb; [ -x build/test/<name> ] && ./build/test/<name> > test/<name>.rb.expected 2>&1
--regen записывает снимок по выводу CRuby. Тесты, обращающиеся к SQLite через FFI, не могут работать под CRuby, поэтому их снимок берётся из скомпилированного бинарного файла (вторая строка выше). Пока снимок устарел, cybertrain spin test завершается с ненулевым кодом, поэтому после него стоит ;, а не &&; проверка [ -x ] пропускает запись, если компиляция не удалась, и старый снимок сохраняется. Тестам, которые не могут работать под CRuby, этот снимок нужен, прежде чем cybertrain spin test сможет пройти; остальные тесты без снимка сравниваются с выводом CRuby.
Вы здесь:Запуск dist/blog
Сборка для развёртывания
ЗачемОдна команда собирает приложение в готовый к развёртыванию каталог dist/ со всеми встроенными представлениями.
cybertrain build
Она записывает dist/: бинарный файл приложения со всеми встроенными представлениями и рядом с ним файлы, которые он использует. Она запускает spin run gen -- --embed-views, spin build blog и spin run gen (который возвращает закоммиченный пустой gen/views.rb, даже после сбоя или Ctrl-C), а затем собирает:
dist/blogскомпилированные фреймворк, контроллеры, модели и интерпретатор шаблонов со всеми встроенными представлениямиdist/public/статические ресурсы и страницы ошибок; можно отдавать их через обратный проксиdist/storage/создаётся пустым; по умолчанию здесь файл SQLite (пересборка никогда его не очищает)dist/tmp/создаётся пустым (пересборка никогда его не очищает)
Скопируйте dist/ на машину с той же ОС и тем же процессором (бинарный файл компонуется с системной SQLite) и запустите там. Собранный бинарный файл по умолчанию работает в окружении production (CYBERTRAIN_ENV переопределяет это). Задайте CYBERTRAIN_SECRET_KEY_BASE; для примера:
cd dist
export CYBERTRAIN_SECRET_KEY_BASE="$(openssl rand -hex 32)"
./blog migrate && ./blog
./blog migrate применяет миграции, вкомпилированные в бинарный файл, а ./blog db status показывает их состояние; PORT задаёт порт. Не меняйте секрет между перезапусками: его смена завершает все сессии.
Cybertrain::Config сначала читает переменные окружения; config/app.rb их переопределяет:
| Переменная | Атрибут | По умолчанию |
|---|---|---|
CYBERTRAIN_ENV | env | "development"; "production" в бинарном файле из cybertrain build (также "test") |
PORT | port | 3000 |
CYBERTRAIN_DATABASE | database_path | storage/#{env}.sqlite3 |
CYBERTRAIN_SECRET_KEY_BASE | secret_key_base | обязательна в production; в dev/test генерируется автоматически в tmp/secret_key |
SPINEL_WORKERS | workers | 1 |
CYBERTRAIN_HOST | host | "127.0.0.1"; 0.0.0.0 слушает на всех интерфейсах (контейнеры) |
CYBERTRAIN_SESSION_SAME_SITE | session_same_site | "Lax"; также Strict или None (None всегда добавляет Secure); любое другое значение останавливает сервер при запуске |
CYBERTRAIN_SESSION_PARTITIONED | session_partitioned | false; 1 или true добавляет Partitioned (и Secure) |
# config/app.rb
Cybertrain.configure do |c|
c.port = 3000
c.workers = 1
end
- Сервер говорит на обычном HTTP/1.1: для TLS и HTTP/2 поставьте перед ним nginx или Caddy. В production cookie сессии имеет флаг
Secure, поэтому отдавайте приложение по HTTPS (задавайтеc.session_secure = falseвconfig/app.rb, только если production действительно работает по обычному http). - Без
CYBERTRAIN_SECRET_KEY_BASEproduction отказывается запускаться. - В production исключение даёт ответ 500, а голый ответ с ошибкой (текстовый 404 маршрутизатора,
head :not_found,render plain:) заменяется наpublic/<status>.html, если такой файл существует;cybertrain newсоздаёт404.htmlи500.html. SIGTERMпрекращает приём соединений, даёт выполняющимся запросам завершиться и выходит, когда они закончатся, или по истеченииdrain_timeoutсервера (10 с).- Слежение за файлами и цикл пересборки никогда не работают в production. Запущенный
cybertrain serverпропускает пересборки, покаcybertrain buildудерживаетtmp/cybertrain-build.lock.
Что нужно знать
ЗачемОграничения и особенности, о которых лучше знать заранее, чтобы они не застали вас врасплох.
- Сессии хранят только строки (String) в cookie с подписью HMAC.
- Только SQLite, через FFI Spinel.
- Бэктрейсов нет: ошибки показывают класс, сообщение, строку запроса и имя/строку шаблона.
- Атрибут с опечаткой в представлении выводится пустым вместо исключения; это известный пробел.
- Ассоциации выводятся только из внешних ключей схемы; нет
has_many :through,includes,pluck,dependent:, enum, STI и полиморфных ассоциаций. - Нет
namespace,respond_to/форматов,constraintsиmount;render json:минимален. - В модели, созданной скаффолдом, валидацию присутствия по умолчанию получает только первое строковое поле.
- Генератор обрабатывает
app/controllersиapp/modelsлексическим сканированием (регулярными выражениями), а не парсером. - Представления встраиваются при сборке: даже изменение только в представлении требует
cybertrain buildи перезапуска, прежде чем оно попадёт в production. - Собирайте
dist/на той же ОС и том же процессоре, что и у машины, где он будет работать: бинарный файл компонуется с системной SQLite. SPINEL_WORKERSпо умолчанию равен1.- Протестирован только Spinel
2026.09.12.
Отличия от Rails
ЗачемПолная таблица из README в одном месте.
| Rails | CyberTrain |
|---|---|
rails console | Консоли нет — в Spinel нет eval |
| Правите код — запущенное приложение подхватывает изменения | Ruby требует пересборки; в режиме разработки cybertrain server делает это за вас (пересборка, остановка, execv) |
| Правите представление — перезагрузка не нужна | Так же — в режиме разработки представления разбираются с диска при каждом запросе |
def new | def new_action (new затенил бы Klass.new(ctx)) |
before_action { do_thing } (неявный self) | before_action { |c| c.do_thing } — для сохранённого блока нет instance_exec, поэтому колбэки получают контроллер/запись явно |
resources :posts do resources :comments end | resources :posts do |posts| posts.resources :comments end — вложенные блоки получают маппер явно |
| ERB компилируется в метод; любой объект, любой метод | Интерпретируется по фиксированной грамматике/таблице диспетчеризации |
| Сессия хранит любой сериализуемый через Marshal объект | Значения сессии — только строки (String), cookie с подписью HMAC |
| Множество адаптеров баз данных | Только SQLite, через FFI |
has_many :through, includes, pluck, dependent:, enum, STI, полиморфные ассоциации | Не реализовано; ассоциации выводятся только из внешних ключей схемы |
namespace, форматы/respond_to, constraints, mount | Не реализовано — плоские имена, только render json: |
| Полный бэктрейс при исключении | Класс, сообщение, строка запроса, имя/строка шаблона — Spinel не предоставляет бэктрейсов |
Rack, его middleware и любой гем в Gemfile | Нет совместимости с Rack; небольшой фиксированный набор middleware; собственный spin-index Spinel, ограниченный тем, что компилируется в рамках его подмножества Ruby |
| minitest / RSpec | Cybertrain::Test — раннеры на основе рефлексии не могут работать при AOT-компиляции |
| Аутентификация, мейлеры, фоновые задачи, ActionCable, asset pipeline, i18n | Не сделано (за рамками MVP) |
Список не исчерпывающий.