Rails新規開発でまずやっている設定とか参考記事を紹介する

# motivation
[yoshixj](https://twitter.com/yoshixj)と申します。
最近、Golangを書き始めてしまいました。
Railsを浮気しそうです。
2020年12月個人的Railsで新規のAPIを開発をするとしたら、こんな感じでやっていくという流れをざっくり書いていきます。
筆者の経験はざっくり、Railsの経験は3-4年くらい、スタートアップでの新規開発を経験多め、運用経験すくなめです。

内容はRailsをとりあえずかけるようになった人がこの辺やっておくとよいとbetterかもてきな感じです。
この記事は、この数年間経験が浅い私が、ひたすらググり、実装してよかったものをまとめたような記事です。
長く冗長的な部分もあるかなと思いますが、おてやわらかに。

# agenda
- git repository 作成
- Gemfileを設置
- dockerの設定
- rails new
- setting rubocop
- install rspec
- install factry bot
- timezoneの設定
- CORSの設定
- エラーレスポンスの統一
- ガイドラインの統一
- やったことないけど、今後新規やるとしたらやるかなぁと思うこと

# git repository 作成
今回は、githubですでにレポジトリがある状態からスタートします。
railsを作成する際、rails new で自動的にgitが設定されますが、rails newをする前にgitを設定が設定されているという状態です。

```sh
$ git pull repository_name
$ cd repository_name
$ ls -al

drwxr-xr-x  14 yoshikimasubuchi  staff       448  5  2 11:08 .git
```

# dockerの設定
Dockerの設定していきます。
rails new から docker-compose でやっていきます。 この辺を参考にしています。
[rails newの先輩オススメ手順](https://maetoo11.hatenablog.com/entry/2016/03/04/144216)

どういうディレクトリ構成にするかはチームに合わせます。

僕が経験したなかだと、
rails単体でdocker-composeを切る場合はこんな感じ。

```
--repository_name
  ├── Gemfile
  ├── docker-compose.yml
  ├── app
  ├── docker
      ├── api
           ├── Dockerfile
```

Dockefileの中身はこんなかんじ

~~~Dockerfile
FROM ruby:2.7.1
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN apt-get update -qq && apt-get install -y \
    build-essential \
    nodejs \
 && rm -rf /var/lib/apt/lists/*

ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

WORKDIR /usr/src/app
COPY . /usr/src/app

RUN gem install bundler

ENTRYPOINT [ \
    "prehook", "ruby -v", "--", \
    "prehook", "bundle install -j3 --quiet", "--"]
~~~

~~~docker-compose.yml
version: '3'
services:
  db:
    image: postgres:11.3
    ports:
      - "5432:5432"
    volumes:
      - db:/var/lib/postgresql/data:z
  api:
    build:
      context: .
      dockerfile: ./docker/api
    volumes:
      - .:/usr/src/app
    command: /bin/sh -c "rm -f /usr/src/app/tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"
    ports:
      - "8080:8080"
    depends_on:
      - db
    env_file:
      - rails.env
    environment:
      - DB_USER=postgres
      - DB_PASS=
      - DB_HOST=db
      - DB_PORT=5432
      # for ruby 2.6 alart https://k-koh.hatenablog.com/entry/2020/02/07/145957
      # - RUBYOPT='-W:no-deprecated -W:no-experimental'
    tty: true
    stdin_open: true

volumes:
   db:
~~~

# Gemfileを設置 railsをinstall
RailsをinstallするためのGemfileを設定します。

```sh
$ docker-compose build
$ docker-compose run api bundle init
```

をすると、Gemfileが設置されます。
設置されたGemfileに `gem 'rails'` を追記します。

~~~Gemfile
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'rails'
~~~

追記したら、bundle します。

```sh
$ docker-compose run api bundle install --path vendor/bundle
```

railsがinstallされました！

docker環境だとあまり関係ないかもしれませんが、bundleの設定も付け加えておきます。


~~~.bundle/config
---
BUNDLE_PATH: vendor/bundle
BUNDLE_JOBS: 4
BUNDLE_DISABLE_SHARED_GEMS: '1'
~~~


# rails new
ようやくrails newをします。
今回はAPIモードで`rails new` を行います。
今回はテストフレームワークにrspecを使用するので、 `--skip-test` します。
以下以外でも `active-strage` などもskipできるますが、もしかしたら使うかもしれないのでskipしません。


```
$ docker-compose run api bundle exec rails new . --api -d=postgresql --skip-git --skip-test --api --skip-bundle
```

`rails new` でアプリができたら、Gemfileを記述していきます。
developmentにはとりあえず最初に入れておこうってやつをいれています。

~~~Gemfile
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

ruby '2.7.1'

gem 'rails'

gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.4.2', require: false
gem 'rack-cors'
gem 'pg'

group :development, :test do
  gem 'brakeman' # for security
  gem 'dotenv-rails'
  gem 'guard-rspec', require: false
  gem 'rspec-benchmark'
  gem 'rspec-rails'
  gem 'rspec_junit_formatter'
  gem 'rubocop'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'faker-japanese'
  gem 'pry-rails'
  gem 'pry-byebug'
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'hirb'
end
~~~


```
$ docker-compose run api bundle
```

DBの設定も書いていきます。

~~~database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['DB_PASS'] %>
  host: <%= ENV.fetch('DB_HOST') { 'localhost' } %>
  port: <%= ENV.fetch('DB_PORT') { 5432 } %>

development:
  <<: *default
  database: earlybird-api_development

test:
  <<: *default
  database: earlybird-api_test
production:
  <<: *default
  database: earlybird-api_production
  username: earlybird-api
  password: <%= ENV['earlybird-api_DATABASE_PASSWORD'] %>
~~~

defaultの部分の設定は `docker-compose.yml`の記述にあわせます。

```
$ docker-compose run api bundle exec rails db:create
```

これで、railsでAPIを作れる状態になりました!

# rubocopの設定
チーム開発でrubocopをちゃんと設定することは重要です。
会社でrubyのプロジェクトがなかく、rubocopの設定に決まりがない場合は、こんな感じで設定しておきます。

[rubocop-rails](https://github.com/rubocop-hq/rubocop-rails)

```.rubocop.yml
require: rubocop-rails

Rails:
  Enabled: true

AllCops:
  TargetRubyVersion: 2.7.3
  TargetRailsVersion: 6.0.2
  Exclude:
    - bin/*
    - db/**/*
    - vendor/**/*
    - test/**/*
    - config/**/*
    - lib/**/*
    - Gemfile
    - db/schema.rb
```


# rspecのinstall
rspecをinstallします。
最初にinstallをし忘れると、modelのspecやら、controllerのspecやら、自動でつくられないのであとあと面倒になります。

```
$ docker-compose run api bundle exec rails generate rspec:install
```

# factry botのinstall
factory botもinstallし忘れると、modelを作成と同時にfileが作られないので、あとあと面倒になります。

~~~spec/rails_helper.rb
RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.include FactoryBot::Syntax::Methods # add
~~~

# timezoneの設定
- ruby プロセスのタイムゾーン
- config.time_zone
- config.active_record.default_timezone
- DBサーバ（postgres等）のタイムゾーン

の設定をします。RailsはTimezoneがややっこしいので最初にそれぞれの設定をしてしまいます。
参考: [Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠)](https://qiita.com/joker1007/items/2c277cca5bd50e4cce5e)

[Railsタイムゾーンまとめ](https://qiita.com/aosho235/items/a31b895ce46ee5d3b444)

今回は日本でのみつかうプロダクトを想定しています。
海外展開もある場合は、一旦全部 UTCで揃えるようにするとよいと思います。

```
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

```config/application.rb
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
```

```DB
set time zone 'Asia/Tokyo';

-- postgresql.conf に timezone='Japan' も記載
```

コーディングで時間を扱う際には、`TimeWithZone`を使用するようにします。


# CORSの設定
CROSの設定をします。
これをしないと、cross origin error でapiをフロントアプリケーションから たたけ無いと思います。

```rb
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module App
  class Application < Rails::Application
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    config.active_record.time_zone_aware_types = [:datetime, :time]

    config.i18n.default_locale = :ja
    config.api_only = true
    # 以下を追加
    config.middleware.use Rack::Cors do
      allow do
        origins ENV['CORS_ALLOWED_ORIGINS']
        resource "*",
                 headers: :any,
                 methods: [:get, :post, :put, :patch, :delete],
                 expose: []
      end
    end
  end
end

```

このあたりで、動くRails アプリの設定ができたと思います。
以下からは、運用を考えて設定を追記していきます。



# エラーレスポンスの統一
エラーのレスポンスを統一するための設定をします。
私は以前まで、ApplicationControllerでレスポンスはresuceしていましたが、どうやらRailsの`exception_app` というのを使うのが良さそうです。
これは、実際に運用したことは無いのですが、たぶん以下のように設定すると思います。


参考: [Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた](https://qiita.com/upinetree/items/273ae574f1c021d24c37)


```rb
# app/controllers/errors_controller.rb
class ErrorsController < ActionController::Base
  rescue_from StandardError, with: :render_500
  rescue_from ActiveRecord::RecordNotFound, with: :render_404
  rescue_from ActiveRecord::RecordInvalid, with: :render_400

  def show
    raise env['action_dispatch.exception']
  end

  def render_400(err)
    render_error(err, 400)
  end

  def render_404(err)
    render_error(err, 404)
  end

  def render_500(err)
    render_error(err, 404)
  end

  def render_error(err, status)
      render json: ErrorSerializer.new(errr), status: status
    end
  end
end

# config/initializers/exceptions_app.rb
Rails.application.configure do
  config.exceptions_app = ErrorsController.action(:show)
end
```


# ガイドラインの統一
APIを開発するにあたって、ロジックをどのように、どこに書いていくかなどのガイドラインを最初に決めます。
基本的な思想は、リソース思考です。リソース=Model にロジックは詰めていきます。
思い当たるのをいかにリストアップし、参考のURLをつらつらとのせていきます。


##### Validationは原則いれる。きつく→ゆるくはできるが、ゆるく→きつくはできない
制約はきつくしておいたほうがよいです。

#####  同じ理由で、Migrationの外部キー制約は基本いれる。
制約はきつくします。

```rb
# frozen_string_literal: true

class CreateShopPhotos < ActiveRecord::Migration[5.2]
  def change
    create_table :shop_photos do |t|
      t.references :shop, index: true, foreign_key: true, null: false
      t.string :image_url, null: false
      t.timestamps
    end
  end
end
```
##### こまったら、FatModelにとりあえずしておく
Modelにロジックを書いておけば、unit testが書きやすいので、あとから手を入れるときも安全に手をいれられます。

#####  whereなどを使う検索ロジックは、scopeで定義する。インスタンスメソッド, クラスメソッドでは定義しない。
scopeで定義すれば、ClassからもInstanceからもつかえますが、 インスタンスメソッド、クラスメソッドで検索ロジックを定義していまうとどちらかでしかつかえません。

#####   controller には基本 defaultの index, show, create, update, delete しか書かない
他の人が見た時に、謎のアクションがないようにできるだけ注意します。

[DHH流のルーティングで得られるメリットと、取り入れる上でのポイント - KitchHike Tech Blog](https://tech.kitchhike.com/entry/2017/03/07/190739)
[DHHはどのようにRailsのコントローラを書くのか /| POSTD](https://postd.cc/how-dhh-organizes-his-rails-controllers/)

#####  APIのレスポンスには原則Serialzierを使う

[マイクロサービス指向 Rails API 開発ガイド/building rails api on microservices - Speaker Deck](https://speakerdeck.com/qsona/building-rails-api-on-microservices?slide=66)
[Rails アプリに RESTful API のレールを敷いて生産性が大きく上がった話 \| Wantedly Engineer Blog](https://www.wantedly.com/companies/wantedly/post_articles/85098)
[Rails に RESTful API のレールを敷く - Qiita](https://qiita.com/Altech/items/3e9aed8b30688bc9d33d)

##### Sidekiqの引数に注意する
引数は増やしたくなったように、最後にhash型のpayloadをとりあえずいれておきます。

[Sidekiq アンチパターン: 序](https://tech.smarthr.jp/entry/2017/04/20/165555)

# やったことないけど、今後新規やるとしたらやるかなぁと思うこと
- committee×OpenAPI をいれて、request specでレスポンスのテストもしたいです

# その他
Ruby は書き方が柔軟なので、書き方にまよったら、とりあえずcookpad さんのスタイルガイドを参考にします。
[cookpadさんのスタイルガイド](https://github.com/cookpad/styleguide/blob/master/ruby.ja.md)


# 終わりに

一人で開発することが多かったので、webでひたすらdigって自分の経験を伸ばしてきました。
今回の記事はそのなかで影響を受けている記事の総まとめのような記事です。
もし、ここがおかしいなどあればコメントくださると幸いです。
2020年もおつかRails！


# 参考記事一覧

[rubocop-rails](https://github.com/rubocop-hq/rubocop-rails)
[Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠)](https://qiita.com/joker1007/items/2c277cca5bd50e4cce5e)
[Railsタイムゾーンまとめ](https://qiita.com/aosho235/items/a31b895ce46ee5d3b444)
[Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた](https://qiita.com/upinetree/items/273ae574f1c021d24c37)
[DHH流のルーティングで得られるメリットと、取り入れる上でのポイント - KitchHike Tech Blog](https://tech.kitchhike.com/entry/2017/03/07/190739)
[DHHはどのようにRailsのコントローラを書くのか /| POSTD](https://postd.cc/how-dhh-organizes-his-rails-controllers/)
[マイクロサービス指向 Rails API 開発ガイド/building rails api on microservices - Speaker Deck](https://speakerdeck.com/qsona/building-rails-api-on-microservices?slide=66)
[Rails アプリに RESTful API のレールを敷いて生産性が大きく上がった話 \| Wantedly Engineer Blog](https://www.wantedly.com/companies/wantedly/post_articles/85098)
[Rails に RESTful API のレールを敷く - Qiita](https://qiita.com/Altech/items/3e9aed8b30688bc9d33d)
[cookpadさんのスタイルガイド](https://github.com/cookpad/styleguide/blob/master/ruby.ja.md)
[Sidekiq アンチパターン: 序](https://tech.smarthr.jp/entry/2017/04/20/165555)
[運用に耐えるRailsによるWebアプリケーションの作り方](https://qiita.com/seri_k/items/605e6f75da838ae00007)
[Fat Modelの倒し方](https://blog.toshimaru.net/how-to-deal-with-fat-model/)
[serodriguez68/rails-antipatterns](https://github.com/serodriguez68/rails-antipatterns)
[rspec style guide](https://github.com/willnet/rspec-style-guide)