Saya ingin 'memalsukan' halaman 404 di Rails. Dalam PHP, saya hanya akan mengirim header dengan kode kesalahan sebagai berikut:
header("HTTP/1.0 404 Not Found");
Bagaimana itu dilakukan dengan Rails?
Saya ingin 'memalsukan' halaman 404 di Rails. Dalam PHP, saya hanya akan mengirim header dengan kode kesalahan sebagai berikut:
header("HTTP/1.0 404 Not Found");
Bagaimana itu dilakukan dengan Rails?
Jawaban:
Jangan membuat 404 diri Anda sendiri, tidak ada alasan untuk melakukannya; Rails sudah memiliki fungsi ini. Jika Anda ingin menampilkan halaman 404, buat render_404
metode (atau not_found
seperti yang saya sebutkan) ApplicationController
seperti ini:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rel juga menangani AbstractController::ActionNotFound
, dan ActiveRecord::RecordNotFound
dengan cara yang sama.
Ini melakukan dua hal lebih baik:
1) Ini menggunakan rescue_from
handler bawaan Rails ' untuk membuat halaman 404, dan 2) itu mengganggu eksekusi kode Anda, membiarkan Anda melakukan hal-hal baik seperti:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
tanpa harus menulis pernyataan kondisional yang jelek.
Sebagai bonus, ini juga sangat mudah untuk ditangani dalam tes. Misalnya, dalam tes integrasi 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)
Dan terkecil:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
ATAU rujuk info lebih lanjut dari Rails render 404 yang tidak ditemukan dari aksi pengontrol
ActionController::RecordNotFound
ini pilihan yang lebih baik?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Untuk mengembalikan header 404, cukup gunakan :status
opsi untuk metode render.
def action
# here the code
render :status => 404
end
Jika Anda ingin merender halaman 404 standar, Anda dapat mengekstrak fitur dalam suatu metode.
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
dan menyebutnya dalam tindakan Anda
def action
# here the code
render_404
end
Jika Anda ingin tindakan membuat halaman kesalahan dan berhenti, cukup gunakan pernyataan kembali.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Juga ingat bahwa Rails menyelamatkan beberapa kesalahan ActiveRecord, seperti ActiveRecord::RecordNotFound
menampilkan halaman kesalahan 404.
Itu berarti Anda tidak perlu menyelamatkan tindakan ini sendiri
def show
user = User.find(params[:id])
end
User.find
memunculkan ActiveRecord::RecordNotFound
ketika pengguna tidak ada. Ini adalah fitur yang sangat kuat. Lihatlah kode berikut
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Anda dapat menyederhanakannya dengan mendelegasikan ke Rails cek. Cukup gunakan versi bang.
def show
user = User.find_by_email!(params[:email])
# ...
end
Jawaban yang baru dipilih yang dikirimkan oleh Steven Soroka sudah dekat, tetapi tidak lengkap. Tes itu sendiri menyembunyikan fakta bahwa ini tidak mengembalikan 404 yang benar - itu mengembalikan status 200 - "sukses". Jawaban aslinya lebih dekat, tetapi berusaha membuat tata letak seolah-olah tidak ada kegagalan yang terjadi. Ini memperbaiki semuanya:
render :text => 'Not Found', :status => '404'
Berikut ini adalah serangkaian tes khas saya untuk sesuatu yang saya harapkan akan kembali 404, menggunakan pencocokan RSpec dan 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
Paranoia yang sehat ini memungkinkan saya untuk menemukan ketidakcocokan tipe-konten ketika segala sesuatu tampak sangat bagus :) Saya memeriksa semua elemen ini: variabel yang ditugaskan, kode respons, tipe konten respons, template yang diberikan, tata letak yang diberikan, pesan flash.
Saya akan melewatkan pemeriksaan jenis konten pada aplikasi yang benar-benar html ... terkadang. Lagi pula, "skeptis memeriksa SEMUA laci" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: Saya tidak merekomendasikan pengujian untuk hal-hal yang terjadi di controller, yaitu "should_raise". Yang Anda pedulikan adalah hasilnya. Tes saya di atas memungkinkan saya untuk mencoba berbagai solusi, dan tes tetap sama apakah solusi meningkatkan pengecualian, render khusus, dll.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
parameter yang disetel ke true dalam environments/development.rb
file Anda . Jika Anda memunculkan kesalahan, seperti dijelaskan dalam solusi yang diterima, dalam pementasan / produksi, Anda pasti akan mendapatkan 404, bukan 200.
Anda juga dapat menggunakan file render:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Di mana Anda dapat memilih untuk menggunakan tata letak atau tidak.
Pilihan lain adalah dengan menggunakan Pengecualian untuk mengendalikannya:
raise ActiveRecord::RecordNotFound, "Record not found."
Jawaban yang dipilih tidak berfungsi di Rails 3.1+ karena penangan kesalahannya dipindahkan ke middleware (lihat masalah github ).
Inilah solusi yang saya temukan yang cukup saya sukai.
Dalam 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
dan di application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
Dan di sumber saya (tampilkan, edit, perbarui, hapus):
@resource = Resource.find(params[:id]) or not_found
Ini tentu saja dapat ditingkatkan, tetapi setidaknya, saya memiliki pandangan yang berbeda untuk not_found dan internal_error tanpa mengesampingkan fungsi inti Rails.
|| not_found
bagian, panggil saja find!
(perhatikan bang) dan itu akan membuang ActiveRecord :: RecordNotFound ketika sumber daya tidak dapat diambil. Juga, tambahkan ActiveRecord :: RecordNotFound ke array dalam kondisi if.
StandardError
dan tidak Exception
, untuk berjaga-jaga. Sebenarnya saya akan meninggalkan halaman statis 500 standar dan tidak menggunakan kustom render_500
sama sekali, artinya saya akan secara eksplisit rescue_from
ini akan membantu Anda ...
Pengontrol Aplikasi
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
Pengontrol kesalahan
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
views / errors / 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] %>
cukup tambahkan ini ke halaman yang ingin Anda render ke halaman kesalahan 404 dan Anda selesai.
Saya ingin melemparkan 'normal' 404 untuk setiap pengguna yang masuk yang bukan admin, jadi saya akhirnya menulis sesuatu seperti ini di 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
Untuk menguji penanganan kesalahan, Anda dapat melakukan sesuatu seperti ini:
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
Jika Anda ingin menangani 404 yang berbeda dengan cara yang berbeda, pertimbangkan untuk menangkapnya di pengontrol Anda. Ini akan memungkinkan Anda untuk melakukan hal-hal seperti melacak jumlah 404 yang dihasilkan oleh kelompok pengguna yang berbeda, memiliki dukungan berinteraksi dengan pengguna untuk mencari tahu apa yang salah / bagian mana dari pengalaman pengguna yang mungkin perlu diubah, lakukan pengujian A / B, dll.
Saya di sini telah menempatkan logika dasar dalam ApplicationController, tetapi juga dapat ditempatkan di pengontrol yang lebih spesifik, untuk memiliki logika khusus hanya untuk satu pengontrol.
Alasan saya menggunakan if dengan ENV ['RESCUE_404'], adalah agar saya dapat menguji peningkatan AR :: RecordNotFound secara terpisah. Dalam tes, saya dapat mengatur ENV var ini menjadi false, dan rescue_from saya tidak akan menyala. Dengan cara ini saya dapat menguji peningkatan terpisah dari logika 404 bersyarat.
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