🦔

Railsで特定のattributeを特定の場合にのみ更新できないようにするvalidationを書く

2024/07/19に公開

状況

Railsで特定のattributeを特定の場合にのみ更新できないようにするvalidationを書きたい。
例えば、以下のようなBookテーブルがある。

db/migrate/20240719000000_create_books.rb
# frozen_string_literal: true

class CreateBooks < ActiveRecord::Migration[7.1]
  def change
    create_table :books do |t|
      t.timestamps

      t.text :content, null: false
      t.boolean :published, null: false, default: false
    end
  end
end

このとき、以下のようなvalidationをBookモデルに定義したい:

  • publishedがtrueの場合はcontentはupdateできない
  • publishedがfalseの場合はcontentはupdateできる

結論

以下のように書いたところうまくいった

app/models/book.rb
# frozen_string_literal: true

class Book < ApplicationRecord
    validate :content_cannot_be_updated, on: :update, if: :published?

    private

    def content_cannot_be_updated
        errors.add(:content, 'cannot be updated if already published') if will_save_change_to_content?
    end
end

RSpec

これに対するRSpecは以下のように書ける

spec/models/book_spec.rb
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Book, type: :model do
    describe 'contentの更新' do
        context 'publishedがfalseのとき' do
            it 'contentを更新できること' do
                book = create(:book, published: false)

                expect do
                    book.update(content: '更新')
                end.to change { book.reload.content }.from(book.content).to('更新')
            end
        end

        context 'publishedがtrueのとき' do
            it 'contentを更新できないこと' do
                book = create(:book, published: true)

                expect do
                    book.update(content: '更新')
                end.not_to change { book.reload.content }
            end
        end
    end
end

Discussion