Eu gostaria de 'falsificar' uma página 404 no Rails. No PHP, eu apenas enviava um cabeçalho com o código de erro da seguinte forma:
header("HTTP/1.0 404 Not Found");
Como isso é feito com o Rails?
Eu gostaria de 'falsificar' uma página 404 no Rails. No PHP, eu apenas enviava um cabeçalho com o código de erro da seguinte forma:
header("HTTP/1.0 404 Not Found");
Como isso é feito com o Rails?
Respostas:
Não faça 404 você mesmo, não há motivo para; O Rails já possui essa funcionalidade. Se você deseja mostrar uma página 404, crie um render_404
método (ou not_found
como eu o chamei) ApplicationController
assim:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Os trilhos também manipulam AbstractController::ActionNotFound
, e ActiveRecord::RecordNotFound
da mesma maneira.
Isso faz duas coisas melhor:
1) Ele usa o rescue_from
manipulador embutido do Rails para renderizar a página 404 e 2) interrompe a execução do seu código, permitindo que você faça coisas legais como:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
sem ter que escrever declarações condicionais feias.
Como bônus, também é super fácil de manusear nos testes. Por exemplo, em um teste de integração rspec:
# RSpec 1
lambda {
visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)
# RSpec 2+
expect {
get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)
E minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OU consulte mais informações do Rails render 404 não encontrado em uma ação do controlador
ActionController::RecordNotFound
é a melhor opção?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Para retornar um cabeçalho 404, basta usar a :status
opção para o método de renderização.
def action
# here the code
render :status => 404
end
Se você deseja renderizar a página 404 padrão, pode extrair o recurso em um método
def render_404
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
e chame na sua ação
def action
# here the code
render_404
end
Se você deseja que a ação renderize a página de erro e pare, basta usar uma declaração de retorno.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Lembre-se também de que o Rails recupera alguns erros do ActiveRecord, como a ActiveRecord::RecordNotFound
exibição da página de erro 404.
Isso significa que você não precisa resgatar essa ação sozinho
def show
user = User.find(params[:id])
end
User.find
gera um ActiveRecord::RecordNotFound
quando o usuário não existe. Este é um recurso muito poderoso. Veja o seguinte código
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Você pode simplificá-lo delegando ao Rails a verificação. Basta usar a versão estrondosa.
def show
user = User.find_by_email!(params[:email])
# ...
end
A resposta recém selecionada por Steven Soroka está próxima, mas não completa. O teste em si oculta o fato de que isso não está retornando um 404 verdadeiro - está retornando um status de 200 - "sucesso". A resposta original estava mais próxima, mas tentou renderizar o layout como se nenhuma falha tivesse ocorrido. Isso corrige tudo:
render :text => 'Not Found', :status => '404'
Aqui está um conjunto de testes típico para algo que espero retornar 404, usando os correspondentes RSpec e Shoulda:
describe "user view" do
before do
get :show, :id => 'nonsense'
end
it { should_not assign_to :user }
it { should respond_with :not_found }
it { should respond_with_content_type :html }
it { should_not render_template :show }
it { should_not render_with_layout }
it { should_not set_the_flash }
end
Essa paranóia saudável me permitiu identificar a incompatibilidade do tipo de conteúdo quando tudo parecia pêssego :) Verifico todos esses elementos: variáveis atribuídas, código de resposta, tipo de conteúdo de resposta, modelo renderizado, layout renderizado, mensagens em flash.
Vou pular a verificação do tipo de conteúdo em aplicativos estritamente html ... às vezes. Afinal, "um cético verifica TODAS as gavetas" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: Eu não recomendo testar coisas que estão acontecendo no controlador, ou seja, "should_raise". O que importa é a saída. Meus testes acima me permitiram tentar várias soluções, e os testes permanecem os mesmos, independentemente de a solução estar gerando uma exceção, renderização especial etc.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
parâmetro definido como true no seu environments/development.rb
arquivo. Se você gerar um erro, como descrito na solução aceita, no estadiamento / produção, você vai definitivamente ter um 404, não um 200.
Você também pode usar o arquivo de renderização:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Onde você pode optar por usar o layout ou não.
Outra opção é usar as exceções para controlá-lo:
raise ActiveRecord::RecordNotFound, "Record not found."
A resposta selecionada não funciona no Rails 3.1+, pois o manipulador de erros foi movido para um middleware (consulte a edição do github ).
Aqui está a solução que encontrei com a qual estou muito feliz.
Em ApplicationController
:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :handle_exception
end
def not_found
raise ActionController::RoutingError.new('Not Found')
end
def handle_exception(exception=nil)
if exception
logger = Logger.new(STDOUT)
logger.debug "Exception Message: #{exception.message} \n"
logger.debug "Exception Class: #{exception.class} \n"
logger.debug "Exception Backtrace: \n"
logger.debug exception.backtrace.join("\n")
if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
return render_404
else
return render_500
end
end
end
def render_404
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
e em application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
E nos meus recursos (mostrar, editar, atualizar, excluir):
@resource = Resource.find(params[:id]) or not_found
Isso certamente poderia ser melhorado, mas pelo menos, tenho visões diferentes para not_found e internal_error sem substituir as principais funções do Rails.
|| not_found
peça, basta chamar find!
(observe o estrondo) e ela lançará ActiveRecord :: RecordNotFound quando o recurso não puder ser recuperado. Além disso, adicione ActiveRecord :: RecordNotFound à matriz na condição if.
StandardError
e não Exception
, apenas por precaução. Na verdade, eu vou deixar página 500 estática padrão e não usar personalizado render_500
em tudo, o que significa que eu vou explicitamente rescue_from
série de erros relacionados a 404
estes irão ajudá-lo ...
Controlador de Aplicação
class ApplicationController < ActionController::Base
protect_from_forgery
unless Rails.application.config.consider_all_requests_local
rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
private
def render_error(status, exception)
Rails.logger.error status.to_s + " " + exception.message.to_s
Rails.logger.error exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: "errors/error_#{status}",status: status }
format.all { render nothing: true, status: status }
end
end
end
Controlador de erros
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
visualizações / erros / error_404.html.haml
.site
.services-page
.error-template
%h1
Oops!
%h2
404 Not Found
.error-details
Sorry, an error has occured, Requested page not found!
You tried to access '#{@not_found_path}', which is not a valid page.
.error-actions
%a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
%span.glyphicon.glyphicon-home
Take Me Home
<%= render file: 'public/404', status: 404, formats: [:html] %>
basta adicionar isso à página que você deseja renderizar à página de erro 404 e pronto.
Eu queria lançar um 404 'normal' para qualquer usuário conectado que não seja um administrador, então acabei escrevendo algo parecido com isso no Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
Para testar o tratamento de erros, você pode fazer algo assim:
feature ErrorHandling do
before do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
end
scenario 'renders not_found template' do
visit '/blah'
expect(page).to have_content "The page you were looking for doesn't exist."
end
end
Se você quiser lidar com 404s diferentes de maneiras diferentes, considere capturá-los em seus controladores. Isso permitirá que você faça coisas como rastrear o número de 404s gerados por diferentes grupos de usuários, tenha suporte para interagir com os usuários para descobrir o que deu errado / que parte da experiência do usuário pode precisar de ajustes, fazer testes A / B etc.
Coloquei aqui a lógica básica no ApplicationController, mas também pode ser colocada em controladores mais específicos, para ter lógica especial apenas para um controlador.
O motivo pelo qual estou usando um if com ENV ['RESCUE_404'] é para poder testar o aumento de AR :: RecordNotFound isoladamente. Nos testes, eu posso definir esse ENV var como false e meu rescue_from não dispara. Dessa forma, posso testar o aumento separado da lógica 404 condicional.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']
private
def conditional_404_redirect
track_404(@current_user)
if @current_user.present?
redirect_to_user_home
else
redirect_to_front
end
end
end