Rails newのテンプレートをちゃんと設定してみた

最近新しくRailsプロジェクトを作って開発を始めることが業務でもプライベートでもあったので、せっかくの機会にRails newするときに使えるテンプレートをちゃんにと設定してみました。

概要

  • 仕事でも個人開発でも久々にRailsで新しいプロジェクト作る機会があった
  • ある程度定番ライブラリとか定石とかあるのでテンプレートカスタマイズしてみよう
    • いままでRailsを使ってきたうまくカスタマイズテンプレートに落としこみつつ知見として残したい
  • とはいえ作るもの次第で変わるものも多いのでどこまでテンプレートにするかは難しかった

What is rails new?

Ruby on Railsでは新しくプロジェクトを作成するとき、

$ rails new project_name

とコマンドを打ちます。これにより、必要なライブラリの設定、必要なファイル郡の生成が行なわれます。

この時に様々なオプションを指定できますが -m template_fileとすることで生成ファイル処理をカスタムした設定を使うことができます。

例えばいつも使うGemを追加したり、ライブラリのインストールや初期設定、コマンドも使えるのでGit commitもできます。

なぜやったか

この8月から仕事のスクラムを異動することになり、新しいプロジェクトに参画することになりました(自分ではないですが)新しいプロジェクトなのでRails newして始まりました。職場では職場の環境も考慮したテンプレートが使われていて、なるほどなーと見てました。

同時に折りしも自分の中でRaisでの個人開発する機運が高まったきて、せっかくなのでテンプレートを作りつつそこに今までの定番とすべき知見を落としこめたら、と思い、ちゃんとRails templateを考えて作ることにしました。

期待したこと

期待したことは、個人で新しく開発するときにrails new -m 自作テンプレートすると、初期設定は完了し、すぐに機能開発からスタートできるようになること、です。

実際やったこと

まず、テンプレートについてあまり理解してなかったのでドキュメントを読みました。

それとこちらもかなり役に立ちました。

そして最終的に出来たものがコレです。

こうしてリモートリポジトリに残しておけばいつでも参照できますし、しっかり形として残るので良いですね。

それではポイントごとに絞って見ていきます。

まずそもそもどうやるか、の想定ですが以下のステップを考えていました。

  1. $ git clone テンプレートリポジトリ
  2. $ bundle install
  3. $ bundle exec rails new ../app_name -m template.rb —skip-gemfile -d database_type -T --skip-webpack-install
  4. $ cd ../app_name

と、今書いて思いましたが、このステップもシェルスクリプトにしちゃっても良さそうですね。

テンプレート自体のファイル構成はこんな感じ。

.
├── .gitignore
├── .rubocop.yml
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── lib
│   └── actions.rb
├── README.md
├── template.rb
└── templates
    ├── .erdconfig
    ├── .gitignore
    ├── .node-version
    ├── .rubocop.yml
    ├── .vscode
    │   └── launch.json
    ├── Gemfile.tt
    ├── lib
    │   └── tasks
    │       └── auto_annotate_models.rake
    └── spec
        └── support
            ├── factory_bot.rb
            └── time_helpers.rb

テンプレートリポジトリでbundle installするのは、Globalにrails gemを入れず、かつnewするrailsのバージョンコントロールをしやすくする目的です。

なので、/Gemfileには

gem rails
gem rubocop

のみです。rubocopはtemplateを書く用に入れていますがなくてもいいかもです。

実際に使われるテンプレートは/template.rbです。基本はこれをゴリゴリ書いていきます。

頭のほうでで

require_relative "lib/actions"

extend Lib::Actions

をやっています。これで、/lib/actionsにカスタムしたメソッドを集約して定義したものを読みこんでいます。そちらでtemplateのsource_pathsが/templatesであることを定義しています。

次に

template "Gemfile", force: true

をやっています。これはsource_pathsに設定されたディレクトリ配下の指定された.ttのつくテンプレートファイルをgenerateします。

.ttのテンプレートファイルでは本来使われているメソッドがそのまま使えるため、オプションに左右されるGemはなるべく渡されたオプションに従ってインストールされるようにしています。

使うであろうものはガンガン入れちゃいます。候補のGemは決まってるけど環境によりけりで使わないこともあるかもしれないと思うものはコメントアウトで入れておきました。開発中に使うときなったらコメントアウト外して入れることを想定しています。選定に時間とられないだけでも十分有用です。

その後にやってるcopy_fileremove_fileはもともとあるメソッドです。対象のファイルをコピーしてきたり、削除したりします。なのでsourch_pathsがrails newしてできるプロジェクトルートに対応するので、/templatesはrailsのデフォルトの構成そのままにするのが使いやすいですね。

その後のポイントは

Bundler.with_unbundled_env do
  run 'bundle config --local build.mysql2 "--with-opt-dir=/usr/local/opt/openssl"' if options[:database] == "mysql"
  run "bundle install --jobs=4"
end

とやっています。これは$ rails newした先で、$ bundle installしています。

僕の環境ではopensslの問題でmysql2 gemのビルド時にオプションが必要なのでこうなっていて、optionsにはrails new時のオプションに応じた値がハッシュで入っているため、MySQLの時だけ設定を使うようにしています。

その後は基本的に必要なファイルを移動、設定に追記、rails generate library:installをどんどんやっています。

特筆すべきは

# devise for authentication
answers[:devise_model] = if yes?("genarate User model with device?(y/n)")
                           "User"
                         else
                           ask("model name?").presence? || "User"
                         end
Bundler.with_unbundled_env do
  generate "devise:install"
  generate "devise #{answers[:devise_model]}"
end
environment "config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }", env: "development"
download "https://raw.githubusercontent.com/tigrish/devise-i18n/master/rails/locales/ja.yml", "config/locales/devise.ja.yml"

でしょうか。

認証機能を提供するgem deviseをインストールしますが、その時にUserという名前でモデル作っちゃっていいか、良くなければモデル名の入力を求めて使います。

yes?no?メソッドは入力なしでenterするとfalse判定になってしまいますのでご注意ください(今思いましたがモンキーパッチ当てちゃったほうが楽かもですね)。

最後のdownloadは前述したLib::Actionsで定義した独自メソッドでcurlで対象のファイルをもってきて指定の場所に配置しています。ここではi18n用の辞書ファイルですね。

そして最後にrubocopで自動修正をかけてGit commitまでやっています。

やってみた結果

テンプレートで指示することのほとんどは必要なGemの導入とその設定がメインですね。今回ちゃんと定めたことで、あらためて必要なGemの整理がつきました。

あまり使う機会はないですが、テンプレートでやってることがわかりましたし、今回の設定では触れてませんが工夫次第では想像以上にいろんなことできそうですね。

逆に、どこまで新環境のセットアップを固定するかは悩みどころですね。作るもの、要件に左右されることがほとんどなので。それとライブラリはメンテされなくなったり、新しくより良いものがでてきたりするなどの要因にも左右されやすいので定期的にテンプレートもメンテが必要そうです。

今回や(れ|ら)なかったこと

もう1つ言及しておくべき良い点として、Webpackerの扱いに悩みました。結果としては使う場合は後入れする対応に寄せました。プロジェクトの途中で方針を変えることはあまりありませんが、このテンプレートで扱うオプション対応やハンドリングに時間がかかりそうだったので今回は見送りました。

基本的には使わないケースでいきたいと思っていますし、インストール時の問題を多く考えたくない、というのもあります。

感想

情報が少ないながらもRailsガイドとRailsの実装、解説あたりを読めば難しいことはないですね。工夫の予知は大きいものの、そこまで労力かけて作るほどのものか、と言われると自己満にしかならなそうです。

結局、開発をスタートするときの手間を減らすものなのでテンプレートを作ったりメンテする時間的コストを考えると愚直にプレーンなnewして自分でつっこんでいくほうが良いのではと思います。

ただし、業務のように使うライブラリが決まってる、固定されている状況など活きてきそうですね。あとは今回の僕のようによく使う関連ライブラリの選定の知見や定番パターンの知見を残しておく1つの方法としてもアリでした。

というわけで今回はRailsテンプレートに焦点を当ててみました。時間かかったけど、満足行くものはできたし、1つの各種設定置き場として機能しそうなので、結果としては良かったと思っています。

僕のReleaseNote[0.38.11] & 僕のRoadMap[0.38.12]マストドンを立ち上げるのに苦戦して知見がついた話