めもめも のーと

ハマったこととか、覚えたこととか

Rails7 (zeitwerk)でgRPC (Protocol Buffers)を使ったらハマった

概要

Amazon.co.jp: スターティングgRPC 技術の泉シリーズ (技術の泉シリーズ(NextPublishing)) eBook : 武上 将樹: Kindleストア

こちらの書籍にあるGo(サーバ) × Rails(クライアント)のサンプルを写経していた際、zeitwerkの仕様にはまってエラーが出たので、回避方法をメモしておきます。

zeitwerkはRails6から採用されたオードロード機構ですが、Rails6ではActive Supportで実装されたものがclassicモードとして利用可能で、それであればエラーは発生しないとのことでした。

Rails7からはclassicモードが使えずzeitwerkで動かす必要があるため、下記サイトを参考にさせていただき対応しました。

qiita.com

アプリケーション外観

最終的なディレクトリ構成は下記の通り。

.
├── api    # サーバ(Golang)
│   ├── gen
│   │   └── pb
│   │       ├── filemeta.pb.go
│   │       ├── image_upload_request.pb.go
│   │       └── image_upload_response.pb.go
│   ├── go.mod
│   ├── go.sum
│   ├── handler
│   │   └── image_upload_handler.go
│   └── server
│       └── server.go
├── client  # クライアント(Rails)
│   ├── Gemfile
│   ├── app
│   │   ├── gen
│   │   │   └── pb
│   │   │       └── image
│   │   │           └── upload
│   │   │               ├── image_upload_request
│   │   │               │   └── filemeta_pb.rb
│   │   │               ├── image_upload_request_pb.rb
│   │   │               ├── image_upload_request_services_pb.rb
│   │   │               └── image_upload_response_pb.rb
│   │   ├── models
│   │   │   └── image_uploader.rb
│   ├── config
│   │   ├── application.rb
├── proto
│   └── image
│       └── upload
│           ├── image_upload_request
│           │   └── filemeta.proto
│           ├── image_upload_request.proto
│           └── image_upload_response.proto

ポイント1)1つのmessageにつき1つのprotoファイル

まずprotoファイルでスキーマを定義します。

最初は一つのprotoファイル内に3つのmessageを記載していましたが、参考先にも記載がある通り、1protoファイルにつき一つのmessageとしました。zeitwerkによる定数読み込みでエラーになるためです。結果的に3つのprotoファイルを書き、大元のimage_upload_request.protoで他の2つをimportして使うようにしました。

定義できたらprotocおよびgrpc_tools_ruby_protocコマンドで各々GoとRubyのコードを自動生成します。

ポイント2)Rails側で読み込みパスを追加

config/application.rbに下記の通り追加します。

config.paths.add Rails.root.join('app',  'gen',  'pb').to_s, eager_load: true

protoで定義して自動生成した定数の名前空間を正しく認識させるために必要でした。

ポイント3)カスタムInflectorを定義

Qiitaの記事にある通り、自前のInflectorを定義して利用します(記事の通りなので割愛)

なお自動生成したrubyのファイルimage_upload_request_services_pb.rbについて、ファイル名はservicersとなっているが中身のmodule名はImageUploadRequestServiceとなり、このままだとzeitwerkの読み込みエラーになったため、取り急ぎここのカスタムInflectorで対応しました(ファイル名末尾にpbがつく場合、かつ指定のファイル名の場合は、当該ファイル名を単数系に変換)。

以上の通り修正し、下記コマンドを実行してexpected file XXX to define constant XXX のようなエラーが出なければOKです。

% bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!