1 개요: 각 요소들이 어떻게 조화를 이루는가
이 가이드는 Model-View-Controller 삼각형에서 Controller와 View 간의 상호작용에 초점을 맞춥니다. 아시다시피 Controller는 Rails에서 요청을 처리하는 전체 프로세스를 조율하는 책임이 있지만, 일반적으로 복잡한 코드는 Model에 위임합니다. 하지만 사용자에게 응답을 보낼 때가 되면, Controller는 View에 작업을 넘깁니다. 이 가이드는 바로 이 작업 위임에 대한 내용을 다룹니다.
큰 틀에서 보면, 이는 어떤 응답을 보낼지 결정하고 그 응답을 생성하기 위한 적절한 메서드를 호출하는 것을 포함합니다. 만약 응답이 완전한 view라면, Rails는 view를 레이아웃으로 감싸고 필요한 경우 partial view들을 가져오는 추가 작업을 수행합니다. 이 모든 과정을 이 가이드에서 살펴볼 것입니다.
2 응답 생성하기
controller의 관점에서, HTTP 응답을 생성하는 방법에는 세 가지가 있습니다:
render
를 호출하여 브라우저로 보낼 전체 응답을 생성redirect_to
를 호출하여 브라우저로 HTTP 리다이렉트 상태 코드를 전송head
를 호출하여 브라우저로 보낼 HTTP 헤더만으로 구성된 응답을 생성
2.1 기본 렌더링: Convention Over Configuration의 실제 예시
Rails가 "convention over configuration"을 지향한다는 말을 들어보셨을 것입니다. 기본 렌더링은 이것의 훌륭한 예시입니다. Rails에서 controller는 기본적으로 유효한 route에 해당하는 이름을 가진 view를 자동으로 렌더링합니다. 예를 들어, BooksController
클래스에 다음과 같은 코드가 있다면:
class BooksController < ApplicationController
end
routes 파일에 다음 내용을 작성하세요:
resources :books
그리고 app/views/books/index.html.erb
view 파일이 있습니다:
<h1>도서들이 곧 출시됩니다!</h1>
Rails는 /books
로 이동할 때 자동으로 app/views/books/index.html.erb
를 렌더링하며 화면에 "Books are coming soon!"이 표시됩니다.
하지만 준비 중 화면은 최소한의 유용성만 있으므로, 곧 Book
모델을 생성하고 BooksController
에 index action을 추가할 것입니다:
class BooksController < ApplicationController
def index
@books = Book.all
end
end
index action의 마지막에 명시적인 render가 없는 것은 "convention over configuration" 원칙에 따른 것입니다. 컨트롤러 액션 마지막에 명시적으로 무언가를 렌더링하지 않으면, Rails는 자동으로 컨트롤러의 뷰 경로에서 action_name.html.erb
템플릿을 찾아서 렌더링하는 규칙이 있습니다. 따라서 이 경우 Rails는 app/views/books/index.html.erb
파일을 렌더링할 것입니다.
뷰에서 모든 책들의 속성을 표시하고 싶다면, 다음과 같은 ERB 템플릿을 사용할 수 있습니다:
<h1>도서 목록</h1>
<table>
<thead>
<tr>
<th>제목</th>
<th>내용</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
<td><%= link_to "보기", book %></td>
<td><%= link_to "수정", edit_book_path(book) %></td>
<td><%= link_to "삭제", book, data: { turbo_method: :delete, turbo_confirm: "정말로 삭제하시겠습니까?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to "새 도서", new_book_path %>
실제 렌더링은 ActionView::Template::Handlers
모듈의 중첩 클래스들에 의해 수행됩니다. 이 가이드에서는 그 과정을 자세히 다루지는 않지만, view의 파일 확장자가 template handler의 선택을 제어한다는 것을 아는 것이 중요합니다.
2.2 render
사용하기
대부분의 경우, 컨트롤러의 render
메서드는 브라우저가 사용할 애플리케이션의 콘텐츠를 렌더링하는 무거운 작업을 수행합니다. render
의 동작을 커스터마이징하는 다양한 방법이 있습니다. Rails 템플릿의 기본 뷰를 렌더링하거나, 특정 템플릿, 파일, 인라인 코드를 렌더링하거나, 아무것도 렌더링하지 않을 수 있습니다. 텍스트, JSON, XML을 렌더링할 수 있습니다. 렌더링된 응답의 content type이나 HTTP 상태도 지정할 수 있습니다.
render
의 호출 결과를 브라우저에서 확인할 필요 없이 정확히 보고 싶다면, render_to_string
을 호출할 수 있습니다. 이 메서드는 render
와 정확히 같은 옵션을 받지만, 브라우저로 응답을 보내는 대신 문자열을 반환합니다.
2.2.1 액션의 뷰 렌더링하기
같은 컨트롤러 내의 다른 템플릿에 해당하는 뷰를 렌더링하고 싶다면, render
에 뷰의 이름을 사용할 수 있습니다:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render "edit"
end
end
만약 update
호출이 실패하면, 이 컨트롤러의 update
액션을 호출할 때 동일한 컨트롤러에 속한 edit.html.erb
템플릿을 렌더링할 것입니다.
원한다면 렌더링할 액션을 지정할 때 문자열 대신 심볼을 사용할 수 있습니다:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render :edit, status: :unprocessable_entity
end
end
2.2.2 다른 Controller의 Action Template 렌더링하기
만약 action 코드가 있는 controller와는 완전히 다른 controller의 template을 렌더링하고 싶다면 어떻게 해야 할까요? template을 렌더링할 전체 경로(app/views
기준)를 받는 render
를 사용하면 됩니다. 예를 들어, app/controllers/admin
에 있는 AdminProductsController
에서 코드를 실행할 때, action의 결과를 app/views/products
의 template으로 다음과 같이 렌더링할 수 있습니다:
render "products/show"
Rails는 문자열에 포함된 슬래시 문자로 인해 이 view가 다른 controller에 속한다는 것을 알 수 있습니다. 명시적으로 표현하고 싶다면 :template
옵션을 사용할 수 있습니다(Rails 2.2 이전 버전에서는 필수였습니다):
render template: "products/show"
위처럼 template: 옵션을 사용하면, 현재 컨트롤러의 템플릿이 아닌 다른 템플릿을 렌더링할 수 있습니다.
2.2.3 정리하기
위의 두 가지 렌더링 방법(같은 controller의 다른 action의 template을 렌더링하는 것과, 다른 controller의 다른 action의 template을 렌더링하는 것)은 실제로 같은 동작의 변형입니다.
사실, BooksController
클래스에서 book이 성공적으로 업데이트되지 않았을 때 edit template을 렌더링하려는 update action 내에서, 다음의 모든 render 호출들은 모두 views/books
디렉토리의 edit.html.erb
template을 렌더링할 것입니다:
render :edit
render action: :edit
render "edit"
render action: "edit"
render "books/edit"
render template: "books/edit"
위 모든 호출은 동일합니다 - 모두 템플릿 path를 찾기 위해 동일한 규칙을 사용합니다. 즉, edit
템플릿을 현재 controller의 뷰 path에서 찾거나, 마지막 두 방법처럼 명시적인 템플릿 참조를 사용합니다.
어떤 것을 사용할지는 스타일과 관례의 문제지만, 경험적 규칙은 작성하는 코드에 가장 잘 맞는 가장 단순한 것을 사용하는 것입니다.
2.2.4 render
와 :inline
사용하기
render
메서드는 :inline
옵션을 사용해 ERB를 메서드 호출의 일부로 제공할 경우 view를 완전히 생략할 수 있습니다. 다음은 완벽하게 유효합니다:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
이 옵션을 사용하는 것은 거의 좋은 이유가 없습니다. 컨트롤러에 ERB를 혼합하는 것은 Rails의 MVC 지향성을 해치며 다른 개발자들이 프로젝트의 로직을 따라가기 어렵게 만듭니다. 대신 별도의 erb view를 사용하세요.
기본적으로 inline rendering은 ERB를 사용합니다. :type
옵션을 사용하여 대신 Builder를 사용하도록 강제할 수 있습니다:
render inline: "xml.p {'끔찍한 코딩 관행!'}", type: :builder
2.2.5 Text 렌더링
render
의 :plain
옵션을 사용하면 마크업이 전혀 없는 순수 텍스트를 브라우저에 보낼 수 있습니다:
render plain: "OK"
텍스트 본문을 "OK"로 렌더링
순수 텍스트를 렌더링하는 것은 적절한 HTML이 아닌 다른 것을 기대하는 Ajax나 웹 서비스 요청에 응답할 때 가장 유용합니다.
기본적으로 :plain
옵션을 사용하면 현재 layout을 사용하지 않고 텍스트가 렌더링됩니다. Rails가 텍스트를 현재 layout에 넣도록 하려면 layout: true
옵션을 추가하고 layout 파일에 .text.erb
확장자를 사용해야 합니다.
2.2.6 HTML 렌더링하기
render
의 :html
옵션을 사용하여 HTML 문자열을 브라우저로 보낼 수 있습니다:
render html: helpers.tag.strong("찾을 수 없음")
HTML 코드의 작은 조각을 렌더링할 때 유용합니다. 하지만 마크업이 복잡한 경우에는 template 파일로 옮기는 것을 고려해보세요.
html:
옵션을 사용할 때, 문자열이 html_safe
를 인식하는 API로 구성되지 않은 경우 HTML 엔티티는 이스케이프 처리됩니다.
2.2.7 JSON 렌더링하기
JSON은 많은 Ajax 라이브러리에서 사용되는 JavaScript 데이터 포맷입니다. Rails는 객체를 JSON으로 변환하고 그 JSON을 브라우저로 다시 렌더링하는 내장 지원 기능을 가지고 있습니다:
render json: @product
json:
키워드와 함께 render
를 사용하면, 지정한 객체가 JSON 형식으로 변환됩니다.
render할 객체에 대해 to_json
을 호출할 필요가 없습니다. :json
옵션을 사용하면 render
가 자동으로 to_json
을 호출합니다.
2.2.8 XML 렌더링
Rails는 객체를 XML로 변환하고 그 XML을 호출자에게 다시 렌더링하는 기능이 내장되어 있습니다:
render xml: @product
product를 XML 포맷으로 렌더링합니다.
렌더링하고자 하는 객체에 대해 to_xml
을 직접 호출할 필요는 없습니다. :xml
옵션을 사용하면 render
가 자동으로 to_xml
을 호출합니다.
2.2.9 Vanilla JavaScript 렌더링하기
Rails는 vanilla JavaScript를 렌더링할 수 있습니다:
render js: "alert('Hello Rails');"
위 코드는 JavaScript 알림창을 통해 "Hello Rails"를 표시하도록 렌더링합니다.
브라우저에 문자열을 text/javascript
MIME 타입으로 전송합니다.
2.2.10 Raw Body 렌더링
:body
옵션을 render
에 사용하여 content type을 설정하지 않고 raw content를 브라우저로 전송할 수 있습니다:
render body: "raw"
render body 옵션은 body의 내용을 raw text로 보내줍니다.
이 옵션은 응답의 content type을 신경쓰지 않는 경우에만 사용해야 합니다. 대부분의 경우 :plain
또는 :html
을 사용하는 것이 더 적절할 수 있습니다.
별도로 재정의하지 않는 한, 이 render 옵션에서 반환되는 응답은 text/plain
이 됩니다. 이는 Action Dispatch 응답의 기본 content type이기 때문입니다.
2.2.11 Rendering Raw File
Rails는 절대 경로의 raw file을 렌더링할 수 있습니다. 이는 에러 페이지와 같은 정적 파일을 조건부로 렌더링할 때 유용합니다.
render file: "#{Rails.root}/public/404.html", layout: false
file: 옵션은 서버의 파일 시스템에 있는 템플릿 경로를 명시합니다. layout: false 옵션은 레이아웃을 사용하지 않도록 지정합니다.
이것은 raw 파일을 렌더링합니다(ERB나 다른 handler들을 지원하지 않습니다). 기본적으로 현재 layout 내에서 렌더링됩니다.
사용자 입력과 함께 :file
옵션을 사용하면 공격자가 파일 시스템의 보안에 민감한 파일에 접근할 수 있으므로 보안 문제가 발생할 수 있습니다.
layout이 필요하지 않은 경우 send_file
이 더 빠르고 좋은 옵션이 될 수 있습니다.
2.2.12 객체 렌더링
Rails는 #render_in
에 응답하는 객체를 렌더링할 수 있습니다. 객체에 #format
을 정의하여 형식을 제어할 수 있습니다.
class Greeting
def render_in(view_context)
view_context.render html: "Hello, World"
end
def format
:html
end
end
render Greeting.new
# => "Hello World"
이것은 현재 view context로 제공된 객체에서 render_in
을 호출합니다. render
의 :renderable
옵션을 사용하여 객체를 제공할 수도 있습니다:
render renderable: Greeting.new
# => "Hello World"
2.2.13 render
의 옵션들
render
메서드 호출은 일반적으로 다음 6가지 옵션을 받습니다:
:content_type
:layout
:location
:status
:formats
:variants
2.2.13.1 :content_type
옵션
기본적으로 Rails는 렌더링 결과를 text/html
의 MIME content-type으로 제공합니다(또는 :json
옵션을 사용하면 application/json
, :xml
옵션을 사용하면 application/xml
). 이를 변경하고 싶을 때가 있을 수 있는데, :content_type
옵션을 설정하여 변경할 수 있습니다:
render template: "feed", content_type: "application/rss"
```를 사용하여 RSS feed를 렌더링합니다.
##### `:layout` 옵션
`render`의 대부분 옵션에서, 렌더링된 콘텐츠는 현재 layout의 일부로 표시됩니다. layout과 그 사용법에 대해서는 이 가이드의 뒷부분에서 자세히 설명합니다.
`:layout` 옵션을 사용하여 Rails에게 현재 action의 layout으로 사용할 특정 파일을 지정할 수 있습니다:
```ruby
render layout: "special_layout"
특정 layout을 지정하여 render
layout 없이 렌더링하도록 Rails에 지시할 수도 있습니다:
layout을 적용하지 않고 렌더링합니다
2.2.13.2 :location
옵션
:location
옵션을 사용하여 HTTP Location
헤더를 설정할 수 있습니다:
render xml: photo, location: photo_url(photo)
photo를 XML로 렌더링하고, location 옵션으로 photo_url(photo)을 전달합니다.
2.2.13.3 :status
옵션
Rails는 자동으로 올바른 HTTP status code로 응답을 생성합니다(대부분의 경우 200 OK
입니다). :status
옵션을 사용하여 이를 변경할 수 있습니다:
render status: 500
render status: :forbidden
status를 명시적으로 지정할 수 있습니다. 정수나 기호 모두 가능합니다.
Rails는 숫자로 된 상태 코드와 아래 표시된 해당 symbol을 모두 이해합니다.
응답 클래스 | HTTP Status Code | Symbol |
---|---|---|
정보성 | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
성공 | 200 | :ok |
201 | :created | |
202 | :accepted | |
203 | :non_authoritative_information | |
204 | :no_content | |
205 | :reset_content | |
206 | :partial_content | |
207 | :multi_status | |
208 | :already_reported | |
226 | :im_used | |
리다이렉션 | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
클라이언트 오류 | 400 | :bad_request |
401 | :unauthorized | |
402 | :payment_required | |
403 | :forbidden | |
404 | :not_found | |
405 | :method_not_allowed | |
406 | :not_acceptable | |
407 | :proxy_authentication_required | |
408 | :request_timeout | |
409 | :conflict | |
410 | :gone | |
411 | :length_required | |
412 | :precondition_failed | |
413 | :payload_too_large | |
414 | :uri_too_long | |
415 | :unsupported_media_type | |
416 | :range_not_satisfiable | |
417 | :expectation_failed | |
421 | :misdirected_request | |
422 | :unprocessable_entity | |
423 | :locked | |
424 | :failed_dependency | |
426 | :upgrade_required | |
428 | :precondition_required |
| | 429 | :too_many_requests | | | 431 | :request_header_fields_too_large | | | 451 | :unavailable_for_legal_reasons | | Server Error | 500 | :internal_server_error | | | 501 | :not_implemented | | | 502 | :bad_gateway | | | 503 | :service_unavailable | | | 504 | :gateway_timeout | | | 505 | :http_version_not_supported | | | 506 | :variant_also_negotiates | | | 507 | :insufficient_storage | | | 508 | :loop_detected | | | 510 | :not_extended | | | 511 | :network_authentication_required |
content가 없는 status code(100-199, 204, 205, 또는 304)와 함께 content를 렌더링하려고 하면, 응답에서 제외될 것입니다.
2.2.13.4 :formats
Option
Rails는 요청에 지정된 format(또는 기본값으로 :html
)을 사용합니다.
symbol이나 array를 포함하는 :formats
옵션을 전달하여 이를 변경할 수 있습니다.
render formats: :xml
render formats: [:json, :xml]
format으로 XML을 렌더링하도록 강제하거나, JSON 또는 XML format으로 렌더링하도록 강제합니다.
지정된 format의 템플릿이 존재하지 않으면 ActionView::MissingTemplate
에러가 발생합니다.
2.2.13.5 :variants
옵션
이것은 Rails에게 동일한 format의 템플릿 변형을 찾도록 지시합니다.
symbol이나 배열과 함께 :variants
옵션을 전달하여 variants 목록을 지정할 수 있습니다.
다음은 사용 예시입니다.
# HomeController#index에서 호출됨
render variants: [:mobile, :desktop]
이러한 variants 설정으로 Rails는 다음 템플릿들을 순서대로 찾아 처음 발견된 것을 사용합니다.
app/views/home/index.html+mobile.erb
app/views/home/index.html+desktop.erb
app/views/home/index.html.erb
지정된 포맷의 템플릿이 존재하지 않으면 ActionView::MissingTemplate
에러가 발생합니다.
render 호출에서 variant를 설정하는 대신 컨트롤러 액션에서 request 객체에 variant를 설정할 수도 있습니다.
def index
request.variant = determine_variant
end
private
def determine_variant
variant = nil
# variant를 결정하는 코드
variant = :mobile if session[:use_mobile]
variant
end
2.2.14 Layout 찾기
현재 layout을 찾기 위해, Rails는 먼저 app/views/layouts
에서 controller와 동일한 base name을 가진 파일을 찾습니다. 예를 들어, PhotosController
클래스의 action을 렌더링할 때는 app/views/layouts/photos.html.erb
(또는 app/views/layouts/photos.builder
)를 사용합니다. 만약 해당 controller에 특정된 layout이 없다면, Rails는 app/views/layouts/application.html.erb
또는 app/views/layouts/application.builder
를 사용합니다. 만약 .erb
layout이 없다면, Rails는 .builder
layout이 존재하는 경우 이를 사용합니다. Rails는 또한 특정 layout을 개별 controller와 action에 더 정확하게 할당할 수 있는 여러 방법을 제공합니다.
2.2.14.1 Controller를 위한 Layout 지정하기
controller에서 layout
선언을 사용하여 기본 layout 규칙을 재정의할 수 있습니다. 예를 들면:
class ProductsController < ApplicationController
layout "inventory"
#...
end
이 선언으로, ProductsController
에 의해 렌더링되는 모든 view들은 app/views/layouts/inventory.html.erb
를 layout으로 사용하게 됩니다.
전체 애플리케이션에 특정 layout을 지정하려면, ApplicationController
클래스에서 layout
선언을 사용하세요:
class ApplicationController < ActionController::Base
layout "main"
#...
end
이 선언을 통해 전체 애플리케이션의 모든 view는 app/views/layouts/main.html.erb
를 레이아웃으로 사용하게 됩니다.
2.2.14.2 Runtime에서 레이아웃 선택하기
심볼을 사용하여 요청이 처리될 때까지 레이아웃 선택을 연기할 수 있습니다:
class ProductsController < ApplicationController
layout :products_layout
def show
@product = Product.find(params[:id])
end
private
def products_layout
@current_user.special? ? "special" : "products"
end
end
이제 현재 사용자가 특별한 사용자라면, product를 볼 때 특별한 layout을 보게 됩니다.
layout을 결정하기 위해 Proc과 같은 인라인 메서드도 사용할 수 있습니다. 예를 들어, Proc 객체를 전달하면 Proc에 제공하는 블록이 controller
인스턴스를 받게 되므로, 현재 request를 기반으로 layout을 결정할 수 있습니다:
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
# xhr(XMLHttpRequest)인 경우 "popup" layout을,
# 그렇지 않은 경우 "application" layout을 사용합니다
end
2.2.14.3 Conditional Layouts
컨트롤러 레벨에서 지정된 레이아웃은 :only
와 :except
옵션을 지원합니다. 이 옵션들은 컨트롤러 내의 메서드 이름에 해당하는 하나의 메서드 이름이나 메서드 이름들의 배열을 사용할 수 있습니다:
class ProductsController < ApplicationController
layout "product", except: [:index, :rss]
end
ProductsController는 index와 rss action을 제외한 모든 action에 대해 "product" layout을 적용합니다.
이 선언으로 product
레이아웃은 rss
와 index
메서드를 제외한 모든 곳에서 사용됩니다.
2.2.14.4 Layout 상속
Layout 선언은 계층 구조에서 아래로 전파되며, 더 구체적인 layout 선언이 항상 일반적인 선언을 오버라이드합니다. 예를 들어:
application_controller.rb
class ApplicationController < ActionController::Base layout "main" end
articles_controller.rb
class ArticlesController < ApplicationController end
special_articles_controller.rb
class SpecialArticlesController < ArticlesController layout "special" end
old_articles_controller.rb
class OldArticlesController < SpecialArticlesController layout false def show @article = Article.find(params[:id]) end def index @old_articles = Article.older render layout: "old" end # ... end
이 애플리케이션에서:
- 일반적으로 view는
main
layout에서 렌더링됩니다 ArticlesController#index
는main
layout을 사용합니다SpecialArticlesController#index
는special
layout을 사용합니다OldArticlesController#show
는 layout을 전혀 사용하지 않습니다OldArticlesController#index
는old
layout을 사용합니다
2.2.14.5 Template Inheritance
Layout Inheritance 로직과 유사하게, template이나 partial이 일반적인 경로에서 찾을 수 없는 경우 controller는 상속 체인에서 렌더링할 template이나 partial을 찾습니다. 예를 들어:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
end
# app/controllers/admin/products_controller.rb
class Admin::ProductsController < AdminController
def index
end
end
admin/products#index
action에 대한 template 검색 순서는 다음과 같습니다:
app/views/admin/products/
app/views/admin/
app/views/application/
이는 app/views/application/
을 공유 partial들을 위한 훌륭한 장소로 만들어주며, 이 partial들은 ERB에서 다음과 같이 렌더링될 수 있습니다:
<%# app/views/admin/products/index.html.erb %>
<%= render @products || "empty_list" %>
<%# app/views/application/_empty_list.html.erb %>
이 목록에는 아직 항목이 <em>없습니다</em>.
2.2.15 Double Render Error 피하기
대부분의 Rails 개발자들은 언젠가는 "Can only render or redirect once per action" 에러 메시지를 보게 될 것입니다. 이는 귀찮은 일이지만 비교적 쉽게 해결할 수 있습니다. 보통 이런 에러는 render
가 작동하는 방식에 대한 근본적인 오해로 인해 발생합니다.
예를 들어, 다음의 코드는 이 에러를 발생시킬 것입니다:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
이 코드는 문제가 있습니다. special?
메서드가 true를 반환하면 special_show
template을 렌더링하려고 시도한 다음 즉시 regular_show
template도 렌더링하려고 시도할 것입니다. 실제로는 DoubleRenderError
가 발생할 것입니다.
@book.special?
이 true
로 평가되면, Rails는 @book
변수를 special_show
뷰에 덤프하는 렌더링 프로세스를 시작할 것입니다. 하지만 이것은 show
액션의 나머지 코드의 실행을 중단하지 않을 것이며, Rails가 액션의 끝에 도달하면 regular_show
뷰를 렌더링하기 시작하고 에러를 발생시킬 것입니다. 해결책은 간단합니다: 하나의 코드 경로에서 render
나 redirect
를 한 번만 호출하도록 해야 합니다. return
이 도움이 될 수 있습니다. 다음은 수정된 메서드 버전입니다:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
return
end
render action: "regular_show"
end
ActionController에 의해 수행되는 암시적 render는 render
가 호출되었는지 감지하므로, 다음과 같은 코드는 오류 없이 동작합니다:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
위 코드는 특수 케이스를 적절히 처리하지만, 일반적인 케이스에서는 암시적인 render를 수행합니다. 메서드 마지막에 있는 action view의 이름을 기반으로 show.html.erb
를 자동으로 렌더링하게 됩니다.
이것은 special?
인 책들은 special_show
템플릿으로 렌더링하고, 다른 책들은 기본 show
템플릿으로 렌더링할 것입니다.
2.3 redirect_to
사용하기
HTTP 요청에 대한 응답을 처리하는 또 다른 방법은 redirect_to
를 사용하는 것입니다. 앞서 보았듯이 render
는 Rails에게 응답을 구성하는데 어떤 view(또는 다른 asset)를 사용할지 알려줍니다. redirect_to
메서드는 완전히 다른 일을 수행합니다: 브라우저에게 다른 URL에 대한 새로운 요청을 보내도록 지시합니다. 예를 들어, 코드의 어느 위치에서든 애플리케이션의 photos 인덱스로 다음과 같이 리다이렉트할 수 있습니다:
redirect_to photos_url
redirect_back
를 사용하여 사용자를 이전 페이지로 되돌릴 수 있습니다.
이 위치는 HTTP_REFERER
헤더에서 가져오는데, 브라우저에서 반드시 설정된다는 보장이 없으므로 이런 경우를 대비해 fallback_location
을 반드시 제공해야 합니다.
redirect_back(fallback_location: root_path)
전 페이지로 리다이렉트합니다. fallback_location
파라미터는 HTTP_REFERER 헤더가 없을 때 리다이렉트할 위치를 지정하는데 필수입니다.
redirect_to
와 redirect_back
은 메서드 실행을 즉시 중단하고 반환하지 않으며, 단순히 HTTP 응답만 설정합니다. 이들 메서드 이후에 오는 구문들도 계속 실행됩니다. 필요한 경우 명시적인 return
또는 다른 중단 메커니즘을 사용하여 실행을 중단할 수 있습니다.
2.3.1 다른 리다이렉트 상태 코드 사용하기
redirect_to
를 호출할 때 Rails는 임시 리다이렉트인 HTTP 상태 코드 302를 사용합니다. 영구 리다이렉트인 301과 같은 다른 상태 코드를 사용하고 싶다면, :status
옵션을 사용할 수 있습니다:
redirect_to photos_path, status: 301
301 status code로 photos_path
로 redirect합니다.
:render
의 :status
옵션과 마찬가지로, redirect_to
의 :status
도 숫자와 심볼 형태의 헤더 지정을 모두 허용합니다.
2.3.2 render
와 redirect_to
의 차이점
때로는 경험이 부족한 개발자들이 redirect_to
를 Rails 코드의 한 위치에서 다른 위치로 실행을 이동시키는 일종의 goto
명령어처럼 생각하기도 합니다. 이는 잘못된 것입니다.
현재 액션은 완료되어 브라우저에 응답을 반환합니다. 이후 코드 실행이 중단되고 새로운 요청을 기다리게 되는데, HTTP 302 상태 코드를 반환함으로써 브라우저에게 다음에 어떤 요청을 해야 하는지 알려준 것뿐입니다.
그 차이를 이해하기 위해 다음 액션들을 살펴보세요:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
render action: "index"
end
end
위 코드는 모든 책 목록을 보여주는 index action과 특정 책의 세부 정보를 보여주는 show action입니다. show action에서 해당 ID의 책을 찾지 못하면 index view로 이동합니다.
이런 형태의 코드에서는 @book
변수가 nil
일 경우 문제가 발생할 수 있습니다. render :action
은 대상 action의 어떤 코드도 실행하지 않으므로 index
뷰에서 필요로 하는 @books
변수가 설정되지 않는다는 점을 기억하세요. 이를 해결하는 한 가지 방법은 렌더링 대신 redirect를 사용하는 것입니다:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
redirect_to action: :index
end
end
index 메서드는 모든 Book 레코드를 조회합니다.
show 메서드는 params[:id]에 해당하는 Book 레코드를 찾습니다. 만약 해당 레코드가 없으면(nil인 경우) index 액션으로 리디렉션됩니다.
이 코드에서는, 브라우저가 index 페이지에 대한 새로운 요청을 만들고, index
메서드의 코드가 실행되어 모든 것이 정상적으로 작동할 것입니다.
이 코드의 유일한 단점은 브라우저와의 왕복이 필요하다는 점입니다: 브라우저가 /books/1
로 show action을 요청하고 컨트롤러는 books가 없다는 것을 발견하면, 컨트롤러는 브라우저에게 /books/
로 이동하라고 지시하는 302 redirect 응답을 보냅니다. 브라우저는 이를 따르고 컨트롤러에 index
action을 요청하는 새로운 요청을 다시 보내고, 컨트롤러는 데이터베이스의 모든 books를 가져와 index 템플릿을 렌더링하여 브라우저로 다시 보내면 브라우저가 이를 화면에 표시합니다.
작은 애플리케이션에서는 이러한 추가 지연이 문제가 되지 않을 수 있지만, 응답 시간이 중요한 경우에는 고려해야 할 사항입니다. 다음과 같은 인위적인 예제로 이를 처리하는 한 가지 방법을 보여드리겠습니다:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
@books = Book.all
flash.now[:alert] = "찾으시는 책이 존재하지 않습니다"
render "index"
end
end
이는 지정된 ID를 가진 books가 없음을 감지하고, model의 모든 books로 @books
instance variable을 채운 다음, index.html.erb
template을 직접 렌더링하여 사용자에게 무슨 일이 일어났는지 알려주는 flash alert message와 함께 브라우저에 반환합니다.
2.4 head
를 사용하여 헤더만 있는 응답 만들기
head
메서드는 브라우저에 헤더만 있는 응답을 보내는 데 사용할 수 있습니다. head
메서드는 HTTP 상태 코드를 나타내는 숫자나 심볼을 인자로 받습니다 (참조 표 참고). options 인자는 헤더 이름과 값의 hash로 해석됩니다. 예를 들어, 오류 헤더만 반환할 수 있습니다:
head :bad_request
이것은 HTTP status를 400 Bad Request로 응답하지만, body는 비어있습니다.
이는 다음과 같은 header를 생성할 것입니다:
HTTP/1.1 400 잘못된 요청
Connection: close
Date: Sun, 24 Jan 2010 12:15:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
X-Runtime: 0.013483
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
또는 다른 정보를 전달하기 위해 다른 HTTP header를 사용할 수 있습니다.
head :created, location: photo_path(@photo)
Response의 body 없이 헤더만 반환합니다. location 헤더를 photo_path로 설정하고 HTTP 상태를 created로 설정하는 것입니다.
위는 다음과 같은 결과를 생성할 것입니다:
HTTP/1.1 201 Created
Connection: close
Date: Sun, 24 Jan 2010 12:16:44 GMT
Transfer-Encoding: chunked
Location: /photos/1
Content-Type: text/html; charset=utf-8
X-Runtime: 0.083496
Set-Cookie: _blog_session=...생략...; path=/; HttpOnly
Cache-Control: no-cache
3 레이아웃 구조화하기
Rails가 응답으로 view를 렌더링할 때, 이 가이드의 앞부분에서 다룬 현재 레이아웃을 찾는 규칙을 사용하여 view를 현재 레이아웃과 결합합니다. 레이아웃 내에서 전체 응답을 형성하기 위해 서로 다른 출력들을 결합하는 세 가지 도구를 사용할 수 있습니다:
- Asset 태그
yield
와content_for
- Partial
3.1 Asset Tag Helpers
Asset tag helper는 view를 feeds, JavaScript, stylesheets, images, videos, audios에 연결하는 HTML을 생성하는 메소드를 제공합니다. Rails에는 여섯 가지 asset tag helper가 있습니다:
이러한 태그들은 layout이나 다른 view에서 사용할 수 있지만, auto_discovery_link_tag
, javascript_include_tag
, stylesheet_link_tag
는 주로 layout의 <head>
섹션에서 사용됩니다.
asset tag helper는 지정된 위치에 asset이 존재하는지 확인하지 않습니다. 단순히 사용자가 알고 있다고 가정하고 링크를 생성합니다.
3.1.1 auto_discovery_link_tag
로 Feeds 연결하기
auto_discovery_link_tag
helper는 대부분의 브라우저와 feed reader가 RSS, Atom 또는 JSON feed의 존재를 감지하는데 사용할 수 있는 HTML을 생성합니다. 이는 링크의 타입(:rss
, :atom
, 또는 :json
), url_for에 전달되는 옵션의 해시, 그리고 태그의 옵션 해시를 인자로 받습니다.
<%= auto_discovery_link_tag(:rss, {action: "feed"},
{title: "RSS 피드"}) %>
auto_discovery_link_tag
에 사용할 수 있는 세 가지 태그 옵션이 있습니다:
:rel
은 링크에서rel
값을 지정합니다. 기본값은 "alternate"입니다.:type
은 명시적 MIME type을 지정합니다. Rails는 자동으로 적절한 MIME type을 생성합니다.:title
은 링크의 제목을 지정합니다. 기본값은 대문자로 된:type
값입니다(예: "ATOM" 또는 "RSS").
3.1.2 javascript_include_tag
로 JavaScript 파일 연결하기
javascript_include_tag
헬퍼는 제공된 각 소스에 대해 HTML script
태그를 반환합니다.
Asset Pipeline이 활성화된 Rails를 사용하는 경우, 이 헬퍼는 이전 Rails 버전에서 사용되던 public/javascripts
대신 /assets/javascripts/
에 대한 링크를 생성합니다. 이 링크는 asset pipeline에 의해 제공됩니다.
Rails 애플리케이션이나 Rails 엔진 내의 JavaScript 파일은 app/assets
, lib/assets
또는 vendor/assets
세 위치 중 하나에 위치합니다. 이러한 위치들은 Asset Pipeline 가이드의 Asset Organization 섹션에서 자세히 설명됩니다.
원하는 경우 문서 루트를 기준으로 한 전체 경로나 URL을 지정할 수 있습니다. 예를 들어, app/assets/javascripts
, lib/assets/javascripts
또는 vendor/assets/javascripts
중 하나에 있는 main.js
JavaScript 파일에 링크하려면 다음과 같이 합니다:
<%= javascript_include_tag "main" %>
Rails는 다음과 같은 script
태그를 출력할 것입니다:
<script src='/assets/main.js'></script>
이 asset에 대한 요청은 Sprockets gem에 의해 처리됩니다.
app/assets/javascripts/main.js
와 app/assets/javascripts/columns.js
와 같은 여러 파일을 동시에 포함하려면:
<%= javascript_include_tag "main", "columns" %>
app/assets/javascripts/main.js
와 app/assets/javascripts/photos/columns.js
를 포함하려면:
<%= javascript_include_tag "main", "/photos/columns" %>
http://example.com/main.js
를 포함하려면:
<%= javascript_include_tag "http://example.com/main.js" %>
3.1.3 CSS 파일을 stylesheet_link_tag
로 링크하기
stylesheet_link_tag
헬퍼는 제공된 각 소스에 대해 HTML <link>
태그를 반환합니다.
"Asset Pipeline"이 활성화된 Rails를 사용하는 경우, 이 헬퍼는 /assets/stylesheets/
로 연결되는 링크를 생성합니다. 이 링크는 Sprockets gem에 의해 처리됩니다. 스타일시트 파일은 app/assets
, lib/assets
, 또는 vendor/assets
중 하나의 위치에 저장될 수 있습니다.
문서 루트를 기준으로 한 전체 경로나 URL을 지정할 수 있습니다. 예를 들어, app/assets
, lib/assets
, 또는 vendor/assets
중 하나의 내부에 있는 stylesheets
디렉토리 안에 있는 스타일시트 파일에 링크하려면 다음과 같이 합니다:
<%= stylesheet_link_tag "main" %>
app/assets/stylesheets/main.css
와 app/assets/stylesheets/columns.css
를 포함하려면:
<%= stylesheet_link_tag "main", "columns" %>
app/assets/stylesheets/main.css
와 app/assets/stylesheets/photos/columns.css
를 포함하려면:
<%= stylesheet_link_tag "main", "photos/columns" %>
http://example.com/main.css
를 포함하려면:
<%= stylesheet_link_tag "http://example.com/main.css" %>
기본적으로 stylesheet_link_tag
는 rel="stylesheet"
와 함께 link를 생성합니다. 이 기본값은 적절한 옵션(:rel
)을 지정하여 재정의할 수 있습니다.
<%= stylesheet_link_tag "main_print", media: "print" %>
3.1.4 image_tag
로 이미지 링크하기
image_tag
헬퍼는 지정된 파일에 대한 HTML <img />
태그를 생성합니다. 기본적으로 파일들은 public/images
에서 로드됩니다.
이미지의 확장자를 반드시 지정해야 한다는 점에 주의하세요.
<%= image_tag "header.png" %>
원하는 경우 이미지 path를 제공할 수 있습니다:
<%= image_tag "icons/delete.gif" %>
추가적인 HTML 옵션들의 hash를 제공할 수 있습니다:
<%= image_tag "icons/delete.gif", {height: 45} %>
사용자가 브라우저에서 이미지를 비활성화한 경우 사용될 이미지의 대체 텍스트를 제공할 수 있습니다. alt 텍스트를 명시적으로 지정하지 않으면 파일명에서 확장자를 제외하고 대문자로 변환한 것이 기본값이 됩니다. 예를 들어 다음의 두 이미지 태그는 동일한 코드를 반환합니다:
<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "홈" %>
포맷이 "{width}x{height}"인 특별한 size 태그도 지정할 수 있습니다:
<%= image_tag "home.gif", size: "50x20" %>
위의 특수 태그들 외에도 :class
, :id
, :name
과 같은 표준 HTML 옵션들의 hash를 마지막에 전달할 수 있습니다:
<%= image_tag "home.gif", alt: "홈으로 가기",
id: "HomeImage",
class: "nav_bar" %>
3.1.5 video_tag
를 사용하여 동영상 링크하기
video_tag
헬퍼는 지정된 파일에 대한 HTML5 <video>
태그를 생성합니다. 기본적으로 파일들은 public/videos
에서 로드됩니다.
<%= video_tag "movie.ogg" %>
Produces는 Accepts와 반대입니다. 주어진 format이나 MIME type의 response를 생성하도록 route를 제한합니다.
get "/articles", to: "articles#index", as: :articles
defaults format: :json
end
위의 route에서 GET /articles는 format: 'json'
으로 response content type이 application/json
인 경우에만 매칭됩니다.
다음과 같이 더 많은 format을 다루는 방법도 있습니다:
constraints format: /json|xml/ do
get "/articles", to: "articles#index"
end
route를 format이나 MIME type으로 제한하려면 constraints
에 format:
또는 ":format" =>
키를 사용할 수 있습니다.
예를 들어:
get "articles/:id", to: "articles#show", constraints: { format: 'json' }
get "articles/:id", to: "articles#show", constraints: { format: /json|xml/ }
<video src="/videos/movie.ogg" />
image_tag
처럼 절대 경로나 public/videos
디렉토리에 대한 상대 경로를 제공할 수 있습니다. 추가로 image_tag
와 마찬가지로 size: "#{width}x#{height}"
옵션을 지정할 수 있습니다. Video tag도 마지막에 HTML 옵션(id
, class
등)을 지정할 수 있습니다.
video tag는 HTML 옵션 해시를 통해 다음을 포함한 모든 <video>
HTML 옵션을 지원합니다:
poster: "image_name.png"
, 비디오가 재생되기 전에 표시할 이미지를 제공합니다.autoplay: true
, 페이지 로드시 비디오 재생을 시작합니다.loop: true
, 비디오가 끝나면 반복 재생합니다.controls: true
, 사용자가 비디오를 제어할 수 있는 브라우저 제공 컨트롤을 제공합니다.autobuffer: true
, 페이지 로드시 사용자를 위해 파일을 미리 로드합니다.
video_tag
에 비디오 배열을 전달하여 여러 비디오를 재생하도록 지정할 수도 있습니다:
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
다음과 같이 출력될 것입니다:
<video>
<source src="/videos/trailer.ogg">
<source src="/videos/movie.ogg">
</video>
3.1.6 audio_tag
를 사용하여 오디오 파일에 링크하기
audio_tag
헬퍼는 지정된 파일에 대한 HTML5 <audio>
태그를 생성합니다. 기본적으로 파일들은 public/audios
에서 로드됩니다.
<%= audio_tag "music.mp3" %>
원하는 경우 audio 파일의 경로를 제공할 수 있습니다:
<%= audio_tag "music/first_song.mp3" %>
audio_tag helper를 사용하여 오디오 파일을 출력합니다.
ID
, class
등과 같은 추가 옵션을 hash로 전달할 수도 있습니다.
video_tag
와 마찬가지로 audio_tag
도 특별한 옵션들이 있습니다:
autoplay: true
, 페이지 로드시 오디오 재생을 시작합니다controls: true
, 사용자가 오디오와 상호작용할 수 있는 브라우저 기본 컨트롤을 제공합니다autobuffer: true
, 페이지 로드시 사용자를 위해 파일을 미리 로드합니다
3.2 yield
이해하기
레이아웃의 맥락에서 yield
는 view의 내용이 삽입되어야 하는 섹션을 식별합니다. 이를 사용하는 가장 간단한 방법은 단일 yield
를 사용하는 것이며, 현재 렌더링되는 view의 모든 내용이 여기에 삽입됩니다:
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
여러 개의 yielding region을 가지는 layout을 생성할 수도 있습니다:
<html>
<head>
<%= yield :head %> <!-- head 섹션에 사용할 컨텐츠 yield -->
</head>
<body>
<%= yield %> <!-- body 섹션에 사용할 메인 컨텐츠 yield -->
</body>
</html>
뷰의 메인 본문은 항상 이름 없는 yield
로 렌더링됩니다. 이름이 있는 yield
에 콘텐츠를 렌더링하려면, 이름이 있는 yield
와 동일한 인자를 사용하여 content_for
메서드를 호출하세요.
새로 생성된 애플리케이션은 app/views/layouts/application.html.erb
템플릿의 <head>
요소 내에 <%= yield :head %>
를 포함하고 있습니다.
3.3 content_for
메서드 사용하기
content_for
메서드를 사용하면 레이아웃의 이름이 있는 yield
블록에 컨텐츠를 삽입할 수 있습니다. 예를 들어, 다음 뷰는 방금 본 레이아웃과 함께 동작할 수 있습니다:
<% content_for :head do %>
<title>간단한 페이지</title>
<% end %>
<p>Hello, Rails!</p>
이 페이지를 제공된 layout에 렌더링한 결과는 다음과 같은 HTML이 됩니다:
<html>
<head>
<title>간단한 페이지</title>
</head>
<body>
<p>안녕하세요, Rails!</p>
</body>
</html>
content_for
메소드는 레이아웃이 사이드바나 푸터와 같이 각각의 콘텐츠 블록이 삽입되어야 하는 구분된 영역을 포함할 때 매우 유용합니다. 또한 페이지별 JavaScript <script>
요소, CSS <link>
요소, 컨텍스트별 <meta>
요소 또는 기타 요소들을 일반적인 레이아웃의 <head>
에 삽입할 때도 유용합니다.
3.4 Partial 사용하기
Partial 템플릿 - 보통 "partial"이라고 불리는 - 은 렌더링 프로세스를 더 관리하기 쉬운 조각으로 나누는 또 다른 도구입니다. Partial을 사용하면 응답의 특정 부분을 렌더링하는 코드를 자체 파일로 이동할 수 있습니다.
3.4.1 Partial 명명하기
뷰의 일부로 partial을 렌더링하려면 뷰 내에서 render
메서드를 사용합니다:
<%= render "menu" %>
이는 렌더링되는 view 내의 해당 지점에서 _menu.html.erb
라는 이름의 파일을 렌더링할 것입니다. 맨 앞의 밑줄 문자에 주목하세요: partial은 일반 view와 구분하기 위해 앞에 밑줄을 붙여서 이름을 짓습니다. 하지만 실제로 참조할 때는 밑줄 없이 사용합니다. 이는 다른 폴더에서 partial을 가져올 때도 마찬가지입니다:
<%= render "application/menu" %>
view partial은 템플릿과 레이아웃과 동일한 템플릿 상속에 의존하기 때문에, 코드는 app/views/application/_menu.html.erb
에서 partial을 가져올 것입니다.
3.4.2 View를 단순화하기 위해 Partial 사용하기
partial을 사용하는 한 가지 방법은 서브루틴처럼 다루는 것입니다: 무슨 일이 일어나는지 더 쉽게 파악할 수 있도록 view에서 세부 사항을 빼내는 방법입니다. 예를 들어, 다음과 같은 view가 있을 수 있습니다:
<%= render "application/ad_banner" %>
<h1>제품</h1>
<p>다음은 우리의 주요 제품들입니다:</p>
<%# ... %>
<%= render "application/footer" %>
여기서 _ad_banner.html.erb
와 _footer.html.erb
partial들은 애플리케이션의 많은 페이지에서 공유되는 콘텐츠를 포함할 수 있습니다. 특정 페이지에 집중할 때 이러한 섹션의 세부 사항을 볼 필요는 없습니다.
이 가이드의 이전 섹션에서 보았듯이, yield
는 layout을 정리하는 데 매우 강력한 도구입니다. 이는 순수 Ruby이므로 거의 모든 곳에서 사용할 수 있다는 점을 기억하세요. 예를 들어, 여러 유사한 리소스에 대한 form layout 정의를 DRY하게 만드는 데 사용할 수 있습니다:
users/index.html.erb
<%= render "application/search_filters", search: @q do |form| %> <p> Name contains: <%= form.text_field :name_contains %> </p> <% end %>
roles/index.html.erb
<%= render "application/search_filters", search: @q do |form| %> <p> Title contains: <%= form.text_field :title_contains %> </p> <% end %>
application/_search_filters.html.erb
<%= form_with model: search do |form| %> <h1>Search form:</h1> <fieldset> <%= yield form %> </fieldset> <p> <%= form.submit "Search" %> </p> <% end %>
애플리케이션의 모든 페이지에서 공유되는 콘텐츠의 경우, layout에서 직접 partial을 사용할 수 있습니다.
3.4.3 Partial Layouts
partial은 view가 layout을 사용할 수 있는 것처럼 자체 layout 파일을 사용할 수 있습니다. 예를 들어, 다음과 같이 partial을 호출할 수 있습니다:
<%= render partial: "link_area", layout: "graybar" %>
_link_area.html.erb
라는 이름의 partial을 찾아 _graybar.html.erb
레이아웃을 사용해 렌더링합니다. partial용 레이아웃은 일반 partial과 마찬가지로 앞에 밑줄이 붙는 명명 규칙을 따르며, 메인 layouts
폴더가 아닌 해당 partial이 속한 폴더와 같은 곳에 위치합니다.
또한 :layout
과 같은 추가 옵션을 전달할 때는 :partial
을 명시적으로 지정해야 합니다.
3.4.4 Local 변수 전달하기
partial에 local 변수를 전달할 수도 있어서 더욱 강력하고 유연하게 사용할 수 있습니다. 예를 들어, 이 기법을 사용하여 new와 edit 페이지 간의 중복을 줄이면서도 일부 고유한 내용을 유지할 수 있습니다:
new.html.erb
<h1>New zone</h1> <%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editing zone</h1> <%= render partial: "form", locals: {zone: @zone} %>
_form.html.erb
<%= form_with model: zone do |form| %> <p> <b>Zone name</b><br> <%= form.text_field :name %> </p> <p> <%= form.submit %> </p> <% end %>
두 view에서 동일한 partial이 렌더링되지만, Action View의 submit helper는 new 액션에서는 "Create Zone"을, edit 액션에서는 "Update Zone"을 반환합니다.
특정 경우에만 partial에 local 변수를 전달하려면 local_assigns
를 사용하세요.
index.html.erb
<%= render user.articles %>
show.html.erb
<%= render article, full: true %> ```
_article.html.erb
<h2><%= article.title %></h2>
<% if local_assigns[:full] %>
<%= simple_format article.body %>
<% else %>
<%= truncate article.body %>
<% end %>
이렇게 하면 모든 local 변수를 선언하지 않고도 partial을 사용할 수 있습니다.
모든 partial은 partial과 동일한 이름을 가진 local 변수(맨 앞의 underscore 제외)를 가지고 있습니다. :object
옵션을 통해 이 local 변수에 object를 전달할 수 있습니다:
<%= render partial: "customer", object: @new_customer %>
customer
partial 내에서 customer
변수는 부모 뷰의 @new_customer
를 참조합니다.
만약 partial에 렌더링할 모델의 인스턴스가 있다면 다음과 같은 축약 문법을 사용할 수 있습니다:
<%= render @customer %>
@customer
instance 변수가 Customer
모델의 인스턴스를 포함한다고 가정하면, 이는 _customer.html.erb
를 사용하여 렌더링하고 부모 뷰의 @customer
instance 변수를 참조할 local 변수 customer
를 partial로 전달합니다.
3.4.5 Collection 렌더링하기
Partial은 collection을 렌더링할 때 매우 유용합니다. :collection
옵션을 통해 collection을 partial로 전달하면, partial은 collection의 각 멤버마다 한 번씩 삽입됩니다:
index.html.erb
<h1>Products</h1> <%= render partial: "product", collection: @products %>
_product.html.erb
<p>Product Name: <%= product.name %></p>
Partial이 복수형 collection과 함께 호출될 때, partial의 개별 인스턴스들은 partial의 이름을 따서 명명된 변수를 통해 렌더링되는 collection의 멤버에 접근할 수 있습니다. 이 경우 partial은 _product
이며, _product
partial 내에서 렌더링되는 인스턴스를 얻기 위해 product
를 참조할 수 있습니다.
이에 대한 축약형도 있습니다. @products
가 Product
인스턴스의 collection이라고 가정하면, 동일한 결과를 얻기 위해 index.html.erb
에서 다음과 같이 간단히 작성할 수 있습니다:
<h1>Products</h1>
<%= render @products %>
Rails는 컬렉션의 모델 이름을 보고 사용할 partial의 이름을 결정합니다. 실제로 이종 컬렉션을 만들어서 이런 방식으로 렌더링할 수도 있으며, Rails는 컬렉션의 각 멤버에 대해 적절한 partial을 선택합니다:
index.html.erb
<h1>Contacts</h1> <%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb
<p>Customer: <%= customer.name %></p>
employees/_employee.html.erb
<p>Employee: <%= employee.name %></p>
이 경우 Rails는 컬렉션의 각 멤버에 대해 적절하게 customer 또는 employee partial을 사용합니다.
컬렉션이 비어있는 경우, render
는 nil을 반환하므로 대체 콘텐츠를 제공하는 것이 매우 간단합니다.
<h1>Products</h1>
<%= render(@products) || "사용 가능한 product가 없습니다." %>
3.4.6 로컬 변수
partial 내에서 커스텀 로컬 변수 이름을 사용하려면, partial을 호출할 때 :as
옵션을 지정하세요:
<%= render partial: "product", collection: @products, as: :item %>
위 코드는 @products collection을 product partial로 렌더링하고, 개별 product 객체를 :item이라는 로컬 변수로 전달합니다.
이 변경 사항으로, partial 내에서 item
로컬 변수를 통해 @products
컬렉션의 인스턴스에 접근할 수 있습니다.
또한 locals: {}
옵션을 사용하여 렌더링하는 partial에 임의의 로컬 변수를 전달할 수 있습니다:
<%= render partial: "product", collection: @products,
as: :item, locals: {title: "상품 페이지"} %>
이 경우, partial은 "Products Page"라는 값을 가진 로컬 변수 title
에 접근할 수 있습니다.
3.4.7 Counter Variables
Rails는 또한 collection에 의해 호출된 partial 내에서 counter 변수를 사용할 수 있게 합니다. 이 변수는 partial의 제목 뒤에 _counter
를 붙여서 이름이 지정됩니다. 예를 들어, @products
collection을 렌더링할 때 _product.html.erb
partial은 product_counter
변수에 접근할 수 있습니다. 이 변수는 첫 번째 렌더링에서 0
값으로 시작하여 해당 view 내에서 partial이 렌더링된 횟수를 인덱싱합니다.
# index.html.erb
<%= render partial: "product", collection: @products %>
# _product.html.erb
<%= product_counter %> # 첫 번째 product는 0, 두 번째 product는 1...
이것은 as:
옵션을 사용하여 지역 변수 이름이 변경된 경우에도 작동합니다. 따라서 as: :item
을 사용한 경우, counter 변수는 item_counter
가 됩니다.
3.4.8 Spacer Templates
:spacer_template
옵션을 사용하여 주요 partial 인스턴스 사이에 렌더링될 두 번째 partial을 지정할 수도 있습니다:
<%= render partial: @products, spacer_template: "product_ruler" %>
이 템플릿은 각각의 partial 렌더링 사이에 지정된 spacer 템플릿을 삽입할 것입니다.
Rails는 각 _product
partial 쌍 사이에 _product_ruler
partial을 렌더링합니다(어떤 데이터도 전달되지 않음).
3.4.9 Collection Partial 레이아웃
collection을 렌더링할 때 :layout
옵션을 사용할 수도 있습니다:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
collection을 사용할 때도 layout을 지정할 수 있습니다.
collection의 각 아이템에 대한 partial과 함께 layout이 렌더링될 것입니다. current object와 object_counter 변수는 partial 내에서와 동일한 방식으로 layout 내에서도 사용할 수 있습니다.
3.5 Nested Layouts 사용하기
애플리케이션에서 특정 컨트롤러를 지원하기 위해 일반적인 애플리케이션 레이아웃과 약간 다른 레이아웃이 필요할 수 있습니다. 메인 레이아웃을 반복하고 수정하는 대신, nested layouts(서브 템플릿이라고도 함)을 사용하여 이를 구현할 수 있습니다. 다음은 예시입니다:
다음과 같은 ApplicationController
레이아웃이 있다고 가정해봅시다:
app/views/layouts/application.html.erb
<html> <head> <title><%= @page_title or "Page Title" %></title> <%= stylesheet_link_tag "layout" %> <%= yield :head %> </head> <body> <div id="top_menu">Top menu items here</div> <div id="menu">Menu items here</div> <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> </body> </html>
NewsController
에 의해 생성된 페이지에서 상단 메뉴를 숨기고 우측 메뉴를 추가하고 싶다면:
app/views/layouts/news.html.erb
<% content_for :head do %> <style> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} </style> <% end %> <% content_for :content do %> <div id="right_menu">Right menu items here</div> <%= content_for?(:news_content) ? yield(:news_content) : yield %> <% end %> <%= render template: "layouts/application" %>
이게 전부입니다. News 뷰는 새로운 레이아웃을 사용하여 상단 메뉴를 숨기고 "content" div 내부에 새로운 우측 메뉴를 추가할 것입니다.
이 기술을 사용하여 다양한 서브 템플릿 구성으로 비슷한 결과를 얻을 수 있는 여러 방법이 있습니다. 중첩 레벨에는 제한이 없다는 점에 주목하세요. ActionView::render
메소드를 render template: 'layouts/news'
를 통해 사용하여 News 레이아웃을 기반으로 새로운 레이아웃을 만들 수 있습니다. News
레이아웃을 서브 템플릿으로 사용하지 않을 것이 확실하다면, content_for?(:news_content) ? yield(:news_content) : yield
를 단순히 yield
로 대체할 수 있습니다.