Rails ジェネレータを使って初期設定を自動化しよう！

Rails は`rails new`コマンドにより、必要なファイルを即座に用意してくれます。
しかし、好みの Gem を追加したりテスト設定したり、初期設定はとても面倒な工程です...

対策としては、GitHub に生成後のテンプレートをリポジトリに残すことも一つの手段でしょう。
ただ、生成内容を調整したい時やバージョンが変わる時は、リポジトリも作り直さなければなりません。それもまた面倒ですね...

Rails は、そんな面倒なことまでも吸収してくれます。
それが**Railsジェネレータ( Rails::Generators::Base )**と呼ばれる機能です。

本記事では、例として[\[Rails\+Vue\.js\]に係るCRUD入門〜Part１：環境構築編〜 \- Qiita](https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253)を利用して、初期設定をコードに落とし込みます。

# 【目的】

`rails new`によるプロジェクト生成時において、初期設定を自動化することです。
対応順は、以下の通りとなります。

なお、変更点として、初期設定の自動化は`rails new`と同時に実行されるため、「プロジェクトを生成」項目が最後になります。
また、元記事から外れた内容ですが Rubocop、RSpec、Bullet も設定します。

もし、各項目の説明が不要な場合は、`[1, 9, 10, 11]`の順番で読み進めてください。

- 対応順
  1. 事前準備
  2. バージョン管理の設定
  3. `rails generate`時の生成ファイルの設定
  4. foreman によるサーバの設定
  5. CSS フレームワークを導入
  6. Rubocop( 追加設定 )
  7. RSpec( 追加設定 )
  8. Bullet( 追加設定 )
  9. rails_template.rbの整理
  10. プロジェクトを生成
  11. 動作確認
  12. 最後に

---
- 開発環境（元記事に準じる）
  - Ruby: 2.5.1
  - Rails: 5.1.2
- 参考 URL
  - [Rails ジェネレータとテンプレート入門 \- Railsガイド](https://railsguides.jp/generators.html)
  - [Class: Thor — Documentation for wycats/thor \(master\)](https://rubydoc.info/github/wycats/thor/master/Thor/Actions)
  - [hilotter/rails\-application\-template: rails\-application\-template](https://gitHub.com/hilotter/rails-application-template)
  - [dennybritz/rails\_startup\_template: A startup template for Ruby on Rails 4 applications](https://gitHub.com/dennybritz/rails_startup_template)
  - [mattbrictson/rails\-template: Application template for Rails 6 projects; preloaded with best practices for TDD, security, deployment, and developer productivity\.](https://gitHub.com/mattbrictson/rails-template)
  - [dao42/rails\-template: A best & newest & fastest rails 6\.x template for senior rails developer\.](https://gitHub.com/dao42/rails-template)

---

# 【本文】

## 1. 事前準備

### ○1： 自動化用ファイルを準備

ディレクトリ名が、プロジェクト名になるため、ご自身のプロジェクトを作成する際は、`rails_vue_bookshelf`の文言を変更願います。

```bash:bash
# プロジェクトディレクトリを生成し移動
$ mkdir rails_vue_bookshelf
$ cd rails_vue_bookshelf

# 自動化用ファイルを生成
# 今後、このファイルに自動化用コードを書き込む
$ touch rails_template.rb
```

### ○2： Node.js の準備

もし、Node.js をインストールしていない場合は、以下の記事を参考に設定願います。

- [MacにNode\.jsをインストール \- Qiita](https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09)
- [nodebrewでnode\.jsのバージョン切り替え \- Qiita](https://qiita.com/teratsyk/items/51d64010cb341b54491b)

ちなみに私の場合、久々に素の開発環境を使用したことから、Node.js のバージョンが古かったため、以下のエラーが発生しました。

```bash:bash
...
       rails  webpacker:install
Webpacker requires Node.js ">=10.13.0" and you are using v10.4.0
Please upgrade Node.js https://nodejs.org/en/download/
Exiting!
```

## 2. バージョン管理の設定

初めに　Git の設定です。
この項目は、`rails new`の後に実行するコマンドでしたね。

その場合、**after_bundle(&block)**メソッドを使用します。
その名の通り、block の中身を`bundle install`の後に実行します。

なお、Rails の**git(commands = {})**メソッドを使用しています。
後述の**run**メソッドで直接コマンドを叩く方法でも可能です。

```ruby:rails_template.rb
after_bundle do
  git :init
  git add: "."
  git commit: "-m 'First Commit'"
end
```

## 3. `rails generate`時の生成ファイルの設定

次は、config/application.rb に `rails generate`で生成しないファイルを設定します。

**environment(data = nil, options = {})**メソッドを使用します。
data に設定したいコードを設定すると、自動的に application.rb に挿入してくれます。

余談ですが、data は block で渡すことも可能ですし、また、options は`env: development`のように引数を指定すると、環境毎の設定も可能になります（ 後述の Bullet 設定で使用）。
他にも`<<-TEXT .. TEXT`は、ヒアドキュメントと呼ばれます。本記事で度々使用されるため、詳細は[公式ドキュメント](https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#here)をご確認ください。

```ruby:rails_template.rb
base_setting = <<-TEXT
config.generators do |g|
  g.assets false
  g.helper false
  g.test_framework :rspec,
                    fixtures: true,
                    routing_specs: false,
                    view_specs: false,
                    helper_specs: false,
                    controller_specs: false,
                    request_specs: true
  g.fixture_replacement :factory_bot, dir: "spec/factories" # 後述の RSpec 設定で必要となるため先に設定
end
TEXT

environment(base_setting)
```

## 4. foreman によるサーバの設定

各段階で分ける必要があるため、手順を分割します。

### ○1： Gemfileに追加

初めに foreman を Gemfile に追加する必要があります。

これは、通常の Gemfile と同じように記述するだけです。

```ruby:rails_template.rb
gem_group :development do
  gem 'foreman'
end
```

### ○2： 次のファイルを作成

**run**メソッドは、引数をシェルコマンドとして実行してくれます。

```ruby:rails_template.rb
# Procfile.dev の設定
run "cat << TEXT > ./Procfile.dev
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
TEXT"
```

```ruby:rails_template.rb
# bin/server の設定
run "cat << TEXT > ./bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev
TEXT"
```

### ○3：  パーミッションを変更

```ruby:rails_template.rb
# デフォルトでは使用不可のため、実行権限を付与
run "chmod 777 bin/server"
```


## 5. CSS フレームワークを導入
各段階で分ける必要があるため、手順を分割します。

### ○1： Gemfile に追加

materialize をプロジェクトに導入します。
`foreman`の時と同じです。

```ruby:rails_template.rb
gem 'materialize-sass'
gem 'material_icons'
```

### ○2： マニフェストファイルに設定追加

```ruby:rails_template.rb
# 拡張子を css -> scss へ変更
run "mv ./app/assets/stylesheets/application.css ./app/assets/stylesheets/application.scss"

# 設定を挿入
run "cat << TEXT > ./app/assets/stylesheets/application.scss
/*
 *= require_tree .
 *= require_self
 *= require material_icons
 */
@import 'materialize';
TEXT"

run "cat << TEXT >> ./app/assets/javascripts/application.js
//= require materialize
TEXT"
```

## 6. Rubocop( 追加設定 )

ここからは前項までの知識を使用し、初期設定を追加していきます。

本項では Rubocop を以下 URL の README を参考に設定します。

- 参考 URL
  - [ruby/rubocop\-airbnb at master · airbnb/ruby](https://gitHub.com/airbnb/ruby/tree/master/rubocop-airbnb)

```ruby:rails_template.rb
gem_group :test, :development do
  gem "rubocop-airbnb", require: false
end

run "cat << TEXT > ./.rubocop_airbnb.yml
require:
  - rubocop-airbnb
TEXT"

run "cat << TEXT > ./.rubocop.yml
inherit_from:
  - .rubocop_airbnb.yml
AllCops:
#  TargetRubyVersion:
#  TargetRailsVersion:

  Exclude:
    - 'bin/**/*'
    - 'config/**/*'
    - 'db/**/*'
    - 'lib/**/*'
    - 'node_modules/**/*'
    - 'spec/**/*'
    - 'vendor/**/*'
    - 'rails_template.rb'
TEXT"
```


## 7. RSpec( 追加設定 )

この項目では、以下 URL の サンプルコードを参考に RSpec を設定します。

なお、System(Feature) Spec の設定については、本記事に記載していないため、各々設定願います。
Request Spec までで十分である場合は、追加設定は必要ないかと思います。

- 参考 URL
  - [Everyday Rails… Aaron Sumner 著 et al\. \[Leanpub PDF/iPad/Kindle\]](https://leanpub.com/everydayrailsrspec-jp)
  - [rspec/rspec\-rails: RSpec for Rails 5\+](https://github.com/rspec/rspec-rails)

### ○1： Gemfile に追加

```ruby:rails_template.rb
gem_group :test, :development do
  gem "rspec-rails"
  gem "spring-commands-rspec"
  gem "factory_bot_rails"
  gem "database_rewinder"
end
```

### ○2： RSpec に必要なファイルを準備

RSpec を設定する`rails generate rspec:install`コマンドは、Gem のインストール後でないと出来ません。
Git の時は、**after_bundle**を使用しましたが、実のところ次のように`bundle install`を先に実行できます。

```ruby:rails_template.rb
run "bundle install -j4"

# Setting rspec-rails
generate "rspec:install"

run "cat << TEXT > ./.rspec
--require spec_helper
--format documentation
TEXT"

# Setting spring-commands-rspec
run "bundle exec spring binstub rspec"

# Enable ./spec/support directory
run "mkdir ./spec/support"
uncomment_lines "spec/rails_helper.rb", /Dir\[Rails\.root\.join/

# Setting factory_bot_rails
run "cat << TEXT > ./spec/support/factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
TEXT"

# Setting database_rewinder
run "cat << TEXT > ./spec/support/database_rewinder.rb
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseRewinder.clean_all
    # or
    # DatabaseRewinder.clean_with :any_arg_that_would_be_actually_ignored_anyway
  end

  config.after(:each) do
    DatabaseRewinder.clean
  end
end
TEXT"
```


## 8. Bullet( 追加設定 )

### ○1： Gemfile に追加

```ruby:rails_template.rb
gem_group :test, :development do
  gem "bullet"
end
```

### ○2： Bullet に必要なファイルを準備

Bullet は、開発環境のみで必要になるライブラリです。
そのため、初期設定の情報は、config/environment/development.rb に記載します。

- 参考 URL
  - [flyerhzm/bullet: help to kill N\+1 queries and unused eager loading](https://github.com/flyerhzm/bullet)

```ruby:rails_template.rb
bullet_application_setting = <<-TEXT
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end
TEXT

environment(bullet_application_setting, env: "development")
```


## 9. rails_template.rbを整理

さて、初期設定の自動化に必要なコードが全て揃いました。
設定情報を整理しましょう。

### ○1： 完成した rails_template.rb

```ruby:rails_template.rb
# ==========================
# Gem settings
gem 'materialize-sass'
gem 'material_icons'

gem_group :development do
  gem 'foreman'
end

gem_group :test, :development do
  gem "rubocop-airbnb", require: false
  gem "rspec-rails"
  gem "spring-commands-rspec"
  gem "factory_bot_rails"
  gem "database_rewinder"
  gem "bullet"
  gem "pry-rails" # 追加箇所1
  gem "pry-byebug" # 追加箇所2
end


# ==========================
# Application settings

# --------------------------
# Base setting
base_setting = <<-TEXT
config.generators do |g|
  g.assets false
  g.helper false
  g.test_framework :rspec,
                    fixtures: true,
                    routing_specs: false,
                    view_specs: false,
                    helper_specs: false,
                    controller_specs: false,
                    request_specs: true
  g.fixture_replacement :factory_bot, dir: "spec/factories" # 後述の RSpec 設定で必要となるため先に設定
end
TEXT
environment base_setting

# --------------------------
# Bullet setting
bullet_application_setting = <<-TEXT
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end
TEXT
environment bullet_application_setting, env: "development"

# --------------------------
# foreman setting
run "cat << TEXT > ./Procfile.dev
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
TEXT"

run "cat << TEXT > ./bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev
TEXT"

run "chmod 777 bin/server"


# ==========================
# CSS framework settings
run "mv ./app/assets/stylesheets/application.css ./app/assets/stylesheets/application.scss"

run "cat << TEXT > ./app/assets/stylesheets/application.scss
/*
 *= require_tree .
 *= require_self
 *= require material_icons
 */
@import 'materialize';
TEXT"

run "cat << TEXT >> ./app/assets/javascripts/application.js
//= require materialize
TEXT"


# ==========================
# Test settings
# --------------------------
# Rubocop
run "cat << TEXT > ./.rubocop_airbnb.yml
require:
  - rubocop-airbnb
TEXT"

run "cat << TEXT > ./.rubocop.yml
inherit_from:
  - .rubocop_airbnb.yml
AllCops:
#  TargetRubyVersion:
#  TargetRailsVersion:

  Exclude:
    - 'bin/**/*'
    - 'config/**/*'
    - 'db/**/*'
    - 'lib/**/*'
    - 'node_modules/**/*'
    - 'spec/**/*'
    - 'vendor/**/*'
    - 'rails_template.rb'
TEXT"


# --------------------------
# RSpec
run "bundle install -j4"

# Setting rspec-rails
generate "rspec:install"

run "cat << TEXT > ./.rspec
--require spec_helper
--format documentation
TEXT"

# Setting spring-commands-rspec
run "bundle exec spring binstub rspec"

# Enable ./spec/support directory
run "mkdir ./spec/support"
uncomment_lines "spec/rails_helper.rb", /Dir\[Rails\.root\.join/

# Setting factory_bot_rails
run "cat << TEXT > ./spec/support/factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
TEXT"

# Setting database_rewinder
run "cat << TEXT > ./spec/support/database_rewinder.rb
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseRewinder.clean_all
    # or
    # DatabaseRewinder.clean_with :any_arg_that_would_be_actually_ignored_anyway
  end

  config.after(:each) do
    DatabaseRewinder.clean
  end
end
TEXT"

after_bundle do
  git :init
  git add: "."
  git commit: "-m 'First Commit'"
end

# 追加箇所3
if yes? "run migrate? yes/no"
  rails_command "db:create"
  rails_command "db:migrate"
end
```

### ○2： 追加箇所の補足

- 追加箇所1、２
  - デバッグをする際に有用な Gem ですので、詳細は割愛します。
  - [参考 URL: pry\-byebug を使ってRailsアプリをデバックする方法 \- Qiita](https://qiita.com/ryosuketter/items/da3a38d0d41c7e20a2d6)
- 追加箇所3
  - **if yes?**は`rails new`の生成途中において、Yes or No で block 内の実行可否を選択できるようになります。
  - **rails_command**は、コマンドラインにおける`rails`に該当します。
  - つまり、プロジェクト生成途中に`run migrate? yes/no`と選択を迫られ`yes`を入力すると、`rails db:create`と`rails db:migrate`が実行されるわけです。


## 10. プロジェクトを生成

仕上げに元記事のコマンドの末尾に`-m ./rails_template.rb`を渡すことで、初期設定の自動化は完了し、初期設定完了済みのプロジェクトが生成されます。

```bash:bash
$ rails new . --webpack=vue  --skip-test -m ./rails_template.rb
```


## 11. 動作確認

以下のコマンドを実行して、いつもの画面が表示されたら初期設定の自動化は成功です。

```bash:bash
$ bin/server
$ open http://localhost:5000

# 以下のコマンドも動作確認
$ rubocop
$ rspec
```

## 12. 最後に
実は、元記事の[動作確認](https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253#-%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D)以降の項目も自動化が可能です。
しかし、本記事は、様々な用途に使えるテンプレートを作成することが目的であったため、そこまでは追求しませんでした。

そのため、もし余力がありましたら、[Rails ジェネレータとテンプレート入門 \- Railsガイド](https://railsguides.jp/generators.html)を読み解きつつ、ぜひ取り組んでみてください。

あと、Gemfile も散らかってしまいますので、整理願います :pray: 

# 【あとがき】
全くの余談ですが、Alchemy、Refinery、Spina などの OSS の初期設定時にもジェネレータが使用されています。また、記事内の `rspec::install`もジェネレータです。

Rails を使用してテンプレートを生成する際には、よく使用される機能なのかもしれませんね。