Rails わからないマンがRailsにプルリク送ってみたらRuby on Rails Contributorになれた話

先日、Ruby on Rails にプルリクエストを出してみました！Rails歴が浅いながらも頑張って作った結果、なんとマージされ、Ruby on Rails Contributorになることができました。作り始めてからマージされるまでの間、色々なことがありましたが、全て非常にいい経験になったと思います。今回は、それを共有できたらと思います。
# 書いている人のスペック
- 年齢: 19歳(プルリクエストを出した3月当時)
- 職業: 大学生
- プログラミング歴: 中3くらいからちょいちょいAndoridでアプリを作っていたものの、当時はクラスやインスタンスといった概念すらわからず、コピペでとりあえず動かしていた。昨年４月に大学に入学してから本格的にRubyとRailsの勉強をはじめ、6月に企業でインターンを始めて今に至る。

# プルリクエストの概要
今回僕が出したプルリクエストはこちらです。
[Add `use_year_names` option to date_select tag](https://github.com/rails/rails/pull/32190)
要するに、Railsの`date_select`タグの年選択欄で西暦以外も表示したいというプルリクエストです。
インターンの仕事で、こんな感じの年選択欄を作ってくれと言われたことがありました。
<img width="124" alt="スクリーンショット 2018-03-08 17.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/f32ebac7-2a8e-0238-4fd6-431d41d9189e.png">
しかし、Railsの`date_select`にはこんな風に西暦以外の値を表示するオプションがありませんでした。
調べていると、実際に同じ問題に苦しんだ方の書いたQiitaも見つかりました。
[Railsでdate_selectの和暦版入力フォームをつくる](https://qiita.com/Kta-M/items/d7dde132f49684429e00)
僕はこれとは違う方法で実装しましたが、やはり苦労しました。その時、「このくらいRailsの標準機能にあってもいいのになあ・・・」という思いが芽生えました。実際、月の入力欄には`use_month_names`というオプションが存在したので、その思いはなおさら強くなりました。
前々からいつかOSSに貢献してみたいという思いは持っていたので、これを機にRailsにプルリクエストを出してみようと思い、色々調べ始めました。
# プルリクエストを出すまで
まず最初に断っておくと、僕がプルリクエストを出すまでの流れは基本的にRailsガイドのこのページ([Ruby on Rails に貢献する方法](https://railsguides.jp/contributing_to_ruby_on_rails.html)）内の **4 Railsのコードに貢献する**　の流れに沿っています。細かい具体的な話とかはいいから、とにかくRailsにプルリクエストを出してみたい！という方は、こちらを読まれることをオススメします。
## 開発するための環境を整える
早速開発を始めましょう。まずは環境構築です。Rails Guide では、[Rails development box](https://github.com/rails/rails-dev-box)を使う方法が勧められていますが、僕の手元ではうまく動作しなかったので、自力で環境構築を行いました。[Rails　コア開発環境の構築方法](https://railsguides.jp/development_dependencies_install.html)からもわかる通り、ここでセットアップするものはRails開発者にとっては馴染みの深いものばかりなので、特に支障はないと思います。自分のPCにgithubから`rails`リポジトリをクローンしてきます。その後、開発を進めるために新しいブランチを切ります。

```shell
git clone git://github.com/rails/rails.git
cd rails
git checkout -b use_year_names
```

その後、必要な`gem`を全てインストールしましょう。後述しますが、Railsにプルリクエストを送るにはテストの作成が必要です。その際に`Gemfile`内の`gem`が必要になります。

```shell
bundle install
```

ついでに、自分の手元にある`rails`を参照するRailsアプリケーションも作りましょう。おなじみの`rails new` コマンドを`rails`リポジトリ内で`--dev`オプション付きで実行することで、手元の`rails`を参照するアプリケーションが作成されます。

```shell
cd rails
bundle exec rails new ~/my-test-app --dev
```
これで準備は完了です。早速、開発にとりかかりましょう。

## 機能を追加する
膨大な`rails`リポジトリの中から自分の修正したい箇所を探し出し、修正を加えていきます。
今回、僕が最初に思いついた修正は、

```erb
  <%= date_select('user_birthday', '', start_year: 1998, end_year: 2000, use_year_names: ['1998(平成10)', '1999(平成11)', '2000(平成12)'])%>
```
のように、表示する年名を配列として列記して`use_year_names`オプションに渡す、というものでした。実際に、先述の`use_month_names`はこのように使われていたからです。
ではどこをいじればこれが実現するのか。まず`git grep date_select` で関連していそうなファイルを探し出しました。幸い、`date_select` に関係していそうなファイルは下のファイルだけのようだったので、僕はこのファイルを読み始めました。
[actionview/lib/action_view/helpers/date_helper.rb](https://github.com/liwii/rails/blob/54f5e0664ea44469cbc03eb80af7ed8b637a38fd/actionview/lib/action_view/helpers/date_helper.rb)
しかしこのファイル、とにかく長く、さらに中でHTMLを生成するなどの見慣れない処理を行なっているためとにかく読みづらいです。それっぽいキーワードをひたすら辿って、お目当ての修正箇所を探し出しました。
どうやら、`date_select`タグの年選択部分は`DateTimeSelector#select_year`の戻り値として実装されているようです。

```ruby:actionview/lib/action_view/helpers/date_helper.rb
def select_year
  # 中略
  build_options_and_select(:year, val, options)
end
```

この`build_options_and_select`の中身を見てみましょう。

```ruby:actionview/lib/action_view/helpers/date_helper.rb
def build_options_and_select(type, selected, options = {})
  build_select(type, build_options(selected, options))
end
```
この中では、`build_select`で第一引数に合わせた`<select>`タグを生成し、その中身となる`<option>`タグは`build_options`で生成しています。
しかし、この`build_options`、　中身にできるのは基本的に `"1"` `"30"` `"03"`といった数字だけです。これでは`"1998(平成10)"`が入った`<option>`タグを作ることができません。
これに対処するべく、`build_year_options`というメソッドを作ります。

```ruby:actionview/lib/action_view/helpers/date_helper.rb
def build_year_options(selected, options = {})
  year_names = options.delete(:use_year_names)
  return build_options(selected, options) if year_names.nil?
  start = options.delete(:start)
  stop = options.delete(:end)
  step = options.delete(:step)
  start.step(stop, step).with_index do |value, i|
    tag_options = { value: value }
    tag_options[:selected] = "selected" if selected == value
    text = year_names[i]
    select_options << content_tag("option".freeze, text, tag_options)
  end
  (select_options.join("\n") + "\n").html_safe
end
```
（かなり前に作った関数であり、後述の理由により記録が全て消えてしまっているので当時作られたものとやや異なる可能性があります）
このメソッドでは、`date_select`タグの`:use_year_names`オプションの値を最初に取ってきます。
そして、値がなかった場合は今までと同様の方法で`<option>`タグを作りますが、値があった場合はその値を使ってタグが生成されます。`content_tag`内で

```html
<option value="1998">1998(平成10)</option>
```
のようなタグが生成され、それが連結されて返されるようなイメージを持っていただけたらと思います。

あとは、`select_year`内でこのメソッドを使うようにするだけです。

```ruby:actionview/lib/action_view/helpers/date_helper.rb
def select_year
  # 中略
  build_select(type, build_year_options(selected, options))
end
```

さあ、これで最初に思い描いたようなセレクトボックスが作れるはずです。
せっかく前もって手元の`rails`を参照するアプリケーションを作っていたので、これで動作確認をしてみましょう。適当な`erb`ファイルに先ほどの`date_select`を書き込み、このアプリケーションに表示させてみます。

```erb:app/views/time/select.html.erb
<%= date_select('user_birthday', '', start_year: 1998, end_year: 2000, use_year_names: ['1998(平成10)', '1999(平成11)', '2000(平成12)'])%>
```

<img width="124" alt="スクリーンショット 2018-03-08 17.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/f32ebac7-2a8e-0238-4fd6-431d41d9189e.png">
ちゃんと表示されることが確認できました。

## テストを書く
Railsでは、変更の一つ一つについてMinitestを使ったテストケースを書く必要があります。自分の変更したファイルに対応するテストケースのファイルが必ず存在するので、そのファイルにテストケースを追記しましょう。
僕の場合は以下のファイルでした。
[actionview/test/template/date_helper_test.rb](https://github.com/liwii/rails/blob/6784a38b5c6ef7e6c0a3ef694e9b809dc5eedeb6/actionview/test/template/date_helper_test.rb)
僕はこれまでMinitestを触ったことはほぼ皆無だったのですが、幸い似たようなテストケースがファイル内にたくさんあったので、それを真似ることで問題なくテストを作成できました。

```ruby:actionview/test/template/date_helper_test.rb
def test_select_year_with_custom_names
  year_names = ["Heisei 15", "Heisei 16", "Heisei 17"]
  expected = %(<select id="date_year" name="date[year]">\n).dup
  expected << %(<option value="2003">Heisei 15</option>\n<option value="2004">Heisei 16</option>\n<option value="2005">Heisei 17</option>\n)
  expected << "</select>\n"

  assert_dom_equal expected, select_year(nil, start_year: 2003, end_year: 2005, use_year_names: year_names)
  end

```

`expected`という変数の中に、今回生成されてほしいHTMLを文字列として入れて、`select_year`で生成されるHTMLと等しいかどうかを比較しています。
それでは、テストを実行して失敗しないことを確認します。`rails`の中には大量のテストケースがあるため、全部実行するにはものすごい時間がかかります。ここでは、修正したファイルは`actionview/test/template`以下にあるので、この下にあるテストを実行すれば十分でしょう。

```bash
cd actionview/test/template
bundle exec rake test

#中略

Finished in 4.219940s, 40.2849 runs/s, 71.3280 assertions/s.
170 runs, 301 assertions, 0 failures, 0 errors, 0 skips
```

一つのテストも失敗していないことがわかります。
これでコードを書く作業はほぼ終わりです。

## 変更内容をまとめる
`rails`の中には、様々なディレクトリ中に`CHANGELOG.md`というファイルがあります。この中に、`rails`に変更を加えた一人一人が変更を書き残しています。自分の変更内容を短くまとめて追加しましょう。もちろん英語で書くことになりますが、変更が良ければ後々ネイティブの方々が英語は直してくれると思うので、そこまで心配しなくても大丈夫だと思います。
僕の場合、`actionview/CHANGELOG.md`を編集しました。

```markdown:actionview/CHANGELOG.md
*   Add `year_format` option to date_select tag. This option makes it possible to customize year
    names. Lambda should be passed to use this option. Example:

        date_select('user_birthday', '', start_year: 1998, end_year: 2000, use_year_names: ['Heisei 10', 'Heisei 11', 'Heisei 12'])

    The HTML produced: 

        <select id="user_birthday__1i" name="user_birthday[(1i)]">
        <option value="1998">Heisei 10</option>
        <option value="1999">Heisei 11</option>
        <option value="2000">Heisei 12</option>
        </select>
        /* The rest is omitted */

    *〇〇〇〇（僕の名前）*
```

これでファイルの編集は終わりです。いよいよ変更をコミットし、プルリクエストを出してしまいましょう。

## コミット
いよいよ変更内容をコミットします。

```bash
git commit -a
```

でコミットメッセージを記入するためのエディタが開きます。Railsガイドでは、コミット内容が一目でわかるようなコミットメッセージを求められているので、できるだけわかりやすく書きましょう。僕の場合、先ほどの`CANGELOG.md`と同様の内容を記述しました。

作業が長引いた場合は、この間に`master`に変更が加えられている可能性があります。最新の`master`をGithubから取り込んで、作業をしているブランチに統合しましょう。

```bash
git checkout master
git pull --rebase
git checkout use_year_names
git rebase master
```
`git pull`の`rebase`オプションや`git rebase`自体については、この記事がわかりやすかったので参考にしてください。
[git pull と git pull –rebase の違いって？図を交えて説明します！](http://kray.jp/blog/git-pull-rebase/)
大事なことは、マージコミットを`rails`のコミットログに残さないということです。（普通にしていたら手元のとリモートの`master`がコンフリクトすることはないと思うので、なぜ`rebase`オプションをつけなければならないのか僕はよくわかっていません・・・）

## プルリクエスト
さあ、ここまで来たらあとは変更をプルリクエストにするだけです。まず、Githubの`rails`リポジトリ上でforkボタンを押し、自分のアカウント内に`rails`リポジトリを複製して来ます。
<img width="1426" alt="スクリーンショット 2018-06-09 23.46.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/ac83cbe5-b1d0-3db9-a56d-12f409d1d991.png">
実際のプルリクエストは、この複製された`<自分のユーザー名>/rails`リポジトリから、`rails/rails`リポジトリに対して出されます。
まずは`<自分のユーザー名>/rails`の方に、今回の変更をpushします。

```bash
git remote add mine git@github.com:<自分のユーザー名>/rails.git
git push mine use_year_names
```

ここまで来たらあと少しです。New Pull Requestボタンを押して、`rails/rails`の　`master`ブランチにプルリクエストを出します。

<img width="993" alt="スクリーンショット 2018-06-10 0.00.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/e2529c86-7f08-ea27-aa73-a92700b7866e.png">
プルリクエストにはわかりやすいタイトルと変更内容を記述しましょう。（僕はここでも`CHANGELOG.md`を流用しました。）

ここまで来るとようやく、

<img width="1070" alt="スクリーンショット 2018-06-10 0.13.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/1be86fd3-b26c-164d-0a67-a681b8453e1b.png">
プルリクエストが作成されました！これで自分の加えようとしている変更が`rails`を見にくる全員に公開されます。感慨深いものがありますね。
# プルリクエストを出してから
## フィードバックを受け取る
しかし、ここで油断していてはいけません。ここから、`rails`に携わる様々な方々からのフィードバックが待っています。彼らにとって`rails`に取り込むに値するプルリクエストでなければ、`master`にマージされることはありません。

初めは僕のような者が書いたコードが強いエンジニアの方々に叩かれるのではないかと想像していましたが、以外にもそんなことはなく、皆さんこの機能追加をどのように改善すればよいかを真剣に議論してくださいました。
僕が最も指摘された点は、「同じような表現の続く配列をオプション引数にとるような作りは美しくない」ということでした。僕もその点には完全に同意していたのですが、代わりの記法を思い浮かばずにいました。
そんな中、レビューしてくださった方の一人がこんな提案をしてくれました。
>Actually, one good way that we can achieve this is maybe letting user pass in a proc?
>`date_select('user_birthday', '', start_year: 1998, end_year: 2000, labels_for_year_options: ->{ |year| convert_year_to_wareki(year) })`

恥ずかしながら、僕は今までRubyの`Proc`オブジェクトの存在を知りませんでした。`Proc`というのはめちゃくちゃ平たくいうと関数を閉じ込めておくオブジェクトです。下のリンクの説明が詳しいかと思います。
[Procを制する者がRubyを制す（嘘）]
(http://melborne.github.io/2014/04/28/proc-is-the-path-to-understand-ruby/)
例えば`convert_year_to_wareki`という西暦の年を受け取り和暦に変換するような関数をオプションに渡せば、先述のような`<option>`が生成されるようにしてはどうか、という提案でした。
これは明らかに僕が思いついたものよりも綺麗な実装です。急いで採用することにして、手元で作業を進めました。

## コードを修正する
実はこの他にも、僕は何回かの指摘を受けました。それらを何回かのコミットに分けて修正した、最終形態がこちらです。

```ruby:actionview/lib/action_view/helpers/date_helper.rb
def select_year
  # 中略
  build_select(type, build_year_options(selected, options))
end

def year_name(number)
  if year_format_lambda = @options[:year_format]
    year_format_lambda.call(number)
  else
    number
  end
end

def build_year_options(selected, options = {})
  start = options.delete(:start)
  stop = options.delete(:end)
  step = options.delete(:step)

  select_options = []
  start.step(stop, step) do |value|
    tag_options = { value: value }
    tag_options[:selected] = "selected" if selected == value
    text = year_name(value)
    select_options << content_tag("option".freeze, text, tag_options)
  end

  (select_options.join("\n") + "\n").html_safe
end

```
`year_format`オプションが渡されていたらそれを使って数字を修飾し、オプションがなければ数字をそのまま返す`year_name`メソッドを定義することで、オプションがある時もない時も同じ動線で`<option>`を生成することができます。さらに、`date_selectを使う時も、

```erb:app/views/time/select.html.erb
<%= date_select('user_birthday', '', start_year: 1998, end_year: 2000, year_format: ->year { "#{year}(平成#{year - 1988})" })%>
```

と、以前に比べて簡潔にかけるようになりました。初期のバージョンと比べると格段に使いやすく、綺麗な実装になったと思います。

## その後
変更をコミットしたら、基本的にそのまま`push`してさらなるレビューを待ちます。しかし、`rails`のコミットログに余計なものが入っていてはいけません。マージされる直前には、`git rebase -i`してコミットを一つにまとめます。
コミットのまとめ方に関しては、下のリンクが参考になります。
[git rebaseでsquashする](http://changesworlds.com/2015/03/git-rebase-squash/)
ちなみに、僕は早とちりしてこの作業を早くにやりすぎたので、途中に書いたコードが全部消えてしまいました。自分のコードの変遷を後から見たかったので残念です。個人的には、コミットをまとめる前に一旦`git checkout`して自分の手元に修正の履歴を残しておくことをオススメします。
また、長いことレビュー・マージを待っていると他の人の修正によって自分のブランチと　`master`がコンフリクトを起こすことがあります。そんな時は、この記事の「コミット」と同じ手順で`master`の変更を自分のブランチに取り込んでください。
ちなみに、僕がプルリクエストを出してから3ヶ月間、マージは行われず、僕は`CHANGELOG.md`に変更を見つけるたびにいつか来るその時を信じて`git rebase -i`し続けました。

## そしてマージの時
そんなこんなで３ヶ月が経ちました。僕はレビューされた点を全て修正し、いつでもマージできる状態にしているつもりだったので、こんな形でプルリクエストを諦めたくありませんでした。
そこで、思い切ってレビューをくださった方の一人に個人的に連絡を入れました。人によっては失礼と捉えられるような行為ですが、その方は非常に親切に様々な方にコンタクトをとるなど、僕のプルリクエストがマージされるように力を貸してくださりました。
また、その頃にこの記事を「*Rails わからないマンがRailsにプルリク送ってみたけどマージはされていない話*」として投稿しました。インターンの社員さんがTwitterでこの記事を拡散してくださり、Rails Comimtterの方がそれに反応してくださったこともありました。
そんなこんなで、2018年6月、ついにその時が訪れました。
<img width="781" alt="スクリーンショット 2018-06-22 17.12.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/e9f9f9ae-b31d-1b88-4a0d-f50465823555.png">
自分で作ったプルリクエストが、`rails`の`master`にマージされました！[Rails Contributors](http://contributors.rubyonrails.org/)というRuby on Rails Contributor　の一覧が載っているページがあるのですが、そのページにも、
<img width="631" alt="スクリーンショット 2018-06-22 17.15.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/171028/5f5cc9b9-f0f0-f2d0-ddc8-6ae68e16b5bc.png">
このように自分の名前が載りました。初めてみたときは鳥肌が止まりませんでした。

　
# 最後に
紆余曲折を経て、初心者だった僕はRuby on Rails Contributor　となることができました。一年間向き合い続けたものを自分の力で少しでもいいものにできたのは非常に嬉しかったです。助けてくださった様々な方々のおかげです。この場をお借りして、感謝を申し上げたいと思います。ありがとうございました。
そして、僕がこの過程で得たものはContributorの称号だけではありません。

 -  Rails内のコードを読んだり、プルリクエストのレビューを受けたりすることで、今まで知らなかったRubyの機能を知れたこと
 -  新参者を迎え入れるOSSのコミュニティの温かさに触れたこと
 -  「コミットログを綺麗に」という哲学のもと、今まで使ってこなかったようなGitの操作を学んだこと
 
など、挙げると霧がありません。きっとこの先長いエンジニアとしての人生に、必ず役立って来ると思います。もしもなんとなく怖い・大変そうという印象でこうした巨大OSSへのプルリクエストを躊躇している人がいたら、ぜひ思い切って一歩を踏み出して見てください。きっと想像よりもずっと大きなものが返って来ると思いますよ。