Can't access custom Exception attribute - ruby-on-rails

I've got a custom Exception class declared like so:
class CustomError < StandardError
def initialize(message = nil)
#message = message
#type = "custom_error"
end
end
That is being handled in my Application controller like so:
rescue_from CustomError do |e|
render json: e.type
end
Now, when I raise the Exception using raise CustomError.new("Oops I did it again") I get a NoMethodError with undefined method `type'
What's going on? Why can't I just access type using e.type?

You can't call e.type because you haven't defined a type method.
You can use
attr_reader :type
To add such an accessor.

Turns out I was missing attr_reader. Here's what the final Exception class looks like:
class CustomError < StandardError
attr_reader :type
def initialize(message = nil)
#message = message
#type = "custom_error"
end
end
Reference: https://stackoverflow.com/a/4371458/2022751

Related

Mock scope method from ActiveRecord in Rails/RSpec?

I have an ActiveRecord class like:
class DBGroup < ActiveRecord:Base
# ...
scope :example_method, -> { // does something }
# ...
end
And now I have another method in another class, something like
def method1
do #open block for rescue method
DBGroup
.example_method
.group(:some_column)
.having("COUNT(*) > 5")
rescue StandardError => e
e
end
end
And in RSpec I am testing kind of like:
it "my test, want to test rescue block"
expect_any_instance_of(DBGroup).to receive(:example_method).and_raise(StandardError)
expect{subject.method1}.to raise_error(StandardError)
end
But I am getting the error like:
RSpec::Mocks::MockExpectationError: DBGroup does not implement #example_method
Scopes are not instance methods, they are class methods, so you should not use expect_instance_of, but expect.
Also, you are rescuing the StandardError inside the method, so the method itself does not raise a StandardError. Actually, a StandardError is raised inside the method and it's rescued inside of it aswel and your test is all about what's happening at it's exterior.
I hope you can see the difference:
def method_that_raises_an_error
raise(StandardError)
end
def method_that_returns_an_error
raise(StandardError)
rescue StandardError => e
e
end
As you can see, your code is more like the second. So, to test it you should do:
expect(DBGroup).to(receive(:example_method).and_raise(StandardError)
method1_return = subject.method1
expect(method1_return).to(be_instance_of(StandardError))

Serializing errors to JSON in Rails 6

I’ve been trying to find and an easy way to serialize all of the errors to JSON in Rails 6. Not writing the custom ones, but just add a standard JSON format for them. Wrote a serializer like:
class ErrorSerializer
def initialize(error)
#error = error
end
def to_h
serializable_hash
end
def to_json(_payload=nil)
to_h.to_json
end
private
def serializable_hash
{
errors: [error.serializable_hash].flatten
}
end
attr_reader :error
end
And in my User controller, tried to render error with:
render json: ErrorSerializer.new(#user.errors.to_hash), status: :bad_request
But getting an error:
NoMethodError (undefined method `serializable_hash')
Has anyone encountered something similar? And, in general, what is the best/easiest way to serialize all errors to JSON in Rails 6 (ideally, if there won’t be a need to raise/render errors on each endpoint in the controller)
In case someone needs some simple errors serializer. This worked fine for me:
class ActiveRecordErrorsSerializer
attr_reader :object
def initialize(object)
#object = object
end
def to_h
serializable_hash
end
def as_json(_payload = nil)
{ data: { type: 'errors' }, attributes: to_h }
end
private
def serializable_hash
object.errors.messages.flat_map do |field, errors|
errors.flat_map do |error_message|
{
source: { pointer: "/data/attributes/#{field}" },
details: error_message
}
end
end
end
end

Rails - custom exceptions (errors)

I'm trying to build my own Exception for tagged logging:
module Exceptions
class GeneralException < StandardError
LOGGER_NAME = 'Base'
def initialize(message)
#logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
#logger.tagged(get_logger_name) { #logger.error message }
#message = message
end
def get_logger_name
self.class::LOGGER_NAME
end
end
class InvalidDataException < GeneralException; end
class SecurityException < GeneralException
LOGGER_NAME = 'Security'
end
class ElasticSearchException < GeneralException
LOGGER_NAME = 'Elastic'
end
end
I'm expecting to be able to call this new exception with:
raise Exceptions::SecurityException "Something security related happened.
The problem is that when I call this I get:
NoMethodError: undefined method 'SecurityException' for Exceptions:Module
Any idea how to correctly raise this error?
Well, quite easy, you need to raise the instance of the error:
raise Exceptions::SecurityException.new "Something security related happend."
or
raise Exceptions::SecurityException, "Something security related happend."

create elasticsearch index transactional

Sorry for my bad English,This is not my first language.
I tried to create an index on elasticsearch but I want to be transactional.
Means if error occurred on saving process or on creating index both processes rollback.
I have these lines to save an entry
ActiveRecord::Base.transaction do
entries.map do |entry|
entry = entries.where(source_entry_id: entry.entry_id).first_or_initialize
entry.data = feed_entry.to_hash(self)
entry.save!
end
end
then I defined this class on concern to have searchable functionality for some entities
require 'elasticsearch/model'
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
after_save { index_document }
after_destroy { delete_document }
end
module ClassMethods
def create_index!
self.__elasticsearch__.create_index! force: true
end
def search_index(*args)
self.__elasticsearch__.search(*args)
end
end
def index_document
return if Rails.env.test?
self.__elasticsearch__.index_document
rescue StandardError => e
handle_error(e)
end
def delete_document
return if Rails.env.test?
self.__elasticsearch__.delete_document
rescue StandardError => e
handle_error(e)
end
def format_date(date)
date.to_s(:iso8601) if date
end
def handle_error(e)
error = Searchable.parse_error(e.message)
if error['result'] != 'not_found'
Raven.capture_message("Elasticsearch: #{error['result']}", extra: error)
end
end
def self.parse_error(string)
parts = string.match(/\[(\d*)\]\s(.*)/)
return ({ 'result': string }) unless parts
result = JSON.parse(parts[2])
result['status'] = parts[1]
result
end
end
and my model include searchable
class
Entry < ApplicationRecord
include Searchable
...
Problem that I faced is if I rescue the error entry will save into database if I throw exception and not handle the exception process will freeze and not doing for the rest entries. how can I do this correctly?
Elasticsearch is not transactional.
You may want to add a transactional queue system in the middle or just log when something goes wrong and manually deal with inconsistencies.
You can also send errors in a message queue system to process them later.

Show only exceptions messages to user

I've created this code to show specific errors messages to user:
application_controller.rb
class ApplicationController < ActionController::Base
rescue_from Exception do |exception|
message = exception.message
message = "default error message" if exception.message.nil?
render :text => message
end
end
room_controller.rb
class RoomController < ApplicationController
def show
#room = Room.find(params[:room_id]) # Can throw 'ActiveRecord::RecordNotFound'
end
def business_method
# something
raise ValidationErros::BusinessException("You cant do this") if something #message "You cant do this" should be shown for user
#...
end
def business_method_2
Room.find(params[:room_id]).do_something
end
end
room.rb
class Room < ActiveRecord::Base
def do_something
#...
raise ValidationErrors::BusinessException("Invalid state for room") if something #message "Invalid state for room" should be shown for user
#...
end
end
app/models/erros/validation_errors.rb
module ValidationErrors
class BusinessException < RuntimeError
attr :message
def initialize(message = nil)
#message = message
end
end
end
example.js
$.ajax({
url: '/room/show/' + roomId,
success: function(data){
//... do something with data
},
error: function(data){
App.notifyError(data) //show dialog with message
}
});
But I can not use the class BusinessException. When BusinessException should be raised,
the message
uninitialized constant Room::ValidationErrors
is shown to user.
if I change this code:
raise ValidationErrors::BusinessException("Invalid state for room") if something
by this:
raise "Invalid state for room" if something
It works.
What change to this code works with BusinessException with messages. I need this
to create specifics rescue_from methods in ApplicationController.
Edit:
Thank you for comments!
My error is it doesn't know ValidationErrors Module. How to import this Module to my class?
I've tested add theses lines to lines:
require 'app/models/errors/validation_errors.rb'
require 'app/models/errors/validation_errors'
But then raise the error:
cannot load such file -- app/models/errors/validation_errors
Solution:
https://stackoverflow.com/a/3356843/740394
config.autoload_paths += %W(#{config.root}/app/models/errors)
raise ::ValidationErrors::BusinessException("Invalid state for room")

Resources