preloadがうまくいかないときに何とかするbulk_loaderというライブラリを作ってみた

github.com

というやつが先々週の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の処理を泥くさくなく書くことができます。(もちろんこのサンプルは非常にきれいに書けるパターンですが。

GitHub - walf443/bulk_loader

ActiveRecordに特に依存しないように実現しているので、drapper などでも定義できるはずですし、railsのupdate時に困ったりする心配も特にはないはずです。

今日からあるRubyKaigi 2017 に参加しているのでご意見などいただけると幸いです。 pixivブースでPawooの象さんのTシャツを着ている人がいたらたぶん私です。