カレーの恩返し

おいしいのでオススメ。

marginalia(ActiveRecord::QueryLogs) を使うと ActiveRecord::Relation#explain が空文字を返す問題

困っている人向けまとめ


marginalia を使おうとしたときにちょっと困ったことがあったのでメモ書き。

marginalia とは

marginalia gem とは、rails/rails を作った basecamp が作った gem で SQL のクエリの先頭か末尾にどの controller / action で発行されたかをクエリのコメントとして追記してくれる機能を持っている。

また、この機能は Rails 7 では ActiveRecord::QueryLogs という名前で標準の機能に取り込まれている。便利で使ってたけどメンテされてる気配がなかったので標準に取り込まれるのは嬉しい。

api.rubyonrails.org

どんな問題が起きたのか

marginalia のデフォルトの挙動では、クエリ発行位置をクエリの末尾にコメントするようになっているが下記のように prepend_comment option を true にするとクエリの先頭にコメントを追記してくれるようになる。

Marginalia::Comment.prepend_comment = true

これを設定した上で ActiveRecord::Relation#explain(e.g. User.all.explain) を実行すると、explain 結果が空文字になってしまう問題が起きていた。

この問題は既知で、ActiveRecord::Relation#explain は with, select, update, delete, insert のいずれかから始まるクエリでないと EXPLAIN が動作しないような実装になっているのが原因とのこと。

github.com

どう対応すればいいのか

既に rails/rails に修正は取り込まれているものの、2022/12/27 時点の Rails 最新版である 7.0.4 ではまだ反映されていないので変更に対応する monkey patch をあてる必要がある。具体的には以下。

# config/initializers/marginalia.rb

# `Marginalia::Comment.prepend_comment = true` で marginalia を動かすと `ActiveRecord::Relation#explain` が空文字を返す問題の対応。
# 対応自体は https://github.com/rails/rails/commit/a32c54e49e46f08a910a993718bae78e57f3d85f で rails/rails の main branch に取り込まれている。
# 2022/12/27時点でのRails 最新バージョン(7.0.4)には取り込まれていないので、取り込まれたバージョンまで Rails が上げることができればこの monkey patch を消すことができる。
if defined?(ActiveRecord::ExplainSubscriber) && ActiveRecord::ExplainSubscriber.const_defined?(:EXPLAINED_SQLS)
  ActiveRecord::ExplainSubscriber::EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
end

新卒で入社した会社を退職しました

この記事は 呉高専 Advent Calendar 2022 - Adventar の21日目の記事です。

新卒で Wantedly という会社に就職し、その会社を今年いっぱいで退職することになりました。 なので、数年後自分がお酒を飲みながらエモい気持ちになるための振り返りとして書き残しておきます。

Wantedly とは

まずは、私が新卒で入社した Wantedly という会社について軽く紹介します。
Wantedly は「シゴトでココロオドルひとをふやす」をミッションに以下のサービスを展開しています。

  1. 気軽に会社訪問できる Wantedly Visit
  2. 知り合いの活躍を確認できる Wantedly People
  3. 会社のコンディションを整える Wantedly Engagement Suite

wantedlyinc.com

私が働いていた期間の多くは Wantedly Visit の開発に携わり、最後の2ヶ月ほどは Wantedly Engagement Suite の開発に携わっていました。

Wantedly に新卒で入ってどうだったか?

間違いなく良かったと思っています。
良かったと思うポイントは色々ありますが、以下の2つを取り上げたいと思います。

  1. 真っ当な toC 向けのプロダクト開発を経験できた
  2. 色々なことに入門できた

1. 真っ当なtoC向けのプロダクト開発を経験できた

Wantedly では 仮説を立てる検証する機能としてリリースする予想とのギャップを整理する予想と機能を修正して再度リリースする → ... という開発サイクルを回していて、 Wantedly のエンジニアはこれらの全工程に関わっています。

参照: docs.wantedly.dev

これらのサイクル自体はよくあるものだと思いますが、会社によってはエンジニアは機能としてリリースする部分だけを担うこともあると聞いています。 分業による生産性の向上などのメリットもあると思いますが、各ステップを一通り経験できたことは間違いなく自分の経験としてプラスに働いたと考えています。

2. 色々なことに入門できた

前述の 1. 真っ当な toC 向けのプロダクト開発を経験できた でもわかるように Wantedly のエンジニアのカバー範囲はとても広いと感じています。

プロダクトを伸ばすために必要だからやる、という総合格闘技としてのプロダクト開発は自分の性に合っていて、様々な分野で 完全に理解した → 何もわからない → チョットデキル → 完全に理解した → ... を繰り返すことでプロダクト開発力が高まっている実感がありました。

【少し入門した分野】
エンジニアリング、プロダクトマネジメント、プロジェクトマネジメント、グロース、データ分析、UXライティング、...

色々なことに入門したことにより、深淵な「プロダクト開発」というトピックに対して学ぶべきロードマップがうっすら見えたような気がしました。

よくある Rails のアプリケーション開発者になるためのロードマップ https://pin.it/4eXqve5

伸ばしたいスキルの変化

学生の頃から現在までで伸ばしたいスキルが変わっていった*1ことも書いておこうと思います。

学生の頃~社会人1年目

学生の頃は プロダクトを作る能力を高める = 技術力を上げる だと思っていたので、技術力を高められる会社として Wantedly を選びました。*2

入社してからも技術力を高めることが最も重要と考えていたような気がします。

読んでいた本

社会人2~3年目

社会人2~3年目になり、一個人としての成果だけでなく1チームとしての成果を考えるようになりました。

やってきたプロジェクトを見返してみると、プロジェクトの中で設計・実装は一部分でしかなく、もし仮に実装・設計の期間が半分になったとしても、トータルのプロジェクト期間はそこまで短くならないことに気付きました。 さらに、プロジェクトが完遂できたとしてもユーザーのインサイトがズレていて思っていたように数字が伸びないことも多々ありました。

これらの経験から、プロダクトを伸ばすためには技術力を高めるよりもプロジェクトマネジメント・プロダクトマネジメントの能力を磨くことが重要だと考えるようになりました。

読んでいた本

社会人4年目~現在

社会人4年目では、新規事業に近いような事業としての不確実性がとても高いプロジェクトを任されていました。

新規事業のフェーズではどう作るよりも何を作るか、それをどう届けるかの方が圧倒的に重要です。そのため、コンセプトを決める段階からのプロダクトマネジメント、UXライティングなどよりユーザーに近い部分のスキルを高めていました。

そのようなスキルを高めていく中でなんとなく、自分の中のエンジニア濃度が薄まっていくような感覚があり危機感を覚えていました。「技術はあくまで手段」と思っていた自分がなぜ危機感を覚えるのか、プロダクトを伸ばす能力は高まっているんだからそれで十分じゃないか、と思う気持ちと「なんか嫌だな」という気持ちが同時に存在していました。

自分の中で気持ちを整理してみたところ、2つの気持ちがあることに気付きました。*3 どうやらプロダクトを伸ばすだけでは満たされなかったようです。

  1. プロダクトを伸ばしたい
  2. エンジニアとしてやっていきたい

そして、エンジニアとしてプロダクトの伸ばす力をより高めるための手段として、転職を決意しました。

 

(具体的な転職の話は脱線してしまうのでこの記事では書きません)

読んでいた本

新卒の会社を選ぶということ

この記事は呉高専 Advent Calendar として書いたものなので、これから新卒の会社を選ぶであろう学生向けのセクションも書いてみようと思います。

まだ新卒の会社以外をほとんど知らない社会人4年目の言うことなので話半分くらいに読んでください。
パッと思いついたのは以下の3つです。

  1. 新卒で入った会社の文化や考え方は自分の価値基準に影響する
  2. 専門性を磨き上げたい!と思っていたとしても新卒で幅広く色んなことができる環境に身を置くのは悪くない
  3. 一度選んだら正しい選択だったかは考えず、選択を正しくすることに全力を注ぐ

1. 新卒で入った会社の文化や考え方は自分の価値基準に影響する

これはよく言われていることですが、新卒で入った会社の文化や考え方は自分の価値基準に影響する気がしています。

新卒のタイミングでは前職が存在しないため、他社の比較することができません。そのため、まずは良くも悪くもその会社の文化や考え方に染まることになると思います。 2社目以降では前職が存在するため、良くも悪くも「前の会社と比較してここは〜で、あそこは〜」と比較してしまいます。

新卒で選んだ1社目が基準になってくるため、どんな考え方を自分にとっての当たり前にしたいかで会社を選んでみるのも悪くないかもしれません。

2. 専門性を磨き上げたい!と思っていたとしても新卒で幅広く色んなことができる環境に身を置くのは悪くない

これは実体験によるものなので賛否両論あると思いますが、新卒では幅広く色んなことができる環境に身を置くのも悪くないなと感じています。

専門性を磨き上げることが目標であれば何の問題もないと思います。しかし、別の目標があり、その目標を達成するために専門性を磨き上げたいと考えているのであれば、目標を達成する手段は特定の専門性を磨き上げる以外にも存在しないかを確かめる期間があっても良いと思っています。

自分が学生のときは プロダクトを作る能力を高める = 技術力を上げる だと思っていましたが、実務でプロダクト開発を行うことで他にも上げるべきパラメータが存在することを知ることができました。

3. 一度選んだら正しい選択だったかは考えず、選択を正しくすることに全力を注ぐ

これもよく言われていることですが、一度新卒の会社を選んだら正しかったかどうかは考えず、選択を正しくすることに全力を注いだ方がいいと思っています。
これに関しては自分の好きな記事があるのでそちらを参照してみてください。

www.wantedly.com

*1:これが転職のきっかけでもある

*2:もちろんミッション共感などは大前提としてあります

*3:こういう整理はサウナの中が一番捗る

ActiveModel::Dirty を使うときに気をつけること

最近ハマってしまったのでメモとして残しておく。

TL;DR

自身のmodel と association を同時に save する場合は dirty attribute が上書きされる可能性があるので注意する必要がある。

本編

雑なサンプルを提示する。

  • User : ユーザー作成時に新規ユーザー向けのメール送信する機能を持っている
  • UserName : User と has_one の関係で UserName 作成時に親の User model の filled_name を更新する
# == Schema Information
#
# Table name: user_names
#
#  id          :integer          not null, primary key
#  user_id     :integer          indexed
#  name        :string(32)
#  created_at  :datetime
#  updated_at  :datetime
#

class UserName < ApplicationRecord
  belongs_to :user
  
  after_create :mark_as_filled_name

  private

  def mark_as_filled_name
    user.filled_name = true
    user.save!
  end
end
# == Schema Information
#
# Table name: users
#
#  id          :integer          not null, primary key
#  filled_name :boolean          default(FALSE), not null, indexed
#  created_at  :datetime
#  updated_at  :datetime
#

class User < UsersModel::User
  has_one :user_name

  after_save :send_mail_for_new_user

  private

  def send_mail_for_new_user
    MailService.send_new_user_mail(user) if saved_change_to_id?
  end
end

上記のような model が存在したとき、以下のような実装で User と UserName の作成を同時に行うとメールが送信されない。

user = User.new
user.user_name.build(name: 'this is name')
user.save!

User と UserName の作成を別々に行うときちんとメールは送信される。(同一トランザクションでも問題ない)

user = User.new
user.save!
user.user_name.create!(name: 'this is name')

どうしてこうなるかを解説していく。
まず、 User と UserName の作成を同時に行う場合の user.save! では以下の処理が順番に実行されている。

  1. User の create
  2. UserName の create
  3. UserName の after_create による User の update
  4. User の after_save

 
4. を実行するタイミングでは 1. の dirty attribute(id column の変化) が残っていることを期待するが 3. の User update(filled_name column の変化) によって 1. の dirty attribute が上書きされ、サンプルコードで言うところのsaved_change_to_id?falseを返してしまう。

学び

  • model の同時 save を行う必要がないときはなるべく別々での save を心がける
  • dirty attribute 難しい

sorbet は production で使えるのか [2020/03/29 時点]

sorbet は少し前に話題になっていた Ruby の type annotaion gem。

sorbet.org

以下のようなコードを書くと静的型チェック、動的型チェックの両方をやってくれる。

# typed: true
require 'sorbet-runtime'

class A
  extend T::Sig

  sig {params(x: Integer).returns(String)}
  def bar(x)
    x.to_s
  end
end

def main
  A.new.barr(91)   # error: Typo!
  A.new.bar("91")  # error: Type mismatch!
end

上記コードのsrb ts実行結果

editor.rb:12: Method barr does not exist on A https://srb.help/7003
    12 |  A.new.barr(91)   # error: Typo!
          ^^^^^^^^^^^^^^
  Autocorrect: Use `-a` to autocorrect
    editor.rb:12: Replace with bar
    12 |  A.new.barr(91)   # error: Typo!
                ^^^^


editor.rb:13: Expected Integer but found String("91") for argument x https://srb.help/7002
    13 |  A.new.bar("91")  # error: Type mismatch!
          ^^^^^^^^^^^^^^^
    editor.rb:5: Method A#bar has specified x as Integer
     5 |  sig {params(x: Integer).returns(String)}
                      ^
  Got String("91") originating from:
    editor.rb:13:
    13 |  A.new.bar("91")  # error: Type mismatch!
                    ^^^^
Errors: 2

sorbet は前から気になっていたけど社内で運用できる状態になっているのかどうか気になったので土日で調べてみた。

気になっていたこと

  • sorbet-typed に更新があったら各リポジトリに更新を反映した PR を自動で作ることは可能なのか
  • 社内 gem 用の sorbet-typed は作れるのか
  • CI に組み込んで型チェックにコケたら PR 上で自動で指摘させることは可能か
  • 型の coverage のようなものを計測することはできるか
  • メタプロで生成されたメソッドの型定義はどうなるのか
  • 周辺環境はどのくらい整備されているのか
    • エディタとの integrate, sorbet の型定義を何かから自動生成する何か

sorbet-typed に更新があったら自身のリポジトリに更新を反映した PR を自動で作ることは可能なのか

sorbet には TypeScript の DefinitelyTyped のような型定義の中央管理システムがあって、gem に型定義を追加する場合はここに PR を投げる形になっている。
GitHub - sorbet/sorbet-typed: A central repository for sharing type definitions for Ruby gems

また、sorbet は型定義ファイルを commit する方針を取っている1ので利用している gem の型定義が更新されると自身のリポジトリsrb rbi updateを実行し、明示的に更新する必要がある。

型定義をいい感じに更新する PR を投げてくれる script がどこかに転がっていればいいなと思ったけど、パッと調べたところ見当たらなかった。作る必要がありそう。

travis cronjob とか使えばいい感じにできるんじゃないかと思っている。しらんけど

docs.travis-ci.com

結論:自作すれば可能

社内 gem 用の sorbet-typed は作れるのか

上に書いたように sorbet は sorbet-typed を使って型定義を共有する仕組みを作っている。 ただ、これは public なので private gem の型定義をここに書いちゃうと全世界に公開することになってしまう。 それは困る。
社内用の sorbet-typed を作り、それを差し込めるような仕組みがあれば private gem でも社内で利用することはできるはず。

sorbet, sorbet-typed の issue や sorbet Slack を眺めたりしたけど private gem に対する言及はどこにもされていなかった。 その辺りのソースコードも眺めてみたけど sorbet-typed の URL がハードコードされていた。 サポートされていない可能性が高い?

じゃあ Stripe はどうしているんだという話はあるし、他で問題になっていないわけがないので何を根本的に勘違いしている可能性がある

結論:本家に PR 投げれば可能?

CI に組み込んで型チェックにコケたら PR 上で自動で指摘させることは可能か

PR 上でコメントをつけてくれるサービスはいくつがあるが、社内では reviewdog を使うことが多い。
reivewdog は sorbet 未対応だったので PR を出した。数日中に使えるようになるはず。

追記:出していた PR が merge された!

github.com

他のサービスが sorbet に対応しているかどうかはちゃんと調べてない

結論:reviewdog を使えば可能

型の coverage のようなものを計測することはできるか

ここのサポートは手厚く、かなり細かい情報まで取得することができる。
Tracking Adoption with Metrics · Sorbet

しかも、導入フェーズに合わせてどの metrics に着目すべきかまで書いてくれているのでとても分かりやすい。
https://sorbet.org/docs/metrics#suggestions-for-driving-adoption

取得できる metrics 一覧

% srb tc --metrics-file=metrics.json
% cat metrics.json | jq ".metrics[].name" | sort
"ruby_typer.unknown..error.max"
"ruby_typer.unknown..error.min"
"ruby_typer.unknown..error.p25"
"ruby_typer.unknown..error.p50"
"ruby_typer.unknown..error.p75"
"ruby_typer.unknown..error.p90"
"ruby_typer.unknown..error.total"
"ruby_typer.unknown..release.build_scm_commit_count"
"ruby_typer.unknown..release.build_timestamp"
"ruby_typer.unknown..run.utilization.context_switch.involuntary"
"ruby_typer.unknown..run.utilization.context_switch.voluntary"
"ruby_typer.unknown..run.utilization.inblock"
"ruby_typer.unknown..run.utilization.major_faults"
"ruby_typer.unknown..run.utilization.max_rss"
"ruby_typer.unknown..run.utilization.minor_faults"
"ruby_typer.unknown..run.utilization.oublock"
"ruby_typer.unknown..run.utilization.system_time.us"
"ruby_typer.unknown..run.utilization.user_time.us"
"ruby_typer.unknown..types.input.bytes"
"ruby_typer.unknown..types.input.classes.total"
"ruby_typer.unknown..types.input.files"
"ruby_typer.unknown..types.input.files.sigil.autogenerated"
"ruby_typer.unknown..types.input.files.sigil.false"
"ruby_typer.unknown..types.input.files.sigil.ignore"
"ruby_typer.unknown..types.input.files.sigil.strict"
"ruby_typer.unknown..types.input.files.sigil.strong"
"ruby_typer.unknown..types.input.files.sigil.true"
"ruby_typer.unknown..types.input.methods.total"
"ruby_typer.unknown..types.input.methods.typechecked"
"ruby_typer.unknown..types.input.sends.total"
"ruby_typer.unknown..types.input.sends.typed"
"ruby_typer.unknown..types.sig.count"

metrics.json を人間が見やすい形式にしてくれる gem も見つけた。これを各 PR に comment する bot 作るととても良さそう。

github.com

bundle exec srb tc --metrics-file /tmp/sorbet_metrics.json
# No errors! Great job.
bundle exec sorbet_progress /tmp/sorbet_metrics.json
# Sorbet Progress

# Progress for sig coverage
# total_signatures  7528
# total_methods     183447
# total_classes     112433

# Progress for file coverage
# sigil_ignore      12      0.20 %
# sigil_false       5466        91.60 %
# sigil_true        460     7.71 %
# sigil_strict      12      0.20 %
# sigil_strong      17      0.28 %
# ---------------------------------------
# Total:        5967    100%
# Keep up the good work 👍

結論:可能

メタプロで生成されたメソッドの型定義はどうなるのか

experimental な機能として、メタプロで定義したメソッドに対して型定義を行うための plugin を書くことができる。

Metaprogramming plugins · Sorbet

# metaprogramming.rb
# typed: true

class Metaprogramming
  def self.macro(name)
    define_method("#{name}_time") { name }
  end

  macro(:bed)
  macro(:fun)
end

hello = Metaprogramming.new
hello.bed_time # error: Method `bed_time` does not exist on Metaprogramming
hello.fun_time # error: Method `fun_time` does not exist on Metaprogramming

上記のような macro を定義する DSL があった場合、以下のような plugin を書くことで sorbet にメソッドが定義されていたかのような挙動をさせることができる。

# macro_plugin.rb

# Sorbet calls this plugin with command line arguments similar to the following:
# ruby --class Metaprogramming --method macro --source macro(:bed)
# we only care about the source here, so we use ARGV[5]
source = ARGV[5]
/macro\(:(.*)\)/.match(source) do |match_data|
  puts "def #{match_data[1]}_time; end"
end
# Note that Sorbet treats plugin output as rbi files

Caveats

Sorbet decides which plugin to call using method names only. This might be a problem if different metaprogramming methods use the same name, have similar usages, but behave differently. To illustrate, using the configuration above, the following calls macro_plugin.rb twice but results in an error.
https://sorbet.org/docs/metaprogramming-plugins#caveats

気をつけないといけないこととして、同じ interface の DSL を作ると見分けるのが難しくなることが挙げられていた。
plugin には string のクラス名が渡ってくるので klass_str.constantize.method(method_name).owner で定義元を確かめることはできるが、クラスを load する必要があるのでsrb tcがかなり遅くなるはず。悩ましい。。。

Rails はメタプロの宝庫なのでどうなるんだろうと思っていたら、ちゃんとプロジェクトが進んでいた。
GitHub - chanzuckerberg/sorbet-rails: A set of tools to make the Sorbet typechecker work with Ruby on Rails seamlessly.

結論:基本的には可能

周辺環境はどのくらい整備されているのか

エディタとの integrate

Language Server Protocolに準拠しているので様々なエディタと互換性はあるらしいが、vscode plugin のようなものは見当たらなかった。 自分で設定して Language Server を立ち上げれば使えるのかも。

結論:可能(と言っている)

sorbet の型定義を何かから自動生成する何か

色々あった。

ちょっと性質は違うけど sorbet 用の rubocop も見つけた。
GitHub - Shopify/rubocop-sorbet

まとめ

今からすぐに使える感じではなかったけど、社内 gem の sorbet-typed 問題さえ解決すれば手の届く範囲にあるなと思った。 前向きに検討したい。


  1. その方針を取った理由は https://sorbet.org/docs/rbi#a-note-about-vendoring-rbis に書いてある

grpc_required_annotator gem つくった

rubygems.org

TL;DR

  • 会社で grpc ruby を使っていて、同じような実装何回もやってんなと思ったので共通化して gem にした
  • request message を required チェックを簡単にできる DSL を提供してくれる
  • いつも書いていた required をチェックするだけの冗長なテストがすごく短くなって生産性上がった

本編

最近会社で grpc ruby を使った backend を書いていて、「同じような実装何回もやってんな」と思うことがあった。 それは request message の required な field に対して、空だったときに GRPC::InvalidArgument を返すやつ。

class SampleService < SamplePb::Sample::Service
  def foo(request, call):
    raise GRPC::InvalidArgument.new("`num` is required") if req.num == 0  # これ
    raise GRPC::InvalidArgument.new("`str` is required") if req.str == "" # これも
    raise GRPC::InvalidArgument.new("`repeated_fields` is required") if req.repeated_fields.empty? # 嫌になってくる
    FooResponse.new(num: req.num, str: req.str, repeated_fields: req.repeated_fields)
  end
end

これに対応するテストを書くのもめんどくさい。

describe SampleService do
  describe "#foo" do
    subject {  described_class.new.foo(request) }

    context "when num is empty" do
      let(:request) {
        SamplePb::Sample::FooRequest.new(
          num: nil,
          str: "str",
          repeated_fields: ["a"]
        )
      }

      it "raises GRPC::InvalidArgument" do
        expect { subject }.to raise_error(GRPC::InvalidArgument)
      end
    end

    context "when str is empty" do
      ...
      it "raises GRPC::InvalidArgument" do
        expect { subject }.to raise_error(GRPC::InvalidArgument)
      end
    end

    context "when repeated_fields is empty" do
      ...
      it "raises GRPC::InvalidArgument" do
        expect { subject }.to raise_error(GRPC::InvalidArgument)
      end
    end
  end
end

shared_exaple を使えばそれなりに共通化はできるけど request のクラスは各 rpc ごとで異なるので微妙に使いにくい。

というわけでシュッと required な field を宣言できて、パッとテストを書けるいい感じの gem を作ろうと思った。
できたのがこちら

github.com

この gem に入っている GrpcRequiredAnnotator module を service の実装クラスに include することで required メソッドが使えるようになる。

class SampleService < SamplePb::Sample::Service
  include GrpcRequiredAnnotator

  required :num, :str, :repeated_fields
  def foo(request, call):
    # raise GRPC::InvalidArgument.new("`num` is required") if req.num == 0  # これ
    # raise GRPC::InvalidArgument.new("`str` is required") if req.str == "" # これも
    # raise GRPC::InvalidArgument.new("`repeated_fields` is required") if req.repeated_fields.empty? # 嫌になってくる
    FooResponse.new(num: req.num, str: req.str, repeated_fields: req.repeated_fields)
  end
end

こんな感じで各 rpc に対応するメソッドのすぐ上で required にしたい field を symbol で宣言すると実行前に validation してくれるようになる。便利。
各 rpc に対応する required な field を取得できるメソッドも生やしているのでテストもかなり行数が減った。

RSpec.describe SampleService do
  describe "#foo" do
    describe "required" do
      it "num, str and repeated_fields are required" do
        expect(described_class.required_fields(:foo)).to eq [:num, :str, :repeated_fields]
      end
    end
  end
end

結構便利だと思うので grpc ruby 書いてる人はぜひ使ってみてください。

method_addedで added された method の挙動を override して再定義してたりとか、その再定義を module の extend で実現しようとしたら grpc interceptor の治安の悪い仕様1によって壊れてしまった話は元気があればまた書きます。


  1. grpc interceptor は intercept した method の method インスタンスを参照できる

ModelとConcernに記述したcallbackの実行順

結論

Concern に記述された callback が先に実行される。
既存の callback を Model もしくは Concern に移すときは気をつける必要がある。


以下確認コード

class Hoge < ApplicationRecord
  include HogeConcern

  before_validation -> { puts "before_validation in Class" }
end

class HogeConcern
  extend ActiveSupport::Concern

  included do
    before_validation -> { puts "before_validation in Concern" }
  end
end
[2] pry(main)> Hoge.create
   (0.5ms)  BEGIN
before_validation in Concern
before_validation in Class
...

マイクロサービス他社事例

マイクロサービスの事例を調べた。 多少まとめた方がいいんだろうけど、一覧があるだけでも一定の価値はあると思ったので載せておく。


その他