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シャツを着ている人がいたらたぶん私です。

bundle update 2016-01-25

activerecord-import v0.11.0

https://github.com/zdennis/activerecord-import/compare/v0.10.0...c0a4393a

AR 5.0 betaまわりの対応を進めているもよう。ただテスト一部通ってないのでまだ微妙かもしれない。

capistrano-rails v1.1.6

https://github.com/capistrano/rails/compare/v1.1.5...v1.1.6#files_bucket

  • zshだとassets:cleanが適切に走らないことがあるのを修正
  • assets:clobberを実行できるようになった
  • deploy:migratingにhookすることで、rake db:migrate以外のことをできるようになった

twitter v5.16.0

https://github.com/sferik/twitter/compare/v5.15.0...v5.16.0#files_bucket

  • http gemを更新
  • mp4のアップロードに対応

mp4のアップロード対応は、入ったとおもってたけど、masterのみにあって、v5.16.0には含まれていないっぽい。

http v1.0.2

https://github.com/httprb/http/compare/v0.9.8...v1.0.2#files_bucket

http 1.0.0のリリース時に一部のobsolateなAPIが削除されているので使っている場合は、注意が必要

nokogiri v1.6.7.2

https://github.com/sparklemotion/nokogiri/compare/v1.6.7.1...v1.6.7.2#files_bucket

libxml2のセキュリティアップデートへ追従

rubocopがもっさりするときは除外ディレクトリを見直してみるとよいかもしれない

前のエントリで書いたrubocopがもっさりする問題を調べてみると、どうやら走査する必要のないディレクトリのファイルまでチェックしてしまっていて非常に遅くなっているのが原因だったようだ。

なお、rubocopは**/*で終わる文字列のパターンをディレクトリを除外する設定と認識して先に除外する、ということをやっているため、ディレクトリを指定するか、他のパターンで指定するかどうか、というのはパフォーマンスにけっこうな影響があるようだ。

AllCops:
  Exclude:
    - 'public/**/*'
    - 'tmp/**/*'
    - 'log/**/*'

あたりは最初に自動生成したファイルには含まれていないが、railsプロジェクトであれば入れておくとよいようだ。

vendor/**/*だけは、Rubocopのdefaultの設定に含まれているので何もしなくても除外してくれる。tmp/**/*もdefaultでこの候補に入れてくれるとうれしいケースが多いんじゃないかと思いつつ、どう提案しようかな... (今回は、tmp以下に大量にファイルがあるが対象から除外されていないのが遅い原因だった。)

bundle update 2016-01-17

rubocop v0.36.0

  • 大量にCopが追加されている関係でいくつかこけるようになる。
  • 設定ファイルにも非互換な変更が含まれているので更新する必要がある。
  • なんか最初の実行開始までがもっさりするようになった気がする。

https://github.com/bbatsov/rubocop/compare/v0.35.1...v0.36.0#files_bucket

.rubocop.ymlの修正

AllCops:
  TargetRubyVersion: 2.3

とか追加しておかないとキーワード引数とか使ったときにrubocopがエラーになるもよう

メッセージ通りだけど、Style/TrailingCommaがStyle/TrailingCommaInLiteralなどに名前がかわったので変更する必要があるみたい。

新しくひっかかったやつ

endの位置をifと揃える

   a = if b
   else
   end

   a = if b
       else
       end

みたいにifのところで揃えないといけなくなった。AutoCorrectで修正はしてくれないらしい。

if else if end endを if elsif endにする

  if a
  else
    if b
    end
  end
  if a
  elsif b
  end

上の方式の方が個人的には理解しやすいことが多いように感じるので好みだけどまぁあわせておく。

他にもいくつかひっかかったけど、手直しが必要なやつの多くは上の2パターンだった。

parser v2.3.0.1

  • ruby2.3.0への追従がメイン ~HEREDOC対応が含まれている。

https://github.com/whitequark/parser/compare/v2.3.0.pre.6...v2.3.0.1

sass v3.4.21

  • いくつかの細かいバグがなおったもよう。わりと細かいケースみたいなのでそんなに踏まなそうなバグではある。

https://github.com/sass/sass/releases/tag/3.4.21

guard-rubocopでsystem経由の呼出しを止めると快適になった

github.com

guard-rubocopの中身を見たらsystemで呼びだしていたので、直接Rubocop::CLIを呼びだして起動するようにしてみたら爆速になって快適になりました。

もっさりするのでテストのあとにrubocopを実行するようにしていたけど、これなら先に実行してもいいんじゃないかなーという速度感。

rubocop.ymlのsyntaxエラーとかの場合のエラーがうまくハンドリングできてないっぽいですが、通常の実行には(まだちょっとしか使ってないけど)問題なさそうです。

お試しは、Gemfileに

gem 'guard-rubocop', github: 'walf443/guard-rubocop', branch: 'do_not_system'

でどうぞ。

bundle update 2016-01-13

aasm v4.7.0

https://github.com/aasm/aasm/compare/v4.5.1...v4.7.0#files_bucket

新しくhookポイントが追加されている。 全イベントの前後やTransactionの前後にイベントが仕込めるようになったもよう。

カラム名が存在しないやつに設定してしまったときのエラーがNoMethodErrorではなくわかりやすいエラーになった。

tilt v2.0.2

https://github.com/rtomayko/tilt/compare/v2.0.1...v2.0.2#files_bucket

babel対応が入っているもよう

sidekiq v4.0.2

https://github.com/mperham/sidekiq//compare/v4.0.1...v4.0.2#files_bucket

テスト用のSidekiq::QueueというAPIが追加されたけど、ActiveJob経由から使うのであまり使わなそう。

spring v1.6.2

https://github.com/rails/spring/compare/v1.6.1...v1.6.2#files_bucket

bin/rakeなどでエラーになっていたのが治ったもよう

コマンドラインを複数回実行して実行時間を計測するツールを書いた

walf443/benchcmd · GitHub

なんか最近ツール系のやつを趣味で高速化したりしていて、PR投げるときに速くなったよと説明するときに手元で何度か計測して、平均値とか調べるのがめんどいなと思って、複数回実行して平均/標準偏差を表示してくれるツールを作った。

もっと定番のがあるような気がするけど、あんまり聞いたことがない。

Usage of benchcmd:
  -n int
          number of times to run (default 10)
  -summary
          only output summary result
$ benchcmd -n 10 'go version'
run "go version"
15.711435ms
19.42685ms
19.967876ms
24.896109ms
19.557516ms
21.698995ms
16.499163ms
22.28795ms
23.461732ms
18.743192ms
count:  10 times executed
avg:    20.225081ms
stdev:  2.634089ms