Ruby on Rails는 웹 애플리케이션을 만드는 방식 자체를 바꿔 놓은 프레임워크입니다. 초기 버전부터 견고한 애플리케이션을 빠르게 구축할 수 있도록 돕는 훌륭한 기능들을 다수 제공해 왔죠.
그럼에도 불구하고 폼(form)을 만들고 처리하는 일은 여전히 까다롭게 느껴질 수 있습니다. 바로 이럴 때 Simple Form이 훌륭한 선택지가 됩니다. 이 글에서는 Simple Form이 무엇인지, 왜 필요한지, 그리고 실제 프로젝트에서 어떻게 활용할 수 있는지 차근차근 살펴보겠습니다.
폼과 Ruby on Rails
Ruby on Rails는 애플리케이션 개발을 크게 단순화해 주지만, 코드베이스를 깔끔하고 일관되게 유지하려면 끊임없는 관심이 필요합니다. 그중 하나의 전략은 추상화(abstraction)를 활용해 직접 작성해야 할 코드의 양을 줄이는 것입니다. 실제 애플리케이션에 존재하지 않는 코드는 관리할 필요도 없으니까요. Simple Form은 바로 이런 추상화 계층으로, 웹 페이지의 폼을 훨씬 간단하게 만들어 줍니다.
폼은 Rails를 사용하더라도 여전히 복잡하게 느껴지는 영역입니다. 필드마다 적절한 입력 타입을 지정하고 올바른 속성(attribute)에 연결해야 하기 때문입니다. Simple Form은 이러한 수고를 덜어 주며, 각 필드의 타입을 일일이 찾아 지정할 필요가 없습니다.
예를 들어 사용자 정보를 받는 폼에 name, username, email 필드가 있다고 가정해 보겠습니다. 비슷한 속성을 가진 User 모델이 있다면, Simple Form으로 작성한 폼은 다음과 같습니다.
<%= simple_form_for @user do |f| %>
<%= f.input :name %>
<%= f.input :username %>
<%= f.input :email %>
<%= f.button :submit %>
<% end %>
주목할 점은 필드별 타입을 전혀 지정하지 않았다는 것입니다. Simple Form은 데이터베이스 컬럼의 타입을 기준으로 각 필드에 알맞은 입력 타입을 자동으로 선택해 줍니다. 덕분에 개발 시간을 아낄 수 있고, 코드도 읽기 쉽고 유지보수하기 편해집니다.
Simple Form 설치하기
프로젝트에 Simple Form을 설치하려면 Gemfile에 simple_form 젬을 추가합니다. bundle install을 실행한 뒤, 포함된 제너레이터(generator)로 애플리케이션에 맞는 기본 설정을 손쉽게 구성할 수 있습니다.
# Gemfile
gem 'simple_form'
bundle install
rails generate simple_form:install
Bootstrap / Zurb Foundation 참고: Simple Form은 Bootstrap과 Zurb Foundation 5를 공식적으로 지원합니다. 두 프레임워크 중 하나를 사용 중이라면 설치 시 --bootstrap 또는 --foundation 옵션을 붙여 추가 설정을 한 번에 적용할 수 있습니다. Bootstrap의 경우는 다음과 같습니다.
rails generate simple_form:install --bootstrap
Simple Form 기본 사용법
앞서 말씀드린 대로, Simple Form은 객체와 관련된 데이터를 기반으로 완전한 폼을 생성합니다. 더 나아가 단순히 필드만 만드는 것이 아니라 각 속성에 맞는 라벨(label)을 붙여 주고, 입력 필드 위에 에러 메시지와 힌트(hint)까지 표시해 줍니다!
실습을 위해 새로운 Ruby on Rails 7.x 애플리케이션을 준비하고, User 모델을 생성한 뒤 Simple Form으로 폼을 만들어 보겠습니다.
rails new simple_form_demo
cd simple_form_demo
rails generate model User name:string username:string email:string password:string
rails db:migrate
이제 new, create, index 액션을 가진 컨트롤러를 생성합니다. 폼으로 직접 조작해 볼 수 있는 동작하는 화면을 만드는 것이 목표입니다.
rails generate controller Users new create index
# config/routes.rb
Rails.application.routes.draw do
resources :users
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
@users = User.all
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to users_path, notice: '사용자가 등록되었습니다.'
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name, :username, :email, :password)
end
end
각 액션에는 대응하는 뷰가 필요합니다. 다음은 회원 가입 또는 사용자 등록을 처리하는 폼입니다.
<!-- app/views/users/new.html.erb -->
<h1>새 사용자 등록</h1>
<%= simple_form_for @user do |f| %>
<%= f.input :name %>
<%= f.input :username, label: '사용자 이름', error: '사용자 이름은 필수 항목입니다.' %>
<%= f.input :email, placeholder: 'user@example.com' %>
<%= f.input :password, hint: '8자 이상 입력하세요.', label_html: { class: 'inline-label' } %>
<%= f.input :password_confirmation, label: false %>
<%= f.button :submit %>
<% end %>
위 예제에서는 username 입력 필드에 커스텀 라벨과 에러 메시지를 지정했습니다. 별도의 지정이 없으면 Simple Form은 속성 이름을 기반으로 적절한 라벨을 최대한 추측해서 만들어 줍니다.
또한 password 필드에는 커스텀 힌트를, email 필드에는 커스텀 플레이스홀더(placeholder)를 설정했습니다. 필드 위에 라벨을 두는 기본 방식(폼에서 가장 흔한 형태) 대신 '인라인 라벨(inline label)'을 지정할 수도 있습니다.
라벨·힌트·에러가 필요 없다면 해당 옵션에 false를 넘겨 비활성화할 수도 있습니다. 예: <%= f.input :password_confirmation, label: false %>
마지막으로 목록 화면 뷰를 추가하면 바로 사용할 수 있는 상태가 됩니다.
<!-- app/views/users/index.html.erb -->
<h1>사용자 목록</h1>
<ul>
<% @users.each do |user| %>
<li><%= user.name %> (<%= user.username %>)</li>
<% end %>
</ul>
<%= link_to '새 사용자 등록', new_user_path %>
이제 rails s로 서버를 실행하고 브라우저에서 localhost:3000/users/new에 접속하면 폼을 확인하고 사용할 수 있습니다. 여기서 클래식한 Rails 폼과 무엇이 다른지에 초점을 맞춰 살펴보겠습니다.
유효성 검사(validation) 참고: Simple Form은 필수 입력 필드를 별도 설정 없이도 처리할 수 있습니다. 예를 들어 User 모델에 다음과 같은 presence 유효성 검사를 추가하면,
validates :username, presence: true
Simple Form이 이를 감지해 username 필드 옆에 '*' 표시를 붙여 필수 항목임을 알려 줍니다. 다만 에러 처리까지 자동으로 해결해 주지는 않으므로, 컨트롤러 액션에서 여전히 에러를 직접 처리하고 오류가 발생한 해당 필드로 되돌아가 적절히 대응해야 합니다.
컬럼 타입과 폼 필드의 관계
앞서 언급했듯이 각 필드의 타입을 따로 지정하지 않았습니다. Simple Form의 README에는 사용 가능한 모든 입력 타입과 컬럼 타입별 기본 매핑이 정리되어 있으니 참고하시길 권합니다. 자주 쓰이는 대표적인 케이스만 짚어 보겠습니다.
불리언(Boolean)
매핑 표에서 확인할 수 있듯이, 불리언 속성은 기본적으로 체크박스로 표현됩니다. 대부분의 경우 이것이 우리가 원하는 형태입니다. 하지만 그렇지 않다면 as 옵션으로 라디오 버튼이나 드롭다운(select) 등 다른 형태로 손쉽게 변경할 수 있습니다.
라디오 버튼으로 지정하는 방법은 다음과 같습니다.
<%= f.input :active, as: :radio_buttons %>
이 코드가 생성하는 HTML은 다음과 같습니다.
<div class='form-group radio_buttons optional user_active'>
<span class='radio'>
<label for='user_active_true'>
<input class='radio_buttons optional' type='radio' value='true' name='user[active]' id='user_active_true' /> 예
</label>
</span>
<span class='radio'>
<label for='user_active_false'>
<input class='radio_buttons optional' type='radio' value='false' name='user[active]' id='user_active_false' /> 아니요
</label>
</span>
</div>
짧은 Ruby 코드 한 줄로 이렇게 완성도 높은 HTML이 만들어진다는 점은 정말 인상적입니다!
HTML 커스터마이징
앞선 예제에서 확인했듯이, Simple Form은 필드 하나당 상당한 양의 HTML을 생성합니다. 여기에는 입력 필드 자체의 HTML과 라벨·입력 필드를 감싸는 wrapper div가 포함됩니다. input_html과 wrapper_html 옵션으로 커스텀 클래스와 ID를 지정하면 이를 원하는 대로 바꿀 수 있습니다.
<%= f.input :username,
input_html: { class: 'my-custom-input', id: 'my-custom-id' },
wrapper_html: { class: 'my-custom-wrapper' } %>
이 방식은 maxlength나 value처럼 관련 HTML 속성을 지정하는 데도 활용할 수 있습니다. 예를 들어 사용자 폼의 패스워드 필드에 maxlength 속성을 적용해 입력 길이를 제한할 수 있습니다.
<%= f.input :password, input_html: { maxlength: 20 } %>
커스텀 입력 필드와 추가 옵션
Simple Form은 HTML 노드를 대신 만들어 주는 Ruby 라이브러리입니다. 기본으로 제공되는 다양한 필드 타입 외에도, Simple Form의 클래스를 상속받아 나만의 입력 필드를 만들 수 있습니다. 예를 들어 실제 값 앞에 '@' 접두사가 붙는 소셜 네트워크 핸들 입력 필드를 정의해 보겠습니다.
# app/inputs/network_input.rb
class NetworkInput < SimpleForm::Inputs::StringInput
def input(wrapper_options = nil)
'@'.html_safe + super
end
end
이제 폼에서 다음과 같이 사용할 수 있습니다.
<%= f.input :network_handle, as: :network %>
단, User 모델에 network_handle 속성이 없다면 마이그레이션으로 컬럼을 추가하거나, 모델에서 attr_accessor를 사용해 임시 속성으로 선언해야 합니다.
i18n(국제화) 지원
폼 작업이 워낙 번거로운 일이다 보니, 여러 언어를 지원하는 웹사이트나 애플리케이션에서는 고민이 더욱 깊어집니다. 다행히 Simple Form은 Ruby on Rails의 국제화(i18n) 표준을 그대로 따릅니다. 로케일(locale) 파일에 simple_form 키를 사용하면 모든 라벨, 힌트, 플레이스홀더, 프롬프트 등에 대한 번역을 한곳에서 관리할 수 있습니다.
간단한 예제는 다음과 같습니다.
# config/locales/simple_form.ko.yml
ko:
simple_form:
labels:
user:
username: '사용자 이름'
email: '이메일 주소'
hints:
user:
password: '영문과 숫자를 조합해 8자 이상 입력하세요.'
placeholders:
user:
email: 'you@example.com'
값 객체(Value Objects)
때로는 폼이 의존하는 주 객체를 ActiveRecord가 아닌 커스텀 클래스로 다루어야 할 때가 있습니다. 비즈니스 로직상의 이유로 여러 모델의 데이터를 하나의 합성 객체로 묶거나, 여러 속성을 단일 객체로 모아야 하는 경우가 그렇습니다.
이유가 무엇이든, 이런 종류의 객체에 대해서도 Simple Form으로 폼을 만들 수 있습니다. 이를 위해서는 객체 클래스가 최소한 세 가지 메서드를 구현해야 합니다: to_model, to_key, 그리고 persisted?.
to_model 메서드는 객체 자신을 가리키면 됩니다.
def to_model
self
end
to_key는 객체의 식별자(identifier) 속성을 가리킵니다. 보통 id라는 이름의 속성을 의미합니다.
def to_key
[id] if id
end
마지막으로 persisted? 메서드는 객체가 이미 저장(persist)된 상태인지 여부를 Simple Form에 알려 줍니다.
def persisted?
id.present?
end
이 메서드가 없으면 f.submit 헬퍼를 사용할 수 없습니다.
다만 더 빠른 방법이 있습니다. 클래스에 ActiveModel::Model 모듈을 포함(include)하면 됩니다.
class CompanyProfile
include ActiveModel::Model
attr_accessor :company_name
end
이제 User 모델과 함께 이 값을 사용해 보겠습니다. 사용자 폼 안에 회사명(company name) 필드를 추가할 수 있습니다.
<%= f.simple_fields_for :company_profile do |cp| %>
<%= cp.input :company_name, label: '회사명' %>
<% end %>
물론 이 속성은 별도의 테이블에 자동 저장되지는 않습니다. 하지만 동일한 개념을 확장하면 여러 모델에서 가져온 속성으로 하나의 값 객체(value object)를 구성하는 데 그대로 활용할 수 있습니다.
마무리
이번 글에서는 Simple Form이 Ruby on Rails 애플리케이션에 얼마나 쉽게 설치·통합할 수 있는지 살펴보았습니다. Simple Form은 폼 내 각 필드의 디테일을 알아서 처리할 뿐만 아니라, 스타일링과 배치가 용이하도록 복잡하고 완전한 HTML까지 생성해 줍니다.
Simple Form이 얼마나 강력한 도구인지 체감하고 싶다면 공식 문서를 직접 살펴보시기를 권합니다.
즐거운 코딩 되세요!
P.S. Ruby Magic의 글이 발행되는 즉시 읽고 싶으시다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!
Thomas Riboulet
게스트 저자 토마 리블레(Thomas Riboulet)는 프랑스에 거점을 둔 백엔드 및 클라우드 인프라 엔지니어이자 컨설턴트입니다. 13년 이상 스타트업과 기업들의 팀, 제품, 인프라 확장을 지원해 왔으며, 프랑스 GNU/Linux 잡지와 개인 블로그에도 여러 차례 글을 발표했습니다.
토마 리블레의 모든 글 보기