読者です 読者をやめる 読者になる 読者になる

見習いの見習いプログラマ

まだまだ先は長い

ActiveRecord 3 でINSERT SELECTするためのgem activerecord-pickin を作ってみたよ

https://github.com/miio/activerecord-pickin

作りました。

経緯としては

  • INSERT SELECTって地味に使いたい時がある
  • ググっても見つからない
  • ar-extensionsっていうのは見つかったけど、Rails2
  • @onkさんに聞いてみても知らないとお返事をもらう

→じゃ、作るかーってことで基本実装だいたい1〜2日くらいだった気がします

コンセプトとしては、SELECTした結果をINSERTしたいということで、Rails3から強化されてるRelationを有効活用してみました。

使い方とか

Map.where(stage_id: 1).pickin(GameMap, {game_stage_id: 24}, [:id])

INSERT INTO 'game_maps' ("id", "game_stage_id", "type", "x", "y", "created_at", "updated_at") SELECT NULL, '24', "type", "x", "y", "created_at", "updated_at" FROM "maps" WHERE "maps"."stage_id" = 1

1つ1つ解いていきますと

SELECTしたいModel.relationのオブジェクト.pickin(Intoしたいクラス, {特定のカラムを固定の文字にしたい場合}, [INSERT対象から外したいカラム])

です。

例えば、idカラムなんかはプライマリキー制約があったりするので外したいってことが割とあると思うんですよね。 しかし、わざわざカラム指定するのも面倒そうなので対象から外すというアプローチをとってみました(もし標準機能であるなら突っ込んでほしいです

次に、値のオーバライドも対応しました。先ほどの例だと、game_stage_idはMapに持っていないから、固定値いれたいよ!って時に使います。

Map.where(stage_id: 1).select([:type]).pickin(GameMap)

INSERT INTO 'game_maps' ("type") SELECT "type" FROM "maps"  WHERE "maps"."stage_id" = 1

selectにも対応しました。この場合は、selectで指定したカラムのみに対してうまいことクエリを作ります

ちなみに、現時点ではv0.9.1になっていて、正式版ではないです(気になる仕様やテスト整備が追いついていないため 更に言うと、v0.9.0まではMySQLでシンタックスエラーになるバグを抱えています。

多分テスト整備したらv1.0.0で正式版にしたいなーと思ってたり。

開発裏話

やっていることとしては、Rails3から追加されたArelを使ってクエリ生成しているだけだったりします。Arel単体にもINSERT SELECTのクエリを発行できるとか出来ないとかありますが、あちこちFIXMEだらけだった記憶があります。

まぁ、Arelに一応存在していると知る以前に実装してしまったので、実際にはINSERTクエリとSELECTクエリを2つに分けて、これを単純に文字列連結しただけという簡単実装です。