Nic Lin's Blog

喜歡在地上滾的工程師

[Rails] 大量呼叫 AASM 的 i18n 根本是災難

AASM 應該算是 rails project 很多人使用的一支 gem 了,在狀態管理可以省下不少時間,不過當網站有國際語系的要求時,我們希望可以直接在表現層依照不同的語系顯示不同的狀態。

看了 AASM 的 wiki , 確實有支持 i18n,使用 job.aasm.human_state 就可以直接顯示相應的狀態了

en:
  activerecord:
    attributes:
      job:
        aasm_state:
          cleaning: Cleaning Up
          running: Working
          sleeping: Asleep

我在 CSV 輸出時,插入每一個 row 都希望能夠顯示相對應語系的狀態,結果我發現超級慢… 經過交叉比對後我懷疑是 aasm 的這個 method 在慢,於是我對他做了 benchmark

使用 aasm.human_state 做翻譯

Development [14] rocket(main)> n = 10000
10000
Development [15] rocket(main)> Benchmark.bm do |x|
Development [15] rocket(main)*   x.report { n.times do; order.aasm.human_state end }
Development [15] rocket(main)* end
       user     system      total        real
  177.270000   0.930000 178.200000 (187.869913)
[
    [0] #<Benchmark::Tms:0x00007fcbead4d6c8 @label="", @real=187.86991300003137, @cstime=0.0, @cutime=0.0, @stime=0.9300000000000002, @utime=177.27, @total=178.20000000000002>
]

使用 I18n.t(“activerecord.attributes.order.aasm_state.#{order.aasm_state}“) 做翻譯

Development [14] rocket(main)> n = 10000
10000
Development [13] rocket(main)> Benchmark.bm do |x|
Development [13] rocket(main)*   x.report { n.times do; I18n.t("activerecord.attributes.order.aasm_state.#{order.aasm_state}") end }
Development [13] rocket(main)* end
       user     system      total        real
   0.170000   0.000000   0.170000 (  0.171148)
[
    [0] #<Benchmark::Tms:0x00007fcbed0ba778 @label="", @real=0.1711479999939911, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.1699999999999875, @total=0.1699999999999875>
]

看輸出結果就不必多說了,整個慢到不行,如果不是大量輸出 csv row 還真的不會注意到,猜測是呼叫太多層導致,不如直接拿 i18n 來的乾脆。

查了一下 aasm.human_state 是如何被呼叫的?

Development [9] rocket(main)> $ Order.last.aasm.human_state

From: /Users/Nic/.rvm/gems/ruby-2.4.4/gems/aasm-4.11.0/lib/aasm/instance_base.rb @ line 32:
Owner: AASM::InstanceBase
Visibility: public
Number of lines: 3

def human_state
  AASM::Localizer.new.human_state_name(@instance.class, state_object_for_name(current_state))
end

是透過 AASM::Localizer 去呼叫的,那我們查一下這邊牽扯到的地方。

Development [14] rocket(main)> AASM::Localizer.ancestors
[
    [ 0] AASM::Localizer < Object,
    [ 1] ActiveSupport::ToJsonWithActiveSupportEncoder,
    [ 2] Object < BasicObject,
    [ 3] ActionDispatch::TestProcess,
    [ 4] ActionDispatch::TestProcess::FixtureFile,
    [ 5] FriendlyId::ObjectUtils,
    [ 6] PP::ObjectMixin,
    [ 7] ActiveSupport::Dependencies::Loadable,
    [ 8] JSON::Ext::Generator::GeneratorMethods::Object,
    [ 9] ActiveSupport::Tryable,
    [10] Kernel,
    [11] BasicObject
]

在看一下直接用 I18n 的部分。

Development [15] rocket(main)> I18n.ancestors
[
    [0] I18n
]

嗯,跟猜測的一樣,有太多層相依,不如直接 I18n 才是最乾脆的。

當然,如果你只是少部分使用,可以忽略不計,但如果輸出 5 萬條 CSV,然後每次都要呼叫 aasm.human_state,那就有罪受了。

comments powered by Disqus