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")
Related
I am using Mailchimp (via the Gibbon gem) to add email addresses to my Mailchimp mailing list, and I want to handle any errors that are returned by Mailchimp and display them in my view.
Here is my Pages controller:
class PagesController < ApplicationController
def subscribe
email = subscriber_params[:email]
if email.empty?
flash[:error] = 'Please provide an email.'
redirect_to root_path
else
subscriber = Mailchimp.new.upsert(email)
if subscriber
flash[:success] = 'You\'re in!'
redirect_to root_path(subscribed: :true)
else
# Return error coming from Mailchimp (i.e. Gibbon::MailChimpError)
end
end
end
end
And here is the app > services > mailchimp.rb file I set up to separate out the Mailchimp logic:
class Mailchimp
def initialize
#gibbon = Gibbon::Request.new(api_key: Rails.application.credentials.mailchimp[:api_key])
#list_id = Rails.application.credentials.mailchimp[:list_id]
end
def upsert(email)
begin
#gibbon.lists(#list_id).members.create(
body: {
email_address: email,
status: "subscribed"
}
)
rescue Gibbon::MailChimpError => e #This is at the bottom of the Gibbon README
raise e.detail
end
end
end
What I'm trying to figure out is how to return/send Gibbon::MailChimpError back to my Pages#subscribe action. I see it being outputted as a RuntimeError in my console, but I'm not sure the right way to access/pass it along.
And please let me know if there's a better practice for this kind of implementation.
You could move the begin/rescue block to the subscribe action inside your controller to handle the error from there, or even better, you can use rescue_from in your controller like this:
class PagesController < ApplicationController
rescue_from Gibbon::MailChimpError do |e|
# Handle the exception however you want
end
def subscribe
# ....
end
end
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."
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.
I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end
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