For a specific role (group of users) I added the :readonly to every find on the active record
def self.find(*args)
if User.current_user.has_role? 'i_can_only_read'
with_scope({:find => {:readonly => true}}) do
result = super *args
end
end
end
Of course it raises now ActiveRecord::ReadOnlyRecord Exceptions in Controller passed on to the user; not very nice.
Can I catch this type of error in one place? Like in production.rb or in the application.rb? Or can I configure a specific error page for this error?
Yes, simply override rescue_action_in_public like this:
class ApplicationController < ActionController::Base
...
def rescue_action_in_public(exception)
case exception
when ActiveRecord::ReadOnlyRecord
# DO SOME LOGIC HERE
end
end
end
end
This will execute your action when in "production", but leave you with an informative stack trace when you are in "development".
Rails has a number of other rescue_action_* methods that might be more suitable to your problem...take a look at http://api.rubyonrails.org/classes/ActionController/Rescue.html
Related
I want to use rescue_from method on a model concern but neither exception error is rescued to the method i specified:
require "active_support/concern"
module CustomErrorHandler
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
included do
rescue_from StandardError, with: :capture_error
end
private
def capture_error(e)
puts "Error catched!"
end
end
class FooClass
include CustomErrorHandler
def some_action
raise StandardError, 'Error'
end
end
err = FooClass.new
err.some_action
Traceback (most recent call last):
2: from (irb):28
1: from (irb):23:in `some_action'
StandardError (Error) # Didn't catch the error!
Anyone can help me on how can i solve this? Thanks!
This idea is fundamentially broken.
rescue_from is basically just syntactic sugar for:
class ThingsController < ApplicationController
around_action :wrap_in_a_rescue
def show
raise SomeKindOfException
end
private
def wrap_in_a_rescue
begin
yield
rescue SomeKindOfException
do_something_else
end
end
def do_something_else
render plain: 'Oh Noes!'
end
end
It only works because the controller declares a callback.
Its use makes sense in a controller as it rescues exceptions that occur in its callbacks as well as in the method itself and can be used with inheritiance to customize the error responses. It use does not make very much sense outside of that context.
While you could maybe fix this code by including ActiveSupport::Callbacks and declaring the requisite callbacks it is most likely a very overcomplicated solution to the original problem which can most likely be handled with a simple rescue or composition.
Let's imagine I have a class
class Test < ActiveRecord::Base
include AuthenticatorHelper
def test
authenticate_or_fail!
puts "If I fail, this should be unreachable"
end
end
and
module AuthenticationHelper
def authenticate_or_fail!
#user = User.find(params[:token])
unless #user
render :json => {code: 401, :err => 'Unauthorized'} and return
end
end
end
What I want to do is either authenticate or reply with a json msg. However, it will obviously ignore my return statement due to nesting and it will always print my message
If I fail, this should be unreachable
Regarding the question
You could extract the call into a before_filter/before_action (based on the rails version).
class Test < ActiveRecord::Base
include AuthenticatorHelper
before_action :authenticate_or_fail!
def test
puts "If I fail, this should be unreachable"
end
end
Please see the documentation for further details.
Because your helper method renders in case of a failure, rails will prevent the test method to be called. You will not need the and return part then, which would only have returned from the method anyway and as such was a NoOp.
Apart from the question but also noteworthy:
I don't want to point out errors for the sake of it. I just want to prevent the OP from running into a series of bugs later on.
User.find(params[:token])
Will raise an exception if no record is found. Because of that, the unless #user part will not be evaluated in case of an invalid token. You could use
User.find_by(id: params[:token])
instead.
Your class which looks like it acts as a controller is named Test and inherits from ActiveRecord::Base. The first is unusual as TestsController would be more along the lines of rails and the seconds looks plain wrong. A controller has to inherit from ApplicationController (which itself inherits from ActionController::Base)
So imagine you have 2 models, Person and Address, and only one address per person can be marked as 'Main'. So if I wanna change a person's main address, I need to use a transaction, to mark the new one as main and unmark the old one. And as far as I know using transactions in controllers is not good so I have a special method in model, thats what I've got:
AddressesController < ApplicationController
def update
#new_address = Address.find(params[:id])
#old_address = Address.find(params[:id2])
#new_address.exchange_status_with(#old_address)
end
end
Model:
class Address < ActiveRecord::Base
def exchange_status_with(address)
ActiveRecord::Base.transaction do
self.save!
address.save!
end
end
end
So thequestion is, if the transaction in the model method fails, I need to rescue it and notify the user about the error, how do I do that? Is there a way to make this model method return true or false depending on whether the transaction was successful or not, like save method does?
I probably could put that transaction in the controller and render the error message in the rescue part, but I guess its not right or I could put that method in a callback, but imagine there is some reason why I cant do that, whats the alternative?
PS dont pay attention to finding instances with params id and id2, just random thing to show that I have 2 instances
def exchange_status_with(address)
ActiveRecord::Base.transaction do
self.save!
address.save!
end
rescue ActiveRecord::RecordInvalid => exception
# do something with exception here
end
FYI, an exception looks like:
#<ActiveRecord::RecordInvalid: Validation failed: Email can't be blank>
And:
exception.message
# => "Validation failed: Email can't be blank"
Side note, you can change self.save! to save!
Alternate solution if you want to keep your active model errors:
class MyCustomErrorClass < StandardError; end
def exchange_status_with(address)
ActiveRecord::Base.transaction do
raise MyCustomErrorClass unless self.save
raise MyCustomErrorClass unless address.save
end
rescue MyCustomErrorClass
# here you have to check self.errors OR address.errors
end
Our production logs are long and contain a lot more than just errors. I'd like a second log file with just the errors/exceptions in.
Is this possible?
We're using rails 2.x
Thanks.
For example, to log all ActiveRecord::Base errors in a file called log/exceptions.log
new_logger = Logger.new('log/exceptions.log')
new_logger.level = Logger::ERROR
new_logger.error('THIS IS A NEW EXCEPTION!')
ActiveRecord::Base.logger = new_logger
For controllers and view(because ActionView logger doesn't have it's own logger, so it depends on the ActionController logger):
ActionController::Base.logger = new_logger
Try the following. Put the rescue_from method in your controller.
I haven't tested this. But maybe it puts you in the right direction
class ApplicationController < ActionController::Base
rescue_from StandardError do |exception|
new_logger = Logger.new('log/exceptions.log')
new_logger.info('THIS IS A NEW EXCEPTION!')
new_logger.info(exception.message)
new_logger.info(exception.backtrace)
# Raise it anyway because you just want to put it in the log
raise exception
end
end
If you use Rails 2.1 (also not tested)
class ApplicationController < ActionController::Base
def rescue_action_in_public(exception)
new_logger = Logger.new('log/exceptions.log')
new_logger.info('THIS IS A NEW EXCEPTION!')
new_logger.info(exception.message)
new_logger.info(exception.backtrace)
# Raise it anyway because you just want to put it in the log
raise exception
end
end
could someone explain the code in catch_exceptions?
i have difficulties to understand.
thanks
class ApplicationController < ActionController::Base
around_filter :catch_exceptions
private
def catch_exceptions
yield
rescue => exception
logger.debug "Caught exception! #{exception}"
raise
end
end
Simple.
You need first to understand the concept of the around_filter. It puts something AROUND a method call. Also you need to understand YIELD, that is executing a block.
so if you have something like an Index action.
def index
# run code run
end
that means it will be sent as a block to that around_filter, which will execute that just as if it were...
def catch_exceptions
def index
#run code run
end
rescue => exception
logger.debug "Caught exception! #{exception}"
raise
end
catch_exceptions is a method that takes a block. You can tell because it contains a yield (which executes the passed in block).
The method is catching any exception that occurs in that block, logging them, then re-throwing them so other code can catch it too.
The 'around_filter' line makes rails pass each controller method that would be executed to the catch_exceptions method instead.
The overall result is that all exceptions thrown by controller methods get logged.