작업중인 프로젝트에서 각각의 유저가 현재 포함되어있는 조직에 대한 로그를 has_one association 으로 처리하고 싶었으나, 이전 그룹에 속한 로그를 가져오는 문제가 발생했다.
전제 조건과 문제는 아래와 같다.
Class User
belongs_to :group, inverse_of: :users
has_one :group_join_log, inverse_of: :user
end
Class Group
has_many :users, inverse_of: :group
has_many :group_join_logs, inverse_of: :group
end
Class GroupJoinLog
belongs_to :user
belongs_to :group
end
이 문제를 해결하기 위해 scope 를 잘 이용하면 해결 될 것이라고 생각했다.
has_one :group_join_log, ->(user) { where(group_id: user.group_id) }
다음과 같이 처리하면 일단 문제는 사라지는 듯 했다. 하지만 >(user)
와 같이 인스턴스를 이미 불러온 뒤 조건이 붙는 형태이기 때문에, ActiveRecrod 인스턴스에 의존성을 가진다.
즉, N+1 쿼리를 해소하기 위해 ager loading를 하려고 하면 아래와 같은 에러를 발생시킨다.
The association scope 'group_join_log' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
Scope 내에서도 Join을 사용할 수 있기 때문에 비교적 조인을 한다는 단점은 있지만 N+1 쿼리를 줄일 수 있는 이점을 이용하기 위해서 아래와 같이 user를 Join하여 Where절로 탐색한 결과를 가져오도록 수정하였다.
Class User
belongs_to :group, inverse_of: :users
has_one :group_join_log, -> { joins(:users).where('group_join_logs.group_id = users.group_id') } , inverse_of: :user
end
적용 후
이 글을 작성하면서 고민을 해보니 또 다른 선택지가 있을 것 같다는 생각도 든다.
group_join_logs
를 가진다.
current_group_join_log
를 가진다
current_group_join_log
혹은 current_join_log
같은 이름으로 별도의 has_one 을 가지는 방법이 있을 것 같다.