Rails'te 404'e nasıl yönlendirme yapılır?


482

Rails'te 404 sayfayı 'taklit etmek' istiyorum. PHP, ben sadece böyle hata kodu ile bir başlık göndermek istiyorum:

header("HTTP/1.0 404 Not Found");

Rails ile bu nasıl yapılır?

Yanıtlar:


1049

404'ü kendiniz yaratmayın, bunun için bir sebep yok; Rails zaten bu işlevselliğe sahiptir. Bir 404 sayfası göstermek istiyorsanız, render_404(veya not_foundbenim dediğim ApplicationControllergibi ) bir yöntem oluşturun :

def not_found
  raise ActionController::RoutingError.new('Not Found')
end

Ayrıca kolları Raylar AbstractController::ActionNotFoundve ActiveRecord::RecordNotFoundaynı şekilde.

Bu iki şeyi daha iyi yapar:

1) rescue_from404 sayfasını oluşturmak için Rails yerleşik işleyicisini kullanır ve 2) kodunuzun yürütülmesini kesintiye uğratır ve aşağıdaki gibi güzel şeyler yapmanıza izin verir:

  user = User.find_by_email(params[:email]) or not_found
  user.do_something!

çirkin koşullu ifadeler yazmak zorunda kalmadan.

Bir bonus olarak, testlerde de kullanımı çok kolaydır. Örneğin, bir rspec entegrasyon testinde:

# 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)

Ve en küçüğü:

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end

VEYA daha fazla bilgi için bkz. Rails render 404 bir denetleyici eyleminden bulunamadı


3
Bunu kendiniz yapmak için bir neden var. Uygulamanız kökten tüm yolları ele geçirirse. Kötü tasarım, ancak bazen önlenemez.
— ablemike

7
Bu yaklaşım ayrıca, hiçbir kayıt bulunamazsa ActiveRecord :: RecordNotFound istisnasını yükselten ActiveRecord patlama bulucularını (find !, find_by _...!, Vb.) Kullanmanızı sağlar (rescue_from işleyicisini tetikler).
— gjvis

2
Bu benim için bir 404 değil, 500 Dahili Sunucu Hatası veriyor. Ne eksik?
— Glenn

3
ActionController::RecordNotFoundDaha iyi bir seçenek gibi görünüyor ?
— Peter Ehrlich

4
Kod harika çalıştı, ancak farklı sözdizimine sahip RSpec 2 kullandığımı fark edene kadar test yapılmadı: expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)/ via stackoverflow.com/a/1722839/993890
— ryanttb

243

HTTP 404 Durumu

Bir 404 üstbilgisi döndürmek için, yalnızca :statusrender yöntemi seçeneğini kullanın .

def action
  # here the code

  render :status => 404
end

Standart 404 sayfasını oluşturmak istiyorsanız, özelliği bir yöntemle ayıklayabilirsiniz.

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

ve onu eylemin içinde çağır

def action
  # here the code

  render_404
end

Eylemin hata sayfasını oluşturmasını ve durmasını istiyorsanız, bir return ifadesi kullanın.

def action
  render_404 and return if params[:something].blank?

  # here the code that will never be executed
end

ActiveRecord ve HTTP 404

Ayrıca Rails'in ActiveRecord::RecordNotFound404 hata sayfasını görüntüleme gibi bazı ActiveRecord hatalarını kurtardığını unutmayın .

Bu, bu eylemi kendiniz kurtarmanıza gerek olmadığı anlamına gelir

def show
  user = User.find(params[:id])
end

User.findActiveRecord::RecordNotFoundkullanıcı yokken bir yükseltir . Bu çok güçlü bir özellik. Aşağıdaki koda bakın

def show
  user = User.find_by_email(params[:email]) or raise("not found")
  # ...
end

Onaylama işlemini Rails'e atayarak basitleştirebilirsiniz. Sadece patlama sürümünü kullanın.

def show
  user = User.find_by_email!(params[:email])
  # ...
end

9
Bu çözümde büyük bir sorun var; yine de şablondaki kodu çalıştırır. Bu nedenle, basit, huzurlu bir yapınız varsa ve birisi var olmayan bir kimlik girerse, şablonunuz var olmayan nesneyi arar.
— jcalvert

5
Daha önce de belirtildiği gibi, bu doğru cevap değildir. Steven'ın deneyin.
— Pablo Marambio

Seçilen cevabı daha iyi uygulamayı yansıtacak şekilde değiştirdi. Yorumlar için teşekkürler çocuklar!
— Yuval Karmi

1
Cevabı daha fazla örnek ve ActiveRecord hakkında bir notla güncelledim.
— Simone Carletti

1
Bang sürümü kod yürütmeyi durdurur, bu yüzden IMHO daha etkili bir çözümdür.
— Gui vieira

60

Steven Soroka tarafından sunulan yeni Seçilen cevap yakın, ancak tam değil. Testin kendisi, bunun gerçek bir 404 döndürmemesi gerçeğini gizler - 200 değerini döndürüyor - "başarı". Orijinal yanıt daha yakındı, ancak hiçbir hata oluşmamış gibi düzeni oluşturmaya çalıştı. Bu her şeyi düzeltir:

render :text => 'Not Found', :status => '404'

RSpec ve Shoulda eşleştiricileri kullanarak 404'e dönmeyi umduğum bir şey için tipik bir test setim:

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

Bu sağlıklı paranoya, her şey şeftali gibi geldiğinde içerik türü uyuşmazlığını tespit etmeme izin verdi :) Tüm bu öğeleri kontrol ediyorum: atanan değişkenler, yanıt kodu, yanıt içerik türü, oluşturulan şablon, oluşturulan düzen, flaş mesajlar.

Kesinlikle html olan uygulamalarda içerik türü denetimini atlayacağım ... bazen. Sonuçta, "şüpheci TÜM çekmeceleri denetler" :)

http://dilbert.com/strips/comic/1998-01-20/

FYI: Denetleyicide olup bitenlerin test edilmesini önermiyorum, yani "gerekir". Önem verdiğiniz şey çıktı. Yukarıdaki testlerim çeşitli çözümleri denememe izin verdi ve çözümün bir istisna, özel oluşturma vb.


3
gerçekten bu cevap gibi, özellikle çıkış test ile ilgili olarak, kontrolörde çağrılan yöntemler değil…
— xentek

Raylar yerleşik 404 statü: render :text => 'Not Found', :status => :not_found.
— Lasse Bunk

1
@JaimeBellmyer - Bunun gelmez eminim değil bir konuşlandırılmış (yani evreleme / prod) ortamda olduğunuzda bir 200 döndürür. Bunu birkaç uygulamada yapıyorum ve kabul edilen çözümde açıklandığı gibi çalışıyor. Belki de bahsettiğiniz şey, büyük olasılıkla dosyanızda config.consider_all_requests_localtrue değerine ayarlanmış olduğunuz hata ayıklama ekranını oluştururken 200 değerini döndürmesidir environments/development.rb. Kabul edilen çözümde açıklandığı gibi, bir aşama / üretimde bir hata ortaya çıkarsanız, kesinlikle 200 değil, 404 alırsınız.
— Javid Jamae

18

Oluşturma dosyasını da kullanabilirsiniz:

render file: "#{Rails.root}/public/404.html", layout: false, status: 404

Düzeni kullanmayı ya da kullanmamayı seçebilirsiniz.

Başka bir seçenek, İstisnaları kontrol etmek için kullanmaktır:

raise ActiveRecord::RecordNotFound, "Record not found."

13

Hata işleyicisi bir ara katman yazılımına taşındığından seçilen yanıt Rails 3.1+ ile çalışmaz ( github sorununa bakın ).

İşte bulduğum çözüm oldukça mutlu.

İçinde 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

ve içinde application.rb:

config.after_initialize do |app|
  app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end

Ve kaynaklarımda (göster, düzenle, güncelle, sil):

@resource = Resource.find(params[:id]) or not_found

Bu kesinlikle geliştirilebilir, ancak en azından çekirdek Rails işlevlerini geçersiz kılmadan not_found ve internal_error için farklı görünümlerim var.


3
bu çok güzel bir çözüm; ancak, || not_foundparçaya ihtiyacınız yoktur , sadece arayın find!(patlama dikkat edin) ve kaynak alınamadığı zaman ActiveRecord :: RecordNotFound atar. Ayrıca, if durumunda diziye ActiveRecord :: RecordNotFound ekleyin.
— Marek Příhoda

1
Her ihtimale karşı kurtarırım StandardErrorve kurtarmam Exception. Aslında standart 500 statik sayfa bırakacağım ve özel kullanmıyorum render_500, yani rescue_from404
— Dr.Strangelove

7

bunlar size yardımcı olacak ...

Uygulama Denetleyicisi

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

Hata denetleyicisi

class ErrorsController < ApplicationController
  def error_404
    @not_found_path = params[:not_found]
  end
end

görünümler / hatalar / 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

3
<%= render file: 'public/404', status: 404, formats: [:html] %>

bunu 404 hata sayfasına oluşturmak istediğiniz sayfaya ekleyin ve işiniz bitti.


1

Yönetici olmayan herhangi bir giriş yapmış kullanıcı için 'normal' 404 atmak istedim, bu yüzden Rails 5'de böyle bir şey yazdım:

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

1
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

0

Hata işlemeyi test etmek için aşağıdakine benzer bir şey yapabilirsiniz:

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

0

Farklı 404'leri farklı şekillerde ele almak istiyorsanız, bunları kontrol cihazlarınızda yakalamayı düşünün. Bu, farklı kullanıcı grupları tarafından oluşturulan 404'lerin sayısını izleme, kullanıcılarla neyin yanlış gittiğini / kullanıcı deneyiminin hangi bölümünün ayarlanması gerektiğini, A / B testi vb.

Burada temel mantığı ApplicationController'a yerleştirdim, ancak yalnızca bir denetleyici için özel mantığa sahip olmak için daha spesifik denetleyicilere de yerleştirilebilir.

ENV ['RESCUE_404'] ile bir if kullanmamın sebebi, AR :: RecordNotFound'un yükseltilmesini izole olarak test edebilmemdir. Testlerde, bu ENV değişkenini false olarak ayarlayabilirim ve rescue_from'um ateşlemez. Bu şekilde yetiştirmeyi şartlı 404 mantığından ayrı olarak test edebilirim.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.