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

Going Rails-way

Ruby on Rails 開発者で気づいたこと、ちょっといいなって思ったものなどを書き留めるブログ。

クラスメソッド increment_counter と インスタンスメソッドincrement!

ActiveRecord

実際の動作

increment_counter

irb(main):001:0> Post.increment_counter(:comments_count, 1)
  SQL (4.1ms)  UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1 WHERE "posts"."id" = ?  [["id", 1]]

increment!

irb(main):001:0> post = Post.create
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "posts" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", 2017-03-02 02:37:16 UTC], ["updated_at", 2017-03-02 02:37:16 UTC]]
   (3.9ms)  commit transaction
=> #<Post id: 1, title: nil, comments_count: nil, created_at: "2017-03-02 02:37:16", updated_at: "2017-03-02 02:37:16">
irb(main):002:0> post.title = 'Hoge'
=> "Hoge"
irb(main):003:0> post.increment!(:comments_count, 1)
  SQL (4.1ms)  UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1 WHERE "posts"."id" = ?  [["id", 1]]
=> #<Post id: 1, title: "Hoge", comments_count: 1, created_at: "2017-03-02 02:37:16", updated_at: "2017-03-02 02:37:16">
irb(main):004:0> post.reload
  Post Load (0.3ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<Post id: 1, title: nil, comments_count: 1, created_at: "2017-03-02 02:37:16", updated_at: "2017-03-02 02:37:16">

Rails4との違い

  • Rails4ではincrement!で他の属性と同時に更新できた
  • Rails4ではincrement!COALESCE関数が使われず、comments_count = 1のようなクエリだった

まとめ

  • increment!の場合タイムスタンプが更新されるが、increment_counterは更新されない
  • どちらも他の属性と同時に(1つのUPDATEで)更新することは、できない
    • Rails4ではできるけど、incrementという名前からして正しくない使い方と考えておこう

特定のspecで外部キー制約を無効にする

外部キー制約を使用すると、コード上のミスによりデータベースに不整合が発生するのを未然に防ぐことができてよい。

Active Record マイグレーション | Rails ガイド

Rails:外部キー制約をマイグレーションで表現する方法

第四章 キーレスエントリ(外部キー嫌い) - Qiita

stub_modelなどを使っていると、制約の影響でsaveでエラーが発生したりして煩わしいこともある。今の主題ではないのでここでは制約のことは置いておきたいのである。

仕方がないので

# spec/support/foreign_key_checks.rb

RSpec.configure do |config|
  config.before(:each, foreign_key_checks: false) do
    ActiveRecord::Base.connection.execute('SET FOREIGN_KEY_CHECKS=0')
  end

  config.after(:each, foreign_key_checks: false) do
    ActiveRecord::Base.connection.execute('SET FOREIGN_KEY_CHECKS=1')
  end
end

こういうのを作っておけば、こうやって特定のspecでだけ外部キー制約を無効にできる。

describe '#hoge', foreign_key_checks: false do
  # この中のexampleは外部キー制約を無視する
  it '...' do 
    # 
  end

  it '...' do 
    # ...
  end
end

いいやり方かはしらない。