ActiveModel::Validatorのインスタンス変数にオブジェクトをメモ化してはならない
タイトルの通り。
実は以前にも踏んだことがあるのだけれど、しばらく期間が開いてまた踏んでしまったので書き残しておくことにする。
ActiveModel::Validatorを継承するValidatorなんて日常的に書くわけではないのでたまに踏んでしまうのは仕方ないという話もあるが…
さて、ActiveRecordにはバリデーションを表現する方法が複数ある。
- validatesでカラムにActiveModel::EachValidatorのサブクラスを対応付ける方法
- validate_withでクラスにActiveModel::Validatorのサブクラスを対応付ける方法
- validateでクラスに定義されたprivatre methodを呼び出させる方法
ある日、仕事でActiveModel::Validatorを継承したValidatorを作っていた。
そのValidatorではとある深遠な理由でバリデーション対象のレコ―ドから辿るのが少々面倒なレコードを複数回参照していたので、思わずValidator内で
def hoge_record @hoge_record ||= record.fuga.piyo.hoge end
というようにインスタンス変数にメモ化してしまった。
すると、request specで一つのテストケースのみを指定した場合はテストがpassするが、2つ以上のテストケースをまとめて実行するとfailするという事象に見舞われる。
これはActiveModel::Validationsが実行すべきValidatorをModelに登録する仕組みから来るもの。
具体的に言うと、Validatorがインスタンス化されるのはvalidate_withによってValidatorがModelに対応付けられるタイミングであって、そのインスタンスが何度も使いまわされるから。
そのため||=
を使ってインスタンス変数にメモ化してしまうと、初回のvalidateでメモ化したオブジェクトが入りっぱなしになってしまうため意図した動作にならない。
validate_withによってValidatorがModelに対応付けられるタイミングでValidatorがインスタンス化される、当該のコードはここ。
def validates_with(*args, &block) options = args.extract_options! options[:class] = self args.each do |klass| validator = klass.new(options, &block) if validator.respond_to?(:attributes) && !validator.attributes.empty? validator.attributes.each do |attribute| _validators[attribute.to_sym] << validator end else _validators[nil] << validator end validate(validator, options) end end
バリデーション対象のレコードから辿れるのであれば、バリデーション対象のレコード側でメモ化してしまうのも手だが、バリデーション対象のレコードのパブリックメソッドにてメモ化してしまうのがドメインモデル間の関係性として本当に適切かなのかは疑問が残るので少し悩ましい。
今週読んだ記事 2020/11/9~2020/11/15
Rails
設計
今書いているプロダクトはPHPで書かれた現行サーバソフトウェアのリプレイスであることもあり、当初はドメイン知識が不足していてどこまでがドメインモデルが持っているべきロジックで、どこからがユースケースに該当するロジックなのかが判断できなかった。
そこで、リクエストされたときの動作としては現行サーバの動作を再現するようにドメインモデルの外側でロジックを書いてしまい、それらの中で頻出する処理をドメインモデルのpublicメソッドへ移す、というリファクタをしている。
こういうときにrequest specはリクエストされたときの動作が変わっていないことを保証してくれるので便利だ。
AWS
EC2に対するLightsailのFargate版っぽい。
その他
今週読んだ記事 2020/11/2~2020/11/8
DevOps / SRE
speakerdeck.com 新卒で入ったのがB2Bのサーバホスティングサービスの障害対応チームだったので障害に起因する辛みはそこそこ身に沁みていることもあり、SRE的な立ち回りは以前から多少興味がある。
今の会社は専任でSREを置くほどの規模ではないのでサービスのコードを主に書きつつ、サービスインフラの改修などはできる範囲で、となっているが。
その状態でもSLI/SLO/SLAの話とか、観測性(observably)の話とか、色々活かせる話はある。
設計
Service層を設けること自体は良いが、Service Classの命名にServiceという単語を含めるのをやめよう、という話。
Serviceという単語を含めると、Userに関するServiceだからUserService、といったようにカテゴリ分けのような扱いになってしまい、Userに関するロジックが雑多に放り込まれるゴミ捨て場になってしまう。
そうならないようにするためには、命名で用途を狭めて、当初想定していた用途とは関係ないロジックが放り込まれようとしているときに違和感を持つような命名にしよう、という話。
これの本筋とは関係ないが、20枚目のスライドでsignIn関数とsignOut関数を持つAuthServiceの改名候補がAuthorizerになっていたのが気になってしまった。signIn, signOutなら認証なのでAuthenticatorでは?
techracho.bpsinc.jp 上記のスライドと合わせて。
命名について、動詞から始めると十分に用途を狭めつつ変な命名になることが少なそう、というのは確かにその通りだと思った。
これに書かれていて納得した一方で仕事のコードではこれに従えていない箇所がいくつか思い当たるので週が明けたらPR出せそう。
その他
ipfs-book.decentralized-web.jp
デバイスの空きストレージを貸し出すとコインが貰えるブロックチェイン通貨、という話。 背景としてIPFSが出てきてなるほどと思ったと同時に、微妙な噛み合わなさも感じた。
IPFSを知ったのは、修士の頃に研究していた情報指向ネットワークの周辺技術として。
ある程度動いているのは凄いと思った反面、ストレージの確保はどうするんだろうかと思った記憶がある。
その答えがこのFilecoinということなのだろうが、記事中では 断片にアクセスするには、ファイルをアップロードした人物の持つ秘密鍵が必要になる
と書かれつつ、IPFSではコンテンツのアドレスさえわかれば誰でも取得可能、というのはどういうことなのだろう。
コンテンツのアドレスさえわかれば誰でも取得可能なのがIPFSであるならば、ファイルをアップロードした人物の持つ秘密鍵は内容の取得には必要ないのでは?
という疑問を抱いたところで頭が疲れたので興味が続けばまた今度。
今週読んだ記事 2020/10/26~2020/11/1
Ruby / Rails
github.com
jbuilderが遅すぎるので最近使い始めたjb、v0.8.0がリリースされた。
MRI2.7.2や、Rails6.1でのAPI変更への追従が主。
既存利用者がアップデート時に必要な変更として、jsonシリアライザとしてojを使っている場合、Oj.optimize_rails
が必要になった。
これまではojが読み込まれている場合、multi_jsonによって自動で使ってくれていたが、jbuilderがmulti_jsonをやめたのに追従してjbもmulti_jsonを依存から外したため。
最新版のjbuilderと同様、Active Supportのto_jsonを使うようになったので、Oj.optimize_rails
すれば引き続きojを使うようにできる。
モジュラモノリス
ma2k8.hateblo.jp
Shopify以外のモジュラモノリスの事例をやっと見つけられた。
ドメイン間の循環参照を防ぐためにShopifyではbundlerを使っていたが、この事例はscalaなのでsbtを使っている。
プライマリアダプタとセカンダリアダプタとの間の違いがまだ想像できていない。
Shared Libraryから各ContextのUse CaseまでのDB書き込み等の部分は依存関係逆転の法則を使ってインターフェイスに依存させておいたDBや外部APIリクエストの実装詳細を注入するのがセカンダリアダプタ、
外部に対してHTTP APIを提供したり、Producer-ConsumerモデルのConsumer側のふるまいを実装したりしてUse Caseを呼び出すのがプライマリアダプタ…
という理解でいいのだろうか。
github.com
ruby-jpのslackで見かけた、これもモジュラモノリス的な構造を採っているアプリケーション。
スペインのオープンソース行政システム(?)らしい。
中身ちゃんと見きれていないので後で改めて読みたい。
CI
長時間かかるE2Eのようなテストと比較的短時間で終わるユニットテストのようなテストがあるときに、短いほうが通ったら一旦マージを許し、マージ後に長いほうが落ちたらそのPRのauthorに修正をリクエストして、修正されるまで本番デプロイを禁止する、という内容だと理解した。
図を見るとマージ時にデプロイもしてしまっていると読めるが、長いテストが落ちているということはどこかで壊れが発生しているはずで、それは変更頻度を優先するために一旦飲んでいるのだろうか…と思ったが文中に書いてあった。
The two long running test suites were added to the CI workflow to ensure a pull request did not break the GitHub experience for our Enterprise Server customers.
この二つの長いテストスイートはPRがGitHub Enterpriseの顧客の体験を壊さないことを保証するために追加されました。It was also clear that these 45-minute test suites did not provide additional value blocking GitHub.com deployments that happen continuously throughout the day.
同時に、この45分かかるテストは日に何度も行われるGitHub.comのデプロイを止めるほどの付加価値は発揮していないことも明白でした。
このこの45分かかるテストは日に何度も行われるGitHub.comのデプロイを止めるほどの付加価値は発揮していない
というのがどのように判断されたのかが少々疑問ではある。
15分かかる短いほうのテストをpassしてマージしたあと、長いほうのテストがpassするのを待ってから自動でデプロイしてしまえばエンジニアがそれを覚えている必要はなくなるのだから十分なのではと思うけれども、それよりもデプロイまでの時間が短いことを選んだのは、カナリアリリースしようとしてエラー率上昇などで切り戻されたとき、エンジニアの頭を45分も前のPRについての状態に戻すのが大変だからなのだろうか。
PC自作関連
www.nichepcgamer.com 既に存在そのものは明言されてたけどついに出荷開始されたと。
Acerがこれを載せたノートPCを出荷するとか。
Destiny2くらいは動くということなので、これが1.37kgくらいのノートPCに積めるのであればピーク性能はともかく電力性能比的には良いのかも。
デスクトップ向けCoreシリーズの11000番台の情報が出てきた。
GPU部分はXe Graphicsの演算ユニット削減版ということは、マイクラくらいならこれだけで動くんじゃなかろうか。
現状のi9 9900Kで全く困ってないので買いはしないのだけれどこういうの見てるとやっぱり欲しくはなってくる。
その他
gigazine.net
モバイル回線を複数束ねて安定性を上げる製品は既に出てたけれど、オープンソースでもできるらしい。
実際に使う機会が来るかは別として。
k-tai.watch.impress.co.jp RANというコンポーネントがどこからどこまでをカバーしているのかわかっていないのだけれど、各基地局の各アンテナから送出する信号を演算する部分なのだとすれば、その辺の分野は行列演算の塊らしいのでGPUと相性が良いというのは想像できる。
僕自身の専攻は無線通信ではなかったので想像が正しいのかわからないけれども。
www.publickey1.jp
PC自作関連に含めるか迷ったけれどサーバ向けCPUの話なので。
数年前にIntelがALTERAを買収したのに続いてAMDがXilinxを買収するという話。
前前職でFPGAを載せたXeonの話は何回か聞いたが、触ることはなかった。
使われる分野が限られ過ぎていて使われているのかもよくわからない。