模索中

人生模索中

【再掲】新米エンジニアがRailsにコントリビュートした話

本記事は7年前の2015年1月、私が前職の技術ブログに投稿した記事の再掲版となります。 元のブログが閉鎖していたのでこちらに移します。

前職の会社が買収された後、文章は取っておいたのですが、特に読まれることもなかろうと下書きで放置していました。

この度伊藤さんのtweetをきっかけに再掲することにしました。

伊藤さん、掘り起こしありがとうございました。

初めての技術ブログに思っていた以上に反響があって当時はとても嬉しかったのをよく覚えています。 b.hatena.ne.jp

改めて読み返してみると苦笑いが浮かんで書き直したい衝動にかられますが、無効なリンクを削除した以外は原文ママです。

以下、再掲です。


皆様はじめまして、spicelifeエンジニア@yuki3738と申します。
あけましておめでとうございます。本年も弊社サービスのTMIXと\SPOTLIGHTS/をよろしくお願い申し上げます。

さて年末のことではありますが、なんとわたくし皆様が大好きなあのフレームワークRuby on Railsにコントリビュートをしました。
今回はエンジニア歴約半年の私がどんなインチキ経緯があってコントリビュートにまで至ったのか、またそれによってどんな学びがあったのかをお話ししたいと思います。

rails dbができない

事の発端は弊社プロダクトの一つであるtmixのdbの中を見ようとrails dbコマンドを叩いたことから始まります。
Railsエンジニアだったら確実にお世話になるであろうこのコマンド、なぜかわたしのtmixリポジトリでは起動することができませんでした。

rails dbを入力すると…

$ rails db
/Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/dbconsole.rb:186:in `exec': Permission denied - /usr/local/var/mysql (Errno::EACCES)
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/dbconsole.rb:186:in `find_cmd_and_exec'
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/dbconsole.rb:45:in `start'
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/dbconsole.rb:11:in `start'
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/commands_tasks.rb:86:in `dbconsole'
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
    from /Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/railties-4.2.0.rc3/lib/rails/commands.rb:17:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'

といったエラーになっていました。

Permission deniedというメッセージから/usr/local/var/mysqlの権限を確認するのですが、 /usr/local/varにて

$ ls -la
total 0
drwxr-xr-x   5 yuki3738  admin  170 10 18 02:14 .
drwxrwxr-x  24 root      admin  816 12 19 14:45 ..
drwxr-xr-x   3 yuki3738  admin  102 10 18 02:14 cache
drwxr-xr-x  14 yuki3738  admin  476  1  1 23:13 mysql
drwx------  16 yuki3738  admin  544  1  2 17:19 postgres

と、所有者に全ての権限はあるようなので、ワカラン!となっていました。

ググっても類似のエラーが見つからず

rails dbコマンドが使えないのでdbの中を見たい時は別途mysqlを起動していました。
しかし事あるごとにmysqlを起動している状況に業を煮やしエラーの解決を図ろうと時間のあるときに調べることにしました。

が、なかなかこれといった情報は見つからず、小一時間ほどで諦めることに。

部長に相談だ

弊社には新米エンジニアであるわたしのレベルを引き上げようと日替わりで先輩エンジニアがペアプロをしてくれるという弊社開発部長の@igaiga555(以下:iga)が制定してくださった素敵な制度があります。
その日は丁度部長が担当の日。普段は開発中のシステムでわからないことを相談するのですが、この日は上記の問題を相談しました。

バグだったりしてw

まずはrailsコマンドではなく通常の方法(mysql -uroot ...)で接続できるかを確認。
できる。
次に他の人はrails dbが起動できるか確認。
できる。

改めてエラーメッセージを見る。
iga「ん〜なんだろね〜。」
@asonasRailsのバグだったりして〜。」
iga「おっコントリビュートしちゃう!?」
みんな「ハッハッハー。」
と冗談を言いっていたのですが、これ全部フラグだったんだなぁと思うと感慨深いです。

Railsのコード・リーディングし始める部長

んじゃこの部分でRailsが何やってるか見てみよう、ということでRailsのコードリーディングが始まります。

とはいえ変数ばかりで最初のうちは何これ?ってなります。
full_path_commandってなんだ?dirs_on_pathって?foundって?

ええい、めんどくさい!pryで停めてしまえー!

というわけで停めたのが以下。

    171: def find_cmd_and_exec(commands, *args)
    172:   commands = Array(commands)
    173:
    174:   dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
    175:   commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
    176:
    177:   full_path_command = nil
    178:   found = commands.detect do |cmd|
    179:     dirs_on_path.detect do |path|
    180:       full_path_command = File.join(path, cmd)
    181:       File.executable?(full_path_command)
    182:     end
    183:   end
    184:
    185:   binding.pry # ←停めたとこ
 => 186:   if found
    187:     exec full_path_command, *args # ←エラーが起こっているとこ
    188:   else
    189:     abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
    190:   end
    191: end

各種変数は以下の通り。

[1] pry(#<Rails::DBConsole>)> commands
[
    [0] "mysql",
    [1] "mysql5"
]
[2] pry(#<Rails::DBConsole>)> dirs_on_path #私のマシンの設定に依るものです
[
    [ 0] "/Users/yuki3738/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bin",
    [ 1] "/Users/yuki3738/.rbenv/versions/2.1.4/bin",
    [ 2] "/Users/yuki3738/.rbenv/libexec",
    [ 3] "/Users/yuki3738/.rbenv/shims",
    [ 4] "/Users/yuki3738/.rbenv/bin",
    [ 5] "/usr/local/var",
    [ 6] "/usr/local/bin",
    [ 7] "/bin",
    [ 8] "/usr/sbin",
    [ 9] "/sbin",
    [10] "/usr/bin",
    [11] "/usr/local/go/bin"
]
[3] pry(#<Rails::DBConsole>)> found
"mysql"
[4] pry(#<Rails::DBConsole>)> full_path_command
"/usr/local/var/mysql"

どうやら、commandsとdirs_on_pathをdetectで回して、それぞれをjoinで結合しつつ、環境内にあるdbの実行ファイルを探しているようです。

マジでバグだ!

pryのお陰で簡単に何やってるかがわかり、なるほどねぇとなったのもつかの間、 full_path_commandにちゃんと"/usr/local/var/mysql"って値が入ってる。

これって冒頭のエラーメッセージ`exec': Permission denied - /usr/local/var/mysql のやつ。
でも所有者権限はある。
/usr/local/varにて

$ ls -la
total 0
drwxr-xr-x   5 yuki3738  admin  170 10 18 02:14 .
drwxrwxr-x  24 root      admin  816 12 19 14:45 ..
drwxr-xr-x   3 yuki3738  admin  102 10 18 02:14 cache
drwxr-xr-x  14 yuki3738  admin  476  1  1 23:13 mysql
drwx------  16 yuki3738  admin  544  1  2 17:19 postgres

ん〜、よくわからんなぁ。
ん〜、
ん〜、
んん?

あれ、
/usr/local/var/mysqlって、、、
ディレクトじゃん。
めっちゃdって書いてあるじゃん!

じゃあmysqlの実行ファイルはどこ?
$ which mysql
/usr/local/bin/mysql

こっちだった!!

なるほど、full_path_commandには/usr/local/bin/mysqlが入るべきだったと。
でもなんでディレクトリである/usr/local/var/mysqlがはいってるの?

full_path_commandの判定してるのは…
こいつだ。

181:       File.executable?(full_path_command)

ここがtrueになっちゃうのがいけないみたいだけど、File.executable?はディレクトリでも実行権限があればtrueを返す。

[1] pry(main)> File.executable?("/usr/local/var/mysql")
true

実行可能かを判定しているだけでファイルかどうかは判定していなかったわけですね。

直した!

ではここに、ファイルであることを確認するコードを加えてあげればよさそう。 というわけで修正を加えたものがこちら。

File.file?(full_path_command) && File.executable?(full_path_command)

File.file?を加えることによってfull_path_commandがディレクトリだとfalseを返すようにしました。

$ rails db
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 25919
Server version: 5.6.21-log MySQL Community Server (GPL)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

うおー、rails dbできたー!!!!!

英語つらい

じゃあプルリクしようという話しになったのですが…。

私「え、もしかして英語でプルリクしなきゃいけない系ですか?」
iga「そりゃそうだ。」
私「」

貧弱な語彙を駆使して一生懸命英文を書きました。
たぶん、コード書くより長い時間使いました…。

この期に及んでまたまた部長を頼ります。
出来上がった英文の推敲をお願いするわけですが、残念ながらほぼ最初から書き直していただくことになりました…。

英語がんばろう…。

プルリク!そしてマージ!

そんなこんなでできたプルリクがこちら。
https://github.com/rails/rails/pull/18049

(向こうの時間で)その日の内にマージされました。速かった。
軽微な修正だと無言のマージが多いみたいです。
(密かにネイティブのLGTMを期待してましたw)

最新のリリースにはまだ反映されていませんが、今後のリリースに加わるでしょう。

からかわれるようになりました

同僚からからかわれ、煽られるようになりました。

口頭でも定期的に発生します。やっかいな人たちです。

調子に乗りました

でも自ら使ってしまいました。

煽られた結果です。

でも名前が載っていたり、

f:id:yuki3738:20150106184448p:plain

 

すんごい人たちと一緒に写っていたりしてテンションあがります。

f:id:yuki3738:20150106184537p:plain

ためになったこと

何が起こっているか調べること

今までエラーになったらエラーメッセージで検索するぐらいしか能がなかったのですが、エラー箇所のコードを読みはじめた部長を見て何故そういったエラーが起こっているか把握することはとても大事だということを学びました。

Railsを疑うということ

/usr/local/var/mysqlが呼びだされていることがわかった時点においても私は、「あぁ、自分のzshrcの設定がいけなかったのかぁ」と思ってRailsのコードに手を加えるという発想がありませんでした。
からしたら天才たちの集大成であるRailsも、人の所業であるがゆえのミスだったり考慮漏れがあったりするわけで、「Railsが間違っているわけがない」という思い込みは完全に思考停止であるということ、「何がダメなのか」をしっかり見極めなければいけないということを学びました。

エラーは改善の始まり!?

というわけで私のコントリビュート劇は以上になります。
こんなマグレはなかなかないと思うので次のコントリビュートははてさて何年後か、むしろできるの?って話しなのですが、今度わけのわからないエラーが起きたらコントリビュートチャンス!?と密かに期待しつつコードリーディングをしたいなと思っています。

最後にタイトルに若干脚色が含まれていたことを謝罪したいと思います。
「新米エンジニアがRailsにコントリビュートした話」
と題しましたが、正確には
「新米エンジニアがベテランエンジニアの力をめっちゃ借りてRailsにコントリビュートした話」
でした。

今度は自分の力だけでコントリビュートできるよう、日々精進したいと思います。


再掲ここまで。