🦔
Railsで特定のattributeを特定の場合にのみ更新できないようにするvalidationを書く
状況
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