Rails에서 스코프(scope)란 무엇이고, 왜 유용할까요?
간단히 말하면…
스코프는 Rails 모델 안에서 scope 메서드를 사용해 정의하는 커스텀 쿼리(custom query)입니다.
모든 스코프는 두 가지 인자를 받습니다:
- 이름(name): 코드에서 해당 스코프를 호출할 때 사용하는 이름입니다.
- 람다(lambda): 실제 쿼리 로직을 구현하는 부분입니다.
실제 코드로 보면 다음과 같습니다:
class Fruit < ApplicationRecord
scope :with_juice, -> { where("juice > 0") }
end
스코프를 호출하면 그 결과로 ActiveRecord::Relation 객체가 반환됩니다.
이것이 의미하는 바는 무엇일까요? 바로 스코프끼리 체이닝(chaining)하고 조합할 수 있다는 점입니다!
예시:
Fruit.with_juice.with_round_shape.first(3)
자, 이제 Rails 스코프에 대해 더 깊이 알아볼 시간입니다. 주제를 계속 탐색해 보겠습니다.
스코프는 언제 사용해야 할까?
스코프가 훌륭한 도구인 것은 분명합니다. 하지만 언제 사용하는 것이 좋을까요?
예시를 통해 살펴보겠습니다.
def index
@books = Book.where("LENGTH(title) > 20")
end
위 코드는 제목이 20자보다 긴 책들을 화면에 표시하고 싶은 index 컨트롤러 액션입니다.
동작 자체는 문제없습니다.
하지만 이 쿼리를 다른 곳에서도 사용해야 한다면 어떻게 될까요? 같은 코드가 여러 곳에 중복되어 작성될 것입니다.
중복된 코드는 프로젝트의 유지보수를 어렵게 만듭니다. 이럴 때 스코프가 빛을 발합니다.
쿼리를 스코프로 옮겨 보겠습니다.
다음과 같이 말이죠:
class Book
scope :with_long_title, -> { where("LENGTH(title) > 20") }
end
그러면 컨트롤러 액션이 훨씬 깔끔해집니다:
def index @books = Book.with_long_title end
정말 깔끔하죠!
인자(Arguments)와 함께 Rails 스코프 사용하기
스코프에 변수를 도입하면 더욱 유연하게 활용할 수 있습니다.
방법은 다음과 같습니다:
class Book
scope :with_long_title, ->(length) { where("LENGTH(title) > ?", length) }
end
물음표(?)는 플레이스홀더(placeholder)로, 실행 시점에 length 값으로 대체됩니다. 이 방식은 SQL 인젝션을 방지해 코드를 더 안전하게 만들어 줍니다.
기본값(default value)을 지정하고 싶다면:
class Book
scope :with_long_title, ->(length = 20) { where("LENGTH(title) > ?", length) }
end
직접 코드를 작성하며 연습해 보세요!
스코프 vs 클래스 메서드(Class Method)
사실 스코프는 마법 같거나 특별한 기능이 아닙니다. 스코프도 결국 하나의 메서드일 뿐입니다.
실제로 클래스 메서드만으로 동일한 결과를 얻을 수 있습니다.
다음과 같이 말이죠:
class Fruit
def self.with_juice
where("juice > 0")
end
end
하지만 클래스 메서드 대신 스코프를 사용하면 설계 측면에서 몇 가지 장점이 있습니다.
그 이유는 다음과 같습니다:
- 스코프는 간결한 문법 덕분에 코드가 더 깔끔해집니다.
- 스코프는 오직 한 가지 목적(쿼리 정의)으로만 사용되므로, 코드를 보는 순간 무슨 역할을 하는지 바로 파악할 수 있습니다.
- 스코프는 다른 일반 메서드와 섞이지 않아 모델 파일에서 쉽게 찾을 수 있습니다.
기능적인 관점에서 유일한 차이점은, 스코프는 항상 ActiveRecord::Relation 객체를 반환한다는 것을 보장하지만 클래스 메서드는 그렇지 않다는 점입니다.
이 차이 덕분에 스코프의 결과가 비어 있을 때 발생할 수 있는 오류를 예방할 수 있습니다.
디폴트 스코프(Default Scope)는 사용하지 마세요
디폴트 스코프는 모델에 정의된 순간 해당 모델의 모든 쿼리에 자동으로 적용되는 스코프입니다.
예시:
class Post
default_scope { where(published: true) }
end
편리해 보이고 매력적으로 느껴질 수 있습니다.
하지만 디폴트 스코프는 대부분 잘못된 선택입니다. 정의해 둔 사실을 잊어버리고, 원인을 알 수 없는 이상한 에러를 만나며, 소중한 디버깅 시간을 낭비하게 되기 쉽기 때문입니다.
참고로…
어쩔 수 없이 디폴트 스코프가 적용된 코드를 다뤄야 한다면, unscoped 메서드를 사용해 현재 적용된 모든 스코프를 해제할 수 있습니다.
마무리: 핵심 요약
수고하셨습니다! 이 글을 읽으면서 Rails 스코프를 가장 효과적으로 사용하는 방법을 배우셨을 것입니다.
- 스코프는 모델 내에서 재사용 가능한 커스텀 쿼리를 정의하는 방법입니다.
- 반환값이 항상
ActiveRecord::Relation이므로 체이닝이 가능합니다. - 인자와 기본값을 활용하면 더 유연한 스코프를 만들 수 있습니다.
- 디폴트 스코프는 예기치 않은 버그의 원인이 될 수 있으니 피하는 것이 좋습니다.
배운 내용을 직접 프로젝트에 적용해 보면 스코프의 동작 방식을 훨씬 오래 기억할 수 있습니다.
읽어주셔서 감사합니다. 🙂