preloadがうまくいかないときに何とかするbulk_loaderというライブラリを作ってみた
というやつが先々週のgithub trendで流行っていたんですが、もっとうまく書けそう、という気がしたので書いてみました。 (GraphQL使ってないので同じ用途に使えるかは今のところ不明ですが。
# app/models/post.rb class Post < ApplicationRecord include BulkLoader::DSL bulk_loader :comment_count, :id, default: 0 do |ids| Comment.where(id: ids).group(:post_id).count end end
こんなふうに書いておくと、次のように使えます。
# app/controllers/posts_controller.rb class PostsController < ApplicationController def index @posts = Post.limit(10) # load comment_count blocks with mapping by #id # you can avoid N+1 queries. Post.bulk_loader.load(:comment_count, @posts) render(json: @posts.map {|post| { id: post.id, comment_count: post.comment_count } }) end end
まだ試していないですが、Polymorphic Associationの関連を効率良く読みこんだり、memcachedとかにcacheしつつなかったときにloadしたりするのにも使えると思います。
bulk_loaderの定義時に、:idをキーに持つHashを返すのがポイントで、:idの情報を利用して元のobjectに紐づけてくれるので、mappingの処理を泥くさくなく書くことができます。(もちろんこのサンプルは非常にきれいに書けるパターンですが。
ActiveRecordに特に依存しないように実現しているので、drapper などでも定義できるはずですし、railsのupdate時に困ったりする心配も特にはないはずです。
今日からあるRubyKaigi 2017 に参加しているのでご意見などいただけると幸いです。 pixivブースでPawooの象さんのTシャツを着ている人がいたらたぶん私です。