同時実行になった時におかしくなっちゃう、というその点でずっと使えなかった #increment! が同時実行を意識した処理になったよ!
嬉しい! 素敵!
rails4
irb(main):001:0> counter = Counter.create
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "counters" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2016-02-22 11:39:38.966295"], ["updated_at", "2016-02-22 11:39:38.966295"]]
   (1.9ms)  commit transaction
=> #<Counter id: 4, value: nil, created_at: "2016-02-22 11:39:38", updated_at: "2016-02-22 11:39:38">
default 設定し忘れて nil になっているけれども value は integer です。
この value を #increment! したいと思います。
irb(main):004:0> counter.increment!(:value)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 1], ["updated_at", "2016-02-22 11:40:48.595257"], ["id", 4]]
   (2.7ms)  commit transaction
=> true
実行された SQL を見ると value に 1 が指定されてるのがわかります。
この 1 は、どこからきたかっていうと、インスタンスがキャッシュしている value の値を元に計算されてるんですね。
つまりこれはどういうことかっていうと、
irb(main):005:0> counter1 = Counter.find(4)
  Counter Load (0.2ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT 1  [["id", 4]]
=> #<Counter id: 4, value: 1, created_at: "2016-02-22 11:39:38", updated_at: "2016-02-22 11:40:48">
irb(main):006:0> counter2 = Counter.find(4)
  Counter Load (0.1ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT 1  [["id", 4]]
=> #<Counter id: 4, value: 1, created_at: "2016-02-22 11:39:38", updated_at: "2016-02-22 11:40:48">
こんな感じで、同一レコードから生成された別のインスタンスがいる時に、それぞれで同時に #increment! したらどうなるかってことです。
irb(main):007:0> counter1.increment!(:value)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 2], ["updated_at", "2016-02-22 11:43:39.842789"], ["id", 4]]
   (1.8ms)  commit transaction
=> true
irb(main):008:0> counter2.increment!(:value)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 2], ["updated_at", "2016-02-22 11:43:45.706247"], ["id", 4]]
   (1.7ms)  commit transaction
=> true
value が 1 の状態から #increment! は 2回呼ばれました。
value が 3 になっていて欲しいのですが、実際には 2 です ![]()
irb(main):011:0> Counter.find(4).value
  Counter Load (0.1ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT 1  [["id", 4]]
=> 2
この同時実行に #decrement! が混ざってくると、もう何が何やらです。
irb(main):024:0> counter1.increment!(:value, 10)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 12], ["updated_at", "2016-02-22 11:50:37.442805"], ["id", 4]]
   (1.8ms)  commit transaction
=> true
irb(main):025:0> counter2.increment!(:value, 20)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 22], ["updated_at", "2016-02-22 11:50:52.249161"], ["id", 4]]
   (1.7ms)  commit transaction
=> true
irb(main):026:0> counter1.decrement!(:value, 10)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "counters" SET "value" = ?, "updated_at" = ? WHERE "counters"."id" = ?  [["value", 2], ["updated_at", "2016-02-22 11:51:04.072007"], ["id", 4]]
   (2.7ms)  commit transaction
=> true
irb(main):027:0> Counter.find(4).value
  Counter Load (0.1ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT 1  [["id", 4]]
=> 2
20 はどこに消えた ![]()
rails5
さあ、同じように Counter インスタンスを作ります。
irb(main):001:0> counter = Counter.create
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "counters" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", 2016-02-22 11:47:23 UTC], ["updated_at", 2016-02-22 11:47:23 UTC]]
   (2.9ms)  commit transaction
=> #<Counter id: 5, value: nil, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
おもむろに #increment! しましょう。
irb(main):002:0> counter.increment!(:value)
  SQL (3.2ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) + 1 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 1, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
発行された SQL を見ると UPDATE 文が変わったのがわかるかと思います。
"value" = COALESCE("value", 0) + 1 となっていますね(嬉
先ほどと同じように、同一のレコードから生成されたインスタンスを複数作って操作してみます。
irb(main):003:0> counter1 = Counter.find(5)
  Counter Load (0.2ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> #<Counter id: 5, value: 1, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):004:0> counter2 = Counter.find(5)
  Counter Load (0.2ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> #<Counter id: 5, value: 1, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):005:0> counter1.increment!(:value)
  SQL (2.1ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) + 1 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 2, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):006:0> counter2.increment!(:value)
  SQL (2.1ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) + 1 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 2, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):007:0> Counter.find(5).value
  Counter Load (0.2ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> 3
今度はちゃんと value が 3 になっています ![]()
#decrement! だって大丈夫!
irb(main):008:0> counter1.increment!(:value, 10)
  SQL (2.2ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) + 10 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 12, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):009:0> counter2.increment!(:value, 20)
  SQL (2.2ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) + 20 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 22, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):010:0> counter1.decrement!(:value, 10)
  SQL (2.2ms)  UPDATE "counters" SET "value" = COALESCE("value", 0) - 10 WHERE "counters"."id" = ?  [["id", 5]]
=> #<Counter id: 5, value: 2, created_at: "2016-02-22 11:47:23", updated_at: "2016-02-22 11:47:23">
irb(main):011:0> Counter.find(5).value
  Counter Load (0.2ms)  SELECT  "counters".* FROM "counters" WHERE "counters"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
=> 23
ただし #increment! や #decrement! した後は、「実際に DB に保存されている値」と「インスタンスのキャッシュ」にズレが出ている可能性があるというのは、意識しておいた方が良さそうです。
irb(main):012:0> counter1.value
=> 2
irb(main):013:0> counter2.value
=> 22