1 Visão Geral: Como as Peças se Encaixam
Este guia se concentra na interação entre Controller e View no triângulo Model-View-Controller. Como você sabe, o Controller é responsável por orquestrar todo o processo de manipulação de uma solicitação no Rails, embora normalmente ele delegue qualquer código pesado para o Model. Mas então, quando é hora de enviar uma resposta de volta para o usuário, o Controller passa as coisas para a View. É essa transferência que é o assunto deste guia.
Em linhas gerais, isso envolve decidir o que deve ser enviado como resposta e chamar um método apropriado para criar essa resposta. Se a resposta for uma visualização completa, o Rails também faz um trabalho extra para envolver a visualização em um layout e possivelmente incluir visualizações parciais. Você verá todos esses caminhos mais adiante neste guia.
2 Criando Respostas
Do ponto de vista do controller, existem três maneiras de criar uma resposta HTTP:
- Chamar
render
para criar uma resposta completa a ser enviada de volta para o navegador. - Chamar
redirect_to
para enviar um código de status de redirecionamento HTTP para o navegador. - Chamar
head
para criar uma resposta consistindo apenas de cabeçalhos HTTP para enviar de volta para o navegador.
2.1 Renderização por Padrão: Convenção sobre Configuração em Ação
Você já ouviu falar que o Rails promove "convenção sobre configuração". A renderização padrão é um excelente exemplo disso. Por padrão, os controllers no Rails renderizam automaticamente visualizações com nomes que correspondem a rotas válidas. Por exemplo, se você tiver este código em sua classe BooksController
:
class BooksController < ApplicationController
end
E o seguinte em seu arquivo de rotas:
resources :books
E você tiver um arquivo de visualização app/views/books/index.html.erb
:
<h1>Livros em breve!</h1>
O Rails renderizará automaticamente app/views/books/index.html.erb
quando você navegar para /books
e você verá "Livros em breve!" em sua tela.
No entanto, uma tela de "em breve" é apenas minimamente útil, então você logo criará seu modelo Book
e adicionará a ação de índice ao BooksController
:
class BooksController < ApplicationController
def index
@books = Book.all
end
end
Observe que não temos uma renderização explícita no final da ação de índice de acordo com o princípio de "convenção sobre configuração". A regra é que, se você não renderizar explicitamente algo no final de uma ação do controller, o Rails procurará automaticamente o template action_name.html.erb
no caminho de visualização do controller e o renderizará. Portanto, neste caso, o Rails renderizará o arquivo app/views/books/index.html.erb
.
Se quisermos exibir as propriedades de todos os livros em nossa visualização, podemos fazer isso com um template ERB como este:
<h1>Listando Livros</h1>
<table>
<thead>
<tr>
<th>Título</th>
<th>Conteúdo</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
<td><%= link_to "Mostrar", book %></td>
<td><%= link_to "Editar", edit_book_path(book) %></td>
<td><%= link_to "Excluir", book, data: { turbo_method: :delete, turbo_confirm: "Tem certeza?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to "Novo livro", new_book_path %>
NOTA: A renderização real é feita por classes aninhadas do módulo ActionView::Template::Handlers
. Este guia não explora esse processo, mas é importante saber que a extensão do arquivo de visualização controla a escolha do manipulador de template.
2.2 Usando render
Na maioria dos casos, o método render
do controller faz o trabalho pesado de renderizar o conteúdo da sua aplicação para uso por um navegador. Existem várias maneiras de personalizar o comportamento do render
. Você pode renderizar a visualização padrão para um template do Rails, ou um template específico, ou um arquivo, ou código inline, ou nada. Você pode renderizar texto, JSON ou XML. Você também pode especificar o tipo de conteúdo ou o status HTTP da resposta renderizada.
DICA: Se você quiser ver os resultados exatos de uma chamada para render
sem precisar inspecioná-la em um navegador, você pode chamar render_to_string
. Este método aceita exatamente as mesmas opções que render
, mas retorna uma string em vez de enviar uma resposta de volta para o navegador.
2.2.1 Renderizando a View de uma Ação
Se você deseja renderizar a view que corresponde a um template diferente dentro do mesmo controller, você pode usar render
com o nome da view:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render "edit"
end
end
Se a chamada para update
falhar, chamando a ação update
neste controller irá renderizar o template edit.html.erb
pertencente ao mesmo controller.
Se preferir, você pode usar um símbolo em vez de uma string para especificar a ação a ser renderizada:
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 Renderizando o Template de uma Ação de Outro Controller
E se você quiser renderizar um template de um controller completamente diferente daquele que contém o código da ação? Você também pode fazer isso com render
, que aceita o caminho completo (relativo a app/views
) do template a ser renderizado. Por exemplo, se você estiver executando código em um AdminProductsController
que está em app/controllers/admin
, você pode renderizar os resultados de uma ação para um template em app/views/products
desta forma:
render "products/show"
O Rails sabe que essa view pertence a um controller diferente por causa do caractere de barra embutido na string. Se você quiser ser explícito, pode usar a opção :template
(que era obrigatória no Rails 2.2 e anteriores):
render template: "products/show"
2.2.3 Conclusão
As duas maneiras acima de renderizar (renderizar o template de outra ação no mesmo controller e renderizar o template de outra ação em um controller diferente) são na verdade variantes da mesma operação.
Na verdade, na classe BooksController
, dentro da ação update
onde queremos renderizar o template edit
se o livro não for atualizado com sucesso, todas as chamadas de renderização a seguir renderizariam o template edit.html.erb
no diretório views/books
:
render :edit
render action: :edit
render "edit"
render action: "edit"
render "books/edit"
render template: "books/edit"
Qual você usa é realmente uma questão de estilo e convenção, mas a regra geral é usar o mais simples que faça sentido para o código que você está escrevendo.
2.2.4 Usando render
com :inline
O método render
pode funcionar sem uma view completamente, se você estiver disposto a usar a opção :inline
para fornecer ERB como parte da chamada do método. Isso é perfeitamente válido:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
ATENÇÃO: Raramente há uma boa razão para usar essa opção. Misturar ERB em seus controllers vai contra a orientação MVC do Rails e tornará mais difícil para outros desenvolvedores entenderem a lógica do seu projeto. Use uma view ERB separada em vez disso.
Por padrão, a renderização inline usa ERB. Você pode forçá-la a usar o Builder em vez disso com a opção :type
:
render inline: "xml.p {'Prática de codificação horrível!'}", type: :builder
2.2.5 Renderizando Texto
Você pode enviar texto simples - sem nenhuma marcação - de volta para o navegador usando a opção :plain
para render
:
render plain: "OK"
DICA: Renderizar texto puro é mais útil quando você está respondendo a requisições Ajax ou de serviços web que esperam algo diferente de HTML adequado.
NOTA: Por padrão, se você usar a opção :plain
, o texto será renderizado sem usar o layout atual. Se você quiser que o Rails coloque o texto no layout atual, você precisa adicionar a opção layout: true
e usar a extensão .text.erb
para o arquivo de layout.
2.2.6 Renderizando HTML
Você pode enviar uma string HTML de volta para o navegador usando a opção :html
para render
:
render html: helpers.tag.strong('Not Found')
DICA: Isso é útil quando você está renderizando um pequeno trecho de código HTML. No entanto, você pode considerar movê-lo para um arquivo de template se a marcação for complexa.
NOTA: Ao usar a opção html:
, as entidades HTML serão escapadas se a string não for composta com APIs que são html_safe
-aware.
2.2.7 Renderizando JSON
JSON é um formato de dados JavaScript usado por muitas bibliotecas Ajax. O Rails possui suporte integrado para converter objetos em JSON e renderizar esse JSON de volta para o navegador:
render json: @product
DICA: Você não precisa chamar to_json
no objeto que deseja renderizar. Se você usar a opção :json
, o render
irá automaticamente chamar to_json
para você.
2.2.8 Renderização de XML
O Rails também possui suporte integrado para converter objetos em XML e renderizar esse XML de volta para o chamador:
render xml: @product
DICA: Você não precisa chamar to_xml
no objeto que deseja renderizar. Se você usar a opção :xml
, o render
irá automaticamente chamar to_xml
para você.
2.2.9 Renderização de JavaScript Vanilla
O Rails pode renderizar JavaScript vanilla:
render js: "alert('Hello Rails');"
Isso enviará a string fornecida para o navegador com um tipo MIME de text/javascript
.
2.2.10 Renderização de Corpo Bruto
Você pode enviar um conteúdo bruto de volta para o navegador, sem definir nenhum tipo de conteúdo, usando a opção :body
para render
:
render body: "raw"
DICA: Essa opção deve ser usada apenas se você não se importar com o tipo de conteúdo da resposta. Usar :plain
ou :html
pode ser mais apropriado na maioria das vezes.
NOTA: A menos que seja substituído, sua resposta retornada dessa opção de renderização será text/plain
, pois esse é o tipo de conteúdo padrão da resposta do Action Dispatch.
2.2.11 Renderização de Arquivo Bruto
O Rails pode renderizar um arquivo bruto a partir de um caminho absoluto. Isso é útil para renderizar condicionalmente arquivos estáticos, como páginas de erro.
render file: "#{Rails.root}/public/404.html", layout: false
Isso renderiza o arquivo bruto (não suporta ERB ou outros manipuladores). Por padrão, ele é renderizado dentro do layout atual.
AVISO: Usar a opção :file
em combinação com a entrada do usuário pode levar a problemas de segurança, pois um invasor pode usar essa ação para acessar arquivos sensíveis de segurança em seu sistema de arquivos.
DICA: send_file
é frequentemente uma opção mais rápida e melhor se um layout não for necessário.
2.2.12 Renderização de Objetos
O Rails pode renderizar objetos que respondem a :render_in
.
render MyRenderable.new
Isso chama render_in
no objeto fornecido com o contexto de visualização atual.
Você também pode fornecer o objeto usando a opção :renderable
para render
:
render renderable: MyRenderable.new
2.2.13 Opções para render
As chamadas para o método render
geralmente aceitam seis opções:
:content_type
:layout
:location
:status
:formats
:variants
2.2.13.1 A opção :content_type
Por padrão, o Rails irá servir os resultados de uma operação de renderização com o tipo de conteúdo MIME text/html
(ou application/json
se você usar a opção :json
, ou application/xml
para a opção :xml
). Há momentos em que você pode querer alterar isso, e você pode fazer isso definindo a opção :content_type
:
render template: "feed", content_type: "application/rss"
2.2.13.2 A opção :layout
Com a maioria das opções de render
, o conteúdo renderizado é exibido como parte do layout atual. Você aprenderá mais sobre layouts e como usá-los mais adiante neste guia.
Você pode usar a opção :layout
para dizer ao Rails para usar um arquivo específico como o layout para a ação atual:
render layout: "special_layout"
Você também pode dizer ao Rails para renderizar sem nenhum layout:
render layout: false
2.2.13.3 A opção :location
Você pode usar a opção :location
para definir o cabeçalho HTTP Location
:
render xml: photo, location: photo_url(photo)
2.2.13.4 A opção :status
O Rails irá gerar automaticamente uma resposta com o código de status HTTP correto (na maioria dos casos, isso é 200 OK
). Você pode usar a opção :status
para alterar isso:
render status: 500
render status: :forbidden
O Rails entende tanto códigos de status numéricos quanto os símbolos correspondentes mostrados abaixo.
Classe de Resposta | Código de Status HTTP | Símbolo |
---|---|---|
Informativo | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
Sucesso | 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 | |
Redirecionamento | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
Erro do Cliente | 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 | |
Erro do Servidor | 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 |
NOTA: Se você tentar renderizar conteúdo junto com um código de status que não é de conteúdo (100-199, 204, 205 ou 304), ele será removido da resposta.
2.2.13.5 A opção :formats
O Rails usa o formato especificado na solicitação (ou :html
por padrão). Você pode alterar isso passando a opção :formats
com um símbolo ou um array:
render formats: :xml
render formats: [:json, :xml]
Se um modelo com o formato especificado não existir, será gerado um erro ActionView::MissingTemplate
.
2.2.13.6 A opção :variants
Isso indica ao Rails para procurar variações de modelo do mesmo formato. Você pode especificar uma lista de variantes passando a opção :variants
com um símbolo ou um array.
Um exemplo de uso seria este.
# chamado em HomeController#index
render variants: [:mobile, :desktop]
Com esse conjunto de variantes, o Rails procurará o seguinte conjunto de modelos e usará o primeiro que existir.
app/views/home/index.html+mobile.erb
app/views/home/index.html+desktop.erb
app/views/home/index.html.erb
Se um modelo com o formato especificado não existir, será gerado um erro ActionView::MissingTemplate
.
Em vez de definir a variante na chamada de renderização, você também pode defini-la no objeto de solicitação em sua ação de controlador.
def index
request.variant = determine_variant
end
private
def determine_variant
variant = nil
# algum código para determinar a(s) variante(s) a serem usadas
variant = :mobile if session[:use_mobile]
variant
end
2.2.14 Encontrando Layouts
Para encontrar o layout atual, o Rails primeiro procura um arquivo em app/views/layouts
com o mesmo nome base do controlador. Por exemplo, a renderização de ações da classe PhotosController
usará app/views/layouts/photos.html.erb
(ou app/views/layouts/photos.builder
). Se não houver um layout específico do controlador, o Rails usará app/views/layouts/application.html.erb
ou app/views/layouts/application.builder
. Se não houver um layout .erb
, o Rails usará um layout .builder
se existir. O Rails também fornece várias maneiras de atribuir layouts específicos a controladores e ações individuais.
2.2.14.1 Especificando Layouts para Controladores
Você pode substituir as convenções de layout padrão em seus controladores usando a declaração layout
. Por exemplo:
class ProductsController < ApplicationController
layout "inventory"
#...
end
Com essa declaração, todas as visualizações renderizadas pelo ProductsController
usarão app/views/layouts/inventory.html.erb
como layout.
Para atribuir um layout específico para toda a aplicação, use uma declaração de layout
em sua classe ApplicationController
:
class ApplicationController < ActionController::Base
layout "main"
#...
end
Com essa declaração, todas as visualizações em toda a aplicação usarão app/views/layouts/main.html.erb
como layout.
2.2.14.2 Escolhendo Layouts em Tempo de Execução
Você pode usar um símbolo para adiar a escolha do layout até que uma solicitação seja processada:
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
Agora, se o usuário atual for um usuário especial, ele receberá um layout especial ao visualizar um produto.
Você pode até mesmo usar um método inline, como um Proc, para determinar o layout. Por exemplo, se você passar um objeto Proc, o bloco que você fornecer ao Proc receberá a instância do controller
, para que o layout possa ser determinado com base na solicitação atual:
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
2.2.14.3 Layouts Condicional
Os layouts especificados no nível do controlador suportam as opções :only
e :except
. Essas opções aceitam um nome de método ou um array de nomes de método, correspondendo a nomes de método dentro do controlador:
class ProductsController < ApplicationController
layout "product", except: [:index, :rss]
end
Com essa declaração, o layout product
será usado para tudo, exceto os métodos rss
e index
.
2.2.14.4 Herança de Layout
As declarações de layout se propagam descendente na hierarquia, e declarações de layout mais específicas sempre substituem as mais gerais. Por exemplo:
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
Nesta aplicação:
- Em geral, as visualizações serão renderizadas no layout
main
ArticlesController#index
usará o layoutmain
SpecialArticlesController#index
usará o layoutspecial
OldArticlesController#show
não usará nenhum layoutOldArticlesController#index
usará o layoutold
##### Herança de Templates
Similar à lógica de Herança de Layout, se um template ou partial não for encontrado no caminho convencional, o controlador procurará um template ou partial para renderizar em sua cadeia de herança. Por exemplo:
# 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
A ordem de busca para uma ação admin/products#index
será:
app/views/admin/products/
app/views/admin/
app/views/application/
Isso torna app/views/application/
um ótimo lugar para seus partials compartilhados, que podem ser renderizados em seu ERB da seguinte forma:
<%# app/views/admin/products/index.html.erb %>
<%= render @products || "empty_list" %>
<%# app/views/application/_empty_list.html.erb %>
Não há itens nesta lista <em>ainda</em>.
2.2.15 Evitando Erros de Renderização Dupla
Mais cedo ou mais tarde, a maioria dos desenvolvedores Rails verá a mensagem de erro "Can only render or redirect once per action" (Só é possível renderizar ou redirecionar uma vez por ação). Embora isso seja irritante, é relativamente fácil de corrigir. Geralmente isso acontece por causa de uma compreensão equivocada sobre a forma como o render
funciona.
Por exemplo, aqui está um código que irá disparar esse erro:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
Se @book.special?
for avaliado como true
, o Rails iniciará o processo de renderização para inserir a variável @book
na view special_show
. Mas isso não impedirá que o restante do código na ação show
seja executado, e quando o Rails chegar ao final da ação, ele começará a renderizar a view regular_show
- e lançará um erro. A solução é simples: certifique-se de ter apenas uma chamada para render
ou redirect
em um único caminho de código. Uma coisa que pode ajudar é o return
. Aqui está uma versão corrigida do método:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
return
end
render action: "regular_show"
end
Observe que o render implícito feito pelo ActionController detecta se o render
foi chamado, então o seguinte funcionará sem erros:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
Isso renderizará um livro com special?
definido com o template special_show
, enquanto outros livros serão renderizados com o template padrão show
.
2.3 Usando redirect_to
Outra maneira de lidar com o retorno de respostas para uma requisição HTTP é com o redirect_to
. Como você viu, render
informa ao Rails qual view (ou outro recurso) usar na construção de uma resposta. O método redirect_to
faz algo completamente diferente: ele diz ao navegador para enviar uma nova requisição para uma URL diferente. Por exemplo, você pode redirecionar de qualquer lugar do seu código para o índice de fotos em sua aplicação com esta chamada:
redirect_to photos_url
Você pode usar redirect_back
para retornar o usuário para a página de onde ele veio. Esta localização é obtida do cabeçalho HTTP_REFERER
, que não é garantido estar definido pelo navegador, então você deve fornecer a fallback_location
para usar nesse caso.
redirect_back(fallback_location: root_path)
NOTA: redirect_to
e redirect_back
não interrompem e retornam imediatamente da execução do método, mas simplesmente definem respostas HTTP. As instruções que ocorrem após eles em um método serão executadas. Você pode interromper com um return
explícito ou outro mecanismo de interrupção, se necessário.
2.3.1 Obtendo um Código de Status de Redirecionamento Diferente
O Rails usa o código de status HTTP 302, um redirecionamento temporário, quando você chama redirect_to
. Se você deseja usar um código de status diferente, talvez 301, um redirecionamento permanente, você pode usar a opção :status
:
redirect_to photos_path, status: 301
Assim como a opção :status
para render
, :status
para redirect_to
aceita tanto designações de cabeçalho numéricas quanto simbólicas.
2.3.2 A Diferença Entre render
e redirect_to
Às vezes, desenvolvedores inexperientes pensam no redirect_to
como uma espécie de comando goto
, movendo a execução de um lugar para outro no seu código Rails. Isso não está correto. Seu código para de ser executado e aguarda uma nova requisição do navegador. Acontece apenas que você informou ao navegador qual requisição ele deve fazer em seguida, enviando de volta um código de status HTTP 302.
Considere essas ações para ver a diferença:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
render action: "index"
end
end
Com o código nessa forma, provavelmente haverá um problema se a variável @book
for nil
. Lembre-se, um render :action
não executa nenhum código na ação de destino, então nada irá configurar a variável @books
que a view index
provavelmente requererá. Uma maneira de corrigir isso é redirecionar em vez de renderizar:
```ruby
def index
@books = Book.all
end
def show @book = Book.find_by(id: params[:id]) if @book.nil? redirect_to action: :index end end ```
Com esse código, o navegador fará uma nova solicitação para a página de índice, o código no método index
será executado e tudo ficará bem.
A única desvantagem desse código é que ele requer uma viagem de ida e volta para o navegador: o navegador solicitou a ação de exibição com /books/1
e o controlador descobre que não há livros, então o controlador envia uma resposta de redirecionamento 302 para o navegador, dizendo para ir para /books/
, o navegador obedece e envia uma nova solicitação de volta ao controlador, agora pedindo a ação index
, o controlador então obtém todos os livros no banco de dados e renderiza o modelo de índice, enviando-o de volta ao navegador, que então o exibe na tela.
Embora em um aplicativo pequeno, essa latência adicional possa não ser um problema, é algo a se pensar se o tempo de resposta for uma preocupação. Podemos demonstrar uma maneira de lidar com isso com um exemplo fictício:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
@books = Book.all
flash.now[:alert] = "Seu livro não foi encontrado"
render "index"
end
end
Isso detectaria que não há livros com o ID especificado, preencheria a variável de instância @books
com todos os livros no modelo e, em seguida, renderizaria diretamente o modelo index.html.erb
, retornando-o para o navegador com uma mensagem de alerta para informar ao usuário o que aconteceu.
2.4 Usando head
para construir respostas apenas com cabeçalhos
O método head
pode ser usado para enviar respostas apenas com cabeçalhos para o navegador. O método head
aceita um número ou símbolo (consulte a tabela de referência) que representa um código de status HTTP. O argumento de opções é interpretado como um hash de nomes e valores de cabeçalho. Por exemplo, você pode retornar apenas um cabeçalho de erro:
head :bad_request
Isso produziria o seguinte cabeçalho:
HTTP/1.1 400 Bad Request
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
Ou você pode usar outros cabeçalhos HTTP para transmitir outras informações:
head :created, location: photo_path(@photo)
O que produziria:
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=...snip...; path=/; HttpOnly
Cache-Control: no-cache
3 Estruturando Layouts
Quando o Rails renderiza uma visualização como resposta, ele combina a visualização com o layout atual, usando as regras para encontrar o layout atual que foram abordadas anteriormente neste guia. Dentro de um layout, você tem acesso a três ferramentas para combinar diferentes partes de saída para formar a resposta geral:
- Tags de ativos
yield
econtent_for
- Partials
3.1 Tags de Ativos
As tags de ativos fornecem métodos para gerar HTML que vinculam visualizações a feeds, JavaScript, folhas de estilo, imagens, vídeos e áudios. Existem seis tags de ativos disponíveis no Rails:
Você pode usar essas tags em layouts ou outras visualizações, embora as tags auto_discovery_link_tag
, javascript_include_tag
e stylesheet_link_tag
sejam mais comumente usadas na seção <head>
de um layout.
AVISO: As tags de ativos não verificam a existência dos ativos nos locais especificados; elas simplesmente assumem que você sabe o que está fazendo e geram o link.
3.1.1 Vinculando a Feeds com o auto_discovery_link_tag
O auxiliar auto_discovery_link_tag
constrói HTML que a maioria dos navegadores e leitores de feeds pode usar para detectar a presença de feeds RSS, Atom ou JSON. Ele recebe o tipo de link (:rss
, :atom
ou :json
), um hash de opções que são passadas para url_for e um hash de opções para a tag:
<%= auto_discovery_link_tag(:rss, {action: "feed"},
{title: "RSS Feed"}) %>
Existem três opções de tag disponíveis para o auto_discovery_link_tag
:
:rel
especifica o valorrel
no link. O valor padrão é "alternate".:type
especifica um tipo MIME explícito. O Rails gerará automaticamente um tipo MIME apropriado.:title
especifica o título do link. O valor padrão é o valor:type
em maiúsculas, por exemplo, "ATOM" ou "RSS". #### Vinculando arquivos JavaScript com ojavascript_include_tag
O auxiliar javascript_include_tag
retorna uma tag HTML script
para cada fonte fornecida.
Se você estiver usando o Rails com o Asset Pipeline habilitado, esse auxiliar irá gerar um link para /assets/javascripts/
em vez de public/javascripts
, que era usado em versões anteriores do Rails. Esse link é então servido pelo asset pipeline.
Um arquivo JavaScript dentro de uma aplicação Rails ou um engine Rails é colocado em um dos três locais: app/assets
, lib/assets
ou vendor/assets
. Esses locais são explicados em detalhes na seção Organização de Ativos no Guia do Asset Pipeline.
Você pode especificar um caminho completo relativo à raiz do documento ou uma URL, se preferir. Por exemplo, para vincular a um arquivo JavaScript que está dentro de um diretório chamado javascripts
dentro de app/assets
, lib/assets
ou vendor/assets
, você faria isso:
<%= javascript_include_tag "main" %>
O Rails então irá gerar uma tag script
como esta:
<script src='/assets/main.js'></script>
A solicitação a esse recurso é então servida pela gem Sprockets.
Para incluir vários arquivos, como app/assets/javascripts/main.js
e app/assets/javascripts/columns.js
ao mesmo tempo:
<%= javascript_include_tag "main", "columns" %>
Para incluir app/assets/javascripts/main.js
e app/assets/javascripts/photos/columns.js
:
<%= javascript_include_tag "main", "/photos/columns" %>
Para incluir http://example.com/main.js
:
<%= javascript_include_tag "http://example.com/main.js" %>
3.1.2 Vinculando arquivos CSS com o stylesheet_link_tag
O auxiliar stylesheet_link_tag
retorna uma tag HTML <link>
para cada fonte fornecida.
Se você estiver usando o Rails com o "Asset Pipeline" habilitado, esse auxiliar irá gerar um link para /assets/stylesheets/
. Esse link é então processado pela gem Sprockets. Um arquivo de folha de estilo pode ser armazenado em um dos três locais: app/assets
, lib/assets
ou vendor/assets
.
Você pode especificar um caminho completo relativo à raiz do documento ou uma URL. Por exemplo, para vincular a um arquivo de folha de estilo que está dentro de um diretório chamado stylesheets
dentro de app/assets
, lib/assets
ou vendor/assets
, você faria isso:
<%= stylesheet_link_tag "main" %>
Para incluir app/assets/stylesheets/main.css
e app/assets/stylesheets/columns.css
:
<%= stylesheet_link_tag "main", "columns" %>
Para incluir app/assets/stylesheets/main.css
e app/assets/stylesheets/photos/columns.css
:
<%= stylesheet_link_tag "main", "photos/columns" %>
Para incluir http://example.com/main.css
:
<%= stylesheet_link_tag "http://example.com/main.css" %>
Por padrão, o stylesheet_link_tag
cria links com rel="stylesheet"
. Você pode substituir esse padrão especificando uma opção apropriada (:rel
):
<%= stylesheet_link_tag "main_print", media: "print" %>
3.1.3 Vinculando imagens com o image_tag
O auxiliar image_tag
cria uma tag HTML <img />
para o arquivo especificado. Por padrão, os arquivos são carregados de public/images
.
ATENÇÃO: Note que você deve especificar a extensão da imagem.
<%= image_tag "header.png" %>
Você pode fornecer um caminho para a imagem, se desejar:
<%= image_tag "icons/delete.gif" %>
Você pode fornecer um hash de opções HTML adicionais:
<%= image_tag "icons/delete.gif", {height: 45} %>
Você pode fornecer um texto alternativo para a imagem, que será usado se o usuário tiver as imagens desativadas no navegador. Se você não especificar um texto alternativo explicitamente, ele será o nome do arquivo, com a primeira letra maiúscula e sem extensão. Por exemplo, essas duas tags de imagem retornariam o mesmo código:
<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "Home" %>
Você também pode especificar uma tag de tamanho especial, no formato "{largura}x{altura}":
<%= image_tag "home.gif", size: "50x20" %>
Além das tags especiais acima, você pode fornecer um hash final de opções HTML padrão, como :class
, :id
ou :name
:
<%= image_tag "home.gif", alt: "Go Home",
id: "HomeImage",
class: "nav_bar" %>
3.1.4 Vinculando vídeos com o video_tag
O auxiliar video_tag
cria uma tag HTML5 <video>
para o arquivo especificado. Por padrão, os arquivos são carregados de public/videos
.
<%= video_tag "movie.ogg" %>
Produz
<video src="/videos/movie.ogg" />
Assim como um image_tag
, você pode fornecer um caminho, seja absoluto ou relativo ao diretório public/videos
. Além disso, você pode especificar a opção size: "#{largura}x#{altura}"
assim como um image_tag
. As tags de vídeo também podem ter qualquer uma das opções HTML especificadas no final (id
, class
, etc).
A tag de vídeo também suporta todas as opções HTML <video>
, através do hash de opções HTML, incluindo:
poster: "nome_da_imagem.png"
, fornece uma imagem para ser exibida no lugar do vídeo antes de começar a reproduzir.autoplay: true
, inicia a reprodução do vídeo ao carregar a página.loop: true
, repete o vídeo quando chega ao final.controls: true
, fornece controles fornecidos pelo navegador para o usuário interagir com o vídeo.autobuffer: true
, o vídeo irá pré-carregar o arquivo para o usuário ao carregar a página. Você também pode especificar vários vídeos para reproduzir passando um array de vídeos para avideo_tag
:
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
Isso irá produzir:
<video>
<source src="/videos/trailer.ogg">
<source src="/videos/movie.ogg">
</video>
3.1.5 Linkando para Arquivos de Áudio com o audio_tag
O helper audio_tag
constrói uma tag HTML5 <audio>
para o arquivo especificado. Por padrão, os arquivos são carregados a partir de public/audios
.
<%= audio_tag "music.mp3" %>
Você pode fornecer um caminho para o arquivo de áudio, se desejar:
<%= audio_tag "music/first_song.mp3" %>
Você também pode fornecer um hash de opções adicionais, como :id
, :class
, etc.
Assim como o video_tag
, o audio_tag
possui opções especiais:
autoplay: true
, inicia a reprodução do áudio ao carregar a páginacontrols: true
, fornece controles fornecidos pelo navegador para o usuário interagir com o áudio.autobuffer: true
, o áudio pré-carregará o arquivo para o usuário ao carregar a página.
3.2 Entendendo o yield
Dentro do contexto de um layout, yield
identifica uma seção onde o conteúdo da view deve ser inserido. A maneira mais simples de usar isso é ter um único yield
, no qual todo o conteúdo da view atualmente sendo renderizada é inserido:
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
Você também pode criar um layout com várias regiões de yield
:
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
O corpo principal da view sempre será renderizado no yield
sem nome. Para renderizar conteúdo em um yield
nomeado, você usa o método content_for
.
3.3 Usando o Método content_for
O método content_for
permite inserir conteúdo em um bloco yield
nomeado em seu layout. Por exemplo, esta view funcionaria com o layout que você acabou de ver:
<% content_for :head do %>
<title>Uma página simples</title>
<% end %>
<p>Olá, Rails!</p>
O resultado de renderizar esta página no layout fornecido seria este HTML:
<html>
<head>
<title>Uma página simples</title>
</head>
<body>
<p>Olá, Rails!</p>
</body>
</html>
O método content_for
é muito útil quando seu layout contém regiões distintas, como barras laterais e rodapés, que devem receber seus próprios blocos de conteúdo inseridos. Também é útil para inserir tags que carregam arquivos JavaScript ou CSS específicos da página no cabeçalho de um layout genérico.
3.4 Usando Parciais
Templates parciais - geralmente chamados apenas de "parciais" - são outro recurso para dividir o processo de renderização em partes mais gerenciáveis. Com um parcial, você pode mover o código para renderizar uma parte específica de uma resposta para seu próprio arquivo.
3.4.1 Nomeando Parciais
Para renderizar um parcial como parte de uma view, você usa o método render
dentro da view:
<%= render "menu" %>
Isso irá renderizar um arquivo chamado _menu.html.erb
naquele ponto dentro da view sendo renderizada. Observe o caractere de sublinhado inicial: os parciais são nomeados com um sublinhado inicial para distingui-los das views regulares, mesmo que sejam referenciados sem o sublinhado. Isso é verdade mesmo quando você está puxando um parcial de outra pasta:
<%= render "shared/menu" %>
Esse código irá puxar o parcial de app/views/shared/_menu.html.erb
.
3.4.2 Usando Parciais para Simplificar Views
Uma maneira de usar parciais é tratá-los como equivalentes a sub-rotinas: como uma maneira de mover detalhes de uma view para que você possa entender o que está acontecendo mais facilmente. Por exemplo, você pode ter uma view que se pareça com isso:
<%= render "shared/ad_banner" %>
<h1>Produtos</h1>
<p>Aqui estão alguns de nossos ótimos produtos:</p>
...
<%= render "shared/footer" %>
Aqui, os parciais _ad_banner.html.erb
e _footer.html.erb
podem conter
conteúdo que é compartilhado por muitas páginas em sua aplicação. Você não precisa ver
os detalhes dessas seções quando está concentrado em uma página específica.
Como visto nas seções anteriores deste guia, yield
é uma ferramenta muito poderosa
para limpar seus layouts. Lembre-se de que é puro Ruby, então você pode usá-lo
quase em qualquer lugar. Por exemplo, podemos usá-lo para DRY up a definição de layout de formulário para vários recursos semelhantes:
users/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %> <p> Nome contém: <%= form.text_field :name_contains %> </p> <% end %>
roles/index.html.erb
<%= render "shared/search_filters", search: @q do |form| %> <p> Título contém: <%= form.text_field :title_contains %> </p> <% end %>
shared/_search_filters.html.erb
<%= form_with model: search do |form| %> <h1>Formulário de busca:</h1> <fieldset> <%= yield form %> </fieldset> <p> <%= form.submit "Buscar" %> </p> <% end %>
DICA: Para conteúdo compartilhado entre todas as páginas do seu aplicativo, você pode usar parciais diretamente nos layouts.
3.4.3 Layouts Parciais
Um parcial pode usar seu próprio arquivo de layout, assim como uma visualização pode usar um layout. Por exemplo, você pode chamar um parcial assim:
<%= render partial: "link_area", layout: "graybar" %>
Isso procuraria um parcial chamado _link_area.html.erb
e o renderizaria usando o layout _graybar.html.erb
. Observe que os layouts para parciais seguem a mesma convenção de nomenclatura com sublinhado inicial que os parciais regulares e são colocados na mesma pasta do parcial ao qual pertencem (não na pasta principal layouts
).
Observe também que é necessário especificar explicitamente :partial
ao passar opções adicionais como :layout
.
3.4.4 Passando Variáveis Locais
Você também pode passar variáveis locais para parciais, tornando-os ainda mais poderosos e flexíveis. Por exemplo, você pode usar essa técnica para reduzir a duplicação entre as páginas de criação e edição, mantendo um pouco de conteúdo distinto:
new.html.erb
<h1>Nova zona</h1> <%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editando zona</h1> <%= render partial: "form", locals: {zone: @zone} %>
_form.html.erb
<%= form_with model: zone do |form| %> <p> <b>Nome da zona</b><br> <%= form.text_field :name %> </p> <p> <%= form.submit %> </p> <% end %>
Embora o mesmo parcial seja renderizado em ambas as visualizações, o helper de submit do Action View retornará "Criar Zona" para a ação de criação e "Atualizar Zona" para a ação de edição.
Para passar uma variável local para um parcial apenas em casos específicos, use 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 %>
Dessa forma, é possível usar o parcial sem a necessidade de declarar todas as variáveis locais.
Todo parcial também possui uma variável local com o mesmo nome do parcial (sem o sublinhado inicial). Você pode passar um objeto para essa variável local por meio da opção :object
:
<%= render partial: "customer", object: @new_customer %>
Dentro do parcial customer
, a variável customer
se referirá a @new_customer
da visualização pai.
Se você tiver uma instância de um modelo para renderizar em um parcial, pode usar uma sintaxe abreviada:
<%= render @customer %>
Supondo que a variável de instância @customer
contenha uma instância do modelo Customer
, isso usará _customer.html.erb
para renderizá-lo e passará a variável local customer
para o parcial, que se referirá à variável de instância @customer
na visualização pai.
3.4.5 Renderizando Coleções
Parciais são muito úteis para renderizar coleções. Quando você passa uma coleção para um parcial por meio da opção :collection
, o parcial será inserido uma vez para cada membro da coleção:
index.html.erb
<h1>Produtos</h1> <%= render partial: "product", collection: @products %>
_product.html.erb
<p>Nome do produto: <%= product.name %></p>
Quando um parcial é chamado com uma coleção pluralizada, as instâncias individuais do parcial têm acesso ao membro da coleção que está sendo renderizado por meio de uma variável com o nome do parcial. Nesse caso, o parcial é _product
, e dentro do parcial _product
, você pode se referir a product
para obter a instância que está sendo renderizada.
Também há uma forma abreviada para isso. Supondo que @products
seja uma coleção de instâncias de Product
, você pode simplesmente escrever isso em index.html.erb
para obter o mesmo resultado:
<h1>Produtos</h1>
<%= render @products %>
O Rails determina o nome do parcial a ser usado ao analisar o nome do modelo na coleção. Na verdade, você pode até criar uma coleção heterogênea e renderizá-la dessa maneira, e o Rails escolherá o parcial apropriado para cada membro da coleção:
index.html.erb
<h1>Contatos</h1> <%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb
<p>Cliente: <%= customer.name %></p>
employees/_employee.html.erb
<p>Funcionário: <%= employee.name %></p>
Nesse caso, o Rails usará os parciais customer
ou employee
conforme apropriado para cada membro da coleção.
No caso de a coleção estar vazia, render
retornará nil, então deve ser bastante simples fornecer um conteúdo alternativo.
<h1>Produtos</h1>
<%= render(@products) || "Não há produtos disponíveis." %>
3.4.6 Variáveis Locais
Para usar um nome de variável local personalizado dentro do parcial, especifique a opção :as
na chamada do parcial:
<%= render partial: "product", collection: @products, as: :item %>
Com essa alteração, você pode acessar uma instância da coleção @products
como a variável local item
dentro do parcial.
Você também pode passar variáveis locais arbitrárias para qualquer parcial que você está renderizando com a opção locals: {}
:
<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Página de Produtos"} %>
Nesse caso, o parcial terá acesso a uma variável local title
com o valor "Página de Produtos".
3.4.7 Variáveis de Contagem
O Rails também disponibiliza uma variável de contagem dentro de um parcial chamado pela coleção. A variável é nomeada de acordo com o título do parcial seguido por _counter
. Por exemplo, ao renderizar uma coleção @products
, o parcial _product.html.erb
pode acessar a variável product_counter
. A variável indexa o número de vezes que o parcial foi renderizado na visualização envolvente, começando com o valor 0
na primeira renderização.
# index.html.erb
<%= render partial: "product", collection: @products %>
# _product.html.erb
<%= product_counter %> # 0 para o primeiro produto, 1 para o segundo produto...
Isso também funciona quando o nome do parcial é alterado usando a opção as:
. Portanto, se você fizer as: :item
, a variável de contagem será item_counter
.
3.4.8 Modelos de Espaçadores
Você também pode especificar um segundo parcial a ser renderizado entre instâncias do parcial principal usando a opção :spacer_template
:
<%= render partial: @products, spacer_template: "product_ruler" %>
O Rails irá renderizar o parcial _product_ruler
(sem dados passados para ele) entre cada par de parciais _product
.
3.4.9 Layouts de Parciais de Coleções
Ao renderizar coleções, também é possível usar a opção :layout
:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
O layout será renderizado junto com o parcial para cada item na coleção. As variáveis de objeto atual e object_counter também estarão disponíveis no layout da mesma forma que estão dentro do parcial.
3.5 Usando Layouts Aninhados
Você pode descobrir que sua aplicação requer um layout que difere ligeiramente do layout regular da aplicação para suportar um controlador específico. Em vez de repetir o layout principal e editá-lo, você pode fazer isso usando layouts aninhados (às vezes chamados de sub-modelos). Aqui está um exemplo:
Suponha que você tenha o seguinte layout ApplicationController
:
app/views/layouts/application.html.erb
<html> <head> <title><%= @page_title or "Título da Página" %></title> <%= stylesheet_link_tag "layout" %> <style><%= yield :stylesheets %></style> </head> <body> <div id="top_menu">Itens do menu superior aqui</div> <div id="menu">Itens do menu aqui</div> <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> </body> </html>
Nas páginas geradas pelo NewsController
, você deseja ocultar o menu superior e adicionar um menu à direita:
app/views/layouts/news.html.erb
<% content_for :stylesheets do %> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} <% end %> <% content_for :content do %> <div id="right_menu">Itens do menu à direita aqui</div> <%= content_for?(:news_content) ? yield(:news_content) : yield %> <% end %> <%= render template: "layouts/application" %>
É isso. As visualizações de Notícias usarão o novo layout, ocultando o menu superior e adicionando um novo menu à direita dentro da div "content".
Existem várias maneiras de obter resultados semelhantes com diferentes esquemas de sub-modelos usando essa técnica. Observe que não há limite nos níveis de aninhamento. Pode-se usar o método ActionView::render
via render template: 'layouts/news'
para basear um novo layout no layout de Notícias. Se você tem certeza de que não irá submodelar o layout de Notícias, você pode substituir content_for?(:news_content) ? yield(:news_content) : yield
simplesmente por yield
.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.
Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.
Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.