はじめに
RelationのModelオブジェクトを Array
に変換せず、 Relation
そのままMemcachedにキャッシュさせる方法について記載していきます。
結論
結論から言いますと、 load
を呼び出すことで強制的にSQLを発行させ、ロードすることができます。
ことのはじまり
Modelオブジェクトをキャッシュしたい
Memcachedへオブジェクトをキャッシュさせるために以下のコードを実行してみましょう。
Rails.cache.fetch('key'){
Character.where(attack_type: 1)
}
あら?キャッシュしたと思ったんですが。。。
Rails.cache.read('key')
D, [2018-12-06T09:41:50.742460 #14296] DEBUG -- : [Shard: slave1] Character Load (7.2ms) SELECT `characters`.* FROM `characters` WHERE (`characters`.`deleted_at` IS NULL) AND `characters`.`attack_type` = 1
[#<Character:0x007fb2802c8280 ...
Memcachedからオブジェクトを取り出して使おうとするたびにSQLが発行されます。これじゃあまりキャッシュする意味がありません。
これは遅延評価により、実際使うまでSQLも発行されず、オブジェクトも生成されないためです。
そしてMemcachedにはSQL発行&レコードがロードがされてない Relation
オブジェクトのみがキャッシュされていることになります。
どうする?
他の記事で書いてある通りに、 to_a
を呼び出すことで、 Array
に変換して保持する方法もあります。それもよい方法だと思います。
変換する段階で(実際操作する段階で)SQLが発行され、オブジェクトが生成されるためです。
Rails.cache.fetch('key'){
Character.where(attack_type: 1).to_a
}
でも、Relationの機能も使いたい。
せっかくRails使っているし、 Relation
の機能もそのまま使いたいという要望もあるかと思います。
(Arrayを無理やりにモンキパッチして、 Relation
風にしてもよいかもしれませんが、そこまでやる?的な感じがしますね。)
Railsのソースをみると以下のロジックが書いてあります。
module ActiveRecord
class Relation
...
# Causes the records to be loaded from the database if they have not
# been loaded already. You can use this if for some reason you need
# to explicitly load some records before actually using them. The
# return value is the relation itself, not the records.
#
# Post.where(published: true).load # => #<ActiveRecord::Relation>
def load(&block)
exec_queries(&block) unless loaded?
self
end
end
end
コメントを見ますと、 " 明示的にレコードをロードする必要があれば、これ(load)をつかってね " と記載されています。
そして、実際のロジックを見ますと、実際にクエリを発行していることがわかります。
結論
結論は、load
をつけることです。
Rails.cache.fetch('key'){
Character.where(attack_type: 1).load
}
obj = Rails.cache.read('key')
[#<Character:0x007fb2802c8280 ...
obj.class
Character::ActiveRecord_Relation
やった!SQLを発行しない。 Array
ではなく、 Relation
ままでキャッシュさせた!
まとめ
-
load
をつけることで、ModelのRelationオブジェクトを強制的にロードできる。そしてそれをMemcachedにいれればピュアなオブジェクトキャッシュができる。