Best way to do a rescue block - ruby-on-rails

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

Related

Display Error Message from Private Method (Rails 6)

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.

Using a transaction to roll back saves

My registration form is complex and fragile. I am trying to use transaction, but it doesn't appear to work the way I think it does.
My intention is to roll back all the saves if something fails down the line. Am I doing this incorrectly?
if #little_class_schedule.valid?
User.transaction do
if #user.save
#little_class.user_id = #user.id
if #little_class.save
if #location.save
if little_class_schedule_form_params["schedule_type"].to_i == 2 || #little_class_schedule.save
if #little_class_session_validation.valid?
sessions.each do |s|
if s.save
next
else
raise ActiveRecord::Rollback
render 'class_account_registration/new'
end
end
ApprovalMailer.request_approval(#user, #little_class).deliver
redirect_to dashboard_path, notice: 'Success!'
else
raise ActiveRecord::Rollback
render 'class_account_registration/new'
end
else
raise ActiveRecord::Rollback
render 'class_account_registration/new'
end
else
raise ActiveRecord::Rollback
render 'class_account_registration/new'
end
else
raise ActiveRecord::Rollback
render 'class_account_registration/new'
end
else
render 'class_account_registration/new'
end
end
else
render 'class_account_registration/new'
end
I would highly recommend you to get a structure in your code and keep it DRY!
Since I don't know what you want to do, I just improved your code. It still isn't the best solution, but since I don't know what you really want to do, I can't change the logic.
successful = false
if #little_class_schedule.valid?
User.transaction do
if #user.save
#little_class.user_id = #user.id
if #little_class.save && #location.save
if little_class_schedule_form_params["schedule_type"].to_i == 2 || #little_class_schedule.save
if #little_class_session_validation.valid?
sessions.each do |s|
if s.save
next
else
raise ActiveRecord::Rollback
end
end
successful = true
end
end
end
end
unless successful
raise ActiveRecord::Rollback
end
end
end
if successful
ApprovalMailer.request_approval(#user, #little_class).deliver_now
redirect_to dashboard_path, notice: 'Success!'
else
render 'class_account_registration/new' unless successful
end
I haven't tested this code so it could have bugs in it. Please try it out and give us feedback if it works or if not what errors you have.
Hope this helps!
Happy coding :)

How to avoid the error on nil object

Record id 116 doesn't exist so it should return nil to #conversation.
I tried to make it redirect when it gets nil, but it still shows the error when I access example.com/messages/show?id=116 .
The error is
undefined method `is_participant?' for nil:NilClass
I definitely see 'is_participant' method existing in
/usr/local/lib/ruby/gems/1.9.1/gems/mailboxer-0.7.0/app/models/conversation.rb
messages_controller.rb
def show
#conversation = Conversation.find_by_id(params[:id])
unless #conversation.is_participant?(current_user)
flash[:alert] = "You do not have permission to view that conversation."
redirect_to :controller => 'messages', :action => 'received'
end
#messages = Message.find_by_id(params[:id])
current_user.read(#conversation)
end
You need to check that #conversation is not nil before you call a method on it. Try
unless #conversation.present? && #conversation.is_participant?(current_user)
You can check for presence of a value or rescue for that error.
def show
#conversation = Conversation.find_by_id(params[:id])
redirect_to somewhere_path if #conversation.nil?
unless #conversation.is_participant?(current_user)
flash[:alert] = "You do not have permission to view that conversation."
redirect_to :controller => 'messages', :action => 'received'
end
#messages = Message.find_by_id(params[:id])
current_user.read(#conversation)
end
or the Rescue!
def show
#conversation = Conversation.find_by_id(params[:id])
unless #conversation.is_participant?(current_user)
flash[:alert] = "You do not have permission to view that conversation."
redirect_to :controller => 'messages', :action => 'received'
end
#messages = Message.find_by_id(params[:id])
current_user.read(#conversation)
rescue NoMethodError
redirect_to somewhere_path
end
Notice that the rescue way is not very friendly, since it can rescue other error and making you have a pain to debug some errors. For example if current_user has no method named read, it would throw and error that would be catch there and you wouldn't notice it came from there.
Christoph Petschnig answer is right, just wanted to mention there is a nice shorthand for
unless #conversation.present? && #conversation.is_participant?(current_user)
which is
unless #conversation.try(:is_participant? , current_user)
try will return nil is #conversation is nil which eventually evaluates to false in the if statement.

Redirecting with Raise ActiveRecord::Rollback?

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.

Rails how to redirect if record is not found

I am trying to redirect if the record is not found.
The page is not redirect and I get the error record not found.
My controller:
def index
#link = Link.find(params[:id])
respond_to do |format|
if #link.blank?
format.html { redirect_to(root_url, :notice => 'Record not found') }
else
format.html { render :action => "index" }
end
end
end
What I've been doing is putting this at the end of the method:
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :flash => { :error => "Record not found." }
Even better, put it as an around_filter for your controller:
around_filter :catch_not_found
private
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :flash => { :error => "Record not found." }
end
error is generated by Link.find - it raises exception if object was not found
you can simplify your code quite a bit:
def index
#link = Link.find_by_id(params[:id])
redirect_to(root_url, :notice => 'Record not found') unless #link
respond_to do |format|
format.html
end
end
You are on the right track, just capture the RecordNotFound exception:
def index
#link = Link.find(params[:id])
# should render index.html.erb by default
rescue ActiveRecord::RecordNotFound
redirect_to(root_url, :notice => 'Record not found')
end
Very tricky one ... I found a simple solution for this.... This solution works for me
#link = Link.where(:id => params[:id]).first
I am using .first because .where will return an array.Yes, of-course this array has only one element. So, When there is no record with such id, it will return an empty array, assign a blank element to #link ... Now check #link is either blank or not....
Conclusion: No need to provide exception handling for a simple check
It is problem with .find it throws exception when no record exist ... Use .where it will return an empty array
And Sorry for my Bad English
I would prefer to use find_by. find_by will find the first record matching the specified conditions. If the record is not found it will return nil, but does not raise exception, so that you can redirect to other page.
def index
#link = Link.find_by(id: params[:id])
redirect_to(root_url, :notice => 'Record not found') unless #link
end

Resources