2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ateam FinergyAdvent Calendar 2024

Day 1

rails-newというCLIツールを触ってみた

Last updated at Posted at 2024-12-01

Githubを眺めてたらrails organizationにrails-newというプロジェクトができていたので触ってみました。

想定読者

  • Railsアプリ開発の経験がある人

はじめにまとめ

  • rails-newはシングルバイナリのCLIツール
  • 事前にrubyrailsのセットアップなしに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は次の流れで処理を行います。

  1. オフィシャルRubyイメージをベースイメージに、gem install railsしたコンテナイメージをビルド
  2. ビルドしたコンテナイメージを使って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オプションを指定するとエラーが発生するようです。

DockerfileにNodeの実行環境のセットアップがないので、それはたしかにそうなるな、という内容です。

また、Windows環境でも動作に問題があるようです。

パスの取り扱いに問題があるらしいです。

いずれの問題もそのうち修正されそうなものではあります。

まとめ

rails-newを試してみました。
プレリリースなためか、まだ一部動作に問題があるものの、それを除けば手軽にRailsアプリを作れる便利なツールでした。

このプロジェクト自体今後どう発展するのかわかりませんが(Railsの標準セットアップ手順になるのか、とか)、引き続き見守っていきたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?