現在地:準備 Ruby、cc、SQLite
必要なもの
なぜCyberTrain は Spinel を通じて C にコンパイルされるため、C ツールチェーンと SQLite のヘッダーが必要です。cybertrain コマンドは gem なので、Ruby が必要です。
- Ruby 3.2+ と RubyGems。
cybertrainコマンドのためだけに必要で、アプリケーションが CRuby 上で動くことはありません。 - Spinel
2026.09.12。他のバージョンでは未テストです。手動でインストールする必要はありません:Spinel を必要とする最初のコマンドが、そのリリースを~/.cybertrainにビルドします(数分、初回のみ)。ただし、そのリリースのspinelがすでにPATH上にある場合は除きます。 - C ツールチェーン(
cc、またはCCで指定したコンパイラ)とmake、さらにgitとcurl。すべてのプログラムを C にコンパイルする Spinel をビルドするために使います。 - SQLite 3 のヘッダーとライブラリ:Debian/Ubuntu では
libsqlite3-dev、macOS では Xcode のコマンドラインツールに含まれています。 - 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 を指定すると、代わりに PREFIX にインストールされた Spinel を使います(そのリリースである必要があります)。PATH 上にある別リリースの spinel が使われたり変更されたりすることはありません。
インストールできるマシンがない場合は、プレイグラウンドを使えば、ステップ 02、03、04、07 を済ませたこのブログを GitHub Codespace で開けます。
現在地:準備 cybertrain CLI
CLI をインストールする
なぜcybertrain は、アプリの作成、リソースのスキャフォールド、マイグレーション、サーバーの起動、ビルドを行うコマンドです。
1 行目で RubyGems からインストールします。残りはチュートリアル全体を一望したもので、以降のステップでは 1 コマンドずつ進めます。
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 で書かれており、gem はこれを CRuby(3.2 以降)で実行します。フレームワークも Spinel も gem には含まれません。コマンドは次のとおりです(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はapp/views/を埋め込んで NAME をビルドし、dist/(バイナリ、public/、storage/、tmp/)を組み立てます。cybertrain spin ARGS...は、固定された Spinel でspinを任意のディレクトリから実行します: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 はそのアプリケーションの spin.toml にある [package] name です。
現在地:自分で書く blog/
アプリケーションを作成する
なぜRails のディレクトリ規約に加えて、3 つの 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 のインストールか、2 つの 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 がこれを使います。最初のマイグレーションで gen/models/ が生成されるまで、bin/blog.rb はコンパイルできないためです)。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
マイグレーション、モデル、コントローラー、5 つのビュー(index、show、new、edit、_form)を書き出し、config/routes.rb に resources :articles を挿入します。フィールドの型は string、text、integer、float、boolean、date、datetime、references で、デフォルトは string です。クイックスタートの cybertrain g scaffold も同じコマンドです。
既存のファイルが上書きされることはありません:各ファイルは create、identical、exist のいずれかとして報告されます。
現在地:自分で書く config/routes.rb
root ルートを設定する
なぜ/ で記事一覧を表示するためです。
# 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、カラム、ファインダーは gen/models/article.rb から来ます。保存に失敗すると、「Title can't be blank」(タイトルは空にできません)や「Body is too short (minimum is 10 characters)」(本文が短すぎます。最小 10 文字)のようなメッセージが報告されます。
現在地:自分で書く app/controllers/
ArticlesController
なぜ完全な CRUD で、Strong Parameters やフラッシュを設定してからのリダイレクトに至るまで Rails 流です。ただし、Spinel がどこでも強制する 2 つの点だけは異なります。
# 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 を書き換えます。順序が重要です:1 回目の spin run gen は新しいマイグレーションを gen/migrations.rb に入れ(これを bin/db.rb が require します)、2 回目は書き換えられた 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
なぜ到達できるアクションは 2 つだけです。親の記事は 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 なので、Rails の @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)にルーティングされます — これらは上のネストした resources ブロックから gen/routes.rb に生成されます。
現在地:ビルド時 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 jar を持ち、アプリをプロセス内で(ソケットなしで)動かすので、セッションと CSRF はブラウザ越しと同じように振る舞います。トークンのないリクエストには 403 が返ります。
テストを実行する
cybertrain spin test
cybertrain spin test は固定された Spinel で spin test を実行します。ツールチェーンが PATH 上にあれば(ステップ 00)、spin test でも同じです。各 test/*.rb をそれぞれ独立したプログラムにコンパイルし、その出力(stdout と stderr をまとめたもの)を、コミット済みのスナップショット test/<name>.rb.expected と比較します。test/support/ 内のファイルは実行されず、require されるだけです。スナップショットを書き出すには:
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 での出力からスナップショットを書き出します。FFI 経由で SQLite に触れるテストは CRuby では実行できないため、そのスナップショットはコンパイル済みバイナリから作ります(上の 2 行目)。スナップショットが古い間は cybertrain spin test が 0 以外で終了するため、後続は ; でつなぎ、&& は使いません。[ -x ] のチェックにより、コンパイルに失敗したときは出力の取り込みをスキップするので、古いスナップショットが残ります。CRuby で実行できないテストは、このスナップショットがないと cybertrain spin test が通りません。スナップショットのないその他のテストは、CRuby での出力と比較されます。
現在地:実行時 dist/blog
デプロイ用にビルドする
なぜひとつのコマンドで、すべてのビューを埋め込んだ、デプロイ可能な dist/ フォルダーにアプリをビルドします。
cybertrain build
これは dist/ を書き出します:すべてのビューを埋め込んだアプリのバイナリと、その横に置かれる、バイナリが使うファイルです。spin run gen -- --embed-views、spin build blog、spin run gen(失敗や Ctrl-C の後でも、コミット済みの空の gen/views.rb を元に戻します)を実行してから、次のものを組み立てます:
dist/blogコンパイルされたフレームワーク、コントローラー、モデル、テンプレートインタプリタ。すべてのビューを埋め込み済みdist/public/静的アセットとエラーページ。リバースプロキシに配信させてもよいdist/storage/空の状態で作成。デフォルトでは SQLite のファイルを置く(再ビルドで空にされることはない)dist/tmp/空の状態で作成(再ビルドで空にされることはない)
dist/ を同じ OS と CPU のマシンにコピーし(バイナリはシステムの SQLite にリンクします)、そこで実行します。ビルドしたバイナリはデフォルトで本番環境として動きます(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"。cybertrain build で作ったバイナリでは "production"(ほかに "test") |
PORT | port | 3000 |
CYBERTRAIN_DATABASE | database_path | storage/#{env}.sqlite3 |
CYBERTRAIN_SECRET_KEY_BASE | secret_key_base | 本番環境では必須。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 を置いてください。本番環境ではセッション cookie が
Secureになるので、HTTPS で配信してください(本番環境が本当に素の http である場合に限り、c.session_secure = falseをconfig/app.rbで設定します)。 - 本番環境では、
CYBERTRAIN_SECRET_KEY_BASEがないと起動を拒否します。 - 本番環境では例外に対して 500 を返し、素のエラーレスポンス(ルーターのプレーンテキストの 404、
head :not_found、render plain:)は、public/<status>.htmlが存在すればそれに置き換えられます。cybertrain newは404.htmlと500.htmlを書き出します。 SIGTERMを受けると新たな受け付けを止め、処理中のリクエストを完了させ、それらが終わった時点か、サーバーのdrain_timeout(10 秒)が過ぎた時点で終了します。- ファイル監視と再ビルドのループが本番環境で動くことはありません。実行中の
cybertrain serverは、cybertrain buildがtmp/cybertrain-build.lockを保持している間、再ビルドをスキップします。
知っておくこと
なぜ思わぬところでつまずく前に知っておきたい、制限とクセ。
- セッションに保持できるのは String のみで、HMAC 署名付きの cookie に入ります。
- データベースは SQLite のみで、Spinel の FFI を経由します。
- バックトレースはありません:エラーにはクラス、メッセージ、リクエスト行、テンプレート名と行番号が表示されます。
- ビュー内で属性名を書き間違えると、例外にならず空としてレンダリングされます。これは既知の課題です。
- 関連付けはスキーマの外部キーからのみ作られます。
has_many :through、includes、pluck、dependent:、enum、STI、ポリモーフィック関連付けはありません。 namespace、respond_to/フォーマット、constraints、mountはありません。render json:は最小限です。- スキャフォールドで生成したモデルでは、最初の string フィールドにだけデフォルトで presence バリデーションが付きます。
- ジェネレーターは
app/controllersとapp/modelsに対して、パーサーではなく字句スキャン(正規表現)を使います。 - ビューはビルド時に埋め込まれます:ビューだけの変更でも、本番環境に反映するには
cybertrain buildと再起動が必要です。 dist/は、実行するマシンと同じ OS と CPU でビルドしてください:バイナリはシステムの 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 はメソッドにコンパイルされる。任意のオブジェクト、任意のメソッドを使える | 固定の文法/ディスパッチテーブルに従って解釈される |
| セッションにはマーシャリングした任意のオブジェクトを保持できる | セッションの値は String のみ、HMAC 署名付き cookie |
| 多数のデータベースアダプター | SQLite のみ、FFI 経由 |
has_many :through、includes、pluck、dependent:、enum、STI、ポリモーフィック関連付け | 未実装。関連付けはスキーマの外部キーからのみ作られる |
namespace、フォーマット/respond_to、constraints、mount | 未実装 — フラットな名前、render json: のみ |
| 例外時には完全なバックトレース | クラス、メッセージ、リクエスト行、テンプレート名と行番号 — Spinel はバックトレースを公開しない |
Rack とそのミドルウェア、Gemfile に書いた任意の gem | Rack 互換性なし。小さな固定のミドルウェアセット。Spinel 独自の spin-index(その Ruby サブセットでコンパイルできるものに限る) |
| minitest / RSpec | Cybertrain::Test — リフレクションに基づくテストランナーは AOT では動かない |
| 認証、メーラー、ジョブ、ActionCable、アセットパイプライン、i18n | 未構築(MVP の範囲外) |
すべてを網羅してはいません。