Rails Active Record에서 `*_was` 메서드 이해 및 활용

How *_was Method Works in ActiveRecord

작성자
발행일
2025년 09월 25일

핵심 요약

  • 1 `*_was` 메서드는 Active Record의 더티 트래킹 시스템의 일부로, 속성이 데이터베이스에 저장되기 전의 이전 값을 반환합니다.
  • 2 주로 `before_update` 콜백 내에서 속성 변경을 감지하고 이전 값과 새 값을 비교하여 조건부 로직을 실행하는 데 유용합니다.
  • 3 이 메서드는 인메모리 변경 사항만 추적하며, `save` 작업 이후에는 더티 상태가 초기화되므로 이전 값을 반환하지 않습니다.

도입

Rails에서 모델 속성의 변경 사항을 감지하는 것은 반응적이고 상태 인지 시스템을 구축하는 데 필수적입니다. 업데이트 로깅, 부수 효과 트리거, 유효성 검사 강제 등 다양한 시나리오에서 값이 변경되기 전의 상태를 아는 것은 매우 중요합니다. Rails는 `*_was` 메서드 패턴을 통해 이러한 기능을 간편하게 제공하며, 이 글에서는 Active Record에서 `*_was`가 어떻게 작동하고 언제 효과적으로 사용해야 하는지, 그리고 주의할 점에 대해 자세히 살펴보겠습니다.

Active Record의 *_was 메서드 이해

*_was 메서드는 Active Record의 더티 트래킹(dirty tracking) 시스템의 핵심 부분입니다. 이 메서드는 모델 속성에 인메모리 변경이 발생하기 전의 값을 반환하여, 객체의 현재 생명 주기 동안(데이터베이스에 저장되기 전) 속성의 이전 값을 검색할 수 있도록 합니다.

Rails는 모델의 각 속성에 대해 동적으로 헬퍼 메서드를 생성합니다. 이 메서드들은 attribute_was라는 핵심 메서드를 기반으로 작동하며, 내부적으로 다음과 같이 구현됩니다: ruby def attribute_was(attr) attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) end 만약 속성이 메모리에서 변경되었다면 attribute_waschanged_attributes 해시에서 이전 값을 반환하고, 변경되지 않았다면 현재 값을 반환합니다.

*_was 메서드의 활용 예시

  1. 단순한 이름 변경 추적: ruby account = Account.create(name: 'John') account.name = 'Jane' account.name_was # => "John" (변경 전 값) account.save account.name_was # => "Jane" (저장 후에는 현재 값) 저장 전에는 name_was가 원래 값(‘John’)을 반환하지만, 저장 후에는 더티 상태가 초기화되어 현재 값(‘Jane’)을 반환합니다.

  2. 상태 전환에 따른 로직 트리거: before_update 콜백에서 모델의 특정 상태 전환에 따라 로직을 실행할 때 *_was 메서드의 진가가 발휘됩니다. 예를 들어, 주문 상태 변경에 따라 알림을 보내는 경우를 들 수 있습니다. ```ruby class Order < ApplicationRecord before_update :handle_status_transition

    private def handle_status_transition return unless status_changed? old_status = status_was new_status = status

    case [old_status, new_status]
    when ['pending', 'shipped']
      notify_shipped # 배송 확인 이메일 발송
    when ['shipped', 'delivered']
      notify_delivery # 배송 완료 알림 발송
    end   end   # ... (notify_shipped, notify_delivery 메서드 구현) end ``` `status_changed?`로 변경 여부를 확인하고, `status_was`로 이전 값을, `status`로 새 값을 가져와 특정 전환(`'pending' → 'shipped'`)에 따라 다른 액션을 트리거할 수 있습니다.
    

중요한 주의사항

  • 저장 전(Before Save)에만 작동: save 작업 후에는 더티 상태가 초기화되므로, *_was는 더 이상 변경 전 값을 반환하지 않고 현재 값을 반환합니다.

  • 변경이 없으면 현재 값과 동일: 속성이 변경되지 않았다면 *_was는 현재 값과 동일합니다. 즉, attribute_was == attribute입니다.

  • 이력(History)을 유지하지 않음: 이 메서드는 인메모리 변경 사항만 추적합니다. 여러 번의 저장에 걸쳐 완전한 감사 로깅이나 이력 추적이 필요하다면 PaperTrail과 같은 Gem을 고려해야 합니다.

결론

Active Record의 `*_was` 메서드는 Rails 애플리케이션에서 모델 속성의 인메모리 변경 사항을 효율적으로 추적하고 이에 반응하는 강력한 도구입니다. 특히 `before_update` 콜백 내에서 이전 값과 현재 값을 비교하여 조건부 로직을 실행하거나, 특정 상태 전환에 기반한 알림 및 부수 효과를 트리거할 때 매우 유용합니다. 그러나 이 메서드는 `save` 작업 이전에만 유효하며, 영구적인 변경 이력을 제공하지 않는다는 점을 명확히 인지하고 사용해야 합니다. 이러한 제한 사항을 이해하고 적절한 시나리오에 활용한다면, `*_was` 메서드는 더욱 반응적이고 견고한 Rails 시스템을 구축하는 데 크게 기여할 것입니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!