SQLではカウンタのincrement/decrementは1つの文で済ますことになっている。
UPDATE table SET counter = counter + 1 WHERE ...
ActiveRecordのモデルにはincrementやdecrementの定義がある。そうか便利だなぁ…??
1.9.3-p545 :021 > Tbl.take.increment!(:counter, 2) D, [2014-09-04T00:36:32.902849 #73566] DEBUG -- : Tbl Load (0.1ms) SELECT "tbls".* FROM "tbls" LIMIT 1 D, [2014-09-04T00:36:32.903201 #73566] DEBUG -- : (0.0ms) begin transaction D, [2014-09-04T00:36:32.904809 #73566] DEBUG -- : SQL (0.4ms) UPDATE "tbls" SET "counter" = ?, "updated_at" = ? WHERE "tbls"."id" = 1 [["counter", 10], ["updated_at", 2014-09-03 15:36:32 UTC]] D, [2014-09-04T00:36:32.914149 #73566] DEBUG -- : (9.1ms) commit transaction => true
SELECTで読んで、Rubyが足し算して、UPDATEで足した結果を書き出す…こんなのってないぞ。それをORマッパーがincrementと名乗るか普通?
正解は、モデルのクラスメソッドのupdate_countersを使う。これは複数のカウンタの操作が可能。第1引数のオブジェクトIDも複数書けるし、第2引数以降のカラム名も複数書ける。オブジェクトから呼べないのが使いにくいのを除けば、普通のSQLが発行される。まあ↓の例ではIDを求めるのにSELECTは発行されるが、UPDATE自体は普通にDB側で加算されてくれる。
1.9.3-p545 :022 > Tbl.update_counters(Tbl.take.id, :counter=>1) D, [2014-09-04T00:37:01.497430 #73566] DEBUG -- : Tbl Load (0.2ms) SELECT "tbls".* FROM "tbls" LIMIT 1 D, [2014-09-04T00:37:01.507024 #73566] DEBUG -- : SQL (9.0ms) UPDATE "tbls" SET "counter" = COALESCE("counter", 0) + 1 WHERE "tbls"."id" = 1 => 1 1.9.3-p545 :023 > Tbl.update_counters(Tbl.take.id, :counter=>1, :counter2=>2) D, [2014-09-04T00:37:06.088688 #73566] DEBUG -- : Tbl Load (0.2ms) SELECT "tbls".* FROM "tbls" LIMIT 1 D, [2014-09-04T00:37:06.098522 #73566] DEBUG -- : SQL (9.4ms) UPDATE "tbls" SET "counter" = COALESCE("counter", 0) + 1, "counter2" = COALESCE("counter2", 0) + 2 WHERE "tbls"."id" = 1 => 1