より安全にadhoqを実行したい

追記:だいたいの内容が反映された、adhoq v0.4.0がリリースされていますので、ご利用ください。

github.com

adhoqを使うと、管理画面などで任意のSQLができるようになって便利そうだ。 ただ調査をしていると、sandbox機構は、Transactionを実行してRollbackする、という方式になっているので、アプリケーション実行ユーザーがDROP TABLEできる権限で動いていたりすると、DROP TABLEなどすることができてしまうので、通常のアプリケーションユーザーとは別のユーザーで実行したかった。(アプリケーションユーザーがDROP TABLEできない方がのぞましいが。

また、本番と同じ系統で任意のSQLを実行できるようにした場合、重いSQLなどを実行されてしまったときに本番で別の部分がSlow queryになってしまうので、サービスから隔離した系統で実行させたいというのもあった。

Adhoqの設定には、 config.database_connection というものがあって、ここを置きかえればよさそうにも思えるが、

https://github.com/esminc/adhoq/blob/master/lib/adhoq.rb#L17

      config.database_connection = proc { ActiveRecord::Base.connection }

ここを置きかえただけだと、Adhoqのユーザーの入力したSQL実行終了後に、Adhoqのレコード入れるときなどのconnectionを元に戻すことができないので、うまくいかないっぽかったのでAdhoqそのものを変更した。

Comparing esminc:master...walf443:connection_switchable · esminc/adhoq · GitHub

インターフェースを変えてしまうのでPRにして通るかどうかわからないし英語で書いてうまく伝わるかなー、といった感じだったのでひとまずブログで書いてみた。

なお、手元ではreadonlyな接続を利用するのに、switch_pointとあわせて利用していて、上記のパッチに加えて、

# config/initializers/adhoq.rb
Adhoq.configure do |config|
  config.hidden_model_names = %w[SwitchPoint::Proxy::MainReadonly]
end

module AdhoqReadonlyable
  def with_connection
    result = nil
    SwitchPoint.with_readonly_all do
      result = yield(ApplicationRecord.connection)
    end
    result
  end
end
Adhoq::Executor::ConnectionWrapper.prepend(AdhoqReadonlyable)

といった感じにして、connectionまわりを差し変えている。

また、触っていて、いくつか気になったところを見つけたのでいくつか別途PRにした。

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などでエラーになっていたのが治ったもよう