1 O que um Controller faz?
ActionController é o C em MVC. Após o router
determinar qual controller usar para a requisição, o controller será responsável por entender a requisição e retornar a resposta apropriada. Por sorte, ActionController faz a maior parte do trabalho fundamental pra você e usa convenções inteligentes pra fazer esse processo ser tão intuitivo quanto possível.
Para a maior parte das aplicações RESTful,o controller receberá a requisição (o que é "invisível" a você que está desenvolvendo), busca e/ou salva dados de um model, e usa a view para criar a saída HTML. Se seu controller precisa tratar requisições um pouco diferente, isso não é um problema, este é apenas o jeito mais comum de um controller trabalhar.
Um controller pode então ser pensado como um intermediário entre um model e uma view. Isso faz com que os dados do model fiquem disponíveis para a view, para que possa ser mostrado ao usuário, e ele salva ou atualiza dados do usuário no model.
Para mais detalhes sobre processo de roteamento, veja Rails Routing from the Outside In
2 Convenção para Nomeclatura de Controllers
A convenção para nomenclatura de controllers no Rails favorece a pluralização da última palavra do nome do controller, embora não seja estritamente necessário (ex: ApplicationController
). Por exemplo, ClientsController
é recomendado ao invés de ClientController
, SiteAdminsController
é recomendado ao invés de SiteAdminController
ou SitesAdminsController
, e assim por diante.
Seguindo essa convenção será possível utilizar o gerador de rotas padrão (ex: resources
, etc) sem precisar configurar cada :path
ou :controller
, e ainda manter consistente o uso dos auxiliares de rotas em todo o seu projeto. Veja Layouts e Guia de Renderização para mais detalhes.
A convenção para nomenclatura de controllers difere da convenção para nomenclatura de models, que devem ser nomeados na forma singular.
3 Métodos e Actions
Um controller é uma classe do Ruby que herda de ApplicationController
e tem métodos como qualquer outra classe. Quando a sua aplicação recebe uma requisição, o roteamento irá determinar qual controller e qual action serão executados, e então o Rails irá criar uma instância desse controller e executará o método que possui o mesmo nome da action.
class ClientsController < ApplicationController
def new
end
end
Como exemplo, se um usuário acessar /clients/new
na sua aplicação para adicionar um novo cliente, o Rails irá criar uma instância de ClientsController
e irá chamar o método new
dele. Repare que o método vazio do exemplo acima funcionaria normalmente porque o Rails por padrão vai renderizar a view new.html.erb
a menos que a action
indique outro caminho. Ao criar um novo Client
o método new
pode tornar uma variável de instância @client
acessível na view
.
def new
@client = Client.new
end
O Guia de Layouts e Renderização explica essa etapa mais detalhadamente.
O ApplicationController
herda de ActionController::Base
, que define uma quantidade de métodos úteis. Este guia vai cobrir alguns destes métodos, mas se você tiver curiosidade para ver o que há neles, você pode ver todos eles na Documentação da API ou no próprio código fonte.
Apenas métodos públicos são executáveis como actions. É uma boa prática diminuir a visibilidade de métodos (utilizando private
ou protected
) que não foram designados para serem actions, como métodos auxiliares ou filtros.
Alguns nomes de métodos são reservados pelo Action Controller. Redefini-los acidentalmente como actions, ou mesmo como métodos auxiliares (helpers), pode resultar no erro SystemStackError
. Se você limitar seus controllers a apenas actions RESTful Resource Routing você não precisará se preocupar com isso.
Se você precisar usar um método reservado como um nome de action, uma solução alternativa é usar uma rota personalizada para mapear o nome do método reservado para o método da action não reservado.
4 Parâmetros
Você provavelmente vai querer acessar os dados enviados pelo usuário ou outros parâmetros nas actions do seu controller. Existem dois tipos de parâmetros possíveis numa aplicação web. O primeiro são os parâmetros que são enviados como parte da URL, chamados parâmetros de query string. A query string é tudo o que vem após o "?" na URL. O segundo tipo de parâmetro é geralmente referido como os dados de POST. Essa informação geralmente vem de um formulário HTML que foi preenchido pelo usuário. Se chamam dados de POST porque estes dados somente podem ser enviados como parte de uma requisição HTTP usando o verbo POST. O Rails não faz distinção sobre parâmetros de query string e parâmetros de POST, ambos são acessíveis por meio do hash params
no seu controller:
class ClientsController < ApplicationController
# Essa action usa parâmetros de query string porque ela é
# executada através de uma requisição HTTP GET, mas isso
# não faz nenhuma diferença como os parâmetros
# são acessados. A URL para essa action seria desse jeito
# para mostrar os clientes ativos: /clients?status=activated
def index
if params[:status] == "activated"
@clients = Client.activated
else
@clients = Client.inactivated
end
end
# Essa action usa parâmetros de POST. Eles provavelmente estão
# vindo de um formulário HTML que o usuário submeteu. A URL
# para essa requisição RESTful será "/clients", e os dados
# serão enviados como parte do corpo da requisição.
def create
@client = Client.new(params[:client])
if @client.save
redirect_to @client
else
# Essa linha sobrescreve o método padrão de renderização,
# que seria chamado para renderizar a *view* "*create*"
render "new"
end
end
end
4.1 Hash e Parâmetros de Array
O hash params
não é limitado a um vetor unidimensional de chaves e valores. Ele pode conter arrays e hashes aninhados. Para enviar um array de valores, concatene um par de colchetes vazio "[]" ao nome da chave:
GET /clients?ids[]=1&ids[]=2&ids[]=3
A URL efetiva neste neste exemplo será codificada como "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3", visto que os caracteres "[" e "]" não são permitidos em URLs. Na maioria do tempo você não precisa se preocupar com isso porque o navegador irá codificar os dados para você, e o Rails vai decodificá-los automaticamente, porém se por acaso você se encontrar na situação de ter que enviar este tipo de requisição ao servidor manualmente você deve ter em mente essa questão.
O valor de params[:ids]
será neste caso ["1", "2", "3"]
. Note que os valores de parâmetros são sempre strings; o Rails não tenta adivinhar ou converter o tipo.
Valores como [nil]
ou [nil, nil, ...]
em params
são substituídos por []
por motivos de segurança por padrão. Veja o Guia de Segurança para mais informações.
Para enviar um hash, você inclui o nome da chave dentro dos colchetes:
<form accept-charset="UTF-8" action="/clients" method="post">
<input type="text" name="client[name]" value="Acme" />
<input type="text" name="client[phone]" value="12345" />
<input type="text" name="client[address][postcode]" value="12345" />
<input type="text" name="client[address][city]" value="Carrot City" />
</form>
Quando esse formulário é enviado o valor de params[:client]
será { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }
. Repare o hash aninhado em params[:client][:address]
.
O objeto params
atua como um hash, mas permite que você use symbols e strings indistintamente como chaves.
4.2 Parâmetros JSON
Se você está construindo uma aplicação web, você pode achar mais confortável receber parâmetros no formato JSON. Se o header "Content-Type" da sua requisição estiver definido como "application/json" o Rails vai automaticamente carregar os seus parâmetros no hash params
, que você pode acessar como acessaria normalmente.
Então por exemplo, se você estiver enviando este conteúdo JSON:
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
O seu controller vai receber params[:company]
no formato { "name" => "acme", "address" => "123 Carrot Street" }
.
Além disso, se você tiver ativado config.wrap_parameters
no seu inicializador ou chamado wrap_parameters
no seu controller, você pode omitir o elemento raiz no seu parâmetro JSON. Neste caso, os parâmetros serão clonados e enpacotados sob uma chave baseada no nome do seu controller. Então a requisição JSON acima pode ser escrita como:
{ "name": "acme", "address": "123 Carrot Street" }
E, assumindo que você está enviando os dados para CompaniesController
, eles serão então encapsulados na chave :company
desta maneira:
{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }
Você pode customizar o nome da chave ou parâmetros específicos que você quer envelopar consultando a documentação da API
Suporte para interpretar parâmetros XML foi extraído para uma gem chamada actionpack-xml_parser
.
4.3 Parâmetros de Rota
O hash params
sempre irá conter as chaves :controller
e :action
, mas você deve usar os métodos controller_name
e action_name
para acessar estes valores. Quaisquer outros parâmetros definidos pela rota, como :id
, também estarão disponíveis. Por exemplo, considere uma listagem de clientes onde a lista pode mostrar os clientes ativos e inativos. Nós podemos adicionar uma rota que captura o parâmetro :status
numa URL "normalizada":
get '/clients/:status', to: 'clients#index', foo: 'bar'
Neste caso, quando um usuário abrir a URL /clients/active
, params[:status]
estará definido como "active". Quando esta rota é usada, params[:foo]
também será definido como "bar", como se tivesse sido enviado por meio da query string. O seu controller também irá receber params[:action]
com o valor "index" e params[:controller]
com o valor "clients".
4.4 default_url_options
Você pode determinar parâmetros padrão globais para a geração de URLs definindo um método chamado default_url_options
no seu controller. Este método deve retornar um hash com os dados padrão desejados, cujas chaves devem ser símbolos:
class ApplicationController < ActionController::Base
def default_url_options
{ locale: I18n.locale }
end
end
Estas opções serão usadas como um ponto de partida na geração de URLs, então é possível que elas sejam sobrescritas pelas opções passadas para chamadas a url_for
.
Se você definir default_url_options
em ApplicationController
, como no exemplo acima, estes padrões irão ser usados para todas as gerações de URL. O método pode também ser definido num controller específico, neste caso afetando somente as URLs geradas a partir desse escopo.
Numa requisição o método não é de fato chamado para toda URL gerada. Por questões de performance o hash retornado é cacheado, e há no máximo uma invocação por requisição.
4.5 Parâmetros Fortes
Com parâmetros fortes (strong parameters), os parâmetros do Action Controller são proibidos de serem usados nas atribuições em massa no Active Model até que sejam deliberadamente permitidos. Isso significa que você tem que tomar uma decisão consciente sobre quais atributos podem ser permitidos para um update em massa. Esta é uma prática mais segura para ajudar a prevenir que acidentalmente os usuários atualizem atributos sensíveis do model.
Além disso, os parâmetros podem ser marcados como obrigatórios e irão seguir por um fluxo de erro e tratamento predefinido que irá resultar num código 400 Bad Request sendo retornado caso todos os parâmetros obrigatórios não forem informados.
class PeopleController < ActionController::Base
# Isso vai lançar uma exceção do tipo ActiveModel::ForbiddenAttributesError
# porque está usando atribuição em massa sem passar pela etapa de permitir
# explicitamente os parâmetros.
def create
Person.create(params[:person])
end
# Isso irá passar contanto que exista uma chave de *person* nos parâmetros,
# caso contrário o código irá lançar uma exceção do tipo
# ActionController::ParameterMissing, que será capturada pelo
# ActionController::Base e transformada num erro 400 Bad Request.
def update
person = current_account.people.find(params[:id])
person.update!(person_params)
redirect_to person
end
private
# Usar um método privado para encapsular os parâmetros permissíveis
# é um bom padrão visto que que você poderá reusar a mesma lista
# para as actions create e update. Você também pode especificar
# este método com a checagem de atributos permitidos de acordo com
# cada usuário.
def person_params
params.require(:person).permit(:name, :age)
end
end
4.5.1 Valores Escalares Permitidos
Chamando permit
como:
params.permit(:id)
permite a chave especificada (:id
) para inclusão se ela aparecer em params
e ela tiver um valor escalar permitido associado a ela. Caso contrário a chave será filtrada, então arrays, hashes, ou quaisquer outros objetos não poderão ser adicionados.
Os tipos escalares permitidos são String
, Symbol
, NilClass
, Numeric
, TrueClass
, FalseClass
, Date
, Time
, DateTime
, StringIO
, IO
, ActionDispatch::Http::UploadedFile
, e Rack::Test::UploadedFile
.
Para declarar que o valor em params
deve ser um array de valores escalares permitidos, mapeie a chave para um array vazio.
params.permit(id: [])
Às vezes não é possível ou conveniente declarar as chaves válidas de um parâmetro de hash ou sua estrutura interna. Apenas mapeie para um hash vazio:
params.permit(preferences: {})
entretanto fique atento porque isso abre a porta para input arbitrário. Neste caso, permit
garante que os valores na estrutura retornada são valores escalares permitidos e faz a filtragem de tudo o que houver além deles.
Para permitir um hash completo de parâmetros, o método permit!
pode ser usado:
params.require(:log_entry).permit!
Este código marca o hash de parâmetros :log_entry
e qualquer sub-hash dele como valores permitidos e não verifica por escalares permitidos, sendo qualquer coisa a partir dele aceita.
Extremo cuidado deve ser considerado ao usar o método permit!
, visto que ele irá permitir que todos os atuais e futuros atributos do model
sejam preenchidos em massa.
4.5.2 Parâmetros Aninhados
Você também pode usar permit
em parâmetros aninhados, da seguinte forma:
params.permit(:name, { emails: [] },
friends: [ :name,
{ family: [ :name ], hobbies: [] }])
Esta declaração permite o preenchimento dos atributos name
, emails
, e friends
. É esperado que emails
seja um array de valores permitidos escalares, e que friends
seja um array de recursos com atributos específicos: deve possuir um atributo name
(com quaisquer valores escalares permitidos), um atributo hobbies
como um array de valores permitidos escalares, e um atributo family
que é restrito a ter um name
(com qualquer valor escalar permitido também).
4.5.3 Mais Exemplos
Você pode também querer usar os atributos permitidos na sua action new
. Isso traz o problema que você não pode chamar require
na chave raiz porque normalmente ela não existe no momento da chamada de new
# usando fetch você pode fornecer um valor padrão e visualizar
# a API de Parâmetros Fortes a partir dele.
params.fetch(:blog, {}).permit(:title, :author)
O método da classe model accepts_nested_attributes_for
te permite atualizar e destruir outros models associados. Isso é baseado nos parâmetros id
e _destroy
:
# permite :id e :_destroy
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])
Hashes com chaves de valor do tipo inteiro são tratados de maneira diferente, e você pode declarar os atributos como se eles fossem atributos filhos imediatos. Você obtém estes tipos de parâmetros quando você usa accepts_nested_attributes_for
combinado com uma associação has_many
:
# Para permitir os seguintes dados:
# {"book" => {"title" => "Some Book",
# "chapters_attributes" => { "1" => {"title" => "First Chapter"},
# "2" => {"title" => "Second Chapter"}}}}
params.require(:book).permit(:title, chapters_attributes: [:title])
Imagine um cenário onde você tem parâmetros representando um nome de produto e um hash de dados arbitrários associado a esse produto, e você queira permitir o preenchimento do atributo de nome do produto e também o hash de dados:
def product_params
params.require(:product).permit(:name, data: {})
end
4.5.4 Fora do Escopo de Parâmetros Fortes
A API de parâmetros fortes foi desenhada com os casos mais comuns em mente. Não houve a intenção de torná-la uma bala prateada para lidar com todos os seus problemas de filtragem de parâmetros. Entretanto, você pode facilmente misturar a API com seu próprio código para se adaptar à sua situação.
5 Sessão
Sua aplicação possui uma sessão para cada usuário, na qual pode-se armazenar quantidades pequenas de dados que serão persistidos entre as requisições. A sessão fica disponível apenas no controller e na view e pode utilizar um dentre vários mecanismos diferentes de armazenamento:
-
ActionDispatch::Session::CookieStore
- Armazena tudo no cliente. -
ActionDispatch::Session::CacheStore
- Armazena os dados no cache do Rails. -
ActionDispatch::Session::ActiveRecordStore
- Armazena os dados em um banco de dados utilizando o Active Record. (a gemactiverecord-session_store
é necessária). -
ActionDispatch::Session::MemCacheStore
- Armazena os dados em um cluster de cache de memória (esta é uma implementação legada; considere utilizar oCacheStore
como alternativa)
Todos os armazenamentos de sessão utilizam um cookie para armazenar um ID único para cada sessão (você deve utilizar um cookie, o Rails não permitirá que você passe o ID da sessão na URL, pois isso é menos seguro).
Para a maioria dos armazenamentos, esse ID é utilizado para procurar os dados da sessão no servidor, por exemplo, em uma tabela do banco de dados. Há apenas uma exceção, que é o armazenamento de sessão recomendado por padrão - o CookieStore - que armazena todos os dados da sessão no próprio cookie (o ID ainda estará disponível para você, se você precisar). A vantagem é a de ser muito leve e requer zero configuração em uma nova aplicação para utilizar a sessão. Os dados do cookie são assinados criptograficamente para torná-los invioláveis, e também é criptografado para que qualquer pessoa com acesso não leia o seu conteúdo. (O Rails não aceitará se estiver sido editado).
O CookieStore pode armazenar cerca de 4 kB de dados - muito menos que os demais - mas geralmente é o suficiente. O armazenamento de grandes quantidades de dados na sessão não é recomendado, independentemente de qual armazenamento de sessão sua aplicação utiliza. Você deve evitar armazenar objetos complexos (como instâncias de model
) na sessão, pois o servidor pode não ser capaz de remontá-los entre as requisições, o que resultará em um erro.
Se as suas sessões de usuário não armazenam dados críticos ou não precisam durar por longos períodos (por exemplo, se você apenas utiliza o flash para mensagens), considere o uso do ActionDispatch::Session::CacheStore
. Isso armazenará as sessões utilizando a implementação de cache que você configurou para a sua aplicação. A vantagem é que você pode utilizar sua infraestrutura de cache existente para armazenar sessões sem precisar de nenhuma configuração ou administração adicional. A desvantagem é que as sessões serão temporárias e poderão desaparecer a qualquer momento.
Leia mais sobre armazenamento de sessão no Guia de Segurança.
Se você precisar de um mecanismo diferente de sessão de armazenamento, você poderá alterá-lo no initializer:
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails g active_record:session_migration")
# Rails.application.config.session_store :active_record_store
O Rails configura uma chave de sessão (o nome do cookie) ao assinar os dados da sessão. Estes também podem ser alterados no initializer:
# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_your_app_session'
Você também pode passar uma chave :domain
e especificar o nome do domínio para o cookie:
# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
O Rails configura (para o CookieStore) uma chave secreta utilizada para assinar os dados da sessão em config/credentials.yml.enc
. Isso pode ser alterado com o comando bin/rails credentials:edit
.
# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 492f...
Alterar a secret_key_base
ao utilizar o CookieStore
invalidará todas as sessões existentes.
5.1 Acessando a Sessão
No seu controller, você pode acessar a sessão através do método de instância session
.
As sessões são lazy loading (carregamento lento). Se você não acessá-las no código da sua action, elas não serão carregadas. Portanto, você nunca precisará desativar as sessões, basta apenas não acessá-las.
Os valores da sessão são armazenados utilizando pares de chave/valor como em um hash:
class ApplicationController < ActionController::Base
private
# Finds the User with the ID stored in the session with the key
# :current_user_id This is a common way to handle user login in
# a Rails application; logging in sets the session value and
# logging out removes it.
def current_user
@_current_user ||= session[:current_user_id] &&
User.find_by(id: session[:current_user_id])
end
end
Para armazenar algo na sessão, basta atribuí-lo à chave como em um hash:
class LoginsController < ApplicationController
# "Create" a login, aka "log the user in"
def create
if user = User.authenticate(params[:username], params[:password])
# Save the user ID in the session so it can be used in
# subsequent requests
session[:current_user_id] = user.id
redirect_to root_url
end
end
end
Para remover algo da sessão, exclua o par de chave/valor:
class LoginsController < ApplicationController
# "Delete" a login, aka "log the user out"
def destroy
# Remove the user id from the session
session.delete(:current_user_id)
# Clear the memoized current user
@_current_user = nil
redirect_to root_url, status: :see_other
end
end
Para redefinir a sessão inteira, utilize reset_session
.
5.2 O Flash
O flash é uma parte especial da sessão, que é limpo a cada requisição. Isso significa que os valores armazenados nele estarão disponíveis somente para a próxima requisição, sendo úteis para enviar mensagens de erro, etc.
O flash é acessado através do método flash
. Assim como a sessão, o flash é representado por um objeto hash.
Usaremos o evento de logout do usuário como exemplo. O controller pode enviar uma mensagem que será exibida na próxima requisição:
class LoginsController < ApplicationController
def destroy
session.delete(:current_user_id)
flash[:notice] = "Você foi deslogado com sucesso."
redirect_to root_url, status: :see_other
end
end
Observe que também é possível definir uma mensagem flash como parte do redirecionamento. Você pode usar :notice
, :alert
ou o mais genérico, :flash
:
redirect_to root_url, notice: "Você foi deslogado com sucesso."
redirect_to root_url, alert: "Você está preso aqui!"
redirect_to root_url, flash: { referral_code: 1234 }
A action destroy
redireciona para a root_url
da aplicação, onde a mensagem será exibida. Note que é responsabilidade da próxima action decidir o que (e até mesmo se algo) será feito com o valor anterior contido no flash. É comum exibir quaisquer erros, alertas ou avisos vindos do flash no layout da aplicação:
<html>
<!-- <head/> -->
<body>
<% flash.each do |name, msg| -%>
<%= content_tag :div, msg, class: name %>
<% end -%>
<!-- resto do conteúdo -->
</body>
</html>
Dessa forma, se uma action gerar um aviso, alerta ou mensagem, o layout o exibirá automaticamente.
É possível armazenar qualquer valor aceito pela session no flash, não ficando limitado somente aos avisos, alertas e mensagens:
<% if flash[:just_signed_up] %>
<p class="welcome">Welcome to our site!</p>
<% end %>
Se quiser que um flash seja acessado em uma outra requisição, use flash.keep
:
class MainController < ApplicationController
# Digamos que essa action corresponde à root_url, mas você quer que
# todas as requisições sejam redirecionadas para UsersController#index.
# Se uma action definir o flash e for redirecionada para cá, os valores
# seriam perdidos quando um ocorrer um outro redirecionamento.
# Para persistir os valores e evitar que isso ocorra, utilize 'keep'.
def index
# Persistirá os valores.
flash.keep
# Usando uma chave é possível persistir somente um tipo de valor:
# flash.keep(:notice)
redirect_to users_url
end
end
5.2.1 flash.now
Por padrão, adicionar valores ao flash os tornará disponíveis somente na próxima requisição, mas em alguns casos, você pode querer acessar estes valores em uma mesma requisição. Por exemplo, se a action create
falhar ao salvar um recurso e você renderizar o template new
diretamente, isso não resultará em uma nova requisição, mas você ainda pode querer exibir a mensagem usando o flash. Para isso, é possível usar o flash.now
, de maneira similar ao flash
simples:
class ClientsController < ApplicationController
def create
@client = Client.new(client_params)
if @client.save
# ...
else
flash.now[:error] = "Não foi possível salvar o cliente."
render action: "new"
end
end
end
6 Cookies
Sua Aplicação pode armazemar pequenas quantidades de dados no cliente - chamados de cookies - que serão mantidas entre requisições e até as sessões. O Rails fornece um fácil acesso para os cookies através do método cookies
, que - assim como a session
- funciona como um hash:
class CommentsController < ApplicationController
def new
# Preencher automaticamente o nome de quem comentou se ele estiver armazenado em um cookie
@comment = Comment.new(author: cookies[:commenter_name])
end
def create
@comment = Comment.new(comment_params)
if @comment.save
flash[:notice] = "Thanks for your comment!"
if params[:remember_name]
# Lembrar o nome de quem fez o comentário
cookies[:commenter_name] = @comment.author
else
# Deletar o cookie do nome de quem fez o comentário, caso exista.
cookies.delete(:commenter_name)
end
redirect_to @comment.article
else
render action: "new"
end
end
end
Perceba que enquanto para valores de sessão você pode definir a chave como nil
, para deletar um valor de cookie você deve usar cookies.delete(:key)
.
O Rails também fornece um cookie jar assinado e um cookie jar criptografado para amazenar dados sensíveis. O cookie jar assinado anexa uma assinaura criptográfica nos valores do cookie para proteger sua integridade. O cookie jar criptografado, criptografa os valores além de assiná-los, para que eles não possam ser lidos pelo usuário final. Consulte a documentação da API (em inglês) para mais detalhes.
Esses cookie jars especiais usam um serializer para serializar os valores atribuídos em strings e desserializa-os em objetos Ruby na leitura.
Você pode especificar qual serializer usar:
Rails.application.config.action_dispatch.cookies_serializer = :json
O serializer padrão para novas aplicações é :json
. Para compatibilidade com
aplicações antigas que usam cookies, o :marshal
é usado quando a opção
serializer
não está especificada.
Você também pode definir esta opção como :hybrid
, nesse caso o Rails desserializaria
de forma transparente os cookies (serializados no formato Marshal
) existentes ao ler e reescrevê-los
no formaro JSON
. Isso é útil para migrar aplicações existentes para o
serializer :json
.
Também é possível passar um serializer personalizado que responda a load
e
dump
:
Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer
Ao usar o serializer :json
ou :hybrid
, lembre-se de que nem todos os
os objetos Ruby podem ser serializados como JSON. Por exemplo, objetos Date
eTime
serão serializados como strings, e os Hash
es terão suas chaves transformadas em string também.
class CookiesController < ApplicationController
def set_cookie
cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
redirect_to action: 'read_cookie'
end
def read_cookie
cookies.encrypted[:expiration_date] # => "2014-03-20"
end
end
É aconselhável que você armazene apenas dados simples (strings e números) nos cookies. Se você precisar armazenar objetos complexos, precisará lidar com a conversão manualmente ao ler os valores em requisições subsequentes.
Se você usar o cookie de armazenamento de sessão, isso também se aplicaria aos hashes session
e flash
.
7 Renderizando Dados XML e JSON
O ActionController faz com que renderizar dados XML
ou JSON
seja extremamente fácil. Se você gerou um controller usando o scaffold, será algo mais ou menos assim:
class UsersController < ApplicationController
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render xml: @users }
format.json { render json: @users }
end
end
end
Você pode observar que no código acima estamos usando render xml: @users
, e não render xml: @users.to_xml
. Se o objeto não é uma String, então o Rails automaticamente chama to_xml
por nós.
8 Filtros
Filtros são métodos que rodam "before" (antes de), "after" (depois de) ou "around" (em torno de) de uma ação de controller.
Filtros são herdados, então se você configurou um filtro em ApplicationController
, o mesmo irá rodar em cada controller da sua aplicação.
Filtros "before" são registrados através do método before_action
. Eles podem interromper o ciclo de uma requisição. Um filtro comum para "before" é o que requer que um usuário está logado para que uma ação seja executada. Você pode definir o método do filtro dessa forma:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # interrompe o ciclo da requisição
end
end
end
Esse método simplesmente armazena uma mensagem de erro no flash e redireciona para o formulário de login se o usuário não estiver logado. Se um filtro "before" renderiza ou redireciona, a ação não será executada. Se filtros adicionais estão programados para executar após esse filtro, eles são cancelados também.
Nesse exemplo, o filtro é adicionado ao ApplicationController
e dessa forma todos os controllers na aplicação irão herdar ele. Isso fará com que tudo na aplicação requeira que o usuário esteja logado para que ele possa usar. Por razões óbvias (o usuário não conseguiria fazer o log in para começo de conversa!), nem todos os controllers devem requerer isso. Você pode evitar esse filtro de ser executado antes de ações em particular com skip_before_action
:
class LoginsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
end
Agora, as ações de new
e create
do LoginsController
irão funcionar como antes sem requerer que o usuário esteja logado. A opção :only
é usada para pular esse filtro somente para essas ações, e existe também a opção :except
que funciona de maneira contrária. Essas opções podem ser utilizadas quando adicionamos filtros também, para que você possa adicionar um filtro que somente executa para as ações selecionadas.
Chamar o mesmo filtro múltiplas vezes com diferentes opções não irá funcionar, já que a última definição do filtro irá sobreescrever as anteriores.
8.1 Filtros after e around
Além de filtros "before", você pode também executar filtros depois que uma ação tenha sido executada, ou antes e depois em conjunto.
Filtros "after" são registrados através do método after_action
. Eles são similares aos filtros "before", mas porque a ação já foi executada eles tem acesso a dados da resposta que serão enviados para o cliente. Obviamente, filtros "after" não podem impedir uma ação de ser executada. Note também que filtros "after" são executados somente após uma ação bem sucedida, mas não quando uma exceção é gerada durante o ciclo de uma requisição.
Filtros "around" são registrados através do método around_action
. Eles são responsáveis por executar as ações associadas por yield, simular a como os middlewares do Rack funcionam.
Por exemplo, em um website aonde alterações possuem um fluxo de aprovação, um administrador pode pré-visualizar as mesmas facilmente, aplicando-as dentro de uma transação.
class ChangesController < ApplicationController
around_action :wrap_in_transaction, only: :show
private
def wrap_in_transaction
ActiveRecord::Base.transaction do
begin
yield
ensure
raise ActiveRecord::Rollback
end
end
end
end
Note que um filtro "around" também envolve a renderização. Em particular, no exemplo acima, se a view efetuar uma leitura no banco de dados (p. ex. usando um scope), a mesma é efetuada dentro de uma transação e então apresenta a informação para visualização.
Você pode escolher não efetuar yield e montar a resposta você mesmo, o que faria com que a ação não fosse executada.
8.2 Outras Formas de Usar Filtros
Enquanto a forma mais comum de se utilizar filtros é criando métodos privados e usando before_action
, after_action
, ou around_action
para adicioná-los, existem duas outras formas para fazer a mesma coisa.
A primeira é utilizar um bloco diretamente com método *_action
. O bloco recebe o controller como um argumento. O filtro require_login
acima pode ser reescrito para utilizar um bloco:
class ApplicationController < ActionController::Base
before_action do |controller|
unless controller.send(:logged_in?)
flash[:error] = "Você deve estar logado para acessar essa seção"
redirect_to new_login_url
end
end
end
Note que, nesse caso, o filtro utiliza send
porque o método logged_in?
é privado e o filtro não é executado no escopo do controller. Essa não é a forma recomendada para implementar esse filtro em particular, mas em casos mais simples, ele pode ser útil.
Especificamente para around_action
, o bloco também acessa a action
:
around_action { |_controller, action| time(&action) }
A segunda forma é utilizar uma classe (na verdade, qualquer objeto que responda aos métodos corretos serve) para gerenciar a filtragem. Isto é útil em casos mais complexos que não são possíveis de serem implementados de uma forma de fácil leitura e reutilizados usando as outras duas abordagens. Por exemplo, você pode reescrever o filtro de login novamente utilizando uma classe:
class ApplicationController < ActionController::Base
before_action LoginFilter
end
class LoginFilter
def self.before(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "Você deve estar logado para acessar essa seção"
controller.redirect_to controller.new_login_url
end
end
end
Novamente, esse não é um exemplo ideal para esse filtro, pois não é executado dentro do escopo do controller mas recebe o mesmo como um argumento. A classe de filtro deve implementar um método com o mesmo nome do filtro, então para o filtro de before_action
a classe deve implementar um método before
, e assim em diante. O método around
deve efetuar yield
para executar a ação.
9 Proteção de falsificação de requisição
Falsificação de requisições cross-site é um tipo de ataque no qual o site engana o usuário a fim de que ele faça requisições em outro site, possivelmente adicionando, alterando ou deletando informações naquela site sem o conhecimento ou a permissão do usuário.
O primeiro passo para evitar isso é ter certeza que todas as ações "destrutivas" (criar, atualizar, e destruir) possam ser acessadas somente via requisições que não sejam GET. Se você está seguindo as convenções RESTful você já está fazendo isso. Contudo, sites maliciosos continuam podendo enviar requisições não GET para o seu site facilmente, e é aí que a proteção de falsificação de requisição entra. Como o nome diz, ela te protege de requisições falsas.
A forma como isso é feito é adicionando um token não adivinhável que é conhecido apenas pelo seu servidor para cada requisição. Desta forma, se uma requisição chega sem um token conhecido, o seu acesso será negado.
Se você gera um form como este:
<%= form_with model: @user do |form| %>
<%= form.text_field :username %>
<%= form.text_field :password %>
<% end %>
Você perceberá como o token é adicionado como um campo invisível.
<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
value="67250ab105eb5ad10851c00a5621854a23af5489"
name="authenticity_token"/>
<!-- fields -->
</form>
O Rails adiciona esse token para cada form que é gerado usando o form helpers, então na maior parte das vezes você não precisa se preocupar com isso. Se você está escrevendo um form manualmente ou precisa adicionar o token para outra sessão, ele está disponível por meio do método form_authenticity_token
.
O form_authenticity_token
gera um token de autenticação válido. Isso é útil em lugar aonde o Rails não adiciona o mesmo automaticamente, como em chamadas Ajax personalizadas.
O Guia de segurança possui mais informações sobre isso e muitos outros problemas relacionados a segurança que você deve estar ciente quando desenvolve uma aplicação web.
10 Os Objetos de Requisição e Resposta
Em todo controller, existem dois métodos de acesso apontando para os objetos de requisição e de resposta associados com o ciclo de requisição que estiver em execução no momento. O método request
contém uma instância de ActionDispatch::Request
e o método response
retorna um objeto de resposta representando o que será enviado de volta ao cliente.
10.1 O Objeto request
O objeto de requisição contém muitas informações úteis sobre a requisição proveniente do cliente. Para obter uma lista completa dos métodos disponíveis verifique a documentação da API do Rails e a documentação do Rack. Entre as propriedades que você pode acessar estão:
Propriedade de request
|
Propósito |
---|---|
host |
O hostname utilizado para esta requisição. |
domain(n=2) |
Os primeiros n segmentos do hostname, iniciando pela direita (o domínio de primeiro nível). |
format |
O tipo de conteúdo requisitado pelo cliente. |
method |
O método HTTP utilizado para a requisição. |
get? , post? , patch? , put? , delete? , head?
|
Retorna true se o método HTTP é GET/POST/PATCH/PUT/DELETE/HEAD. |
headers |
Retorna um hash contendo os headers associados com a requisição. |
port |
O número (integer) da porta utilizada para a requisição. |
protocol |
Retorna uma string contendo o protocolo utilizado, além do trecho "://". Por exemplo: "http://". |
query_string |
A query string da URL (todo o trecho após "?"). |
remote_ip |
O endereço IP do cliente. |
url |
A URL completa utilizada para a requisição. |
10.1.1 path_parameters
, query_parameters
, e request_parameters
O Rails armazena todos os parâmetros enviados com a requisição no hash params
, não importando se eles foram enviados como parte da query string ou no corpo da requisição. O objeto de requisição tem três métodos de acesso que te fornecem acesso a estes parâmetros dependendo de onde eles vieram. O hash query_parameters
contem os parâmetros que foram enviados por meio da query_string enquanto o hash request_parameters
contém os parâmetros enviados através do corpo da requisição. O hash path_parameters
contém os parâmetros que foram reconhecidos pelo roteamento como parte do caminho que leva ao controller e action sendo executados.
10.2 O Objeto response
O objeto de resposta geralmente não é usado diretamente, mas é construído durante a execução da action e renderização dos dados que serão enviados de volta ao usuário, porém às vezes - como num filtro posterior - ele pode ser útil para acessar a resposta diretamente. Alguns destes métodos de acesso também possuem setters, lhe permitindo mudar seus valores. Para obter uma lista completa dos métodos disponíveis verifique a documentação da API do Rails e a documentação do Rack;
Propriedade de response
|
Propósito |
---|---|
body |
Esta é a string de dados sendo enviada de volta ao usuário. Na maioria dos casos se trata de código HTML. |
status |
O código de status HTTP para a resposta, como um código 200 para uma requisição bem sucedida ou 404 para um arquivo não encontrado. |
location |
A URL que o cliente estiver sendo redirecionado para, caso haja alguma. |
content_type |
O tipo de conteúdo da resposta. |
charset |
O conjunto de caracteres sendo utilizado na resposta. O valor padrão é "utf-8". |
headers |
Headers utilizados para a resposta. |
10.2.1 Definindo Headers customizados
Se você quer definir headers customizados para uma resposta então response.headers
é o local indicado para ajustar isto. O atributo headers é um hash que mapeia os nomes dos headers para seus respectivos valores, e o Rails irá definir alguns deles automaticamente. Se você quiser adicionar ou modificar um header, basta sinalizá-lo para response.headers
da seguinte maneira:
response.headers["Content-Type"] = "application/pdf"
No caso acima faria mais sentido utilizar o setter content_type
diretamente.
11 Autenticações HTTP
O Rails vem com três mecanismos de autenticação HTTP embutidos:
- Autenticação Basic
- Autenticação Digest
- Autenticação Token
11.1 Autenticação HTTP Basic
Autenticação HTTP basic é um esquema de autenticação que é suportado pela maioria dos navegadores e outros clientes HTTP. Como um exemplo, considere uma página de administração que será acessável apenas informando um nome de usuário e uma senha na janela de autenticação HTTP basic do navegador. Usar a autenticação embutida é bem fácil e apenas requer que você use um método, http_basic_authenticate_with
.
class AdminsController < ApplicationController
http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end
Com isso, você pode criar controllers com namespaces que herdam de AdminsController
. O filtro vai, assim, ser executado para todas as ações nos controllers, protegendo-os com a autenticação HTTP basic.
11.2 Autenticação HTTP Digest
A autenticação HTTP digest é superior à autenticação basic porque ela não requer que o cliente envie uma senha sem criptografia pela rede (embora a autenticação HTTP basic seja segura via HTTPS). Usar a autenticação digest com Rails é bem fácil e requer apenas o uso de um método, authenticate_or_request_with_http_digest
.
class AdminsController < ApplicationController
USERS = { "lifo" => "world" }
before_action :authenticate
private
def authenticate
authenticate_or_request_with_http_digest do |username|
USERS[username]
end
end
end
Como visto no exemplo acima, o bloco authenticate_or_request_with_http_digest
recebe apenas um argumento - o nome de usuário. E o bloco retorna a senha. Retornar false
ou nil
em authenticate_or_request_with_http_digest
causará falha de autenticação.
11.3 Autenticação HTTP Token
A autenticação de token HTTP é um esquema para habilitar o uso de tokens Bearer no header HTTP Authorization
. Existem muitos formatos de token disponíveis e sua descrição está fora do escopo desta documentação.
Por exemplo, suponha que você queira usar um token de autenticação emitido com antecedência para realizar a autenticação e o acesso. Implementar autenticação via token com Rails é bastante fácil e requer apenas o uso de um método, authenticate_or_request_with_http_token
.
class PostsController < ApplicationController
TOKEN = "secret"
before_action :authenticate
private
def authenticate
authenticate_or_request_with_http_token do |token, options|
ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
end
end
end
Como visto no exemplo acima, o bloco authenticate_or_request_with_http_token
recebe dois argumentos: o token e umHash
contendo as opções que foram analisadas a partir do header HTTP Authorization
. O bloco deve retornar true
se a autenticação for bem-sucedida. Retornar false
ounil
causará uma falha de autenticação.
12 Streaming e Downloads de Arquivos
Às vezes você pode querer enviar um arquivo para o usuário ao invés de uma página HTML. Todos os controllers no Rails possuem os métodos send_data
e send_file
, que transmitem dados ao navegador. O método send_file
permite que você proveja o nome do arquivo no disco e seu conteúdo será transmitido.
Para transmitir dados para o navegador, use send_data
:
require "prawn"
class ClientsController < ApplicationController
# Gera um documento PDF com informações no navegador e
# o retorna. O usuário receberá o documento PDF como um download.
def download_pdf
client = Client.find(params[:id])
send_data generate_pdf(client),
filename: "#{client.name}.pdf",
type: "application/pdf"
end
private
def generate_pdf(client)
Prawn::Document.new do
text client.name, align: :center
text "Address: #{client.address}"
text "Email: #{client.email}"
end.render
end
end
A action download_pdf
no exemplo acima está chamando um método privado que na verdade cria o documento PDF e o retorna como uma string. Essa string será então transmitida ao navegador como um arquivo para download e o nome do arquivo será sugerido ao usuário. Às vezes, quando arquivos são transmitidos aos usuários, você pode não querer que façam o download do arquivo. Imagens, por exemplo, podem ser embutidas em páginas HTML. Para comunicar ao navegador que não deve ser feito o download do arquivo, você pode utilizar a propriedade inline
na opção :disposition
. A propriedade oposta e padrão para essa opção é "attachment".
12.1 Enviando Arquivos
Se você deseja enviar um arquivo que já existe no disco, utilize o método send_file
.
class ClientsController < ApplicationController
# Transmite um arquivo que já foi gerado e salvo no disco.
def download_pdf
client = Client.find(params[:id])
send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
filename: "#{client.name}.pdf",
type: "application/pdf")
end
end
Esse método irá ler e transmitir o arquivo 4 kb por vez, evitando carregar o arquivo inteiro em memória de uma vez. Você pode desativar a transmissão com a opção :stream
ou ajustar o tamanho do bloco com a opção :buffer_size
.
Se a opção :type
não for especificada, será presumido de acordo com a extensão especificada no :filename
. Se o tipo do conteúdo (content-type
) não estiver registrado para a extensão, será usado application/octet-stream
.
Tenha cuidado ao utilizar dados vindos do navegador ( params, cookies, etc.) para localizar um arquivo no disco, pois é um risco de segurança que pode permitir a alguém ter acesso a arquivos não permitidos.
Não é recomendado que você transmita arquivos estáticos através do Rails, você pode, ao invés disso, mantê-los em uma pasta pública no seu servidor web. É muito mais eficiente deixar os usuários baixarem os arquivos diretamente utilizando Apache ou outro servidor web, evitando que a requisição passe, sem necessidade, por todo o fluxo do Rails.
12.2 RESTful Downloads
Apesar de o método send_data
funcionar tranquilamente, se você está criando uma aplicação RESTful, geralmente não é necessário ter actions separadas para o download de arquivos. Na terminologia REST, o arquivo PDF do exemplo acima pode ser considerado apenas uma outra representação do recurso do navegador. Rails provê um jeito fácil e prático de fazer "downloads RESTful". Veja como você pode re-escrever o exemplo para que o download do PDF seja parte da action show
, sem qualquer transmissão:
class ClientsController < ApplicationController
# O usuário pode solicitar receber este recurso como HTML ou PDF.
def show
@client = Client.find(params[:id])
respond_to do |format|
format.html
format.pdf { render pdf: generate_pdf(@client) }
end
end
end
Para que este exemplo funcione, você deve adicionar o MIME type
ao Rails. Isso pode ser feito adicionando a seguinte linha ao arquivo config/initializers/mime_types.rb
:
Mime::Type.register "application/pdf", :pdf
Arquivos de configuração não são recarregados a cada requisição, então você precisa reiniciar seu servidor para que as mudanças tenham efeito.
Agora os usuários podem requerer um arquivo em PDF só adicionando ".pdf" ao final da URL:
GET /clients/1.pdf
12.3 Transmissão Ao Vivo de Dado Arbitrários
O Rails permite que você transmita mais do que apenas arquivos. Na verdade, você pode transmitir o que você desejar
como um objeto de resposta. O modulo ActionController::Live
permite
a você criar uma conexão persistente com o navegador. Utilizando esse módulo, você é capaz
de enviar dados arbitrários ao navegador sem depender de uma requisição HTTP.
12.3.1 Implementando Transmissão Ao Vivo ( Live Streaming )
Incluindo ActionController::Live
dentro do seu controller irá prover a todas as
actions do seu controller a habilidade de transmitir dados. Você pode mesclar o modulo
da seguinte forma:
class MyController < ActionController::Base
include ActionController::Live
def stream
response.headers['Content-Type'] = 'text/event-stream'
100.times {
response.stream.write "hello world\n"
sleep 1
}
ensure
response.stream.close
end
end
O código acima manterá uma conexão constante com o navegador e mandará 100 mensagens de "hello world\n"
, cada uma com um segundo de diferença.
Existem algumas coisas a serem notadas no exemplo acima. Nós precisamos
ter certeza de que a resposta da transmissão foi terminada. Esquecer de encerrar a transmissão deixará
o socket aberto pra sempre. Nós também precisamos estabelecer o tipo de conteúdo (content_type
) para text/event-stream
antes de responder a transmissão. Isso é necessário, pois headers não podem ser escritos
depois que uma resposta foi enviada (quando response.committed?
retorna um
valor truthy), que ocorre quando você escreve ou envia (commit
) a resposta de uma transmissão.
12.3.2 Exemplo de Uso
Vamos supor que você estivesse criando uma máquina de Karaokê e um usuário quer achar a letra de uma música em particular. Cada música (Song
) tem um número específico de linhas e cada linha tem um tempo (num_beats
) para terminar de ser cantada.
Se nós quiséssemos retornar as letras no estilo de Karaokê (mandar a linha só quando a linha anterior for terminada de cantar), então poderíamos usar ActionController::Live
da seguinte forma:
class LyricsController < ActionController::Base
include ActionController::Live
def show
response.headers['Content-Type'] = 'text/event-stream'
song = Song.find(params[:id])
song.each do |line|
response.stream.write line.lyrics
sleep line.num_beats
end
ensure
response.stream.close
end
end
O código acima envia a próxima linha apenas depois que a pessoa cantando completou a linha anterior.
12.3.3 Considerações da Transmissão
Transmitir dados arbitrários é uma ferramenta extremamente poderosa. Como mostrado nos exemplos anteriores, você pode escolher quando e o que enviar na resposta da transmissão. Entretanto, você deveria se atentar aos seguintes pontos:
- Cada transmissão cria uma nova thread e copia sobre as variáveis locais da thread, as variáveis da thread original. Ter muitas variáveis locais em uma thread local pode impactar negativamente na performance. Da mesma forma, um grande número de threads pode também piorar a performance.
- Deixar de encerrar a transmissão deixará o socket correspondente aberto para
sempre. Certifique-se de chamar o método
close
sempre que estiver transmitindo dados. - Os servidores WEBrick armazena todas as respostas, então apenas incluir no
controller
ActionController::Live
não irá funcionar. Você deve usar um servidor web que não armazene automaticamente as respostas.
13 Filtragem de Log
Rails mantém um arquivo de log pra cada ambiente na pasta log
. Eles são bastante úteis quando estamos depurando o que está de fato acontecendo na sua aplicação, porém você pode não querer que sejam salvos todas as informações sejam armazenadas no arquivo de log em uma aplicação ativa.
13.1 Filtrando Parâmetros
Você pode evitar que parâmetros sensíveis da requisição sejam salvos no seu arquivo de log
adicionando-os a config.filter_parameters
na configuração da aplicação.
Esses parâmetros aparecerão como [FILTERED] no arquivo de log.
config.filter_parameters << :password
Os parâmetros fornecidos serão filtrados correspondendo parcialmente a uma expressão
regular. O Rails por padrão adiciona :passw
, :secret
, :token
no initializer
apropriado (initializers/filter_parameter_logging.
) e se preocupa com parâmetros típicos da aplicação
como password
, password_confirmation
e my_token
.
13.2 Filtrando Redirecionamentos
Às vezes é desejável que sejam filtrados dos arquivos de log alguns locais sensíveis para os quais sua aplicação está redirecionando.
Você pode fazer isso utilizando uma opção de configuração config.filter_redirect
:
config.filter_redirect << 's3.amazonaws.com'
Você pode utilizar uma String, uma expressão regular ou um array com ambos.
config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]
URLs correspondentes com a expressão regular serão marcadas como '[FILTERED]'.
14 Rescue
Muito provavelmente sua aplicação irá conter bugs ou enviar exceções que precisam ser tratadas. Por exemplo, se o usuário acessar um link que não possui uma fonte no banco de dados, o Active Record enviará ActiveRecord::RecordNotFound
como exceção.
A exceção padrão do Rails apresenta a mensagem "500 Server Error" para todas as exceções. Se a requisição for feita localmente, um belo traceback e outras informações serão mostradas assim você pode verificar o que deu errado e tratar o problema. Se a requisição for remota o Rails apenas apresentará a mensagem "500 Server Error" para o usuário, ou um "404 Not Found" se houver um erro na rota ou o registro não puder ser encontrado. As vezes você pode querer customizar como esses erros são encontrados e como são apresentados ao usuário. Há diversos níveis de tratamento de excessões disponiveis em uma aplicação Rails:
14.1 Os Templates 404 e 500 Padrão
Por padrão no ambiente de produção a aplicação irá renderizar uma mensagem em um template de erro 404 ou 500. No ambiente de desenvolvimento todas as mensagens de erro são disparadas. Essas mensagens são armazenadas em templates estáticos de HTML na pasta public, em 404.html
e 500.html
respectivamente. Você pode customizar essas páginas e adicionar algumas estilizações, mas lembre-se elas são HTML estáticos; i.e. você não pode usar ERB, SCSS, CoffeeScript, ou layouts para elas.
14.2 rescue_from
Se você quiser fazer algo mais elaborado quando estiver lidando com erros, você pode usar rescue_from
, que trata as exceções de um certo tipo (ou de vários tipos) em um controller inteiro e nas subclasses.
Quando uma exceção acontece e é pega por uma diretiva rescue_from
, o objeto da exceção é passado ao handler. O handler pode ser um método ou um objeto Proc
passado com a opção :with
. Você também pode usar um bloco diretamente ao invés de um objeto Proc
.
Aqui está um exemplo de como você pode usar rescue_from
para interceptar todos os erros ActiveRecord::RecordNotFound
e fazer algo com eles.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "404 Not Found", status: 404
end
end
É claro, que este exemplo não é nada elaborado e não melhora muito a forma original de lidar com os erros, mas uma vez que você capture todas essas exceções você é livre para fazer o que quiser com elas. Por exemplo, você pode criar uma exceção personalizada para quando o usuário não tem acesso a uma parte da aplicação:
class ApplicationController < ActionController::Base
rescue_from User::NotAuthorized, with: :user_not_authorized
private
def user_not_authorized
flash[:error] = "You don't have access to this section."
redirect_back(fallback_location: root_path)
end
end
class ClientsController < ApplicationController
# Check that the user has the right authorization to access clients.
before_action :check_authorization
# Note how the actions don't have to worry about all the auth stuff.
def edit
@client = Client.find(params[:id])
end
private
# If the user is not authorized, just throw the exception.
def check_authorization
raise User::NotAuthorized unless current_user.admin?
end
end
Ao usar rescue_from
com Exception
ou StandardError
pode causar efeitos colaterais já que previne o Rails de lidar com as exceções apropriadamente. Dessa forma, não é recomendado fazer sem uma boa razão.
Quando rodando em ambiente de desenvolvimento, todos os erros
ActiveRecord::RecordNotFound
renderizam uma página 404. A não ser que você precise de uma forma especifica de tratar isso você não precisa tratar isso.
Certas exceções são tratadas apenas pela classe ApplicationController
, já que são acionadas antes do controller ser iniciado a exceção é executada.
15 Forçar o Protocolo HTTPS
Se você quiser garantir que a comunicação com seu controller seja possível apenas
via HTTPS, você deve fazer isso ativando o middleware ActionDispatch::SSL
via
config.force_ssl
na configuração do seu ambiente.
Feedback
Você é incentivado a ajudar a melhorar a qualidade deste guia.
Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.
Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.
Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.
E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na forum oficial do Ruby on Rails e nas issues do Guia em português.