How can i catch all exception in ruby class like this:
class ServiceException
rescue => e
puts 'Error!!!'
end
class ServiceA < ServiceException
def say_hello
error_code_here
end
end
ServiceA.new.say_hello
# => Error!!!
OK so i managed do accomplish this with some metaprogramming. First define this module:
module MetaprogrammingStuff
def rescue_all_methods(klass, *errors_to_rescue, &blk)
patch_all_methods(klass) do |orig_method, *args, &caller_blk|
begin
orig_method.call *args, &caller_blk
rescue *errors_to_rescue => e
blk.call e
end
end
end
def patch_all_methods(klass, &blk)
patch_class_methods(klass, &blk)
patch_instance_methods(klass, &blk)
end
def patch_class_methods(klass, &blk)
klass.methods(false).each do |fn_name|
orig_method = klass.method fn_name
klass.singleton_class.send(:define_method, fn_name) do |*args, &caller_blk|
blk.call orig_method, *args, &caller_blk
end
end
end
def patch_instance_methods(klass, &blk)
klass.instance_methods(false).each do |fn_name|
orig_method = klass.instance_method fn_name
klass.send(:define_method, fn_name) do |*args, &caller_blk|
blk.call orig_method.bind(self), *args, &caller_blk
end
end
end
end
then extend it to a class and run rescue_all_methods:
class TestClass
extend MetaprogrammingStuff
def foo
raise ArgumentError
end
def self.foo
raise RuntimeError
end
rescue_all_methods(self, ArgumentError, RuntimeError) do |error|
puts "#{error.class} was raised."
end
end
It's important that rescue_all_methods be called after the methods are defined.
You can see it inserts a rescue block around all instance and class methods:
TestClass.foo
# => RuntimeError was raised.
TestClass.new.foo
# => ArgumentError was raised.
How it works - basically it turns the methods into anonymous fuctions using the method and instance_method methods, then re-assigns those method names to new methods which call the original but with a begin..rescue wrapper.
Related
Basically, this is normal code:
class Foo
def hi
# your code here....
rescue => e
# Raise error here
end
def hello
# your code here...
rescue => e
# Raise error here
end
end
But in PHP, i can use __call magic method to create abstract class, look like this:
class FooAbstract {
public function __call($name, $args) {
# Try catch in here...
}
}
class Foo extends FooAbstract {
public function hi() {
# Code with try catch...
}
}
How can i use __call method in Ruby class???
You could define a module that when included defines a method_added hook that wraps all new methods inside a begin..rescue block:
require 'set'
module ExceptionHandler
def self.included(klass)
super
klass.send(:extend, ClassMethods)
end
module ClassMethods
def exception_handler(&block)
#__exception_handler = block.to_proc
end
def handle_exception(exception)
defined?(#__exception_handler) ? #__exception_handler.call(exception) : super
end
def handle_method_exceptions(method_name)
old_method = instance_method(method_name)
return if (#__hooked_methods ||= Set.new).include?(method_name)
#__ignoring_added_methods = true # avoid infinite define_method/method_added loop
define_method method_name do |*args, &block|
begin
old_method.bind(self).(*args, &block)
rescue => ex
self.class.handle_exception(ex)
end
end
#__ignoring_added_methods = false
#__hooked_methods << method_name
end
def method_added(method_name)
super
unless #__ignoring_added_methods
handle_method_exceptions(method_name)
end
end
end
end
This would be used like:
class Foo
include ExceptionHandler
exception_handler do |exception|
puts "Catched an exception:"
puts "---------------------"
puts "Exception class: #{exception.class}"
puts "Message: #{exception.message}"
puts "Backtrace:"
puts exception.backtrace.join("\n ")
puts
puts "reraising.."
puts
raise exception
end
def this_raises
raise "somebody set up us the bomb"
end
end
Foo.new.this_raises
This would output:
Catched an exception:
---------------------
Exception class: RuntimeError
Message: somebody set up us the bomb
Backtrace:
errorhandler.rb:62:in `this_raises'
errorhandler.rb:26:in `call'
errorhandler.rb:26:in `block in handle_exceptions'
errorhandler.rb:67:in `<main>'
reraising..
I'm not sure if it is a good idea.
You could take out the method_added part and it would look something like:
class Foo
with_rescue def foofoo(arg)
puts arg.inspect
end
end
(You can just rename the handle_method_exceptions to with_rescue and remove all the #__ignoring_added_methods trickery and the method_added method and it should work as described).
I'm not sure what you want to achieve here, but the Ruby equivalent of PHP's __call() is method_missing.
By default, when you try to call a non-existing method you will get an exception. But if you want to implement an "abstract class". You could also try this solution: https://stackoverflow.com/a/512505/185870
class A
def a_method
#..
end
end
class B < A
def method_1
# ...
a_method
end
def method_2
# ...
a_method
end
# ...
def method_n
# ...
a_method
end
end
The a_method ocassionally throws an AException.
I want to rescue from that exception, like:
class B < A
def method_1
# ...
a_method
rescue AException => e
p e.message
end
# ...
end
I want to rescue the same way in each methods inside class B (method_1, method_2, ..., method_n). I'm stuck on figuring out a nice and clean solution, that would not require to duplicate the rescue code block. Can you help me with that?
How about to use a block:
class B < A
def method_1
# some code here which do not raised an exception
with_rescue do
# method which raised exception
a_method
end
end
def method_2
with_rescue do
# ...
a_method
end
end
private
def with_rescue
yield
rescue => e
...
end
end
If you want to always rescue the exception, you could just override a_method in B:
class B < A
def a_method
super
rescue AException => e
p e.message
end
# ...
end
In addition you might want to return a value (like nil or false) to indicate the failure.
Like this, perhaps?
class B < A
def method_1
# ...
safe_a_method
end
private
def safe_a_method
a_method
rescue AException => e
...
end
end
You can wrap your methods using a Module like this.
The benefit is that unlike the other solutions you can call your methods with their regular names and the methods themselves don't have to be changed.
Just extend the class with the ErrorHandler method en at the end enumerate the methods to wrap them with your errorhandling logic.
module ErrorHandler
def wrap(method)
old = "_#{method}".to_sym
alias_method old, method
define_method method do |*args|
begin
send(old, *args)
rescue => e
puts "ERROR FROM ERRORHANDLER #{e.message}"
end
end
end
end
class A
extend ErrorHandler
def a_method v
"a_method gives #{v.length}"
end
(self.instance_methods - Object.methods).each {|method| wrap method}
end
class B < A
extend ErrorHandler
def method_1 v
"method_1 gives #{v.length}"
end
(self.instance_methods - Object.methods).each {|method| wrap method}
end
puts A.new.a_method "aa" # a_method gives 2
puts A.new.a_method 1 # ERROR FROM ERRORHANDLER undefined method `length' for 1:Fixnum
puts B.new.method_1 1 # ERROR FROM ERRORHANDLER undefined method `length' for 1:Fixnum
This my implementation to developing way to run code before all method in your model
The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_used
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail
#rathrio This is my implementation using method_added that you talked. Thanks
module ExecutionHooks
def validation
p "works1"
end
def self.included(base)
base.send :extend, ClassMethods
end
end
module ClassMethods
attr_writer :hooked
def hooked
#hooked ||= []
end
def method_added(method)
return if #hooks.nil?
return unless #hooks.include?(method)
m = self.instance_method(method)
unless hooked.include?(method)
hooked << method
define_method(method) do |*args, &block|
validation
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
end
def hooks
#hooks ||= []
end
end
end
class BalanceChart < BalanceFind
include ExecutionHooks
before_hook :months_data, :months_used, :debits_amount, :test
def test
"test"
end
end
Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.
I'm writing aspect-ish code more or less lifted from the chosen solution for this question that looks like the following:
class Module
def add_logging klass, *method_names
method_names.each do |method_name|
original_method = instance_method method_name
define_method method_name do |*args, &blk|
log.debug("#{klass}.#{method_name} called")
original_method.bind(klass).call(*args, &blk)
end
end
end
end
The solution in the other post doesn't require the klass parameter, but it only works for instance methods, whereas I hope to call my code like this:
module MyModule
def MyModule.module_method
p "hello"
end
class << self
add_logging self, :module_method1
end
end
Unfortunately when I run this code I get in 'bind': singleton method called for a different object (TypeError). Seeing as I pass self in the context of the class << self block, I don't understand why the bind call in the above code thinks it isn't binding to the exact same metaclass.
This should work for you:
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = method(method_name).unbind
define_singleton_method(method_name) do |*args, &blk|
puts "#{self}.#{method_name} called"
original_method.bind(self).call(*args, &blk)
end
end
end
end
# class method example
module MyModule
def self.module_method1
puts "hello"
end
add_logging :module_method1
end
MyModule.module_method1
# output:
#
# MyModule.module_method1 called
# hello
I currently have a superclass which has a function that I want all the subclass to call within each of its function. The function is supposed to behave like a before_filter function in rails but I am not sure on how to go about implementing before_filter. Here is an example
class Superclass
def before_each_method
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
This is one way to do it:
class Superclass
def before_each_method name
p [:before_method, name]
end
def self.method_added name
return if #__last_methods_added && #__last_methods_added.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
#__last_methods_added = [name, with, without]
define_method with do |*args, &block|
before_each_method name
send without, *args, &block
end
alias_method without, name
alias_method name, with
#__last_methods_added = nil
end
end
class SubclassA < Superclass
def my_new_method
p :my_new_method
end
def my_new_other_method
p :my_new_other_method
end
end
SubclassA.new.my_new_method
SubclassA.new.my_new_other_method
This will create a wrapper method using the alias_method_chaining method as soon as the method you'd like to wrap is defined in the subclass.
This is my solution:
require 'active_support/all'
module BeforeEach
extend ActiveSupport::Concern
module InstanceMethods
def before_each
raise NotImplementedError('Please define before_each method')
end
end
module ClassMethods
def method_added(method)
method = method.to_s.gsub(/_with(out)?_before$/, '')
with_method, without_method = "#{method}_with_before", "#{method}_without_before"
return if method == 'before_each' or method_defined?(with_method)
define_method(with_method) do |*args, &block|
before_each
send(without_method, *args, &block)
end
alias_method_chain(method, :before)
end
end
end
To use it, just include BeforeEach into your class like so:
class Superclass
include BeforeEach
def before_each
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
Subclass.new.my_method
# => Before Method
Hopefully this will work for you!
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_data, :months_used, :debits_amount
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end