Updated Puma configuration, Object#with_block, and more! | This Week in Rails
Active Supportに関する実装
withメソッドにブロックを渡すことができる
そのブロックの内側だけ、そのオブジェクトの特定のattributeが返す値を変えることができる
attributeの名前と返す値はwithメソッドの引数として指定する
code:rb
client.timeout # => 5
client.with(timeout: 1) do
client.timeout # => 1
end
client.timeout # => 5
今回のプルリクではwithメソッドに渡すブロックの引数として、インスタンス(そのwithメソッドのレシーバ)を渡せるようになった
↑の例を書き換えるなら、こうなる
code:rb
client.with(timeout: 1) do |c|
c.timeout # => 1
end
たしかに、withに渡すブロックのなかに client がなんども出てくるとちょっと違和感あるかもね
違和感というか、ブロックの外なのか中なのかがパッと分からないかも
今回の実装のおかげで、ブロックの中と外を区別しやすくなった
Active Recordに関する実装
もともと ActiveRecord::Relation#explain はあった
クエリの実行計画を返してくれるメソッド
けれど last pluck count などの実際の結果を返す処理(ActiveRecord::Relation を返さない処理)では機能しなかった
何を返すんだろ、無視されるのかな。あとでやってみる。
やってみた
code:rb
Loading development environment (Rails 6.1.7.6)
irb(main):001> User.all.explain
User Load (3.6ms) SELECT "users".* FROM "users"
=>
EXPLAIN for: SELECT "users".* FROM "users"
QUERY PLAN
----------------------------------------------------------
Seq Scan on users (cost=0.00..5.04 rows=104 width=1704)
(1 row)
irb(main):002> User.all.explain.count
User Load (2.3ms) SELECT "users".* FROM "users"
(irb):2:in `count': wrong number of arguments (given 0, expected 1+) (ArgumentError)
User.all.explain が String のインスタンスを返すので String#count が呼ばれて引数が足りないぜというエラーになってる 今回のプルリクでは last などの処理でも実行計画を返してくれるようになった
対応したのは8処理: pluck, first, last, average, count, maximum, minimum, sum
code:rb
User.all.explain.count
# EXPLAIN SELECT COUNT(*) FROM users
Active Supportに関する実装
ActiveSupport::CurrentAttributes クラスの attribute メソッドに引数としてデフォルト値を渡すことができるようになった
ActiveSupport::CurrentAttributes は、Ruby on RailsのActive Supportライブラリの一部であり、Railsアプリケーション内でスレッドセーフなグローバルな状態を管理するための仕組みを提供します。これは、特にマルチスレッド環境での共有データの取り扱いに役立ちます。
なるほどな〜
マルチスレッドにおけるデータの取り扱いに注意深く取り組んだ経験がほとんどないなぁ
この辺も詳しくなりたいところだ
code:ChatGPTさんが提示したサンプルコード.rb
require 'active_support'
require 'active_support/current_attributes'
# ActiveSupport::CurrentAttributesをincludeするクラスを定義します
class MyContext
include ActiveSupport::CurrentAttributes
# カスタムのカレント属性を定義します
attribute :user_id
attribute :request_id
end
# カレント属性に値を設定します
MyContext.user_id = 1
MyContext.request_id = 'abc123'
# 別のスレッドで同じクラスのインスタンスを使って値にアクセスできます
Thread.new do
end.join
# メインスレッドでの値は変更されていません
ActiveSupport::CurrentAttributes はクラスだけど、includeできる?
試してみる
ChatGPTさんのやつ
code:rb
irb(main):001* class MyContext
irb(main):002* include ActiveSupport::CurrentAttributes
irb(main):003*
irb(main):004* # カスタムのカレント属性を定義します
irb(main):005* attribute :user_id
irb(main):006* attribute :request_id
irb(main):007> end
(irb):2:in `include': wrong argument type Class (expected Module) (TypeError)
include ActiveSupport::CurrentAttributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
そうだよね、できないよね
APIドキュメントではクラスを継承してるのでその書き方でやってみる
code:rb
irb(main):001* class MyContext < ActiveSupport::CurrentAttributes
irb(main):002* attribute :user_id
irb(main):003* attribute :request_id
irb(main):004> end
irb(main):005> MyContext.user_id = 1
=> 1
irb(main):006> MyContext.request_id = 'abc123'
=> "abc123"
irb(main):007* Thread.new do
irb(main):010> end.join
User ID in thread:
Request ID in thread:
User ID in main thread: 1
=> nil
Request ID in main thread: abc123
=> nil
スレッド内(7~10行目)では user_id と request_id が設定されていない
つまり、スレッドごとに異なるクラス変数を保持できる、ということ
比較として ActiveSupport::CurrentAttributes を継承せずに素朴にやってみるとこうなる
code:rb
irb(main):001* class MyClass
irb(main):002* cattr_accessor :user_id
irb(main):003* cattr_accessor :request_id
irb(main):004> end
=> :request_id=
irb(main):005> MyClass.user_id = 1
=> 1
irb(main):006> MyClass.request_id = 'abc123'
=> "abc123"
irb(main):007* Thread.new do
irb(main):010> end.join
User ID in thread: 1
Request ID in thread: abc123
User ID in main thread: 1
=> nil
Request ID in main thread: abc123
=> nil
スレッド内(7~10行目)でも user_id と request_id が設定されている
つまり、スレッドをまたがってクラス変数が共有されている、ということ