I'm trying to handle ActionController::UnpermittedParameters.
I've defined a block in ApplicationController
rescue_from ActionController::UnpermittedParameters do |error|
message = "Invalid parameter: %s. " % error.params.to_sentence
message << 'Please verify that the parameter name is valid and the values are the correct type.'
format.html { redirect_to :back, alert: 'You passed wrong params! ' + message }
end
But, when I execute code, which should be handled, the rescue block is not executed.
What am I doing wrong?
I guess your request is not of HTML format, so format.html { } block will not be executed.
Try to leave only redirect_to :back, alert: 'You passed wrong params! ' + message part
I don't know what was wrong about my question, but I write my own ErrorHandler module, which works fine now.
# Error module to Handle errors globally
# include Error::ErrorHandler in application_controller.rb
module Error
module ErrorHandler
def self.included(klass)
klass.class_eval do
rescue_from ActionController::UnpermittedParameters, with: :unpermitted_parameter
end
end
private
def unpermitted_parameter(error)
message = "You have entered an unpermitted parameter: %s. " % error.params.to_sentence
message << 'Please verify that the parameter name is valid and the values are the correct type.'
Rails.logger.info(Rainbow(" !!! UNPERMITTED: !!! #{error.to_s}").fg(:red))
respond_to do |format|
format.html { redirect_back fallback_location: { action: "index" },
:alert => message }
format.js
end
end
end
end
Related
I have a method, create, which calls a private method get_title:
def create
#article = #user.articles.new(article_params)
#article.title = get_title(#article.link)
if #article.save
redirect_to user_path, success: "Article Saved."
else
flash.now[:danger] = "Failed to save article."
end
end
The get_title method works so long as there are no errors in the get request:
def get_title(url)
request = HTTParty.get(url)
document = Nokogiri::HTML(request.body)
title = document.at_css "title"
return title.text
end
I can catch the error with a begin/rescue before the Nokogiri call:
begin
HTTParty.get(url)
rescue HTTParty::Error
...
rescue StandardError
...
else
request = HTTParty.get(url)
end
I don't know how to stop the method and return the error to the create method.
How do I return an error from the rescue statement to the create method and display that error message in a flash?
What you probably want to do is "harden" this method so it doesn't explode unless something really unusual happens:
class TitleFetchError < StandardError
end
def get_title(url)
request = HTTParty.get(url)
Nokogiri::HTML(request.body).at_css('title').text
rescue HTTParty::Error => error
raise TitleFetchError, error.to_s
rescue StandardError => error
raise TitleFetchError, error.to_s
rescue ... (some other type of error, e.g. Nokogiri parse errors)
end
Where in my opinion that should actually be a method on the #article model, like #article.fetch_title which would do the same thing, using its own link property.
One thing to note is this pattern for writing controller actions helps simplify things:
def create
#article = #user.articles.new(article_params)
#article.title = get_title(#article.link)
#article.save!
redirect_to user_path, success: "Article Saved."
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved
redirect_to user_path, danger: "Failed to save article."
rescue TitleFetchError => error
redirect_to user_path, danger: "Failed to save article. Error: #{error}"
end
Where you have a nice clean "normal" path, and then your exceptions are handled on the side if/when that becomes necessary. This keeps the two flows separated and avoids additional nesting from the if.
Is this the correct way to do a rescue in a block?
Also, is it the shortest?
def rescue_definition
begin
user.begin_rescue
rescue Example::ParameterValidationError => e
redirect_to :back, error: e.message_to_purchaser
rescue Example::ProcessingError => e
redirect_to :back, error: e.message_to_purchaser
rescue Example::Error
redirect_to :back, error: e.message_to_purchaser
else
if user
flash['success'] = 'OK'
else
flash['error'] = 'NO'
end
end
redirect_to :back
end
The idea to use begin, rescue, ensure is to put the methods than can generate an error in the begin block, then you can handle the errors in one o more rescue blocks and finally you can put an optional ensure block for sentences that you want to execute on both success or fail scenarios for example release resources.
When you want all the method be handled by begin, rescue, ensure blocks, then begin keyword is optional, you can use or not the keyword, since you are asking for the shortest option, you will need to do some minor changes to your code:
def rescue_definition
user.begin_rescue
# and any other stuff you want to execute
if user
flash['success'] = 'OK'
else
flash['error'] = 'NO'
end
redirect_to :back
rescue Example::ParameterValidationError => e
redirect_to :back, error: e.message_to_purchaser
rescue Example::ProcessingError => e
redirect_to :back, error: e.message_to_purchaser
rescue Example::Error
redirect_to :back, error: e.message_to_purchaser
end
If you want it even shorter you can rescue multiple exception types in one rescue block:
def rescue_definition
user.begin_rescue
# and any other stuff you want to execute
if user
flash['success'] = 'OK'
else
flash['error'] = 'NO'
end
redirect_to :back
rescue Example::ParameterValidationError, Example::ProcessingError, Example::Error => e
redirect_to :back, error: e.message_to_purchaser
end
Aguar's post handles the current implementation. If you handle all the errors the same way then you can have them inherit from a custom class and just catch them all with 1 rescue call.
def stuff
user.stuff
if user
flash['success'] = 'Ya'
else
flash['error'] = 'Nah'
end
redirect_to ....
rescue CustomClassError => e
redirect_to :back, error: e.message_to_purchaser
rescue NonCustomErrors => e
#handle it
end
Given the following Rails 4.2 controller:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
render json: {}, status: :unauthorised
end
end
end
end
When, with RSpec 3, I try to call this index action and expect to have the status 401 I always have the status 200.
The only moment where I got the 401 is to replace the index action content with head 401 but I would like to respond with the error 401 and also build a "nice" body like { error: 401, message: 'Unauthorised' }.
Why is the status: :unauthorised ignored ?
Use error code instead of it's name:
render json: {}, status: 401
I had to replace my controller with this following:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
self.status = :unauthorized
self.response_body = { error: 'Access denied' }.to_json
end
end
end
end
Using render is not preventing the called action to be executed. Using head :unauthorized is returning the right status code but with a blank body.
With self.status and self.response_body it's working perfectly.
You can see have a look to the source code my gem where I had this issue here: https://github.com/YourCursus/fortress
Replace unauthorised by unauthorized
Given the following controller method:
def create
#client = User.find(params[:client][:id])
respond_to do |format|
if #client.present? && #client.add_manager(current_user)
format.html { redirect_to clients_path, notice: "Successfully added manager" }
else
format.html { redirect_to clients_path, error: "Could not find client" }
end
end
end
How do I get this to fail properly in the else block instead of throwing a RuntimeError which turns into a "Something went wrong" on production?
def add_manager(user)
raise "Already a manager" if self.manager_users.include?(user)
self.manager_users << user if user.trainer?
end
Is the code...
You can try something like this:
In your controller
class YourAppName::AlreadyManagerError < StandardError
end
Now change "Already a manager" to the name of your custom error
def add_manager(user)
raise YourAppName::AlreadyManagerError if self.manager_users.include?(user)
self.manager_users << user if user.trainer?
end
Then in your ApplicationController
rescue_from YourAppName::AlreadyManagerError do |exception|
render :nothing => "Already a manager", :status => 404
end
This article goes into more detail. Also check out rescue_from
I can't redirect correctly, it keeps throwing me ActiveRecord::RecordInvalid but it should be redirecting :back to the original page.
def create_multiple
Product.transaction do
begin
#products = current_user.products.create!(params[:products].map { |_k, p| p.merge params[:product] })
redirect_to :back, :notice => "Success!"
rescue ActiveRecord::Rollback
redirect_to :back, :notice => "An error occured, please try again."
end
end
end
end
How do I get it to redirect?
If you want to catch the ActiveRecord::RecordInvalid exception, then why aren't you rescuing that instead of ActiveRecord::Rollback?
def create_multiple
Product.transaction do
#products = current_user.products.create! ...
end
notice = "Success!"
rescue ActiveRecord::RecordInvalid
notice = "An error occurred, please try again"
ensure
redirect_to :back, :notice => notice
end
I would write this function something like the above.