こんにちは、鈴木です。
Rails でリクエストごとにロケールを変更するには、リクエストごとに (before_action などで) I18n.locale に :ja や :en などを代入します。
I18n ってスレッドセーフ?
先日、「Rails4 のロケールってスレッドセーフだよね?」「デフォルトでスレッドセーフになったから大丈夫なはずだよね?」という話がありました。
結論から言うと、I18n はスレッドセーフに作られています。
スレッドセーフにするには?
そもそも Ruby でスレッドセーフなコードを書くときにやるべきことを振り返っておきましょう。
スレッドセーフにするには、以下のいずれかの方法をとります。
- 複数スレッドで共有するリソースに対して Mutex で保護する。
- スレッドローカル変数を用いて、スレッド間でリソースを共有しないようにする。
I18n のスレッドセーフな実装
気になったので I18n のソースコードを読んでみると、このようになっていました。
1 2 3 4 5 6 7 8 |
def config Thread.current[:i18n_config] ||= I18n::Config.new end # Sets I18n configuration object. def config=(value) Thread.current[:i18n_config] = value end |
Confin というクラスが I18n のオプションを保持する役割をしており、それがスレッドごとに保持されるようになっています。
そして、I18n.locale などのクラスメソッドは次のように実装されていました。
1 2 3 4 5 6 7 8 9 10 11 12 |
%w(locale backend default_locale available_locales default_separator exception_handler load_path).each do |method| module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1 def #{method} config.#{method} end def #{method}=(value) config.#{method} = (value) end DELEGATORS end |
メタプログラミングに馴染みのない方には分かりづらいかもしれませんが、%w(locale backend ... load_path) のそれぞれについて、config から値の取得/設定を行うアクセッサを定義しています。
Config のインスタンスをスレッドごとに保持するようにしつつ、メタプログラミングでアクセッサをまとめて定義する、という手法です。
Ruby ではこのような実装を簡単に実現できて良いですね(^^