patorashのブログ

方向性はまだない

Vagrant + Hyper-V + Ubuntu18.04でRailsを動かせた

ずっとWindowsでの開発環境構築を試しているのですが、とりあえずベストではないにしてもベターまで来たかなと思うのでメモを残します。過去のトラブルはこちら。

patorash.hatenablog.com

patorash.hatenablog.com

なお、Hyper-Vを使わずにVirtualBoxを使うほうを強くお勧めします!(ハマりどころが少ないだろうから)

VagrantHyper-Vを選ぶ際のTips

VagrantでプロバイダをHyper-Vにすることができますが、制限事項がかなり多いです。

フォルダ共有にはsmbを選んだほうがよさそう

フォルダ共有はNFSがいいという意見がネットでは多いのですが、どうもVirtualBoxだとうまく動くみたいなのですが、Hyper-Vだと動かない模様。試行錯誤したのですが、私にはわからなかったため、smbを使って共有しています。以下のissueでは、Hyper-VだったらSMBでいいという話になっているみたいでした。

github.com

また、SMBでフォルダ共有を行う場合は、vagrant upを実行する都度、Windowsにログインするためのユーザ名とパスワードの入力を求められるので、これを最初はVagrantfileに直書きしていたのですが、vagrantのdotenvプラグインがあるとのことだったので、これを使うようにしました。

vagrant plugin install dotenv

これで、.envファイルにログイン情報を移動させることができます。

SMB_USER = "" # Windowsのユーザ名
SMB_PASS = "" # Windowsのパスワード

そして、これを使うようにVagrantfileを編集します。

# -*- mode: ruby -*-
# vi: set ft=ruby :
Dotenv.load # 追加

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.provider "hyperv"

  config.vm.synced_folder "~/vagrant",
    "/vagrant",
    create: true,
    type: "smb",
    mount_options: ["vers=3.0"],
    smb_password: "#{ENV['SMB_PASS']}",
    smb_username: "#{ENV['SMB_USER']}"

  # 略
end

これでOK…と思ったら、ところがどっこい、vagrant upするとホストのIPが見つからないというエラーで共有できませんでした。これは、接続する仮想スイッチのネットワークをパブリックとワークからプライベートネットワークにすれば解決しました。上のほうに載せている過去記事を見ていただければと思います。

Vagrantのポートフォワーディングは使えない

とりあえずこれで、Windows側とHyper-V上のUbuntuでフォルダ共有できたので、Vagrantfileを修正してポートフォワードするようにして、Ubuntu側でRailsを起動してみました。

# 追加
config.vm.network "forwarded_port", guest: 3000, host: 3000, host_ip: "127.0.0.1"

が、うんともすんとも言いません…。調べてみると、どうもHyper-Vの場合はネットワークの設定は全て無視されるようです。 なので、上記の設定は何の意味もない模様。

では、Hyper-Vの場合はどうすればいいのか?Windows側でポートフォワーディングの設定を行う必要があります。参考にちょうどよいページがありました。

4thsight.xyz

これを参考に、設定してみます。192.168.212.51は、VM側に設定されているIPです。

netsh interface portproxy add v4tov4 listenport=3000 listenaddress=127.0.0.1 connectport=3000 connectaddress=192.168.212.51

これで、VM側の3000番ポートがホスト側にポートフォワードされるようになりました。Windowsのブラウザでhttp://localhost:3000にアクセスしたら、Ubuntuで起動中のRailsアプリにアクセスできました!なお、Railsアプリを起動するときはbin/rails s -b 0.0.0.0を忘れずに!!

これで、とりあえずやった!というお気持ちを表明。

Hyper-Vの仮想スイッチ(Default Switch)は使えない

ここで、過去にPCを再起動したらVMのIPが変わっていたことを思い出しました。

ということで、Windowsを起動すると仮想スイッチのDefault SwitchのIPがコロコロと変わり、全く意味がない模様。

yamanxworld.blogspot.com

せっかくnetshでポートフォワードの設定を書いたのに、台無しです…。こうなると、自分で仮想スイッチを定義するのがよさそうです。しかし、自分で作った仮想スイッチだとVMにIPの割り当てを行ってくれず、VM側でIPを設定する必要がある模様。なんとか自動でIPをふる方法を調べていたのですが、見つからなかったので、公式の言う通りにしてみます。

docs.microsoft.com

仮想スイッチの作成とNATの設定

以降の処理は、PowerShellで、管理者権限で。まず、Switchを作ります。

New-VMSwitch -SwitchName "VagrantNAT" -SwitchType Internal

次に、Switchの情報を見ます。ifIndexが必要な情報となります。

Get-NetAdapter

Name                      InterfaceDescription                    ifIndex Status       MacAddress             LinkSpeed
----                      --------------------                    ------- ------       ----------             ---------
vEthernet (VagrantNAT)    Hyper-V Virtual Ethernet Adapter #2          10 Up           00-15-5D-8F-1B-31        10 Gbps
# 他は省略

次に、仮想スイッチにIPを割り当てます。今回は192.168.100.1にしました。-InterfaceIndexに先ほどのifIndexの値を指定します。

New-NetIPAddress -IPAddress 192.168.100.1 -PrefixLength 24 -InterfaceIndex 10

次に、NATネットワークを作ります。

New-NetNat -Name VagrantNATnetwork -InternalIPInterfaceAddressPrefix 192.168.100.0/24

これで、ネットワークは完成です。VMへのIPの割り当ては別途VMに入って行います。

VM側のIP設定を行う

VMはとりあえずvagrant upを行います。IPは割り当てられませんが、作業にはvagrant sshが使えるので問題ありません。

vagrant sshを使ってVMの中に入り、IPを設定します。Ubuntu18.04からIPの設定方法が変わった模様です。

jyn.jp

sudo vi /etc/netplan/01-netcfg.yaml

これで、以下のように設定しました。

network:
  version: 2  
  ethernets:
    eth0: 
      addresses:
        - 192.168.100.2/24
      gateway4: 192.168.100.1
      dhcp4: false
      nameservers:
        addresses:
          - 8.8.8.8

nameserversは、192.168.100.1にしたらapt-getに失敗したので、8.8.8.8にしています。 そして、netplanの設定を反映します。

sudo netplan apply

これで、VMに固定IPが設定されました。

ポートフォワードの設定をやり直し

既存の設定は意味がないので、resetで削除し、先ほど登録したVMのIPでポートフォワードの設定をやり直します。

netsh interface portproxy reset
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=127.0.0.1 connectport=3000 connectaddress=192.168.100.2

これで完了!VM側でRailsアプリを起動したところ、http://localhost:3000でアクセスできました🎉🎉🎉

なお、PCを再起動しても問題ありませんでした!ただ、Vagrant自動起動にしていたらSMBの共有に失敗していたので、vagrant reloadしたらうまくいきました。

まだまだ開発環境としては問題がありそうなのですが(SMB上にRailsアプリを置いてrails sすると遅い…)、とりあえず動かせたので、よしとします。

RubyKaigi2019に参加してきた

元号が令和になりました。GW楽しんでいますでしょうか? さて、10日以上過ぎてしまいましたが、4月18日~20日に福岡で開催されたRubyKaigi2019に参加してきましたのでそのことについて書こうと思います。

弊社からは私を含む3名が参加しまして、岡山から車で移動しました。道中は仕事のこととか話しつつ、大体6時間くらいで到着しました。

0日目

前日入りしたのですが、その日はLINE 福岡さん主催のLINE Developer Meetupに参加しました。

line.connpass.com

LINEさんのオフィスに行ってみたかったというのが主なところでしたが、監視に関する発表を聞き、懇親会ではElasticの大谷さん(@johtani)にご挨拶することができたのでよかったです。担当プロジェクトでElasticsearchを使っているのですが、困っているときにたくさんのアドバイスを頂いて助かっています😃ElasticStackを使った監視にも、かなり興味が湧いたので、個人のプロジェクトとかでやってみたいと考えています。

LINEさんの懇親会では寿司とピザが出てきて、イケてる会社の勉強会って感じでした。LINEの中の方とも多少お話することができまして、LINEはRubyはほとんど使ってなくて、バックエンドはJavaとKotlinと伺いました。なので、「あー、RubyKaigiあるんだなぁ」くらいの感じでした。

1日目

博多駅周辺にホテルを取っていたので、移動は3日間ともSmartHRさんがスポンサーをされていたシャトルバスで移動しました。これはとてもありがたかったです🙏

Ruby3について

Ruby3x3に向けての取組について、Keynoteと途中経過レポートで聞くことができました。Matzは型を書きたくないので、型アノテーションのような仕組みは使いたくないとのことで、型プロファイルを行って型ファイル(拡張子はrbi)を自動生成するようにしたいとのことでした。2.6でMJITが入ってRubyの性能は上がったけれどRailsで遅くなったりと、なかなか高速化の道は難しそうです。あとは並行性を良くするための取組についてもありましたが、GuildやAutoFiberなど、既にありそうな名前と競合していて、名前付けも難しい…。

社内報告会をしたときに、「sorbetみたいに型アノテーションを書くことで、処理を手堅くしたいところだけでも使えたらすごくいいのにな」という意見もありました。

OpenAPI3について

OpenAPIはあまり詳しくなかったのですが、APIの話なので聞きに行ってみました。OpenAPI3はAPI Schemaファーストで開発をするための仕組みで、OpenAPI3でスキーマを定義すると、サーバサイド、クライアントサイドでの開発が捗るという話でした。

  • OpenAPI3はRESTの拡張のため、既存の資産を生かせる。RailsはRESTベースなので導入しやすい。
  • 実装にスキーマ定義を守らせるための仕組み(gem committee)を導入し、実装が乖離しないようにすることができる。
  • openapi_parserを使って定義ファイルからRubyObjectに変換できる。

今ちょうどGraphQLを使ったAPI実装をしている最中なのですが、これを知っていたらOpenAPI3でもよかったかなという気持ちになりました。ただ、不要なフィールドを渡さないような仕組みがRESTだとないような気がしているので、やはり自分の利用にはGraphQLのほうが合ってたかなとは思います。

RMagickについて

Rubyistならほぼ誰しもがお世話になっていると思われるRMagickのお話でした。RMagickはRubyKaigi2019現在、最新は3.1.0となっていて、不具合をかなり潰したので最新のRMagickと最新のImageMagickを使ってほしいとのこと。今回は2系から3系に上げるときに行ったことについての発表でした。

  • MacWindowsのインストールでこけないようにした
  • メモリに関する不具合をたくさん潰した

とのことでした。MacでRMagickを入れようとすると、結構な頻度でエラーが起きていたのですが、homebrewで入れたImageMagickを検知してエラーを起きにくくしたということでした。Windowsも似たような感じだったかと思います。メモリの不具合に関しては、メモリリークが発生していたパターンの紹介や、ImageMagick自体のメモリリークの不具合を報告したりなどをされていたということでした。そのissueもcloseされて新バージョンがリリースされていたので、6系の最新のImageMagickを使うのがやはりよいそうです。

7系への対応は今年いっぱいくらいかかりそうで、6系と7系の対応をどうするかでメンテナで意見が分かれているけれど、1つのバージョンで6系、7系両対応のほうが優勢らしいです。

これもまた社内勉強会では、「メンテナが大変になるし、バージョン分けてもいいんじゃないか?」という意見がありました。

GraphQLについて

決済サービスのSQUAREでGraphQLを使ったという話だったのですが、メインどころの話はGraphQLの型定義が膨大な数に及ぶのでメタプログラミングした、という話のように思えました。方針としてはわからなくはなかったのですが、そこでメタプログラミングするとGraphQLの型ファイルドキュメントを作りにくいから微妙だなと感じました。せめて型定義ジェネレータを作るほうが筋がいいのでは?🤔

オフィシャルパーティー

オフィシャルパーティーは川端商店街を貸し切っての、400mの懇親会場で商店街を練り歩きながら好きなところでご飯や飲み物をもらって好きなテーブルで話をするという感じでとてもダイナミックなイベントでした。しかしこれがめちゃくちゃ良くて、普通の広い一間の懇親会よりも交流がしやすかったですし、外国人の方々は商店街でお土産を買うことができるし、オフィシャルパーティー終了後に近くの飲み屋に移動することも簡単なのでその付近にたくさんのお金が落ちるという仕組みで最高じゃないかと思います(運営の皆さんはそのぶん大変だとは思いますが!)

ふだんから人見知りの私ですが、いつもよりオープンな会場だったので、フォロワーの方とお話できたり、そのつてで大阪・京都界隈の方たちともお話できたり、以前に自分のgemにPRをしてくださった、はてな@onkさんとお話できたのでよかったです。

2日目

2日目と3日目は会場で朝食が食べられるように、Medleyさんが朝食スポンサーをされていました。今回は朝食を会場でいただくことができて助かりました。明太子がおいしかった!

Rubyの安定版のメンテナンス

Rubyの安定版のメンテナをされている@nagachikaさんのKeynoteでした。安定版のメンテナを募集とのことと、メンテナとして気を付けていることや失敗談などを共有してくださいました。こういう方々に支えられているのだなと、感謝しきりでした。

CrystalBall

テスターの方の発表でした。プロジェクトが大きくなってくると、テスト対象のファイルも膨大になり、テストにかかる時間も長くなります。それを、gitの変更点からテスト対象を絞り込んで該当しそうなテストのみを実行するという仕組みとしてCrystalBallを作ったということでした。

これはテスト戦略としてはとても素晴らしいと共感しました!変更点だけになれば相当なテスト時間を削減できます。ただ、全体テストが不要になるわけではないので、ローカルでテストを実行する場合はCrystalBallでテストを行い、CIに全体テストを任せる、ということなのかなと思いました。入れてみてもいいなと考えています。

フロントエンドフレームワーク Ovto

OvtoはhyperappをRubyで書けるJSトランスパイラーのOpalで移植したフレームワークです。発表者のyharaさんがDIYをするために作成されたとのことで、今回の発表はOvtoの2人目のユーザーを作るためとのことでした。「ちょっと最高のフレームワークができたので共有したくて」という感じで和やかに始まりましたが、私もOvto使ってみたいなぁ~と思いました。

フロントエンドをRubyで書く意味って?と言われそうですが、Rubyで書けたら面白いじゃん!と思うので、個人プロジェクトとかで試してみようかなと思います。 なお、Ovtoについては、るびまに記事が上がっています。

magazine.rubyist.net

TruffleRubyでChrome DevToolsを使ったデバッグ

Chrome Devtools Protocolというプロトコルがあり、それを使うとChrome Devtoolsのデバッガを使ってどんな言語でもデバッグできるらしいです。私の解釈ですが、GraalVMで動作する言語であれば、Chrome Devtools Protocolを使うことができるのでデバッグが容易になった、ということだと思いました。RubyのオブジェクトがChrome Devtools上で見られるようにJSのオブジェクトにうまくマッピングされていてすごい!と感じました。

LT

LTはどれも面白かったのですが、印象に残っているのは、Unicode元号ライブラリの令和対応のやつでした。あとmrubyを搭載した人工衛星が飛ぶという話。

PRTimes Drinkupに参加

2日目はPRTimesさんが主催のドリンクアップに参加してきました。LT枠で申し込んでいたため、LTしましたが、プロジェクタの後ろのほうで話すことになったり、会場がガヤガヤしていてあんまり聞こえてなかったみたいでした😭しかし、それキッカケで話すことができた方もいましたし、やってよかったなと思います。いろんな方と話すことができて楽しかったです!PRTimesさん、ありがとうございました!

実のところ、LTするから緊張しすぎてあんまりご飯が喉を通らなかったため、終わってからとんこつラーメンを食べに行ってきました。やはり博多、めっちゃうまかったです。

3日目

Committers VS the World

事前に集めた質問にRuby Committerさん達が答えてくれるという恒例のセッションでしたが、Rubyの進歩についてとか、記法どうする?とかの話題で面白かったです。マルチバイト文字(絵文字)で遊ぶやつ、いいですね~。

🍣 = Sushi.new

そして、大きなニュースとしては、Rubyリポジトリsvnからgitになるとのことでした。RubyKaigi後のやりとりを見ているとgitになってからのほうが大変そうでしたが、慣れるまでの時間だと…いいな…。

Numbered Parametersに関しては、私はGroovyやKotlinと同じくitがいいのですが、itはRSpecで使っているからという意見もあって、難しそう…。

(1..10).map { @1 * 10 } # 現在の候補は@1の模様
(1..10).map { it * 10 } # 個人的にはこれがいい

巨大なアプリケーションのコードのクリーンアップ

Cookpadの中の人による、成長したアプリケーションのコードの削除についてのお話でした。CookpadRailsになって10年が経ち、削除した機能などがあるのだけれど、コードベースでは残ってしまっているものが多数あるとのこと。それを削除するための取組についてでした。

面白かったのが、使われていないコードをあぶりだすため、処理が実行されたらログを出すようにするのにRubyコミッターにRuby自身のコードに処理を仕込んでもらった、というところでした。これぞ、コミッターを会社で雇っている実践的な活用法ですごい!と思いました。自分的には「その発想はなかった!」と唸りました。

あとはoneshot_coverageという行単位で実行されたらログを出すやつも紹介されていて、よさそうでした。SimpleCov形式で実行結果がみられるのが便利そう。 これらの取組によって、CookpadRailsアプリの起動が1.5秒改善されたとのことで、地道な改善は大事だなと思わされました。

WebAPIクライアント開発のベストプラクティス

今回のRubyKaigiで個人的に一番よかったのは@sue445さんの、この発表でした。

WebAPI Clientを作るための知見がめっちゃ詰まっていてとてもよかったです!👌WebAPI Clientの責務やどこまで面倒を見るか等は、ついついやりすぎてしまう場合がありそうなので、参考になりました!私もこれから担当製品のWebAPIとそのClientを作っていかなければなーと思っていたところだったので気を付けたいと思います。faradayを使ったことが多分なかったと思うので、今後はfaradayを使っていくようにしようと思いました。

dRubyワークショップ

なんとなく気になっていたdRubyのワークショップに参加。Rubyのオブジェクトを異なるプロセス間で受け渡して処理が実行できるという不思議さ…。うーん、本当に不思議。

dRubyの本を読んでもわからないところがあったら、もう一冊買ってくださいって言っててウケました😂

The send-pop optimization

@shyouheiさんのRubyの最適化のお話でした。Rubyは処理毎に必ず返り値があるのだけれど、必ずしもそれを利用することがないため、Ruby側で返り値を利用していない場合は返り値を返す処理を実行しないようにすれば速くなるのではないか?という仮定で取り組んだとのことでした。

ベンチマークの結果、遅くなる処理もあったけれど突出して速くなる箇所もあったとのことで、採用されたそうです(たしか)。しかし、Railsの起動には0.3秒程度遅くなったとのこと。返り値を使っているかの検証処理が挟まれたため、仕方がないのかもしれませんが、動き出したらトータルでは速くなるのかな~?と思います。

Keynote

データベース用のライブラリであるSequelの作者の方のキーノートでしたが、最適化の鬼という感じで「これはもうRubyである必要があるのか???」と思いながらも楽しく(?)見ていました。出てくるコードがすべてハチャメチャでした。

閉会

あっという間の3日間でしたが、とても楽しく、とても刺激的でした。次回は長野県の松本市とのことだったので、来年どうやって行こう?🤔と今から悩んでいます!運営の皆様、お疲れ様でした&ありがとうございました!

全体を通して

我々はRubyというプログラミング言語と通して知り合い、そしてRubyKaigiで一同に集まり、ここで直接ご本人に会って感謝を伝えることができたりする貴重な機会でもあるので、人見知りだからといって恥ずかしがっているのはほんまに勿体ないなーとヒシヒシと感じました。今回は結構頑張ってお世話になっている様々な方にご挨拶できましたし、アフターパーティーにあぶれた人たちで集まって飲み屋さんに行って交流できたりもしたので、充実したRubyKaigiとなりました。

昼食の屋台や、コーヒーを頂いたり、スポンサーブースで色々お話できたことなど、本当にたくさん楽しませていただきました。新たな試みが多くて思わず人に話したくなるカンファレンスでした!

続・WindowsのVagrantでフォルダ共有でハマったのでメモ

先日、WindowsVagrantのフォルダ共有でハマったのでメモ、という記事を書きました。

patorash.hatenablog.com

この時に、SMBでフォルダ共有をする際にipconfigで取得したIPをsmb_hostに設定すると書いていたのですが、PCを再起動したらこのIPが変わってしまいました😰PCを再起動する度にipconfigをしてsmb_hostを書き換えるのは現実的ではないな…と思ったので、回避策を探したのですが、全然見つかりませんでした。そもそもsmb_hostを設定していない例ばっかりが出てくるのです。これはおかしい…。

PS > vagrant reload
==> default: Attempting graceful shutdown of VM...
    default: Configuring the VM...
==> default: Starting the machine...
==> default: Waiting for the machine to report its IP address...
    default: Timeout: 120 seconds
    default: IP: fe80::215:5dff:fe8f:1b2a
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: fe80::215:5dff:fe8f:1b2a:22
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Machine booted and ready!

Vagrant requires administrator access for pruning SMB shares and
may request access to complete removal of stale shares.
==> default: Preparing SMB shared folders...

Vagrant requires administrator access to create SMB shares and
may request access to complete setup of configured shares.
==> default: Mounting SMB shared folders...
We couldn't detect an IP address that was routable to this
machine from the guest machine! Please verify networking is properly
setup in the guest machine and that it is able to access this
host.

As another option, you can manually specify an IP for the machine
to mount from using the `smb_host` option to the synced folder.

仕方がないのでホスト側のIPを取得する処理をVagrantfileに書く、というアプローチを取ろうとしたのですが、インターフェースの情報は取れているのに、IPは取れない。そもそもそれが出来ていたらVagrantもできてるやろ…と後で思いましたが、以下のを試していました。

qiita.com

これをやってもAddrオブジェクトがnilを返してダメでした。

その後、CentOSにしてみる、SSHして固定IPを設定するなど試しましたが、全部ダメ😥。ゲスト側からホスト側のIPにpingを打っても音沙汰なし。しかし、ホスト側からゲスト側のIPにはpingが通りました。ここで再び、ホスト側(Windows)のネットワークの設定を疑いました。

結論からいうと、Vagrantで作られたDefault Switchのネットワークがデフォルトでパブリックになっていることが原因でした。パブリックの場合、他のPCなどから自分のPCが見えないようにする設定がされています。そのため、ゲスト側からホスト側が見えなかったのでしょう。Default SwitchのネットワークはPCの中だけだし、プライベートに変更しました。

カスペルスキーの設定にはなりますが、以下のように設定していきます。

f:id:patorash:20190413120548p:plain
設定画面を開く

f:id:patorash:20190421031215p:plain
ネットワークをクリックする

f:id:patorash:20190421031221p:plain
Vagrantで選択したネットワークがパブリックだったらプライベートに変更する

これで、SMBの設定ではsmb_hostを省略できるようになりました👌設定は以下の通り。

# フォルダ共有設定
config.vm.synced_folder ".",
  "/vagrant",
  create: true,
  type: "smb",
  mount_options: ["vers=3.0"],
  smb_username: '**********' # Windowsのログインユーザー名
  smb_password: '**********', # Windowsのログインパスワード

教訓

ググっても解決策がない場合、FireWallとネットワークの公開範囲設定を疑おう👊

WindowsのVagrantでフォルダ共有でハマったのでメモ

Windowsで開発環境構築をしようとしていていろいろ模索中。

VagrantUbuntuを入れてフォルダ共有で作業しやすくしようと思って色々試したのだけれど、かなり四苦八苦したのでメモを残す。 なお、VagrantのプロバイダはHyper-Vで入れてる。

NFSでファイル共有できず

よく見かける記事はNFSで共有とあるのだけれど、

Windows users: NFS folders do not work on Windows hosts. Vagrant will ignore your request for NFS synced folders on Windows.

と書いてあるように、使えないらしい。

www.vagrantup.com

が、vagrant-winnfsdプラグインを入れれば使えるらしい。

が!😰結論からいうとHyper-Vを使っているからなのか、使えなかった。詳細は、以下のissueを見るとよい。

github.com

結局、vagrant-winnfsdプラグインは削除した。VitrualBoxを使っている人は問題ないと思います😊

sambaで共有する

先ほどのissueに書いてあるけれど、Hyper-Vでやる場合はsambaがいいらしい。速さも遜色ないよ、と。

ということで、sambaでやることに。smb_usernamesmb_passwordは書いておくとvagrant upの都度聞かれなくて済む。 ただ、ハードコードするのはなんか嫌なので、環境変数とかにしたい(後の課題とする)。

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.provider "hyperv"

  # フォルダ共有設定
  config.vm.synced_folder ".",
    "/vagrant",
    create: true,
    type: "smb",
    mount_options: ["vers=3.0"],
    smb_host: '172.17.112.161', # ipconfigで取得したvEthernet (Default Switch)のIP
    smb_username: '**********' # Windowsのログインユーザー名
    smb_password: '**********', # Windowsのログインパスワード

  config.vm.provider "hyperv" do |hyperv|
    hyperv.cpus = 4
    hyperv.memory = 4096
    hyperv.maxmemory = 1024 * 16
  end
end

ファイアウォールの設定を変更する

設定はこれでOKなのだけれど、まだダメだった…。 原因は、Windowsに入れてるアンチウィルスソフトのファイアウォール機能だった。これに気づけなくて数時間を無駄にした😫

私はアンチウィルスソフトにカスペルスキーを使っているので、その変更方法を書いておく。

f:id:patorash:20190413120548p:plain
設定画面を開く

f:id:patorash:20190413120626p:plain
ファイアウォールの設定画面を開く

f:id:patorash:20190413120755p:plain
パケットルールの設定を開く

f:id:patorash:20190413120943p:plain
Local Servicesの設定を、ブロックから許可に変更する

これでsambaで使用するポートが解放されるため、WindowsVMが通信できるようになった。

ActiveRecordのmodel名が〜Typeで終わるものをgraphql-rubyで扱う

なかなか情報がなかったので、書いときます。

model名が◯◯Typeで終わることって、ままあるのかなと思います。CompanyモデルとCompanyTypeモデルのような感じで。これをGraphQLでTypeを定義しようとすると、Types::CompanyTypeTypes::CompanyTypeTypeになってしまいます。読みにくすぎる…。

後ろにTypeがつかないといかんのか?っていうところも疑問だったのですが、別にそんなルールもないみたいでした。 GraphQL - Scalars をみていたら、class Types::Url < Types::BaseScalarとやっていたので。

また、デフォルトのディレクトリ構造のままだとtypesディレクトリに大量のファイルが作られて見辛くなってきたので、ディレクトリを分けました。

blog.spacemarket.com

ここを参考に、ほぼ同様の構成にしました。

app/graphql/enum_types/
           /input_types/
           /interface_types/
           /mutations/
           /object_types/
           /scalar_types/
           /types/
           /union_types/

ディレクトリ名を名前空間として、ファイル名をクラス名にした構成にしないといけないため、ファイルの移動後はリネーム祭りに。 Types::FooTypeObjectTypes::Fooのように修正です。

これで、Types::CompanyTypeObjectTypes::Companyへ、Types::CompanyTypeTypeObjectTypes::CompanyTypeへと分かりやすく変更できてよかった!と思っていたのですが、これだけだと動きません。

query {
  company(id: 1) {
    id
    name
    companyType {
      id
      name
    }
  }
}

上記のようなクエリを発行しようとすると、エラーで落ちました。

{
  "error": {
    "message": "Duplicate type definition found for name 'Company' at 'Field Company.companyType's return type' (ObjectTypes::Company, ObjectTypes::CompanyType)",
    "backtrace": [
      # 略

Companyで定義されているcompanyTypeに当てはまるTypeが重複してるぞ!って言われてます。

module ObjectTypes
  class Company < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    field :company_type, ObjectTypes::CompanyType, null: false # ここ
  end
end

いやいや、ObjectTypes::CompanyTypeって指定してるじゃん!って思うんですが、メタプログラミングしてるんでしょう。多分、別名をつけるメソッドとかあるだろう…と思ってググるDSL形式の頃のgraphql-rubyのコードだったけれど、name = 'CompanyType'みたいなことをしているページを発見。

Shinosaka.rb #27 (GraphQL) に参加した - @znz blog

CompanyTypeのほうに、雑にname 'CompanyType'を追加してみます。

module ObjectTypes
  class CompanyType < Types::BaseObject
    name 'CompanyType' # 雑に追加

    field :id, ID, null: false
    field :name, String, null: false
  end
end

その後、適当にクエリを投げてみると、エラーメッセージが変わりました。

{
  "error": {
    "message": "The new name override method is `graphql_name`, not `name`. Usage: graphql_name \"CompanyType\"",
    "backtrace": [
      # 略

graphql_nameメソッドで別名を定義しろと。

module ObjectTypes
  class CompanyType < Types::BaseObject
    graphql_name 'CompanyType' # 修正

    field :id, ID, null: false
    field :name, String, null: false
  end
end

すると、エラーが消え、ちゃんとcompany(id: 1)に紐づくCompanyTypeの情報が取得できました😀

追記:rails cで検証

検証のために、該当箇所をコメントアウトしてみます。

module ObjectTypes
  class CompanyType < Types::BaseObject
    # graphql_name 'CompanyType' # コメントアウト

    field :id, ID, null: false
    field :name, String, null: false
  end
end

rails cして確認します。

$ bin/rails c
Loading development environment (Rails 5.2.2.1)

Frame number: 0/16
[1] pry(main)> ObjectTypes::CompanyType.graphql_name
=> "Company"
[2] pry(main)> ObjectTypes::Company.graphql_name
=> "Company"

CompanyTypeのgrqphql_nameはCompanyとなってしまい、重複してますね。その後、コメントアウトを解除して再度rails cしてみます。

$ bin/rails c
Loading development environment (Rails 5.2.2.1)

Frame number: 0/16
[1] pry(main)> ObjectTypes::CompanyType.graphql_name
=> "CompanyType"
[2] pry(main)> ObjectTypes::Company.graphql_name
=> "Company"

重複しなくなったのでOK!

GraphQLのスキーマ情報からドキュメントを生成する

漠然と考えていたことが既に書かれていたので、非常に参考になったし面白かった。

developer.kaizenplatform.com

GraphQL APIの設計を中心にフロントエンド、バックエンドをそれぞれ作っていくと、それぞれの実装待ちが発生しなくてよい。しかもAPI仕様のドキュメントを作成することも簡単だった。

graphql-rubyからGraphQLのスキーマ情報を作成する

Railsプロジェクトでgraphql-rubyを使ってスキーマ情報を生成するのは簡単。Rakefileに以下を追記して、rake taskを追加する。

#!/usr/bin/env rake

require File.expand_path('../config/application', __FILE__)
require 'graphql/rake_task' # 追加
Rails.application.load_tasks

GraphQL::RakeTask.new(schema_name: 'SampleSchema') # 追加

確認してみる。

$ bin/rake -T | grep graphql
rake graphql:pro:validate[gem_version] # Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org
rake graphql:schema:dump               # Dump the schema to JSON and IDL
rake graphql:schema:idl                # Dump the schema to IDL in ./schema.graphql
rake graphql:schema:json               # Dump the schema to JSON in ./schema.json

rake graphql:schema:dumpで、schema.graphqlとschema.jsonを生成してくれるようになった。

ドキュメントHTMLの生成

ドキュメントHTMLの生成も簡単だけれど、実は結構ハマった。

さきほどの参考記事では、graphdocが紹介されていて、最初はそれを使おうとしたのだけれど、schema.graphqlからドキュメントを作成するときにエラーになった。どうもコメントに対応できていないらしい…。また、リポジトリが1年以上メンテされてないみたいでこれ大丈夫か???と思っていた。 すると、どうもgraphidocsにフォークしてメンテナンスされているらしい。そのため、npmパッケージの名前もgraphdocからgraphidocsに変わっていた。

graphidocsorg.github.io

以下で、グローバル環境にインストールできる。

npm install -g @graphidocs/docs

しかし、node-gyp rebuildが走ってエラーが出ていた(ただ、インストール自体は成功していた…)。 解決方法がわからなかったので(Nodeを6系までダウングレードすれば通る、みたいなのはあったが、今は10系を使っているから嫌だった)、エラーメッセージは放置した。

ただ、プロジェクトのドキュメントを作るのにグローバルにインストールしたくないなぁと思って、そこは変更した。

$ yarn add @graphidocs/docs --dev

エンドポイントからドキュメントを作る

既にGraphQLのAPIがあるのであれば、そこをエンドポイントして指定して実行すれば、生成してくれる。ローカルでやるのであれば、bin/rails sをした状態で、以下を実行する

$ yarn run graphidocs -e http://localhost:3000/graphql -o ./doc/schema

毎回オプションをつけるのは大変なので、package.jsonにオプションを設定しておくことも可能。

{
  "dependencies": {
    # 省略
  },
  "devDependencies": {
    "@graphidocs/docs": "^1.0.4"
  },
  "graphidocs": {
    "endpoint": "http://localhost:3000/graphql",
    "output": "./doc/schema"
  }
}

こうすると、以下でOK。

$ yarn run graphidocs

スキーマからドキュメントを作る

しかし、エンドポイントからドキュメントを作るのでは、さきほどの参考記事のようにフロントエンド、バックエンドでAPI仕様を議論しながらは進められない。必ずバックエンドの実装後になってしまうので、やはりスキーマ情報からドキュメントを作りたい。

スキーマ情報からドキュメントを作るには、オプションを変更する。既にある程度API実装があるのであれば、先ほど追加したrake taskを実行して、スキーマ情報をダンプしておく。

$ bin/rake graphql:schema:dump # schema.graphqlとschema.jsonを生成
$ yarn run graphidocs -schema ./schema.graphql -o ./doc/schema

ここでは、schema.graphqlを指定したが、schema.jsonでも動く。

こちらも毎回オプションをつけるのは大変なので、package.jsonにオプションを設定できる。

{
  "dependencies": {
    # 省略
  },
  "devDependencies": {
    "@graphidocs/docs": "^1.0.4"
  },
  "graphidocs": {
    "schemaFile": "./schema.graphql",
    "output": "./doc/schema"
  }
}

こうすると、以下でOK。

$ yarn run graphidocs

これで、schema.graphqlを介してフロントエンド、バックエンドの人で意見交換して、ドキュメントを生成してそれぞれが開発する土台ができたかなと思う。

GraphQLのドキュメントを見るためのWebサーバを定義する

ドキュメントは./doc/schema配下にできたものの、そこを開きにいくのも面倒なので、WEBrickでWebサーバを作った。 プロジェクトのルートフォルダにgraphidocs.rbを作成した。

# graphidocsで生成したgraphqlのスキーマ情報を見るためのサーバを立ち上げます。
# ./doc/schemaが存在しないか古い場合、まず`yarn run graphidocs`を実行してください。
# その後、`ruby graphidocs.rb`を実行してください。
require 'webrick'
include WEBrick
server = HTTPServer.new(
  DocumentRoot: './doc/schema',
  BindAddress: '0.0.0.0',
  Port: 8000
)

%w(INT TERM).each do |sig|
  trap(sig) { server.shutdown }
end
server.start

これを実行する。

$ ruby graphidocs.rb
[2019-04-03 10:53:47] INFO  WEBrick 1.4.2
[2019-04-03 10:53:47] INFO  ruby 2.5.5 (2019-03-15) [x86_64-darwin16]
[2019-04-03 10:53:47] INFO  WEBrick::HTTPServer#start: pid=4858 port=8000

http://localhost:8000 にアクセスすると、GraphQLのドキュメントが読めるようになった。サーバの終了はCtrl + CでOK。

プロジェクト配下のnodeのコマンドを実行する

Rubyでいうところのbundle exec 〜に該当するNodeのコマンドってなんだろう?と思って雑に呟いたら、色々と教えてもらえました。

というわけで、yarn run 〜がよさそう。npxもいいけれど、間違えて入れていないパッケージの命令を書いたらその場でダウンロードが始まるので遅いし。