【Rails】俺のための初期設定 + Gem導入

rails newをするときに、随時設定をしたりGemを入れるのが面倒だったので、自分用にcloneするだけでいいリポジトリを作りました。
https://github.com/aiandrox/rails_app

ついでに作業をざっくりまとめました。

DockerやCircleCI用の設定はしていません。
実際に導入する際にはGitHubでバージョン指定や手順などを確認してください。

## バージョン

- Ruby 2.7.1
- Rails 6.0.3.3
- yarn 1.22.5

初期コマンドは`rails new rails_app --skip-test-unit --database=mysql`です。

## `.gitignore`に`/vendor/bundle`を追加

```Gemfile
/vendor/bundle
```

を追加する。


## 日本語化・時刻の設定

```rb:config/application.rb
...
module RailsApp
  class Application < Rails::Application
    ...
    # 言語・タイムゾーンを日本に設定
    config.i18n.default_locale = :ja
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
  end
end
```

```rb:Gemfile
gem 'rails-i18n'
gem 'enum_help'
```

ついでにモデル用の日本語データも準備しておく。
en.ymlは不要なら消してしまってもいい。

```yml:config/locales/model.ja.yml
ja:
  activerecord:
    # User.model_name.human
    models:
      user: ユーザー
    # User.human_attributes_name(:name)
    attributes:
      id: ID
      created_at: 作成日時
      updated_at: 更新日時
      user:
        name: 名前

  enums:
    user:
      role:
        admin: 管理者
        general: 一般
        guest: ゲスト
```

### リンク

- [svenfuchs/rails\-i18n](https://github.com/svenfuchs/rails-i18n)
- [rails\-i18n/ja\.yml at master · svenfuchs/rails\-i18n](https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml)
  - i18nを上書きする場合はこれをコピーして書き換える。
- [zmbacker/enum\_help](https://github.com/zmbacker/enum_help)

## デバッグ用のGem

`binding.pry`を使いたいので、byebugを削除してpryを入れる。

```rb:Gemfile
group :development, :test do
  # byebugは不要なら削除してよい
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # 以下3行を追加
  gem 'bullet'
  gem 'pry-byebug'
  gem 'pry-rails'
end

group :development do
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  # 以下2行を追加
  gem 'better_errors'
  gem 'binding_of_caller'
end
```

```rb:config/environments/development.rb
Rails.application.configure do
  ...
  # bulletの設定
  config.after_initialize do
    Bullet.enable = true
    Bullet.bullet_logger = true
    Bullet.console = true
    Bullet.rails_logger = true
    Bullet.add_footer = true
  end
end
```

better_errorsとbinding_of_callerによって、エラー画面がデフォルトのものから詳細なものに変えて、エラー画面でコンソール操作ができる。

pry-byebugとpry-railsによって、コードの途中に`binding.pry`を書いておくと、そこで動作を止めてデバッグができる。

### リンク

- [flyerhzm/bullet](https://github.com/flyerhzm/bullet)
- [deivid\-rodriguez/pry\-byebug](https://github.com/deivid-rodriguez/pry-byebug)
- [rweng/pry\-rails](https://github.com/rweng/pry-rails)
- [BetterErrors/better\_errors](https://github.com/BetterErrors/better_errors)


## フォーマッター

### Rails Best PracticesとRubocopの導入

```rb:Gemfile
group :development do
  # 以下4行を追加
  gem 'rails_best_practices'
  gem 'rubocop', require: false
  gem 'rubocop-performance', require: false
  gem 'rubocop-rails', require: false
  gem 'rubocop-rspec', require: false
end
```

`.rubocop.yml`でrubocop-performanceとrubocop-railsを読み込む。
また、カスタマイズする場合は`.rubocop.yml`に記述を追加することでオーバーライドできる。

```yml:.rubocop.yml
# This file overrides https://github.com/bbatsov/rubocop/blob/master/config/default.yml

require:
  - rubocop-performance
  - rubocop-rails
  - rubocop-rspec

AllCops:
  Exclude:
    - 'tmp/**/*'
    - 'vendor/**/*'
    - 'db/**/*'
    - 'bin/**/*'
    - 'spec/**/*'
    - 'node_modules/**/*'
  DisplayCopNames: true

# 日本語でのコメントを許可
Style/AsciiComments:
  Enabled: false

# Admin::BaseControllerのような書き方を許可
Style/ClassAndModuleChildren:
  Enabled: false

# frozen_string_literal: trueはなくていい
Style/FrozenStringLiteralComment:
  Enabled: false

# each_key, value, transform_keys, valueを使用する
Style/HashEachMethods:
  Enabled: true
Style/HashTransformKeys:
  Enabled: true
Style/HashTransformValues:
  Enabled: true

# クラスにコメントを残さない
Style/Documentation:
  Enabled: false

# コントローラのメソッド名に「set_」「get_」を許可
Naming/AccessorMethodName:
  Exclude:
    - "app/controllers/**/*"

# 一行の長さは100字まで。コメントは制限しない
Metrics/LineLength:
  Max: 100
  IgnoredPatterns: ['\A#']
  Exclude:
    - "db/migrate/*.rb"

# メソッドの行数を20行までにする
Metrics/MethodLength:
  CountComments: false
  Max: 20

# ABC sizeは緩めにする
Metrics/AbcSize:
  Max: 30 # default 15
```

ついでに`bundle exec rubocop -a`で自動整形しておく。


### ESlintの導入

```terminal
$ yarn add -D eslint eslint-loader
```

```js:config/webpack/loaders/eslint.js
module.exports = {
  test: /\.js$/,
  loader: "eslint-loader",
  enforce: "pre",
  options: {},
};
```

```js:config/webpack/environment.js
const { environment } = require("@rails/webpacker");
const eslint = require("./loaders/eslint");  // 追加

environment.loaders.append("eslint", eslint);  // 追加
module.exports = environment;
```

```terminal
$ yarn run eslint --init
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · commonjs
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JSON
Successfully created .eslintrc.json file in /Users/k_end/study/rails_app
✨  Done in 92.50s.
```

なんとなく読みながらオプションを選んでいくと`.eslintrc.json`が生成される。

```js:.eslintrc.json
{
    "env": {
        "browser": true,
        "commonjs": true,
        "es2021": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 12
    },
    "rules": {
    }
}
```

これでWebpackerのコンパイル時にESlintが走るようになる。`bin/webpack-dev-server`で確認。
`yarn run eslint --fix`でESlint自動修正。


### リンク

- [flyerhzm/rails\_best\_practices](https://github.com/flyerhzm/rails_best_practices)
- [rubocop\-hq/rubocop](https://github.com/rubocop-hq/rubocop)
- [eslint\-loader \| webpack](https://webpack.js.org/loaders/eslint-loader/)
  - [Rails \+ webpacker \+ Vue\.jsプロジェクトにESLintを導入 \- Qiita](https://qiita.com/daiki7nohe/items/3b752eff42ab7ba1eb4c)


## RSpec, FactoryBot

`test/`ディレクトリは削除する。

```rb:Gemfile
group :development, :test do
  # 以下2行を追加
  gem "rspec-rails"
  gem "factory_bot_rails"
end
```

`bundle exec rails generate rspec:install`でファイルが生成される。

```rb:spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'factory_bot'  # 追加
require 'rspec/rails'

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }  # コメントアウト解除

begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!

  config.include FactoryBot::Syntax::Methods  # 追加
end
```

`spec/spec_helper.rb`の`config.filter_run_when_matching :focus`をコメントアウト解除する。

```:.rspec
--require rails_helper
--format documentation
```

- rails_helperでspec_helperを読み込んでいる
- 素のRubyファイルをテストすることはまずない

という点から、`--require rails_helper`に変更しておく。
これにより、それぞれのspecファイルで`require rails_helper`を付けなくてもよくなる。


```rb:config/application.rb
module RailsApp
  class Application < Rails::Application
    ...
    # generateで作成するファイルの制限
    config.generators do |g|
      g.assets false
      g.skip_routes true
      g.helper false
      g.test_framework :rspec,
                       view_specs: false,
                       helper_specs: false,
                       routing_specs: false,
                       controller_specs: false,
                       request_specs: true,
                       model_spec: true,
                       fixtures: true
      g.fixture_replacement :factory_bot, dir: 'spec/factories'
    end
  end
end
```

ここのtrue or falseは必要に応じて変える。

### リンク

- [rspec/rspec\-rails](https://github.com/rspec/rspec-rails)
- [thoughtbot/factory\_bot](https://github.com/thoughtbot/factory_bot)


## seed_fu

```rb:Gemfile
gem 'seed-fu'
```

seed-fuが読み込むのは`db/fixtures/`配下のファイルなので、`fixtures/`ディレクトリを作成しておく。

### リンク

- [mbleigh/seed\-fu](https://github.com/mbleigh/seed-fu)

## foreman

```rb:Gemfile
group :development do
  gem 'foreman'  # 追加
end
```

```:Procfile
rails: rails s --port=3000
webpacker: bin/webpack-dev-server
```

```terminal
$ bundle exec foreman check
valid procfile detected (rails, webpacker)
$ bundle exec foreman start
```

### リンク

- [ddollar/foreman](https://github.com/ddollar/foreman)


## slim

```rb:Gemfile
gem 'slim-rails'
gem 'html2slim'
```

`bundle exec erb2slim app/views app/views -d`で置換する。[ERB 2 SLIM](https://erb2slim.com/)を使うのもあり。
置換したら`html2slim`は不要なので削除する。

```rb:config/application.rb
module RailsApp
  class Application < Rails::Application
    ...
    config.generators do |g|
      ...
      g.template_engine = :slim  # 追加
    end
  end
end
```

### リンク

- [slim\-template/slim\-rails](https://github.com/slim-template/slim-rails)
- [slim\-template/html2slim](https://github.com/slim-template/html2slim)


## annotate

```rb:Gemfile
group :development do
  gem 'annotate'
end
```

`bundle exec rails g annotate:install`を実行する。

```rb:lib/tasks/auto_annotate_models.rake
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if Rails.env.development?
  require 'annotate'
  task set_annotation_options: :environment do
    # You can override any of these by setting an environment variable of the
    # same name.
    Annotate.set_defaults(
      'active_admin' => 'false',
      'additional_file_patterns' => [],
      'routes' => 'false',
      'models' => 'true',
      'position_in_routes' => 'before',
      'position_in_class' => 'before',
      'position_in_test' => 'before',
      'position_in_fixture' => 'before',
      'position_in_factory' => 'before',
      'position_in_serializer' => 'before',
      'show_foreign_keys' => 'true',
      'show_complete_foreign_keys' => 'false',
      'show_indexes' => 'true',
      'simple_indexes' => 'false',
      'model_dir' => 'app/models',
      'root_dir' => '',
      'include_version' => 'false',
      'require' => '',
      'exclude_tests' => 'false',
      'exclude_fixtures' => 'false',
      'exclude_factories' => 'false',
      'exclude_serializers' => 'false',
      'exclude_scaffolds' => 'true',
      'exclude_controllers' => 'true',
      'exclude_helpers' => 'true',
      'exclude_sti_subclasses' => 'false',
      'ignore_model_sub_dir' => 'false',
      'ignore_columns' => nil,
      'ignore_routes' => nil,
      'ignore_unknown_models' => 'false',
      'hide_limit_column_types' => 'integer,bigint,boolean',
      'hide_default_column_types' => 'json,jsonb,hstore',
      'skip_on_db_migrate' => 'false',
      'format_bare' => 'true',
      'format_rdoc' => 'false',
      'format_yard' => 'false',
      'format_markdown' => 'false',
      'sort' => 'false',
      'force' => 'false',
      'frozen' => 'false',
      'classified_sort' => 'true',
      'trace' => 'false',
      'wrapper_open' => nil,
      'wrapper_close' => nil,
      'with_comment' => 'true'
    )
  end

  Annotate.load_tasks
end
```


### リンク

- [ctran/annotate\_models](https://github.com/ctran/annotate_models)