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.