Tôi muốn 'giả' một trang 404 trong Rails. Trong PHP, tôi sẽ chỉ gửi một tiêu đề với mã lỗi như sau:
header("HTTP/1.0 404 Not Found");
Làm thế nào được thực hiện với Rails?
Tôi muốn 'giả' một trang 404 trong Rails. Trong PHP, tôi sẽ chỉ gửi một tiêu đề với mã lỗi như sau:
header("HTTP/1.0 404 Not Found");
Làm thế nào được thực hiện với Rails?
Câu trả lời:
Đừng tự kết xuất 404, không có lý do gì để; Rails có chức năng này được tích hợp sẵn. Nếu bạn muốn hiển thị một trang 404, tạo ra một render_404
phương pháp (hoặc not_found
như tôi gọi nó) trong ApplicationController
như thế này:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails cũng xử lý AbstractController::ActionNotFound
, và ActiveRecord::RecordNotFound
cách tương tự.
Điều này làm hai điều tốt hơn:
1) Nó sử dụng Rails' xây dựng trong rescue_from
xử lý để làm cho trang 404, và 2) nó làm gián đoạn việc thực hiện mã của bạn, cho phép bạn làm những điều tốt đẹp như:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
mà không cần phải viết báo cáo có điều kiện xấu xí.
Như một phần thưởng, nó cũng siêu dễ dàng để xử lý trong các bài kiểm tra. Ví dụ: trong thử nghiệm tích hợp 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)
Và nhỏ nhất:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
HOẶC tham khảo thêm thông tin từ Rails render 404 không tìm thấy từ hành động của bộ điều khiển
ActionController::RecordNotFound
là lựa chọn tốt hơn?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ qua stackoverflow.com/a/1722839/993890
Để trả về một tiêu đề 404, chỉ cần sử dụng :status
tùy chọn cho phương thức kết xuất.
def action
# here the code
render :status => 404
end
Nếu bạn muốn kết xuất trang 404 tiêu chuẩn, bạn có thể trích xuất tính năng này trong một phương thức.
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
và gọi nó trong hành động của bạn
def action
# here the code
render_404
end
Nếu bạn muốn hành động để làm cho trang báo lỗi và dừng lại, chỉ cần sử dụng lệnh return.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Cũng nhớ rằng Rails cứu một số lỗi ActiveRecord, chẳng hạn như ActiveRecord::RecordNotFound
hiển thị trang lỗi 404.
Điều đó có nghĩa là bạn không cần phải tự giải cứu hành động này
def show
user = User.find(params[:id])
end
User.find
tăng ActiveRecord::RecordNotFound
khi người dùng không tồn tại. Đây là một tính năng rất mạnh mẽ. Nhìn vào đoạn mã sau
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Bạn có thể đơn giản hóa nó bằng cách ủy quyền cho Rails kiểm tra. Đơn giản chỉ cần sử dụng phiên bản bang.
def show
user = User.find_by_email!(params[:email])
# ...
end
Câu trả lời mới được chọn bởi Steven Soroka rất gần, nhưng không đầy đủ. Bản thân bài kiểm tra đã che giấu thực tế rằng điều này không trả về 404 thực sự - nó trả về trạng thái 200 - "thành công". Câu trả lời ban đầu gần hơn, nhưng đã cố gắng hiển thị bố cục như thể không có lỗi xảy ra. Điều này sửa mọi thứ:
render :text => 'Not Found', :status => '404'
Đây là một bộ thử nghiệm điển hình của tôi cho một thứ tôi mong đợi sẽ trả về 404, sử dụng bộ so khớp RSpec và 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
Sự hoang tưởng lành mạnh này cho phép tôi phát hiện ra sự không phù hợp về kiểu nội dung khi mọi thứ khác trông có vẻ như vậy :) Tôi kiểm tra tất cả các yếu tố này: biến được gán, mã phản hồi, loại nội dung phản hồi, mẫu được hiển thị, bố cục được hiển thị, thông báo flash.
Đôi khi tôi sẽ bỏ qua việc kiểm tra loại nội dung trên các ứng dụng hoàn toàn là html ... Rốt cuộc, "một người hoài nghi kiểm tra TẤT CẢ các ngăn kéo" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: Tôi không khuyên bạn nên thử nghiệm cho những thứ đang xảy ra trong bộ điều khiển, tức là "Should_raise". Điều bạn quan tâm là đầu ra. Các thử nghiệm của tôi ở trên cho phép tôi thử các giải pháp khác nhau và các thử nghiệm vẫn giữ nguyên cho dù giải pháp đó có đưa ra một ngoại lệ, kết xuất đặc biệt, v.v.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
tham số được đặt thành đúng trong environments/development.rb
tệp của mình . Nếu bạn nêu ra một lỗi, như được mô tả trong giải pháp được chấp nhận, trong việc dàn dựng / sản xuất, bạn chắc chắn sẽ nhận được 404, không phải 200.
Bạn cũng có thể sử dụng tệp kết xuất:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Nơi bạn có thể chọn để sử dụng bố trí hay không.
Một lựa chọn khác là sử dụng các ngoại lệ để kiểm soát nó:
raise ActiveRecord::RecordNotFound, "Record not found."
Câu trả lời được chọn không hoạt động trong Rails 3.1+ vì trình xử lý lỗi đã được chuyển sang phần mềm trung gian (xem vấn đề github ).
Đây là giải pháp tôi thấy mà tôi khá hài lòng.
Trong 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
và trong application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
Và trong tài nguyên của tôi (hiển thị, chỉnh sửa, cập nhật, xóa):
@resource = Resource.find(params[:id]) or not_found
Điều này chắc chắn có thể được cải thiện, nhưng ít nhất, tôi có các chế độ xem khác nhau cho not_found và Internal_error mà không ghi đè các hàm Rails cốt lõi.
|| not_found
phần đó, chỉ cần gọi find!
(chú ý tiếng nổ) và nó sẽ ném ActiveRecord :: RecordNotFound khi tài nguyên không thể được lấy. Ngoài ra, thêm ActiveRecord :: RecordNotFound vào mảng trong điều kiện if.
StandardError
và không Exception
, chỉ trong trường hợp. Trên thực tế, tôi sẽ rời khỏi trang tĩnh 500 tiêu chuẩn và hoàn toàn không sử dụng tùy chỉnh render_500
, nghĩa là tôi rõ ràng sẽ có một rescue_from
loạt lỗi liên quan đến 404
những thứ này sẽ giúp bạn ...
Bộ điều khiển ứng dụng
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
Bộ điều khiển lỗi
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
lượt xem / lỗi / 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] %>
chỉ cần thêm phần này vào trang bạn muốn kết xuất vào trang lỗi 404 và bạn đã hoàn thành.
Tôi muốn ném 404 'bình thường' cho bất kỳ người dùng nào đã đăng nhập không phải là quản trị viên, vì vậy cuối cùng tôi đã viết một cái gì đó như thế này trong 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
Để kiểm tra xử lý lỗi, bạn có thể làm một cái gì đó như thế này:
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
Nếu bạn muốn xử lý các 404 khác nhau theo các cách khác nhau, hãy xem xét việc bắt chúng trong bộ điều khiển của bạn. Điều này sẽ cho phép bạn thực hiện những việc như theo dõi số 404 do các nhóm người dùng khác nhau tạo ra, có hỗ trợ tương tác với người dùng để tìm ra lỗi sai / phần nào của trải nghiệm người dùng có thể cần điều chỉnh, thực hiện kiểm tra A / B, v.v.
Ở đây tôi đã đặt logic cơ sở trong ApplicationContoder, nhưng nó cũng có thể được đặt trong các bộ điều khiển cụ thể hơn, để có logic đặc biệt chỉ cho một bộ điều khiển.
Lý do tôi đang sử dụng if với ENV ['RESCUE_404'], vì vậy tôi có thể kiểm tra việc tăng AR :: RecordNotFound trong sự cô lập. Trong các thử nghiệm, tôi có thể đặt var ENV này thành false và cứu_from của tôi sẽ không kích hoạt. Bằng cách này, tôi có thể kiểm tra việc tăng tách biệt với logic 404 có điều kiện.
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