RailsのActiveRecordのインスタンス生成は重い。
- 大量のインスタンスを生成すると、性能が劣化しがちになる。
- 参照用データであればJSONに変換してキャッシュに入れるなどの工夫もあるかと思いますが、全部それで対応するのはなかなかしんどい。
インスタンス生成コストをこんな感じで抑えられるよっていうひとつのやり方。
書いててなかなか最低なやり方だなと思ったが、背に腹は代えられない。悪手なので、どうしてもって場合用
前提
Railsガイドにある関連をサンプルにする。環境はRails 4.1.8
class Customer < ActiveRecord::Base
has_many :orders, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :customer
end
課題
普通はいろんな条件で絞ると思うが、さらっと
customer.orders
とかかれていて、ActiveRecordインスタンスが何百個も生成されるような場合、性能劣化の要因になる。
対応案
1. Modelと同じカラム構造をもつStructを定義する
class Order < ActiveRecord::Base
belongs_to :customer
ModelStruct = Struct.new(:id, :customer_id, :order_date)
end
2. データ取得時にpluckで抜き出してStruct型にする
orders = cutomer.orders.
pluck(:id, :customer_id, :order_date).
map do |id, customer_id, order_date|
Order:: ModelStruct.new(id, customer_id, order_date)
end
これでActiveRecordインスタンスを生成せずに、擬似的にActiveRecordと同じようにインスタンスを扱える。
orders.each do |order|
order.id
order.customer_id
order.order_date
end
HashではなくStructにするのは
- ActiveRecordインスタンスと同じ感覚で扱える
- Struct内にメソッドを定義できる
- 存在しないメソッドを指定するとエラーが返る(Hashだとnilになる)
といったメリットがあるため。
Structはメソッドも定義できる。余計カオスになるのでオススメはしません。
class Order < ActiveRecord::Base
belongs_to :customer
ModelStruct =
Struct.new(:id, :customer_id, :order_date) do
attr_accessor :hogehoge
def method_name
# method
end
end
end
その他
- 保守性/拡張性には欠ける対応なので、使いすぎるのはよろしくないと思います。他にいい案あったら求む。
- pluck_to_hashというgemもあったのですが、試したところ遅かったので使うのをやめました。