🚫

Helper를 쓰지 않는 이유

My Convention — 데이터 로직은 Controller에, Helper는 최소한으로

Rails의 Helper는 뷰에서 사용할 수 있는 메서드를 정의하는 모듈입니다. ApplicationHelper에 메서드를 추가하면 모든 뷰에서 호출 가능합니다.

언뜻 편리해 보이지만, 실제 프로젝트에서 Helper를 적극적으로 사용하면 다음과 같은 문제가 쌓입니다.

왜 Helper를 피하는가?

1. 전역 스코프 오염

Rails Helper는 기본적으로 모든 뷰에 include됩니다. PostsHelper에 정의한 메서드가 UsersController의 뷰에서도 호출됩니다. config.action_controller.include_all_helpers = false로 끌 수 있지만, 대부분의 프로젝트에서 기본값(true)을 유지합니다.

결과: Helper 파일이 늘어날수록 네임스페이스 충돌 위험이 커지고, 어디에 정의된 메서드인지 추적이 어려워집니다.

2. 데이터 가공 로직의 잘못된 위치

Helper에 DB 조회, 배열 변환, 조건부 데이터 가공 같은 로직이 들어가면 MVC 경계가 무너집니다.

# ❌ Bad — Helper에 데이터 가공
def recent_posts_for_sidebar
  Post.published.order(created_at: :desc).limit(5)
end

# ✅ Good — Controller에서 준비
# controller
@recent_posts = Post.published.order(created_at: :desc).limit(5)

Controller에서 @인스턴스변수로 넘기면 흐름이 한 눈에 보입니다: 요청 → Controller(데이터 준비) → View(표시). Helper에 넣으면 View가 직접 데이터를 가져오는 형태가 되어 디버깅이 어려워집니다.

3. 테스트 어려움

Helper 메서드는 뷰 컨텍스트(self가 ActionView)에서 실행됩니다. url_for, content_tag, t() 같은 뷰 헬퍼에 의존하는 순간, 단위 테스트에서 이 컨텍스트를 재현해야 합니다.

Controller의 인스턴스 변수는 Request Spec에서 자연스럽게 테스트됩니다.

4. 비대해지는 Helper 파일

Helper는 "잡동사니 서랍"이 되기 쉽습니다. 분류 기준이 모호하고 파일 하나에 관련 없는 메서드가 쌓입니다.

5. IDE/에디터 지원 부족

Helper 메서드는 뷰에서 method_name으로 직접 호출되므로, IDE의 "정의로 이동(Go to Definition)"이 잘 작동하지 않는 경우가 많습니다. Controller의 @변수는 추적이 명확합니다.

6. 흔한 이름 + 출처 불명 = 의도치 않은 삭제 사고

Helper 메서드는 이름이 평범한 경우가 많습니다: format_date, status_label, badge_color 등. 뷰에서 badge_color(user) 형태로 호출되면, 이것이 어느 Helper 파일에 정의된 건지 즉시 알 수 없습니다.

문제는 리팩토링이나 정리 작업 중에 발생합니다. 누군가가 PostsHelper를 정리하면서 '이거 안 쓰이는 것 같은데?' 하고 badge_color를 삭제하면, 전혀 다른 컨트롤러의 뷰에서 undefined method 에러가 터집니다. Helper가 전역이기 때문에, 정의된 파일과 실제 사용처가 완전히 다른 곳에 있을 수 있기 때문입니다.

# app/helpers/posts_helper.rb
def badge_color(status)  # Posts 전용인 줄 알고 삭제 →
  status == 'active' ? 'green' : 'gray'
end

# 그런데 실제로는 app/views/admin/users/index.html.erb 에서도 사용 중!
# → NoMethodError: undefined method 'badge_color'

Controller의 @변수라면 해당 Controller 파일만 보면 되므로 이런 사고가 원천적으로 발생하지 않습니다.


그러면 대안은?

추천: Decorator / Presenter 패턴

Helper의 근본 문제는 모델에 속하는 표시 로직인데 전역 함수로 떠돌아다닌다는 것입니다. Decorator/Presenter는 이 로직을 모델 객체에 감싸서 해결합니다.

# app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
  def badge_color
    active? ? 'green' : 'gray'
  end

  def display_name
    name.presence || email.split('@').first
  end

  def joined_at
    created_at.strftime('%Y년 %m월 %d일')
  end
end

# Controller
def show
  @user = UserDecorator.new(User.find(params[:id]))
end

# View — 그냥 모델처럼 쓰면 됨
<span class=\"bg-<%= @user.badge_color %>-500\"><%= @user.display_name %></span>
<p>가입일: <%= @user.joined_at %></p>

Decorator vs Helper 비교:

  • Helper: badge_color(user) → 어디에 정의? 전역 탐색 필요

  • Decorator: @user.badge_color → UserDecorator 파일 하나만 보면 됨

  • Helper: 삭제해도 어디서 에러 날지 모름

  • Decorator: 해당 모델 관련 뷰에서만 사용, 추적 명확

SimpleDelegator를 상속하면 gem 없이 순수 Ruby로 구현 가능합니다. 원본 모델의 모든 메서드를 그대로 위임(delegate)하므로, @user.email처럼 기존 속성도 그대로 접근됩니다.

기타 대안

  • Controller @변수: 데이터 가공/조회는 여기서

  • 뷰 인라인: 1~2곳에서만 쓰는 삼항연산자 수준의 표시 로직

  • Partial: 재사용 가능한 UI 조각 (render 'shared/badge')

  • Service Object: 복잡한 비즈니스 로직

구조 다이어그램

❌ Anti-Pattern: Helper에 데이터 로직
Controller
(비어있음)
Helper
DB 조회 + 가공
전역 오염!
View
helper_method()
View Helper (DB 접근) Controller 스킵!
MVC 흐름 파괴 — View가 직접 데이터를 가져옴
✅ Good: Controller에서 @변수로 전달
Controller
@data = 가공 결과
데이터 준비 담당
Helper
SVG 아이콘만
(최소한)
View
&lt;%= @data %&gt;
Controller @변수 View (표시만)
MVC 흐름 유지 — 데이터는 Controller, 표시는 View
💥 실제 사고: 흔한 이름 + 출처 불명 = 삭제 사고
posts_helper.rb
def badge_color(s)
정의된 곳
posts/index.erb
badge_color(post)
사용처 A
admin/users/index.erb
badge_color(user)
사용처 B (전혀 다른 곳!)
🗑️ 리팩토링 중 posts_helper.rb 정리
"badge_color? Posts 전용인 줄 알았는데 안 쓰이는 것 같으니 삭제"
💥 admin/users/index.erb에서 에러 발생!
NoMethodError: undefined method 'badge_color'
Helper가 전역이라 Posts와 무관한 곳에서 터짐
✨ 추천 대안: Decorator / Presenter
Decorator
badge_color
display_name
모델에 귀속!
Controller
@user =
UserDecorator.new(user)
View
@user.badge_color
@user.display_name
Controller Decorator(모델 감싸기) View (@user.method)
표시 로직이 모델 객체에 귀속 — 추적 명확, 전역 오염 없음
⚖️ Helper vs Decorator 비교
Helper
Decorator
호출 방식
badge_color(user)
@user.badge_color
정의 위치
전역 (어디?)
UserDecorator
스코프
모든 뷰에 노출
해당 모델만
삭제 안전성
어디서 터질지 모름
영향 범위 명확
테스트
뷰 컨텍스트 필요
일반 Ruby 테스트
🤔 판단 기준
Helper에 메서드를 추가하려는 순간 →
모델의 표시 로직?
badge_color,
display_name, etc.
→ Decorator
데이터 조회/가공?
DB 접근, 배열 변환, 조건부 데이터 구성
→ Controller @변수
순수 표시 유틸?
SVG 아이콘, 날짜 포맷, 5곳 이상 재사용
→ Helper (최후 수단)
핵심: 모델의 표시 로직은 Decorator, 데이터 준비는 Controller — Helper는 최후의 수단

핵심 포인트

1

Helper에 메서드를 추가하려는 순간 → "이건 모델의 표시 로직인가?" 자문

2

모델의 표시 로직이면 → Decorator(SimpleDelegator)를 만들어 모델에 감싸기

3

DB 조회/데이터 가공이면 → Controller에서 @변수로 전달

4

복잡한 비즈니스 로직이면 → Service Object로 분리

5

재사용 가능한 UI 조각이면 → Partial로 추출

6

1~2곳에서만 쓰는 간단한 표시이면 → 뷰에 인라인 (삼항연산자)

7

그래도 Helper가 필요하면 → 순수 표시 함수(SVG, 날짜 포맷)만, 5곳 이상 재사용 확인

장점

  • MVC 흐름이 명확: Controller(데이터) → View(표시)
  • Request Spec에서 자연스럽게 테스트 가능
  • IDE에서 @변수 추적 용이
  • 전역 네임스페이스 오염 방지
  • Helper 파일이 비대해지지 않음
  • 새 팀원이 코드 흐름을 빠르게 파악

단점

  • Controller가 약간 길어질 수 있음
  • 공통 표시 함수도 Helper 없이 구현하면 중복 가능성
  • Rails 공식 가이드와 다소 다른 접근 (Helper를 적극 권장하는 자료가 많음)
  • render_pattern_icon 같은 순수 표시 함수는 Helper가 자연스러움

사용 사례

user.badge_color, user.display_name → Decorator post.reading_time, post.status_label → Decorator DB 조회 결과를 뷰에 전달 → Controller @변수 배열 가공/매핑 → Controller에서 처리 여러 곳에서 재사용되는 UI 카드/배지 → Partial 인라인 조건부 CSS 클래스 → 뷰에서 직접 삼항연산자 SVG 아이콘 렌더링 → Helper (유일하게 허용)