Ruby의 each 메서드를 정말 좋아하지만, 한 가지 아쉬운 점이 있습니다. 컬렉션이 비어 있으면 어떻게 될까요?
[].each를 호출하면 아무 일도 일어나지 않고 []가 그대로 반환됩니다. 때로는 이것이 원하는 동작일 수 있습니다. 하지만 대부분의 경우, 특히 UI를 개발할 때는 빈 목록을 특별한 방식으로 처리하고 싶어집니다. 예를 들어 데이터가 하나도 없을 때는 별도의 안내 메시지를 보여주고 싶은 경우가 많죠.
그런데 [].each는 nil이 아니라 []를 반환하기 때문에, 어쩔 수 없이 이런 코드를 작성해야 합니다:
<% if @users.empty? -%>
<p>You don't have any users. <%= link_to "Add one!", new_user_path %></p>
<% else -%>
<% @users.each do |user| -%>
<p><%= user.name %></p>
<% end -%>
<% end -%>
동작은 하지만 뭔가 어색합니다. 차라리 "컬렉션의 각 항목에 대해 이 작업을 수행하되, 컬렉션이 비어 있다면 완전히 다른 작업을 수행하라"라고 선언하고 싶습니다. 즉, "or else" 절이 붙은 each 같은 것이 필요한 순간입니다.
Rails에서 컬렉션 렌더링하기
뷰(view) 안에서 작업한다면 Rails가 도움을 줄 수 있습니다. Rails는 객체 컬렉션을 렌더링하는 데 탁월한 프레임워크입니다:
<%= render @users %>
<p><%= user.name %></p>
render에 @users를 전달하면, @users 안의 각 사용자마다 _user.html.erb partial이 한 번씩 렌더링됩니다. each 루프를 아예 생략할 수 있는 셈이죠.
게다가 보너스로, @users가 비어 있으면 render는 우리가 원하던 대로 nil을 반환합니다! 따라서 다음과 같이 작성하면 처음 버전과 동일한 출력을 얻을 수 있습니다:
<%= render(@users) || render('empty') %>
<p><%= user.name %></p>
<p>You don't have any users. <%= link_to "Add one!", new_user_path %></p>
Rails의 관례(convention)를 이해하고 나면 훨씬 더 직관적이고 깔끔한 코드가 됩니다.
뷰 밖에서는 어떻게 할까?
Rails 방식을 따른다면, 컬렉션과 함께 사용하는 render는 객체 목록을 화면에 그려내는 빠르고 강력한 도구입니다.
하지만 매번 각 항목을 위한 별도의 partial 파일을 만들고 싶지 않을 때도 있습니다. 혹은 render가 원하는 디자인을 지원하지 않을 수도 있고, 애초에 뷰 계층 밖에서 작업 중일 수도 있습니다.
지금까지 찾은 최선의 해결책은 바로 presence입니다.
list.presence는 사실상 다음 코드와 같습니다:
if list.present?
list
else
nil
end
즉, 목록에 내용이 있으면 목록 자체를 반환하고, 비어 있으면 nil을 반환합니다.
presence를 활용하면 이렇게 작성할 수 있습니다:
@users.each do |user|
puts user.name
end.presence || puts("You don't have any users.")
@users에 사용자가 있으면 각 이름을 출력하고, 비어 있으면 "You don't have any users."를 출력합니다.
그래도 presence 없이 해결할 수 있다면 더 좋겠습니다. 솔직히 말해 이건 일종의 꼼수(hack)이기 때문입니다. 언어 차원에서 지원된다면 다음과 같은 문법이 훨씬 자연스러울 것입니다:
@users.each do |user|
puts user.name
end || puts("You don't have any users.")
혹은 Smalltalk 스타일의:
@users.each(if_empty: lambda { puts "You don't have any users." }) do |user|
puts user.name
end
혹은 (깜짝 놀라실지도 모르지만!):
for user in @users
puts user.name
else
puts "You don't have any users."
end
참고로 Python의 for-else 문법이 실제로 마지막 방식과 유사하게 동작한다는 점이 흥미롭습니다. 하지만 당분간은 presence나 기본적인 if blank?; else each 패턴을 사용하는 것이 현실적인 선택일 것입니다.
여러분은 어떤 방법이 더 낫다고 생각하시나요? 빈 목록은 보통 어떻게 처리하시나요? 더 좋은 방법을 찾으셨다면 의견을 들려주세요!