'Dirty Objects'에 해당되는 글 1건

  1. [2008/04/16] Rails 2.1 : Dirty Objects

Rails 2.1 : Dirty Objects

[Development]

원문(http://inocrazy.com/docs/7)에서 좀 더 깔끔하게 보실 수 있습니다.

속성의 변화를 추적하기 - ActiveRecord::Dirty

다음과 같은 경우를 생각해보자. Book이라는 모델이 있고, Book은 category를 나타내는 category_id라는 컬럼을 가지고 있다. Book의 category가 변경될 때마다 (즉, category_id 값이 변할 때마다) 특정 counter 테이블을 변경시켜줘야 한다면 어떻게 구현할 수 있을까? Book이 update되는 경우에 category_id가 변경되었는지 여부를 확인해서 counter 테이블을 갱신해야 할 것이다. 막상 구현하려고 하면, Book의 before_update callback을 이용해야하는 것은 바로 감이 오지만, category_id의 변경여부를 모니터링하는 방법은 바로 해결책을 내놓기가 어렵다.


즉, 위의 예처럼 모델의 특정 속성의 변경 여부를 모니터링할 필요가 있을 때, 쉽게 사용할 수 있는 방법이 Changeset 9127에서 추가되었다. 물론 기존에도 동일한 기능을 하는 acts_as_modified 라는 플러그인이 존재하였으나, Rails에 기능으로서 포함됨으로써 보다 편하게 사용할 수 있게 되었다. ActiveRecord::Dirty 모듈은 ActiveRecord::Base 클래스에 mix-in 되면서, '_changed?', '_change', '_will_change!', '_was' 접미사를 갖는 suffixed attribute method(접미사가 붙은 속성메소드)를 추가한다. suffixed attribute method는 ActiveRecord::AttributeMethodsattribute_method_suffix 클래스 메소드에 의해서 쉽게 추가할 수 있는데, 추후 다른 문서에서 ActiveRecord::AttributeMethods에 대해서 구체적으로 살펴볼 것이다.


ActiveRecord::Dirty에 의해 추가되는 메소드는 다음과 같다.

  • obj.<attr>_changed?
  • obj.<attr>_was
  • obj.<attr>_change
  • obj.<attr>_will_change!
  • obj.changed?
  • obj.changed
  • obj.changes

사용법 파악하기

위에서 열거한 메소드들을 하나씩 살펴보면서 사용법을 파악해보자. Person이라는 모델이 name이라는 속성을 갖는다고 가정하자. (이 단락의 소스코드들은 하나의 코드를 편의상 나누어둔 것이다.)


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name          # => 'Dongkyu Kim'
  3. person.name_changed? # => false

'Dongkyu Kim'이라는 name 속성을 갖는 레코드를 가져온다. <attr>_changed? 메소드는 해당 속성의 변경 여부를 알려준다. 단, 이 변경 여부는 저장되기 전에만 확인할 수 있다. 저장이 되면서, 내부에서 변경정보를 유지하고 있는 @changed_attributes 속성이 리셋되기 때문이다.


  1. person.name = 'Kim Dongkyu'
  2. person.name_changed? # => true
  3. person.name_was      # => 'Dongkyu Kim'
  4. person.name_change   # => ['Dongkyu Kim', 'Kim Dongkyu']

name이 변경되었기 때문에 name_changed?는 true를 리턴한다. <attr>_was 메소드는 변경 이전의 값을 알려준다. 또한 <attr>_change 메소드를 통해서 변경 이전 값과 이후의 값을 Array로 받을 수 있다.


  1. person.changed?  # => true
  2. person.changed   # => ['name']
  3. person.changes   # => { 'name' => ['Donkyu Kim', 'Kim Dongkyu'] }

obj.changed? 메소드는 하나의 속성이라도 변경되었을 경우, true를 리턴한다. obj.changed 메소드는 변경된 속성명을 Array로 알려준다. 또한 obj.changes 메소드는 변경된 속성명과 함께 해당 속성의 변경 이력을 Hash 값으로 전달해준다.


  1. person.save
  2. person.changed?      # => false
  3. person.name_changed? # => false

위에서 언급하였듯이 일단 저장하게 되면, 저장 이전의 변경 정보는 알 수 없게 된다.


몇 가지 주의사항들

위에서 설명한 메소드들은 전혀 어려울 것 없이 코드에서 사용할 수 있다. 이 단락에서는 ActiveRecord::Dirty의 메소드를 사용하는데 있어서 주의해야할 몇 가지를 언급하면서 문서를 마치고자 한다.


<attr>= writer로 속성을 변경하지 않는 경우

ActiveRecord::Dirty의 메소드들은 기본적으로 <attr>= writer로 값을 변경하는 경우에, 변경 정보를 모니터링할 수 있다. 따라서 다른 메소드로 속성 값을 변경하는 경우에는, <attr>_will_change! 메소드로 해당 속성이 변경될 것임을 미리 알려주어야만 속성의 변경 여부를 파악할 수 있다.


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name_will_change!  # => name 속성이 다른 메소드로 변경될 것임을 알림.
  3. person.name.upcase!       # => <attr>= writer로 변경하지 않음.
  4. person.name_changed?      # => true

같은 값으로 변경하는 경우에는?

만약 속성의 값을 같은 값으로 설정하는 경우, 또는 A에서 B로 변경했다가 다시 A로 변경하는 경우의 changed?의 결과는 어떻게 될까?


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name                     # => 'Dongkyu Kim'
  3. person.name_changed?            # => false
  4. person.name = 'Dongkyu Kim'     # 같은 값으로 설정한다.
  5. person.name_changed?            # => false
  6. person.name = 'Kim Dongkyu'
  7. person.name_changed?            # => true
  8. person.name_change              # => ['Dongkyu Kim', 'Kim Dongkyu']
  9. person.name = 'Dongkyu Kim'
  10. person.name_changed?            # => true
  11. person.name_change              # => ['Dongkyu Kim', 'Dongkyu Kim']
  12. person.changed?                 # => true

위의 예제에서 보는 것과 같이 같은 값으로 변경하는 경우에는 변경된 것으로 인식하지 않는다. 하지만 한번이라도 다른 값으로 변경된 속성은 changed?를 true로 리턴하게 된다.


참조

이 글은 스프링노트에서 작성되었습니다.

2008/04/16 03:47 2008/04/16 03:47