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_was는 changed_attributes 해시에서 이전 값을 반환하고, 변경되지 않았다면 현재 값을 반환합니다.
*_was 메서드의 활용 예시
-
단순한 이름 변경 추적:
ruby account = Account.create(name: 'John') account.name = 'Jane' account.name_was # => "John" (변경 전 값) account.save account.name_was # => "Jane" (저장 후에는 현재 값)저장 전에는name_was가 원래 값(‘John’)을 반환하지만, 저장 후에는 더티 상태가 초기화되어 현재 값(‘Jane’)을 반환합니다. -
상태 전환에 따른 로직 트리거:
before_update콜백에서 모델의 특정 상태 전환에 따라 로직을 실행할 때*_was메서드의 진가가 발휘됩니다. 예를 들어, 주문 상태 변경에 따라 알림을 보내는 경우를 들 수 있습니다. ```ruby class Order < ApplicationRecord before_update :handle_status_transitionprivate 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을 고려해야 합니다.