how to add attributes to custom rails exceptions - ruby-on-rails

Say I have a custom error class that inherits from a parent error class. How can I add an attribute to that class, so something like:
class CustomError <CustomParentErrorClass
status: 500
end
So that in my controller I could do something like
rescue CustomErroClass => e
head(e.status)
I essentially want to access my attribute from my rails error class from my controller but am not sure how to. Any thoughts?

You can define attributes on the CustomError class just like any other error class
class CustomError < StandardError
attr_reader :status
def initialize(status)
#status = status
end
end
Here is a sample usage
begin
raise CustomError.new(500)
rescue CustomError => e
head(e.status)
end
If the attributes are hard-coded and do not need to be passed from where the error is raised, you can hard-code it
class CustomError < StandardError
attr_reader :status
def initialize
#status = 500 # you can also define a constant instead of attribute if the value will be a constant
end
end
That said, a word of caution for the example that you shared:
You are defining a status attribute. I am guessing this error will be raised from your model or service class and rescued in the Controller. If this is the case, consider avoiding coupling the model class to the HTTP status that the controller should return.

Related

RoR How to catch general error in parent class?

class ParentClass
def run
rescue StandardError => e
p 'catch in parent class'
end
end
class ChildClass < ParentClass
def run
rescue ArgumentError => e
p 'catch in child class'
end
end
Kindly review above code, I have a series of children classes.
instance = ChildClass.new
instance.run
when instance run and meet ArgumentError, ChildClass will handle it. If not, since the parent class will catch all general StandardError, any error not caught in ChildClass, will be caught in parent class eventually.
How to implement the above logic?

Exit Ruby On Rails controller method from helper

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)

Rails generic errors array

In my Rails 4 app I have a Service object that handles communication with Stripe Payments Processor. I want it as a service object so that multiple Controllers/Models can utilize the methods within it.
However, I also need to be able to trap errors when communicating with the Stripe API which then causes the problem as the errors need to be assigned to a particular object.
Here is a method in my StripeCommunicator.rb class:
def create_customer(token,object)
customer = Stripe::Customer.create(:description => 'Accommodation', :email => object.email, :card => token)
return customer
rescue Stripe::CardError => e
#account.errors.add :base, e.message
false
end
as you can see - the errors are being added to the #account object - which essentially renders it useless when I want to use this method from another controller with a View that refers to another object to display errors.
Any ideas?
Simplest thing is to just pass the #account instance in as another argument. Errors is going to be on any model instance, e.g.
def create_customer(token,object,model_instance)
Stripe::Customer.create(description: 'Accommodation', email: object.email, card: token)
# return customer <- don't need this. whatever is last evaluated will be returned
rescue Stripe::CardError => e
model_instance.errors.add :base, e.message
false
end
If you were doing the error handling in the controller instead of a service object, you could take advantage of rescue_from which can handle exceptions falling out from action methods, e.g. in your controller or ApplicationController, etc., do the following:
rescue_from Stripe::CardError, with: :add_error_message_to_base
def add_error_message_to_base(e)
# this assumes that you set #instance in the controller's action method.
#instance.errors.add :base, e.message
respond_with #instance
end
or more generically:
rescue_from Stripe::CardError, with: :add_error_message_to_base
def add_error_message_to_base(e)
model_class_name = self.class.name.chomp('Controller').split('::').last.singularize
instance_value = instance_variable_get("##{model_class_name}")
instance_value.errors.add :base, e.message if instance_value
respond_with instance_value
end
or in a concern, you could do either of the above, putting the rescue_from into the included block:
module StripeErrorHandling
extend ::ActiveSupport::Concern
included do
rescue_from Stripe::CardError, with: :add_error_message_to_base
end
def add_error_message_to_base(e)
# see comment above...
#instance.errors.add :base, e.message
respond_with #instance
end
end
And you can use config.exceptions_app to handle errors at the Rack-level as José Valim describes here.
You could also inherit the method vs. having a separate service class, or have a concern/module. You might even do through hooks, e.g.:
# not exactly what you were doing but just for example.
# could put in app/controller/concerns among other places.
module ActionsCreateStripeCustomer
extend ::ActiveSupport::Concern
included do
around_action :create_stripe_customer
end
def create_stripe_customer
# this (indirectly) calls the action method, and you will
# set #instance in your action method for this example.
yield
customer = Stripe::Customer.find_or_create_by(description: 'Accommodation', email: object.email, card: token)
# could set customer on #instance here and save if needed, etc.
rescue Stripe::CardError => e
if #instance
#instance.errors.add :base, e.message
respond_with #instance
else
logger.warn("Expected #instance to be set by #{self.class.name}##{params[:action]}")
raise e
end
end
end
Then in the controller:
include ActionsCreateStripeCustomer
There is also before_action, after_action, etc. Also, you can just include modules and when instance methods are called they call on the including class instance first, then the first included module, then the second, etc. if you do super if defined?(super) to call the prior method, and it automatically puts in all the arguments and block.
And, if it were about getting the model class name rather than the instance, that is easy, too. Say the class you were calling from was AccountStripeCommunicator, then #model_class after the following would be Account:
qualified_class_name = self.class.name.chomp('StripeCommunictor')
#model_class = qualified_class_name.split('::').last.singularize.constantize
All kinds of possibilities.

How can I use ExceptionNotifier in my models?

I have a transaction in one of my model. When something go wrong, I want to be notified.
If it would be transaction in controller, I would simply do:
begin
(my transaction...)
rescue => exception
ExceptionNotifier.deliver_exception_notification(exception, self, request, data)
end
But I want to do similar thing in my model, passing nils as self and request does not help. What can I do about it?
In our project, we use it differently:
For model, we create an initializer in project_directory/config/initializers to add it to
ActiveRecord::Base.
class ActiveRecord::Base
include ExceptionNotifiable
end
Doing this, all models exception will invoke ExceptionNotifier email as per our configuration.
For controllers, we include it in our ApplicationController so if there is an exception in any controllers, we will receive an email.
class ApplicationController < ActionController::Base
include ExceptionNotifiable
.....
end
For transaction in the controller, i will do:
class MyController < ApplicationController
.....
def create
MyModel.transaction do
.....
end
end
end
Without rescuing the exception, the ExceptionNotifier will be called automatically.
Hopefully it helps.

RoR Catch Exception on Application Level

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

Resources