[Ruby on Rails] 速習実践ガイドの環境をDockerで構築する [Docker]

# はじめに
Ruby on Railsの入門書籍として知られる”速習実践ガイド”
とても勉強になる良い書籍ですが、いかんせんRubyやRailsのバージョンが古いこと、手元の環境がMac M1が相まって開発環境構築に難儀しました。
そこで、試行錯誤の末、RSpecが動くまでの環境を残したいと思います。
これからRuby on Railsを始める方、速習実践ガイドを始める方のお役に立てれば幸いです。

# 環境構築
## 手元環境
- Macbook Air M1
- Docker Desktop

## dockerfile
dockerfileは以下の通りです。
[こちら](https://matsuand.github.io/docs.docker.jp.onthefly/samples/rails/)のサイトを参考に構築しています。
速習実践ガイドのRuby2.5なので、それに合わせています。

```dockerfile
# syntax=docker/dockerfile:1
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

# コンテナー起動時に毎回実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# イメージ実行時に起動させる主プロセスを設定
CMD ["rails", "server", "-b", "0.0.0.0"]
```

entrypoint.sh
```shell
#!/bin/bash
set -e

# Rails に対応したファイル server.pid が存在しているかもしれないので削除する。
rm -f /myapp/tmp/pids/server.pid

# コンテナーのプロセスを実行する。（Dockerfile 内の CMD に設定されているもの。）
exec "$@"
```

## docker-compose.yaml
docker-compose.yamlは以下の通りです。
RSpecでシステムテストを実行するためには、ブラウザが必要です。
ただ、webコンテナの中にブラウザをインストールするのは大変です。
なので、docker-compose.yamlで別途ブラウザ用のコンテナを用意します。
webコンテナのdepends_onとenvironmentでブラウザ用コンテナを利用するように設定します。

```yaml
version: "3.9"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      SELENIUM_DRIVER_URL: http://webdriver_chrome:4444/wd/hub
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
      - webdriver_chrome

  webdriver_chrome:
    image: seleniarm/standalone-chromium
    ports:
      - "4444:4444"
```

## Gemfile
続いて、Gemfileです。
bin/rails new で追加されるモジュールの一部は省略しています。
速習実践ガイドでは　Headless Chromeが紹介されています。しかし、このChromeは今はあまり使われていないようです（よくわかってないです）
なので、`group :test do` の箇所には`gem 'selenium-webdriver'`を指定します。
また、Rubyのバージョンですが、速習実践ガイドのバージョンは素直に使えなかったので、2.5.9に変更しています。

```gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.9'
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 3.7'
  gem 'factory_bot_rails', '~> 4.11'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'slim-rails'
gem 'html2slim'
gem 'bootstrap'
gem 'rails_autolink'
```

## RSpecの設定
続いて、RSpecの設定です。
まずspec/rails_helper.rbの25行目あたりの記述をコメントインします。
```ruby
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
```

続いて、spec/support/capybara.rb をフォルダ含めて作成します。
capybara.rbの内容は以下の通りです。
```ruby
require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :remote_chrome
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 3000
    Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end

  config.before(:each, type: :system, js: true) do
    driven_by :remote_chrome
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 3000
    Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
end

# Chrome
Capybara.register_driver :remote_chrome do |app|
  url = 'http://webdriver_chrome:4444/wd/hub'
  caps = ::Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions' => {
      'args' => [
        'no-sandbox',
        'headless',
        'disable-gpu',
        'window-size=1680,1050'
      ]
    }
  )
  Capybara::Selenium::Driver.new(app, browser: :remote, url: url, desired_capabilities: caps)
end
```

# 実行方法
ここまでで、設定は終了です。
最後に環境の実行方法は以下の通りです
```shell
$ docker compose build
$ docker compose up
```

# 補足情報
残りは実行時に詰まりそうな情報です。

## RSpec Systemテストが失敗する
私の環境では、チュートリアル通りに進めたところ、fill_inが正しく動作せず、入力失敗していました。

こちらの方の情報通りに直して動作しました。click_onは念の為、生成されるボタンのcommitを指定しました。
[【RSpec/capybara】fill_inが上手く動作しな](https://qiita.com/Takara1356/items/cc8535c4a6e66c5977e2)

``` ruby
before do
  visit login_path

  fill_in 'session_email', with: 'a@example.com'
  fill_in 'session_password', with: 'password'
  click_on 'commit'
end
```

## Docker環境でのコマンド実行の方法
Dockerで構築したwebコンテナで速習実践ガイドで紹介されているコマンドを実行する場合は以下の通りです。
```shell
$ docker compose run web {実行したいコマンド}
# 例えば、db:migrationやrailsコマンドを実行したい場合
$ docker compose run web db:migrate
$ docker compose run web bin/rails g controller
```

## gemを追加する方法
gemfileに新しいモジュールを追加した場合、コマンド実行だけでは不十分です。
必ずBuildしましょう。何が動作がおかしい場合は、--no-cacheコマンドでしっかりBuildしましょう。

``` shell
$ docker compose build
```

以上です。皆様の参考になれば幸いです。