Githubを眺めてたらrails organizationにrails-newというプロジェクトができていたので触ってみました。
想定読者
- Railsアプリ開発の経験がある人
はじめにまとめ
-
rails-new
はシングルバイナリのCLIツール - 事前に
ruby
やrails
のセットアップなしにRailsアプリを作成できる - まだプレリリースなので注意
環境など
- macOS Sequoia
- Apple Silicon M1
- rails-new 0.4.1
- Docker Desktop 4.36.0
- Ruby 3.3.6
rails-new
はDockerを利用するので事前にインストールする必要があります。
インストール
READMEに従ってダウンロードして好きなところに展開します。
今回は適当な作業ディレクトリに置きました。
$ wget https://github.com/rails/rails-new/releases/download/v0.4.1/rails-new-aarch64-apple-darwin.tar.gz
$ tar xvfz rails-new-aarch64-apple-darwin.tar.gz
ヘルプを見てみる
とりあえずヘルプを見てみます。
$ ./rails-new --help
A CLI tool to generate a new Rails project
Usage: rails-new [OPTIONS] <ARGS>...
rails-new [OPTIONS] [ARGS]... <COMMAND>
Commands:
rails-help Prints `rails new --help`
help Print this message or the help of the given subcommand(s)
Arguments:
<ARGS>... arguments passed to `rails new`
Options:
-u, --ruby-version <RUBY_VERSION> [default: 3.3.4]
-r, --rails-version <RAILS_VERSION> [default: 7.2.0]
-h, --help Print help
-V, --version Print version
--ruby-version
や--rails-version
でRubyとRailsのバージョンを指定できるみたいです。
その他コマンドライン引数はrails new
と同じものが指定できそうです。
実行してみる
ひとまずシンプルにrails-new
を実行してみます。
./rails-new rails-app
実行ログ
$ ./rails-new rails-app
[+] Building 62.5s (6/6) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 344B 0.0s
=> [internal] load metadata for docker.io/library/ruby:3.3.4 2.6s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/ruby:3.3.4@sha256:d4233f4242ea25346f157709bb8417c615e7478468e2699c8e86a4e1f0156de8 48.0s
=> => resolve docker.io/library/ruby:3.3.4@sha256:d4233f4242ea25346f157709bb8417c615e7478468e2699c8e86a4e1f0156de8 0.0s
=> => sha256:faa8a0eb6d78b389c99ff48e6ecd13a435be5c7a2df30f220f79fb3ea23fe3ca 143B / 143B 0.2s
=> => sha256:61f10e05a7d7a4b5b48011aa8493e5e68e8240719192e66a9e0bdb17dd4f6b30 38.05MB / 38.05MB 15.0s
=> => sha256:cf06774bbc5695ecd62a3b3c88ac91b5da1014564df7a11c9eaf9e4e37e25154 197B / 197B 0.9s
=> => sha256:b46e144614e1ae9b82b5d89d16a31a506542733eabceebfac041e0192dfafcf4 202.62MB / 202.62MB 45.0s
=> => sha256:275677961327bd0cf394699228e29d7caf27f171c627899a20ebc9eeb550e209 63.99MB / 63.99MB 29.4s
=> => sha256:1593650c75729f64218ae272e8ffff9da7bbba9599bd1815877da99a2651fd9b 23.59MB / 23.59MB 10.8s
=> => sha256:7b24851aa36de07cd94173b8e2052846573dacc3b241620d713254e647352394 49.59MB / 49.59MB 18.0s
=> => extracting sha256:7b24851aa36de07cd94173b8e2052846573dacc3b241620d713254e647352394 0.8s
=> => extracting sha256:1593650c75729f64218ae272e8ffff9da7bbba9599bd1815877da99a2651fd9b 0.3s
=> => extracting sha256:275677961327bd0cf394699228e29d7caf27f171c627899a20ebc9eeb550e209 0.9s
=> => extracting sha256:b46e144614e1ae9b82b5d89d16a31a506542733eabceebfac041e0192dfafcf4 2.5s
=> => extracting sha256:cf06774bbc5695ecd62a3b3c88ac91b5da1014564df7a11c9eaf9e4e37e25154 0.0s
=> => extracting sha256:61f10e05a7d7a4b5b48011aa8493e5e68e8240719192e66a9e0bdb17dd4f6b30 0.3s
=> => extracting sha256:faa8a0eb6d78b389c99ff48e6ecd13a435be5c7a2df30f220f79fb3ea23fe3ca 0.0s
=> [2/2] RUN if [ -z "7.2.0" ] ; then gem install rails ; else gem install rails -v 7.2.0 ; fi 10.7s
=> exporting to image 1.2s
=> => exporting layers 0.9s
=> => exporting manifest sha256:939eb034dc2bc93bd12eafdc8d4356af3b3c8299c9170d3fefad04ba54cc89cf 0.0s
=> => exporting config sha256:7df179bfb130ad543331f1f8575c54ba56807a62257ba090b24c86638c64be33 0.0s
=> => exporting attestation manifest sha256:f7942a5d73f58031f5aadaa7fb933f2a7fcb7c179a440669f36c2c7484f100a0 0.0s
=> => exporting manifest list sha256:d79d8d2851a1df0fb6149b07135e341e43905ca257db62cc0dc82ecdf6b3d4ad 0.0s
=> => naming to docker.io/library/rails-new-3.3.4-7.2.0:latest 0.0s
=> => unpacking to docker.io/library/rails-new-3.3.4-7.2.0:latest 0.2s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/pu8pj522n2f80lc9k12vg6p92
What's next:
View a summary of image vulnerabilities and recommendations → docker scout quickview
create
create README.md
create Rakefile
create .ruby-version
create config.ru
create .gitignore
create .gitattributes
create Gemfile
run git init -b main from "."
Initialized empty Git repository in /private/tmp/rails-new-tap/rails-app/.git/
create app
create app/assets/config/manifest.js
create app/assets/stylesheets/application.css
create app/channels/application_cable/channel.rb
create app/channels/application_cable/connection.rb
create app/controllers/application_controller.rb
create app/helpers/application_helper.rb
create app/jobs/application_job.rb
create app/mailers/application_mailer.rb
create app/models/application_record.rb
create app/views/layouts/application.html.erb
create app/views/layouts/mailer.html.erb
create app/views/layouts/mailer.text.erb
create app/views/pwa/manifest.json.erb
create app/views/pwa/service-worker.js
create app/assets/images
create app/assets/images/.keep
create app/controllers/concerns/.keep
create app/models/concerns/.keep
create bin
create bin/brakeman
create bin/rails
create bin/rake
create bin/rubocop
create bin/setup
create Dockerfile
create .dockerignore
create bin/docker-entrypoint
create .rubocop.yml
create .github/workflows
create .github/workflows/ci.yml
create .github/dependabot.yml
create config
create config/routes.rb
create config/application.rb
create config/environment.rb
create config/cable.yml
create config/puma.rb
create config/storage.yml
create config/environments
create config/environments/development.rb
create config/environments/production.rb
create config/environments/test.rb
create config/initializers
create config/initializers/assets.rb
create config/initializers/content_security_policy.rb
create config/initializers/cors.rb
create config/initializers/filter_parameter_logging.rb
create config/initializers/inflections.rb
create config/initializers/new_framework_defaults_7_2.rb
create config/initializers/permissions_policy.rb
create config/locales
create config/locales/en.yml
create config/master.key
append .gitignore
create config/boot.rb
create config/database.yml
create db
create db/seeds.rb
create lib
create lib/tasks
create lib/tasks/.keep
create lib/assets
create lib/assets/.keep
create log
create log/.keep
create public
create public/404.html
create public/406-unsupported-browser.html
create public/422.html
create public/500.html
create public/icon.png
create public/icon.svg
create public/robots.txt
create tmp
create tmp/.keep
create tmp/pids
create tmp/pids/.keep
create vendor
create vendor/.keep
create test/fixtures/files
create test/fixtures/files/.keep
create test/controllers
create test/controllers/.keep
create test/mailers
create test/mailers/.keep
create test/models
create test/models/.keep
create test/helpers
create test/helpers/.keep
create test/integration
create test/integration/.keep
create test/channels/application_cable/connection_test.rb
create test/test_helper.rb
create test/system
create test/system/.keep
create test/application_system_test_case.rb
create storage
create storage/.keep
create tmp/storage
create tmp/storage/.keep
remove config/initializers/cors.rb
remove config/initializers/new_framework_defaults_7_2.rb
run bundle install --quiet
run bundle lock --add-platform=x86_64-linux
Writing lockfile to /private/tmp/rails-new-tap/rails-app/Gemfile.lock
run bundle binstubs bundler
rails importmap:install
apply /usr/local/bundle/gems/importmap-rails-2.0.3/lib/install/install.rb
Add Importmap include tags in application layout
insert app/views/layouts/application.html.erb
Create application.js module as entrypoint
create app/javascript/application.js
Use vendor/javascript for downloaded pins
create vendor/javascript
create vendor/javascript/.keep
Ensure JavaScript files are in the Sprocket manifest
append app/assets/config/manifest.js
Configure importmap paths in config/importmap.rb
create config/importmap.rb
Copying binstub
create bin/importmap
run bundle install --quiet
rails turbo:install stimulus:install
apply /usr/local/bundle/gems/turbo-rails-2.0.11/lib/install/turbo_with_importmap.rb
Import Turbo
append app/javascript/application.js
Pin Turbo
append config/importmap.rb
run bundle install --quiet
apply /usr/local/bundle/gems/stimulus-rails-1.3.4/lib/install/stimulus_with_importmap.rb
Create controllers directory
create app/javascript/controllers
create app/javascript/controllers/index.js
create app/javascript/controllers/application.js
create app/javascript/controllers/hello_controller.js
Import Stimulus controllers
append app/javascript/application.js
Pin Stimulus
Appending: pin "@hotwired/stimulus", to: "stimulus.min.js"
append config/importmap.rb
Appending: pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
append config/importmap.rb
Pin all controllers
Appending: pin_all_from "app/javascript/controllers", under: "controllers"
append config/importmap.rb
run bundle install --quiet
何やらコンテナイメージがビルドされたあと、見慣れたrails new
のログが流れて完了します。
完了後、Railsアプリのディレクトリが作られているので見てみます。
$ ls -al rails-app
total 96
drwxr-xr-x 26 okonomi wheel 832 Nov 4 16:56 ./
drwxr-xr-x@ 12 okonomi wheel 384 Nov 4 16:55 ../
-rw-r--r-- 1 okonomi wheel 854 Nov 4 16:55 .dockerignore
drwxr-xr-x 10 okonomi wheel 320 Nov 4 16:55 .git/
-rw-r--r-- 1 okonomi wheel 348 Nov 4 16:55 .gitattributes
drwxr-xr-x 4 okonomi wheel 128 Nov 4 16:55 .github/
-rw-r--r-- 1 okonomi wheel 767 Nov 4 16:55 .gitignore
-rw-r--r-- 1 okonomi wheel 249 Nov 4 16:55 .rubocop.yml
-rw-r--r-- 1 okonomi wheel 11 Nov 4 16:55 .ruby-version
-rw-r--r-- 1 okonomi wheel 2196 Nov 4 16:55 Dockerfile
-rw-r--r-- 1 okonomi wheel 2154 Nov 4 16:55 Gemfile
-rw-r--r-- 1 okonomi wheel 7829 Nov 4 16:56 Gemfile.lock
-rw-r--r-- 1 okonomi wheel 374 Nov 4 16:55 README.md
-rw-r--r-- 1 okonomi wheel 227 Nov 4 16:55 Rakefile
drwxr-xr-x 11 okonomi wheel 352 Nov 4 16:56 app/
drwxr-xr-x@ 10 okonomi wheel 320 Nov 4 16:56 bin/
drwxr-xr-x 16 okonomi wheel 512 Nov 4 16:56 config/
-rw-r--r-- 1 okonomi wheel 160 Nov 4 16:55 config.ru
drwxr-xr-x 3 okonomi wheel 96 Nov 4 16:55 db/
drwxr-xr-x 4 okonomi wheel 128 Nov 4 16:55 lib/
drwxr-xr-x 4 okonomi wheel 128 Nov 4 16:56 log/
drwxr-xr-x 9 okonomi wheel 288 Nov 4 16:55 public/
drwxr-xr-x 3 okonomi wheel 96 Nov 4 16:55 storage/
drwxr-xr-x 12 okonomi wheel 384 Nov 4 16:55 test/
drwxr-xr-x 7 okonomi wheel 224 Nov 4 16:56 tmp/
drwxr-xr-x 4 okonomi wheel 128 Nov 4 16:56 vendor/
見慣れたファイルが並んでいて、 Railsアプリが作成されているのがわかります。
依存gemがインストールされてないので、インストールしてから、
bundle config set path 'vendor/bundle' && bundle install
Railsアプリを起動します。
$ bin/rails s
=> Booting Puma
=> Rails 7.2.2 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.4.3 (ruby 3.3.4-p94) ("The Eagle of Durango")
* Min threads: 3
* Max threads: 3
* Environment: development
* PID: 54632
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
http://127.0.0.1:3000/
をブラウザで開くと、いつものRailsの画面が表示されます。
rails-new
でRailsアプリを作成できることが確認できました。
RubyやRailsのバージョンを確認しておきます。
$ ruby -v
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24]
$ bin/rails version
Rails 7.2.2
どちらもrails-new
がデフォルトで設定するバージョンになっています。
先ほどヘルプで見た--ruby-version
や--rails-version
で指定してみると、
./rails-new --ruby-version 3.3.6 --rails-version 8.0.0 rails-app-80
無事バージョンが指定のものに変わりました。
$ ruby
ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin24]
$ bin/rails version
Rails 8.0.0
ひとつ注意点で、rails-new
のオプションはアプリ名の前に書きます。アプリ名以降はrails new
のオプションとして認識されます。
無効なオプションは無視されてそのままrails new
が進んでしまうので気付きづらい…。
./rails-new rails-app-mistake --ruby-version 3.3.6 --rails-version 8.0.0
$ ruby -v
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin24]
$ bin/rails version
Rails 7.2.2
仕組み
rails-new
の使い方が確認できたところで、次はコードを読みながら仕組みを見てみます。
rails-new
は次の流れで処理を行います。
- オフィシャルRubyイメージをベースイメージに、
gem install rails
したコンテナイメージをビルド - ビルドしたコンテナイメージを使って
rails new
を実行
いずれもrails-new
からdocker
コマンドを実行する形になっています。
コンテナイメージのビルドはこの箇所:
// Run docker build --build-arg RUBY_VERSION=$RUBY_VERSION --build-arg RAILS_VERSION=$RAILS_VERSION -t rails-new-$RUBY_VERSION-$RAILS_VERSION
// passing the content of DOCKERFILE to the command stdin
let mut child = DockerClient::build_image(
&ruby_version,
&rails_version,
os_specific::get_user_id(),
os_specific::get_group_id(),
)
.spawn()
.expect("Failed to execute process");
Dockerfile
は標準入力から渡される。
let mut stdin = child.stdin.take().expect("Failed to open stdin");
std::thread::spawn(move || {
stdin.write_all(os_specific::dockerfile_content()).unwrap();
});
Unix系とそれ以外でDockerfile
が分けられていて、Unix系はこれ:
macOSとWindowsはこちら:
ふたつのDockerfile
の違いはユーザーが作成されるかどうかで、Rootlessモードが考慮されてるのかなと思います。
この切り替えはRustの条件付きコンパイルで行われるようです。
#[cfg_attr(all(unix, not(target_os = "macos")), path = "unix.rs")]
#[cfg_attr(any(windows, target_os = "macos"), path = "windows.rs")]
コンテナビルドの次、rails new
を実行するのはこの箇所:
// Run the image with docker run -v $(pwd):/$(pwd) -w $(pwd) rails-new-$RUBY_VERSION-$RAILS_VERSION rails new $@
command = DockerClient::run_image(&ruby_version, &rails_version, cli.args)
-v $(pwd):/$(pwd)
によってホスト側のカレントディレクトリがマウントされ、そこにrails new
の生成ファイルが書き込まれるので、結果としてrails-new
を実行したディレクトリにrails new
したRailsアプリが出来上がります。
以上で、コンテナイメージをビルドして、そのコンテナイメージでコンテナを起動してrails new
を実行する、という流れが確認できました。
Dockerfile
もほとんどgem install rails
するだけのシンプルなものでした。
rails new
に使うrails
コマンドをどうやってインストールするか問題
個人的に、Railsアプリを開発するときrails
コマンドをどこにインストールするかという問題があると思っています。
Railsアプリがすでに作成されていればbin/rails
を使えば問題ないです。
問題はrails new
で、このときのrails
コマンドをどうするか。
gem install rails
するのがまず考えられますが、Railsの依存でいろんなgemがグローバル環境に入るのであんまりやりたくありません。好みの問題かもですが…。
なので、まずプロジェクトローカルにrails
をインストールしてからbundle exec rails new . -f
する、みたいなことをやってました。
rails-new
を使えばグローバル環境を汚染することなくrails new
を実行できます。
Railsのバージョン指定もオプションできて手軽です。
現時点の動作上の問題
rails new
の--css
や--js
オプションを指定するとエラーが発生するようです。
- Rails new --css boostrap failed · Issue #31 · rails/rails-new
- npx missing when using
--js esbuild
· Issue #22 · rails/rails-new
Dockerfile
にNodeの実行環境のセットアップがないので、それはたしかにそうなるな、という内容です。
また、Windows環境でも動作に問題があるようです。
パスの取り扱いに問題があるらしいです。
いずれの問題もそのうち修正されそうなものではあります。
まとめ
rails-new
を試してみました。
プレリリースなためか、まだ一部動作に問題があるものの、それを除けば手軽にRailsアプリを作れる便利なツールでした。
このプロジェクト自体今後どう発展するのかわかりませんが(Railsの標準セットアップ手順になるのか、とか)、引き続き見守っていきたいと思います。