Ruby on Rails의 ActiveRecord는 레코드가 생성, 변경, 제거되었을 때 동작하거나 데이터베이스에 커밋이 일어난 이후에 동작할 수 있는 콜백을 지원한다.
콜백으로 레코드의 라이프 사이클을 편하게 관리할 수 있어 백그라운드 작업을 처리할 때 많이 사용한다.
그런데 after_*_commit 을 여러개 정의하다보면 때로는 개발자의 의도와 다르게 특정 콜백이 동작하지 않는 것을 볼 수 있다.
특정 콜백이 동작하지 않음을 마주했다면 아마도 정의한 콜백이 override되었기 때문이라고 짐작된다.
이 글에서는 after_*_commit을 여러개 정의했을 때 특정 콜백이 동작하지 않은 이유를 설명하고 대안을 함께 소개하고자 한다.
우선은 after_*_commit
에 대해 다루기 전에, 주요 메소드이면서 내부 동작에서 사용되는 after_commit
을 먼저 설명해본다.
아래처럼 ActiveRecord 타입의 Dog 클래스를 정의하였다.
class Dog < ActiveRecord::Base
after_commit :add_to_index_later, on: :create
after_commit :update_in_index_later, on: :update
after_commit :remove_from_index_later, on: :destroy
end
Dog 레코드가 생성되면 콜백에 의해서 add_to_index_later
메소드가 수행이 된다.
업데이트 된다면 update_in_index_later
가 수행되고, 제거된다면 remove_from_index_later
메소드가 수행된다.
이 콜백을 on
옵션을 붙여서 어떤 동작(생성, 변경, 제거)에서 수행할 것인지 정의할 수 있다.
2015년도 12월에 after_commit
콜백에 대해서 명확하게 표현할 수 있는 alisas 메소드인 after_create_commit
, after_update_commit
, after_destroy_commit
를 Ruby on Rails 를 만든 David Heinemeier Hansson가 제안했다.
그리고 이슈를 본 불가리아의 한 개발자가 바로 구현해서 풀리퀘스트를 올렸고.. 당일에 리뷰까지 완료되어 바로 머지가 된 메소드다.
after_*_commit 메소드는 Rails 5.0.0 릴리즈부터 사용할 수 있다.
https://github.com/rails/rails/pull/22516
Rails 5.0.0부터는 위의 ActiveRecord 타입의 Dog 클래스를 아래처럼 나타낼 수 있다.
class Dog < ActiveRecord::Base
after_create_commit :add_to_index_later
after_update_commit :update_in_index_later
after_destroy_commit :remove_from_index_later
end
after_*_commit은 내부적으로 after_commit 과 동일하게 처리하는데
# ActiveRecord::Transactions::ClassMethods
def after_commit(*args, &block)
set_options_for_callbacks!(args)
set_callback(:commit, :after, *args, &block)
end
def after_save_commit(*args, &block)
set_options_for_callbacks!(args, on: [ :create, :update ])
set_callback(:commit, :after, *args, &block)
end
def after_create_commit(*args, &block)
set_options_for_callbacks!(args, on: :create)
set_callback(:commit, :after, *args, &block)
end
def after_update_commit(*args, &block)
set_options_for_callbacks!(args, on: :update)
set_callback(:commit, :after, *args, &block)
end
def after_destroy_commit(*args, &block)
set_options_for_callbacks!(args, on: :destroy)
set_callback(:commit, :after, *args, &block)
end
private
def set_options_for_callbacks!(args, enforced_options = {})
# options = args.last
# if options.is_a?(Hash) && options[:on]
# 이전까지 처리되던 방식
options = args.extract_options!.merge!(enforced_options)
args << options
if options[:on]
fire_on = Array(options[:on])
assert_valid_transaction_action(fire_on)
options[:if] = Array(options[:if])
options[:if] << "transaction_include_any_action?(#{fire_on})"
end
end
after_create_commit 기준으로 설명한다면set_options_for_callbacks
private 메소드를 호출 할 때 {on: :create}
을 넘긴다.
이전까지는 args의 마지막 요소를 options으로 두었다면, after_*_commit 동작을 위해서 args 에서 options 파라미터를 추출하고 enforced_options 파라미터와 병합을 하는 방식으로 변경되었다.
그리고는 이전과 동일하게 args 내의 Hash 타입의 options 변수에 transaction_include_any_action?(create)
를 붙이도록 처리한다.
이렇게 내부적으론 after_commit으로 처리되다보니 after_*_commit
메소드를 쓸 때 가끔은 의도와 다르게 동작되는 것을 볼 수 있다.
Dog 객체가 생성되거나 변경될 때 bark
를 출력하는 것을 고려해 아래와 같이 작성하였다.
class Dog < ActiveRecord::Base
after_create_commit :puts_bark
after_update_commit :puts_bark
end
코드만 보았을 때는 create commit과 update commit이 발생할 때 puts_bark
메소드가 호출될 거라고 생각할 수 있다.
하지만 실제 동작은 변경시에만 puts_bark
메소드가 호출되고 생성 시에는 콜백이 일어나지 않는다.
이런 이유는 내부적인 동작에서 이전의 선언을 override 하는 동작으로 처리되기 때문이다.
set_options_for_callbacks!(args, on: :create)
set_callback(:commit, :after, *args, &block)
set_options_for_callbacks!(args, on: :update)
set_callback(:commit, :after, *args, &block)
따라서 이런 동작을 방지하기 위해서는 아래처럼 after_commit
을 이용하는 것이 더 직관적일 수 있다.
class Dog < ActiveRecord::Base
after_commit :puts_bark, on: [:create, :update]
end
Dog 객체를 담당하던 개발자는 강아지가 태어나서 2주에서 3주 정도 뒤에 발성을 하는 사실을 알게 되었다.
그래서 이 점을 고려해 조건문을 추가하기로 했다
class Dog < ActiveRecord::Base
after_create_commit :puts_bark, if -> { weeks > 2 }
after_update_commit :puts_bark, if -> { weeks > 3 }
end
생성할 땐 weeks
값이 2보다 커야 하고, 변경할 땐 weeks
값이 3보다 커야 puts_bark
메소드가 호출되게 만들고자 한다.
그러나 생성할 때의 콜백을 설정한 내용은 after_update_commit
의 과정에서 override가 되어 동작하지 않는다.
위에서 진행한 방법대로 on
옵션을 이용해 after_commit
으로 표현하려고 했지만 이런 내용을 담을 때는 아래처럼 표현할 수 없다.
update할 때 콜백이 수행되는 조건은 weeks 가 3보다 클 때이기 때문이다.
class Dog < ActiveRecord::Base
after_commit :puts_bark, if -> { weeks > 2 }, on: [:create, :update]
end
이러한 경우에선 아래처럼 메소드를 정의해 내부에서 조건 분기 처리를 하여 진행할 수 있다.
class Dog < ActiveRecord::Base
after_create_commit :puts_bark_with_create
after_update_commit :puts_bark_with_update
private
def puts_bark_with_create
puts_bark if weeks > 2
end
def puts_bark_with_update
puts_bark if weeks > 3
end
end
'Back-end' 카테고리의 다른 글
Redis의 String이 빠른 이유 (0) | 2023.06.10 |
---|---|
Redis 7.0.0 부터 CLI 내에서 명령어의 세부 설명을 확인할 수 있습니다 (0) | 2022.04.28 |
AWS 서버리스 단축URL 서비스 만들기 - 4 (2) | 2021.07.30 |
AWS 서버리스 단축URL 서비스 만들기 - 3 (0) | 2021.07.25 |
AWS 서버리스 단축URL 서비스 만들기 - 2 (3) | 2021.07.25 |
댓글