LoginSignup
18
11

More than 5 years have passed since last update.

RailsのActiveRecordのインスタンス生成コストを抑止する

Last updated at Posted at 2017-03-20

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もあったのですが、試したところ遅かったので使うのをやめました。
18
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
11