【再掲】新米エンジニアがRailsにコントリビュートした話
本記事は7年前の2015年1月、私が前職の技術ブログに投稿した記事の再掲版となります。 元のブログが閉鎖していたのでこちらに移します。
前職の会社が買収された後、文章は取っておいたのですが、特に読まれることもなかろうと下書きで放置していました。
この度伊藤さんのtweetをきっかけに再掲することにしました。
久々にこの記事を読もうと思ったんですが、もうアクセスできなくなっちゃってますね。。
— Junichi Ito (伊藤淳一) (@jnchito) 2022年1月10日
南谷さんの個人ブログに転載できたりしないかな〜?
伊藤さん、掘り起こしありがとうございました。
初めての技術ブログに思っていた以上に反響があって当時はとても嬉しかったのをよく覚えています。 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「ん〜なんだろね〜。」
@asonas「Railsのバグだったりして〜。」
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)
最新のリリースにはまだ反映されていませんが、今後のリリースに加わるでしょう。
からかわれるようになりました
同僚からからかわれ、煽られるようになりました。
コントリビュート先輩
— やまま (@kirikiriyamama) 2014, 12月 17
コントリパイセン語呂がいいので積極的に使って行く
— Kuniaki Hori (@HolyGrail) 2014, 12月 17
口頭でも定期的に発生します。やっかいな人たちです。
調子に乗りました
でも自ら使ってしまいました。
そうです、わたしがコントリパイセンです。 http://t.co/C5O9c86Cru
— Yuki3738 (@yuki3738) 2014, 12月 21
煽られた結果です。
でも名前が載っていたり、
すんごい人たちと一緒に写っていたりしてテンションあがります。
ためになったこと
何が起こっているか調べること
今までエラーになったらエラーメッセージで検索するぐらいしか能がなかったのですが、エラー箇所のコードを読みはじめた部長を見て何故そういったエラーが起こっているか把握することはとても大事だということを学びました。
Railsを疑うということ
/usr/local/var/mysqlが呼びだされていることがわかった時点においても私は、「あぁ、自分のzshrcの設定がいけなかったのかぁ」と思ってRailsのコードに手を加えるという発想がありませんでした。
私からしたら天才たちの集大成であるRailsも、人の所業であるがゆえのミスだったり考慮漏れがあったりするわけで、「Railsが間違っているわけがない」という思い込みは完全に思考停止であるということ、「何がダメなのか」をしっかり見極めなければいけないということを学びました。
エラーは改善の始まり!?
というわけで私のコントリビュート劇は以上になります。
こんなマグレはなかなかないと思うので次のコントリビュートははてさて何年後か、むしろできるの?って話しなのですが、今度わけのわからないエラーが起きたらコントリビュートチャンス!?と密かに期待しつつコードリーディングをしたいなと思っています。
最後にタイトルに若干脚色が含まれていたことを謝罪したいと思います。
「新米エンジニアがRailsにコントリビュートした話」
と題しましたが、正確には
「新米エンジニアがベテランエンジニアの力をめっちゃ借りてRailsにコントリビュートした話」
でした。
今度は自分の力だけでコントリビュートできるよう、日々精進したいと思います。
再掲ここまで。