Redmineのコードリーディング用環境をDocker上に構築する

## 概要

[redmine](https://github.com/redmine/redmine)のソースコードを読むための環境をDockerで構築します。以下の状態をゴールとします。

* [redmine](https://github.com/redmine/redmine)のmasterブランチのコードがDocker上で動作する
* Debug用のツールが使える

## モチベーション

動作環境を構築するのは実際のアプリケーションの挙動とコード内容を頭の中で紐付けながらコードリーディングしたいからです。

### なぜコードリーディングしたいのか?
人が書いたコードを読むことは新たな発見や学びに繋がると考えています。
私の場合は、クラスの分け方、命名、テストコードの書き方、エラーハンドリングの仕方などに悩むことが最近多いので、そのあたりの引き出しを増やしたいというモチベーションがあります。

### なぜRedmineか？
Redmineを選んだのは以下の理由です。

* 普段からRedmineを使っているため馴染みがあり、画面や外部仕様をある程度知っている。
* 長年メンテナンスされており、世界中で使用されている。
* 普段はPHPをつかっているため、あえて別の言語やFWに触れてみる。

## 環境構築

以下を参考にすすめていきます。

* [Redmineのインストール - Redmineガイド](http://guide.redmine.jp/RedmineInstall/)
* [Quickstart: Compose and Rails | Docker Documentation](https://docs.docker.com/compose/rails/)

### リポジトリの用意

1. [redmine](https://github.com/redmine/redmine)のレポジトリをフォーク
1. フォークしたリポジトリをクローン
1. `redmine`ディレクトリに移動
1. masterブランチから別ブランチを切る(自分は`code_reading`にしました)

### Docker関連ファイルの作成

`redmine`ディレクトリ直下に以下のファイルを作成します。

* Dockerfile
* docker-compose.yml
* entrypoint.sh

ファイルの内容は以下の通りにします。

#### Dockerfile

RedmineのGemfileが`config/database.yml`を参照しているため、`COPY . /redmine`をした後に`bundle install`をする必要があります。
ちなみに参照して何をしているかというと、使用するDBにあわせてインストールするGemを変えています。例えば`config/database.yml`にpostgresの接続情報が書いてあると、postgresに接続するためのGemがインストールされます。
Gemfileで指定するgemを動的に変えるという発想があまりなかったので既に学びがあったと感じています。

```DockerFile:Dockerfile
FROM ruby:2.5

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn
RUN mkdir /redmine
WORKDIR /redmine
COPY Gemfile /redmine/Gemfile
COPY Gemfile.lock /redmine/Gemfile.lock
COPY . /redmine
RUN bundle install

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```


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

# Remove a potentially pre-existing server.pid for Rails.
rm -f /redmine/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

#### docker-compose.yml

コメントアウトしているのは[ruby-debug/ruby-debug-ide](https://github.com/ruby-debug/ruby-debug-ide)用の設定です。
導入しましたが自分の環境では動作が安定しなかったので一旦使わないようにしています。

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    ports:
        - "5433:5432"
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rdebug-ide --host 0.0.0.0 --port 1234 -- bin/rails s -p 3000 -b 0.0.0.0"
    command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b 0.0.0.0"
    volumes:
      - .:/redmine
    ports:
      - "3003:3000"
      # - "1234:1234"
      # - "26162:26162"
    stdin_open: true
    tty: true
    depends_on:
      - db
```

### デバッグ用のGemの追記

* [【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita](https://qiita.com/ryokky59/items/284892be879996e4f77c)
* [pry-byebugでrubyをデバッグする - Qiita](https://qiita.com/AknYk416/items/6f0bec58712edaf4940e)

などの記事を参考にGemfile.に以下を追加します。

*  better_errors
*  binding_of_caller
*  pry-rails
*  pry-byebug

```ruby:Gemfile
group :development do
  gem "yard" #この下に追記する
  gem "better_errors"
  gem "binding_of_caller"
  gem "pry-rails"
  gem "pry-byebug"
  # 安定動作しなかったのでコメントアウト
  # gem "ruby-debug-ide"
  # gem "debase"　　　　　 
end
```


また、better_errosをDocker上の環境で使えるようにするため、`development.rb`に以下を追記します。

```ruby:config/enviroments/development.rb
BetterErrors::Middleware.allow_ip! "0.0.0.0/0"
```

### database.ymlの設定

`config/database.yml`を作成し以下の内容にします。

```yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: postgres
  password: password

development:
  <<: *default
  database: redmine_development

test:
  <<: *default
  database: redmine_test

production:
  <<: *default
  database: redmine
```

### 環境の立ち上げ

ファイルの用意が完了したのでコンテナを起動し、アプリケーションを動かします。

```bash
# 空のGemfile.lockを作る
$ touch Gemfile.lock
# コンテナイメージのビルド
$ docker-compose build
# コンテナの起動
$ docker-compose up -d
# DBの作成
$ docker-compose run --rm web rake db:create
# マイグレーションの実行
$ docker-compose run --rm web bin/bundle exec rake db:migrate
# Redmineのデフォルトデータ投入タスク
$ docker-compose run --rm web bin/bundle exec rake redmine:load_default_data
```

起動が成功していれば`docker-compse ps`で以下のようにStatusがUpになります。

```bash
$ dcom ps
    Name                   Command               State    Ports                                  
--------------------------------------------------------------------------------
redmine_db_1    docker-entrypoint.sh postgres    Up      0.0.0.0:5433->5432/tcp                                                  
redmine_web_1   entrypoint.sh bash -c rm - ...   Up      0.0.0.0:3003->3000/tcp
```

この後に`http://localhost:3003`にアクセスするとRedmineの画面が表示されます。
また、最初はIDとパスワード共に`admin`でログインすることができます。
これで環境構築は完了です。

## pry-byebugによるデバッグ

`docker-compose up`でコンテナが起動している状態でコンテナにアタッチします。

```bash
$ docker attach redmine_web_1
```

確認したいコードの該当箇所に`binding.pry`を追記します。以下の例はRedmineのルートパスにアクセスした時に実行される`wellcome#index`に追加しています。

```ruby:app/controllers/welcome_controller.rb
  def index
    binding.pry #調べたいところに追加する
    @news = News.latest User.current
  end
```

この状態で`http://localhost:3003`にアクセスするとターミナルに以下の内容が表示されステップ実行などができるようになります。

```bash
From: /redmine/app/controllers/welcome_controller.rb:25 WelcomeController#index:

    23: def index
    24:   binding.pry
 => 25:   @news = News.latest User.current
    26: end

[1] pry(#<WelcomeController>)> Started GET "/" for 172.18.0.1 at 2020-09-27 07:41:34 +0000
[1] pry(#<WelcomeController>)> 
```
`[1] pry(#<WelcomeController>)>`の箇所にコマンドを打つことでステップ実行などができます。

* next
    * ステップイン
* step
    * ステップオーバー
* continue
    * プログラムの実行を続行しpryを終了

以上