티스토리 뷰
오늘의 포스팅 내용은 ActiveRecord 를 사용하면서 scope 를 이용했을 때 first, last 를 포함하면 안 되는 이유에 대해서 알아볼 예정이다. 우선 scopes 에 대한 정의는 여기에서 확인할 수 있다.
간단하게 설명하자면 scope 는 ActiveRecord 에서 일반적으로 사용하는 Query 들을 지정할 수 있어서 필요시마다 함수 호출처럼 사용할 수 있게 도와준다. scopes 에서는 where, joins, includes 등이 사용 가능하고 실행결과로 ActiveRecord::Relation 혹은 nil 을 반환한다.
scope 의 활용 예시는 다음과 같다.
module Accounts
class User < ApplicationRecord
scope :today_sign_up_users, -> { where('created_at >= ?',Time.now.beginning_of_day) }
...
end
end
Accounts::User.today_sign_up_users
>> #<ActiveRecord::Relation [#<Accounts::User id: 1, ... >, #<Accounts::User id: 2, ... >, #<Accounts::User id: 3, ... >, ...]>
물론 scope 를 쓰지 않고 Class Method 로 직접 호출하는 것도 가능하다.
Accounts::User.where('created_at >= ?',Time.now.beginning_of_day)
>> #<ActiveRecord::Relation [#<Accounts::User id: 1, ... >, #<Accounts::User id: 2, ... >, #<Accounts::User id: 3, ... >, ...]>
문제가 되는 부분은 scope 를 써도 되고 안 쓰고 직접 호출해도 되다 보니, 간혹 scope 에 추가하면 안 되는 first, last 를 넣는 실수를 할 때가 있다. 거기다 first, last 를 넣었을 때 잘 동작하는 것처럼 보이기 때문에 쉽게 문제를 인지하지 못하고 있다가 엉뚱한 상황에서 장애가 발생하게 된다.
문제가 발생할 수 있는 상황에 대한 예시는 다음과 같다.
"오늘 가입한 사용자 중에서 가장 마지막 사용자를 구하고 싶다. " 라는 생각에 다음과 같은 scope 를 정의하고 호출해보자.
module Accounts
class User < ApplicationRecord
scope :today_sign_up_users, -> { where('created_at >= ?',Time.now.beginning_of_day) }
scope :today_latest_sign_up_user, -> { where('created_at >= ?',Time.now.beginning_of_day).last }
...
end
end
Accounts::User.today_latest_sign_up_user
>> #<Accounts::User id: 50, ... >
뭔가 기대했던 그럴듯한 값을 반환했다. 실제로 해당 값은 오늘 가입한 사용자들 중에 가장 마지막 사용자의 정보가 맞다.
문제는 다음과 같은 상황이 되었을 때 발생한다.
만약 "오늘 가입한 사용자가 1명도 없는 상태인데 오늘 가입한 사용자들 중에 마지막 사용자를 구하려고 한다" 면 어떤 값을 반환해야 할까? 기대하는 값은 당연히 nil 혹은 비어있는 ActiveRecord::Relation 일 것이다. 그러나 실제로 동작해보면 다음과 같은 값을 반환한다.
* 예시 scope 는 결과값이 없는 경우를 만들기 위해서 미래(내일)에 가입된 사용자들을 조회한다.
module Accounts
class User < ApplicationRecord
scope :today_sign_up_users, -> { where('created_at >= ?',Time.now.beginning_of_day) }
scope :today_latest_sign_up_user, -> { where('created_at >= ?',Time.now.tomorrow).last }
...
end
end
Accounts::User.today_latest_sign_up_user
>> #<ActiveRecord::Relation [#<Accounts::User id: 1, ... >, #<Accounts::User id: 2, ... >, #<Accounts::User id: 3, ... >, ...]>
결과는 엉뚱하게도 전체 User 의 모든 데이터를 반환한다.
이렇게 엉뚱한 결과가 나오는 이유는 scope 는 기본적으로 ActiveRecord::Relation 이나 nil 을 반환하는 것으로 정의되어 있기 때문에 first / last 등의 단일 ActiveRecord 를 조회하는 옵션을 지원하지 않는다.(근데 문제는 막상 쓰면 지원하는 것처럼 보여서 더 문제다.) 이와 관련된 내용은 여기 에서도 확인할 수 있다.
예시와 같은 이유로 scope 에서는 first, last 등을 사용하지 말고 만약 필요하다면 다음과 같이 scope 밖에서 별도로 호출하도록 하자.
module Accounts
class User < ApplicationRecord
scope :today_sign_up_users, -> { where('created_at >= ?',Time.now.beginning_of_day) }
...
end
end
Accounts::User.today_sign_up_users.last
>> #<Accounts::User id: 50, ... >
오늘은 여기까지~
누군가에게 도움이 되었길 바라면서 오늘의 포스팅 끝~
'Ruby' 카테고리의 다른 글
[RoR] ActiveRecord 의 count, length, size (1) | 2020.06.30 |
---|---|
[RoR] Rails ActiveRecord (0) | 2019.10.03 |
[RoR] Rails Autoloading 알아보기 (0) | 2019.01.27 |
[Ruby] 루비 상수참조 규칙 알아보기 (0) | 2019.01.13 |
[Ruby] 루비 메타프로그래밍(6) - Singleton Class (2) | 2018.07.14 |
- Total
- Today
- Yesterday
- 인덱스
- MySQL 족보
- autoload_paths
- Autoloading
- MySQL
- 페어프로그래밍
- lock
- db
- 루비
- 페어 프로그래밍
- 루비 메타프로그래밍
- ruby
- ruby meta programming
- 갭 락
- 메타프로그래밍
- MySQL 인덱스
- gap lock
- 루비 상수
- 넥스트 키 락
- 되추적
- 엘라스틱서치 기초
- dead lock
- Elasticsearch Cluster
- mysql lock
- InnoDB
- metaprogramming
- innoDB lock
- Pair-programming
- 트랜잭션
- next key lock
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |