1. tanaken on Rails
  2. #002: Object#with, ActiveRec..
2024-01-13 26:55

#002: Object#with, ActiveRecord::Relation#explain, ActiveSupport::CurrentAttributes

https://scrapbox.io/tanaken0515/Updated_Puma_configuration,_Object%23with_block,_and_more!_%7C_This_Week_in_Rails

00:08
こんにちは、たなけんです。
2024年1月13日土曜日の午後です。
今、17時ごろですね。
東京は、雪と雨と間ぐらいの天気になっています。
今週のTanaken on Railsやっていきます。
今週は140コミットぐらい、この1週間でRailsにコミットが積まれたということで、
たくさん更新があるんですけども、
その中でまたしても3つピックアップしたので、お話ししていこうと思います。
1つ目、プールリクエストのタイトルが、
Yield Instance to Object with Blockというやつですね。
これは、アクティブサポートに関する実装です。
Object withに関しては、
僕が別でやっているテクテクラジオというところで、
まだ話したけど、公開されていないのか。
まあいいや。
Object withというやつが去年実装、2023年に実装されて、
それは何かという話をまずしておきます。
このプールリクエストは、
Object withに修正というか、新しく機能が加わったという感じですね。
Object withが何かと言いますと、
オブジェクト、全部オブジェクトなんですけど、
オブジェクト、全てのオブジェクトにwithというメソッドで、
メソッドが生えましたよと。
このメソッドはブロックを渡すことができます。
何かというと、そのブロックの内側だけ、
そのオブジェクトの特定のアトリビュート、
属性が返す値を変更することができるという、
メソッドです。
特定のアトリビュートの値を変更するというのは、
どういうふうにやるのかというと、
withメソッドの引数に、
アトリビュートの名前と、
返してほしい値というのを、
引数に渡すことで、
そのブロックの中だけ、
そのアトリビュートは、
指定した引数を返すというような動きになります。
サンプルコードが書かれていて、
ちょっと口頭で読むので、
うまく伝わるかわからないのですが、
03:01
例えば、クライアントという名前の変数に、
何らかのオブジェクトが入っているとしましょう。
クライアントという名前なので、
例えば、何らかのAPIクライアントみたいなものが、
そのクライアント変数に入っているとしましょう。
クライアント.タイムアウト、
クライアントという変数が、
タイムアウトというアトリビュートを持っていて、
素朴にクライアント.タイムアウトを呼び出すと、
5という値が返ってきたとしましょう。
そのクライアントに関して、
クライアント.タイムアウトを呼び出すと、
クライアントに関して、
クライアント.with、withメソッドですね。
withメソッドを呼び出して、
引数にタイムアウト1、
キアワード引数としてタイムアウトで値を1というのを括弧で渡しますと。
なので、クライアント.with括弧始まり、
タイムアウトコロン1括弧閉じですね。
その次にブロック、do endでブロックを渡して、
そのブロックの中で、
クライアント.タイムアウトというのを呼び出すと、
さっきwithメソッドの引数にタイムアウト1を入れたので、
このブロックの中だけはクライアント.タイムアウトは1を返す。
このブロックの外側に出ると、
クライアント.タイムアウトはもともと入っていた値である5というのが返ってくるよというような、
そんなメソッドですね。
なのでもう1回言うと、
withメソッドに渡したブロックの中だけ、
そのwithメソッドに渡した引数のアトリビュートの値が変わると。
そんな感じの処理になっています。
これは何が嬉しいかというと、
テストコードとかで特定の場合だけ、
このオブジェクトのアトリビュートを変えたいんだよねとか、
普通の動きだとトゥルーなんだけど、
このテストの時はだけはフォルスにしておきたいんだよねとか、
そういったシーンってあると思うんですけど、
そういった場合にこのwithメソッドを使うと、
すごく綺麗に書けるよというところが便利ですよという、
実装が2023年にありましたと。
ここまででもう5分ぐらい喋ってますね。
これでもって、今回のプルリクエスト、
今週の出たプルリクエストでは、
このwithメソッドに渡すブロック、
このブロックのブロック引数として、
インスタンスを渡すことができるようになったと。
インスタンス何ですかというと、
このwithメソッドを呼び出しているレシーバーのインスタンスを渡せるようになったと。
06:06
さっきの例でいうと、
さっきのクライアントという変数がありました。
クライアントの中に、
何らかのAPIクライアントのオブジェクトが入ってますよという例で、
先ほどはクライアント.with タイムアウト1 do endで、
そのdo endのブロックの中で、
クライアント.タイムアウトというのを呼び出して、
値が変わってますよみたいな話をしたんですけど、
そのdo endのところで、
インスタンス変数、
インスタンスとして、
クライアント自体をインスタンスとして、
例えば、
ルーで小文字のcという変数で渡しておいて、
そのブロックの中では、
c.タイムアウトという形で呼び出せるみたいな、
そういった形の実装が増えましたよ。
今まではブロックに引数渡すというのができなかったんですけど、
今回のプリリックで、
ブロックにインスタンスを引数として渡せるという形になりました。
これは何かというと、
ブロックの中でその特定の引数を使っているんだよ、
このインスタンスを使っているんだよというのが、
明確になるというのが利点かなと思ってます。
この実装前だと、
このブロックの中もクライアントという、
クライアントwithdoendの中に、
クライアントという変数が出てくるみたいな実装になるんですけども、
そうするとそのクライアントという名前の変数は、
どっちの文脈でのクライアントなのかが、
一瞬ではちょっと区別しにくくなる可能性があるなと思っていて、
今回のブロックの引数として、
明確に別の名前を付けて渡すことができるとなると、
これはブロックの中で使うための変数なんだなというのが、
分かりやすくなるというので便利かなと思いました。
というのが1点目でございます。
続いて、
プルリクエストのタイトルが、
Add explain support for methods like last, proc, and count。
何かというと、
Explain methodのサポート対象を増やしました。
何かというと、
ラストとか、
これはアクティブレコードに関する実装でございます。
もともとアクティブレコードリレーションに、
09:00
Explain methodというものがありました。
これはその名の通り、
プルリクエストの中で使うための、
アクティブレコードに関する実装でございます。
もともとアクティブレコードリレーションに、
Explain methodというものがありました。
これはその名の通り、
クエリの実行計画を返してくれるメソッドですね。
よくやりますよね、パフォーマンスチューニング。
パフォーマンスが悪いな、みたいなクエリのときに、
実行計画を見て、
これはなんだ、
ちゃんとシーケンシャル、
インデックスがちゃんと効いてるのか、とか、
全部のレコードを操作しにいっちゃってないか、みたいな、
あたりをチェックしたりすると思うんですけど、
僕実際は、このアクティブレコードリレーションの
Explain methodっていうの自体は、
正直使ったことなかったんですよね。
だいたいクエリが、
APMとか、
データドックを使うことが多いんですけど、
お仕事だと。
APMとかに、このクエリが重い、みたいなのが出てたりとか、
するんで、
それを使って、
Explainは別の、
本番のDBをレプリケーションしてる方のデータベースで、
Explainして、
どうなるかな、みたいな、
本番に影響出ないように、
Explainも一応、
一応というか普通に、
負荷かかったりすると思うので、
実行計画、
Explainも、
何種類かあるんですよね。
RDB、
BOSGREとかASKとかによって、
もちろん違うと思うんですけど、
あまり詳しくないんで、
詳しくなりたいのが、
今年の目標ではありますが、
実際にクエリを、
実行する、
みたいなやつと、
本当に計画、
実行計画だけ見る、
みたいなやつ多分、
あったと思うんですけど、
なるべく本番でやらないように、
とか、
やってたりするので、
何が話したかったかというと、
アクティブレコードリレーションの、
Explainメソッド自体は、
そんなに僕のこれまでの経験では、
活躍の場がなかったんですよね。
なので実際、
知らなかった存在自体が、
あるんだなというのを、
このブルーリクのおかげで知りました。
そんで、
そういうメソッドがありましたよと。
けれども、
例えば、
アクティブレコードリレーションの、
例えば、
user.lastとか、
プロックIDとか、
userall.countとかって、
その実際の結果、
アクティブレコードリレーションではなくて、
カウントだったら件数を返すし、
ラストだったら、
一番最新のレコードの、
オブジェクトを返すし、
プロックだったら、
12:00
IDを例えば指定するんだったら、
IDの配列が変えるし、
みたいな、
アクティブレコードリレーションを返さないと、
アクティブレコードリレーションを返さないような処理っていうのは、
アクティブレコードにはあるんですよね。
いくつかね。
で、
そういうのを使うってことも、
結構日々の業務で多いですよね。
そういったメソッドを使っている場合に、
エクスプレインをしても、
うまく動かなかったという問題があったと。
で、うまく動かないっていうのはどういうことかというと、
例えば、
user.allは、
user.all.explained.countとかってやりたいと。
user.all.countってやっちゃうと、
数字、
カウントで数字が返ってきちゃう、
インテジャーが返ってきちゃうので、
user.allまでは、
アクティブレコードリレーションなんで、
そこにexplainedっていうのをやって、
.countでそのカウントも、
なんだろうな そのカウントクエリ をセレクトカウントフロムユーザーズ
の結果をエクスプレインしたいん じゃみたいな気持ちがあるんだ
けど そういうことをやりたいって やろうとすると ユーザー.オール
エクスプレインってやった時点で そのユーザー.オールのエクスプレイン
が実行されて つまりエクスプレイン セレクトユーザーズ セレクトユーザーズ
オール なんだろうな エクスプレイン セレクトフロムユーザーズみたいな
ユーザーズテーブルから全部取って くるぞをエクスプレインしちゃう
で エクスプレイン自体はストリング のインスタンスを返すので ストリング
に対するメソッド 今回だと ユーザー.オール.エクスプレイン.カウント
みたいなのを 例えばやりたいって なったら ストリングに対して.カウント
をやるみたいな カウントメソッド を呼ぶみたいなことになっちゃ
って 引き図が足りないよっていう エラーになったりとか その他
ラストとかファーストとかブルック とかでも一緒なんですけど それぞれ
ストリングに対してないメソッド はエラーになるし メソッドがあって
も引き図が足りないとか 例えば ラストとかだったら最後の文字が
多分返ってくるんですけど とかってやって エクスプレイン
ができないと つまり カウントクエリ とかラストとかブルックとかっていう
メソッドを呼び出したときのエクスプレイン がうまくできないんだっていうのが問題
だったと 今回のプレリクエスト では ラストとか 対応したのは
全部で8個のメソッド 8個の処理 なんですけど 全部言うと ブルック
ファースト ラスト アベレージ カウント マキシマム ミニマム
15:02
8個の処理について さっきのカウント 例えば カウントをしていきたい
だったら user.all.explain.count っていう記載で エクスプレイン
セレクトカウント フロムユーザーズ というのを発行してくれるよう
になるということですね これで それらの8個の処理に関しても
実行計画を見ることができるよう になりましたということです
これ 具体的な実装の中身 ちゃんと 読んでないですけども 読み方
実装としてちょっとすごいなと思 ったのが user.all.explainってやった
後に その後にカウントとかブルック とかファーストとかそういうメソッド
が読まれるかどうかによって エクスプレインする処理っていう
のを変えるみたいなことになる から すごいよくできた実装だな
と思って エクスプレイン自体は user.allに対して読んでるのに 後から
何が呼ばれるか そのエクスプレイン に対して 今までは単にストリング
を返してたんですけど ストリング 返さないようにしたんでしょうね
多分 それともまた何か公文 何 だろうな 実装見てみないと分かんない
ですけど 後出しで何が呼ばれる かによって 実行計画を何返す
かを変えるみたいなの なかなか すごいなというふうに思ったんで
ちょっと後で実装を見てみよう かなと思っております
ここまでで17分ぐらい話しました この収録話なんですけど 収録が
podcasters.spotify.comでやっており まして ブラウザー上で録音ボタン
ポチッと押してるんですよ 最大 30分の録音なんで 今17分しゃべった
んで あと13分しかしゃべれない 13分あれば十分なんですけどっていう
感じになっておるので 最大でも 30分という感じになっております
podcasters.spotify.comで BGMとかもピッ と選んで付けられるんで 非常に
楽チンだなと思って でも BGMも 最初だけ音大きくして徐々に小さく
してってみたいなことを勝手に やってくれるんで それに乗っかって
楽チンをしていこうという感じ でございます
さて あと12分ぐらいですね 三つ目 タイトルがAdd Default Support for Active
Support CurrentAttributes.attributeという プリクエストです これはアクティブ
18:07
サポートに関する実装です Active Support CurrentAttributesというクラス
があります このクラスのアトリビュート メソッドに引数としてデフォルト
の値を渡すことができるようになった よというのが このプリクエスト
です そもそもActive Support Current Attributes
というものに僕は馴染みがなかった ので ちょっと調べてみました
チャットGPTさんに聞いたところ ドキュメントにもちゃんと書いて
あるんですけど Active Support Current Attributesについて アクティブサポート
のライブラリの一部であり それは そうだよな Railsアプリケーション
内でスレッドセーフなグローバル な状態を管理するための仕組み
を提供します これは特にマルチスレッド環境
での共有データの取り扱いに役立ち ますということで 僕はこれまで
あんまりマルチスレッドにおける 実装みたいなものに注意深く取り
込んだことがないなと改めて思 っていまして なんで アプリケーション
実装のスレッド周りの話って もちろんサービスの特性によって
はすごいシビアなところもたくさん あると思うんですけど 僕が今まで
やった経験だとそんなないんですよ ね 要するに そもそもそんなに重要性
度が高いシーンがなかったりした というのもあって なんで この
辺も詳しくなりたいなと思った ので 今回はありがたいお題材
だなと思っています Active Support Current Attributesっていう
のはどういうふうに使うんじゃい ということなんですが アクティブ
サポートカレントアトリビュート クラスを継承して 何らかのクラス
を定義して そのクラスの中でアトリビュート メソッドを呼び出して クラス変数
をアトリビュートメソッドに渡 した名前で 名称でクラス変数を
定義できると そのクラス変数は スレッドごとに独立した変数として
取り扱うことができるというような 感じです ざっくり言うと
具体的なサンプルコードをうまく 口頭で伝わるかな やってみる
と 例えばクラスMyContextという クラスを定義したとします この
MyContextクラスがActive Support Current Attributesを継承していると この
21:06
MyContextクラスの中でアトリビュート がUserIDというアトリビュート
を持っているとしましょう メイン スレッドでMyContext.UserID
UserIDイコール1みたいな感じで MyContext.UserIDイコール1みたいな
感じで代入しましたと そうすると もちろんこのMyContext メインスレッド
の中では このUserIDはアクセス すると1というのを返します なんで
MyContext.UserIDは1を返すということ なんですけど その状態でスレッド
新しく作って スレッド.new do endで そのブロックの中でこのMyContext.UserID
っていうのが何を返すのかっていう のをちょっとチェックしてみる
と スレッドを実行すると その スレッドの中ではMyContext.UserIDは
まだ何も代入されてない なんで nilですね nilが変えると つまり
この新しいスレッドの中ではまだ メインスレッドとは独立した
空っぽの状態のUserIDがあるよ みたいな感じに分かれているということ
ですね っていうようなことができる よと これを使わずに 素朴にただの
純粋なクラス変数として定義して しまうと 例えばMyClass さっきは
MyContextっていうクラスだったんですけど MyClassっていうクラスにしましょう
アクティブサポートをカレント アトリビュートを継承せずに 素朴
にクラスを定義して クラス変数 を定義しますと アトリビュート
アクセサーの先頭にCが付いている やつ これ Railsで これもアクティブ
サポートで実装されてるのかな どこで実装されてるか分かんない
けど CアクセサーとかでUserIDっていう のを定義すると MyClass.UserID
イコール1みたいな感じで1を代入 しておいて 代入というか1をセット
しておいて スレッドニューで新しい スレッドの中でMyClass.UserIDを最初
してみると このスレッドの中でも 1っていうのが返ってきちゃうということ
ですね なのでスレッドの外 メイン スレッドでもこの新しいスレッド
24:02
の中でも 両方ともUserIDは1を返す っていう感じですね この場合は
スレッドによらずクラス変数の値 が共有されてると スレッドをまた
がって共有されているよという のが素朴な感じの実装ですね アクティブ
サポート カレントアトリビュート を使わずに実装するとそうなる
ねみたいなことですね なので スレッドごとに異なるクラス変数
を保持して何か扱いたいという 場合には アクティブサポート カレント
アトリビュートを使うと便利ですよ というようなことを学びました
じゃあこのプリティック何だったん だっていうと このカレントアトリビュート
のこのアトリビュートメソッド にデフォルトの値を渡すことが
できるようになったよということで もともと渡せなかったんだねということ
ですね もともとはクラス変数の 名前をシンボルで渡すみたいな
感じになってたんですけど そこに デフォルトの値も渡すことができる
ようになって便利だねという感じ でございます
よし 残り4分ぐらい OK 今回は この三つを紹介しました おさらい
すると一つ目がオブジェクトwith というメソッドでwithメソッド
に渡すブロックの引数としてインスタンス そのwithメソッドのレシーバー
を渡すことができるようになった ということと 二つ目がアクティブ
レコードリレーションのExplain メソッドがラストとかブロック
とかカウントとか そういったアクティブ レコードリレーションを返さない
ようなメソッドにも対応することが できるようになりました 三つ目
がアクティブサポートカレント アトリビューツというクラスの
アトリビュートメソッドに引数 としてデフォルトの値を渡すことが
できるようになりましたよという 話でございました よしよし しっかり
時間どおりにできました こんな 感じで引き続き Tanaken On Rails毎週
やっていきたいと思います じゃあ おしまい バイバイ また来週
26:55

コメント

スクロール