Ruby - Executing same code after most methods in a class - ruby

I'm trying to create some kind of module or superclass that wraps one method call after each method of the subclass.
There are some constraints though: I wouldn't want the method to be run after initialize() is called nor after a few other methods of my choice is called.
Another constraint is that I would only want that method to be executed IF the flag #check_ec is set to true.
I have classes with more than 60 methods that I have hard-coded the same piece of code that ispasted all over the place.
Is there a way that I could make a wrapper that would automatically execute that method for my class methods?
So the idea is this:
class Abstract
def initialize(check_ec)
#check_ec = check_ec
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[EC]}"
end
def methods(method) # below each method it would execute after
result = method() # execute the given method normally
after(result) if #check_ec and method != :initialize and method != :has_valid_params
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a() # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end

This is trivially easy using method_missing, and composition instead of inheritance. You can build a very simple class which forwards method invocations, and then executes an after callback, except for specific method names:
class Abstract
def initialize(object)
#object = object
end
def method_missing(method, *arguments)
result = #object.send(method, *arguments)
after() unless method == "has_valid_params"
result
end
def after
# whatever
end
end
o = Abstract.new(MyClass.new)

A solution using singleton class.
class MyClass
def initialize(name, some_stuff)
# do some stuff...
#name = name
end
def my_method_a # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end
module ErrorCodeChecker
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:EC]}"
end
def addErrorCodeCheck(exclude = [])
methods = self.class.superclass.public_instance_methods(false) - exclude
class << self
self
end.class_exec {
methods.each {|method|
define_method(method) {|*p|
super(*p).tap {|res| after(res)}
}
}
}
end
end
class MyClassEC < MyClass
include ErrorCodeChecker
def initialize(name, some_stuff, check_error_code, exclude = [])
super name, some_stuff
addErrorCodeCheck(exclude) if check_error_code
end
end
'addErrorCodeCheck' opens up the singleton class of an instance of MyClassEC, and redefines instance methods of MyClass not in the exclude list. The redefined methods hide the original methods but call them via 'super' method inside before calling 'after'.
You can apply 'addErrorCodeCheck' repeatedly later if needed.
Execution example: (tested in Ruby 1.9.3)
my = MyClassEC.new('test', 'abc', true, [:has_valid_params])
my.my_method_a # => ERROR CODE: 0
my.my_method_b # => ERROR CODE: 7
my.has_valid_params # => (nothing)

What about this? It has a major drawback which is that your methods must be already defined before calling check_error_code, but it may suit your needs. You could look for inspiration for a better solution in Rails callbacks, or defer the redefinition of each method until that method is added using the method_added hook.
Include ErrorCodeChecker and call check_error_code in each class you want to check the error code (as in the last line of the snippet).
module ErrorCodeChecker
def self.included(base)
base.send(:extend, ClassMethods)
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:ec]}"
end
module ClassMethods
def check_error_code(options = {})
check_on = instance_methods(false) - Array(options[:except])
check_on &= Array(options[:only]) if options[:only]
class_eval do
check_on.each do |method|
alias_method "#{ method }_without_ec", method
define_method(method) do |*args, &block|
send("#{ method }_without_ec", *args, &block).tap { |result| after(result) if #check_ec }
#if you want to actually return the return value of calling after:
#result = send("#{ method }_without_ec")
##check_ec ? after(result) : result
end
end
end
end
end
end
class Abstract
include ErrorCodeChecker
def initialize(check_ec)
#check_ec = check_ec
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a # execute after() after this method
{ec: 0}
end
def my_method_b # execute after() after this method
{ec: 7}
end
def has_valid_params # don't execute after() on this method
true
end
check_error_code except: :has_valid_params
#or whitelisting:
#check_error_code only: [:my_method_a, :my_method_b]
#or both:
#check_error_code only: :my_method_a, except: [:has_valid_params, dont_check_this_one]
end

Related

Why would you call `super` with a block in the initialize method of a class inherting from Module in Ruby?

When inheriting from Module you can pass a block to super() and it is executed:
class Foo < Module
def initialize
super() do
# do some other stuff...
end
end
end
Why would you use this over just calling super(), without the block, followed by the rest of the code, such as:
class Foo < Module
def initialize
super()
# do some other stuff...
end
end
An less contrived example would be:
class QuestionAttr < Module
def initialize(method_name)
super() do
define_method "#{method_name}?" do
!!public_send(method_name)
end
end
end
end
Fruit = Struct.new(:colour) do
include QuestionAttr.new(:colour)
end
fruit = Fruit.new('red')
fruit.colour? # => true
Why would I define_method inside the block passed to super(), as per the above example, instead of omitting the block passed to super() and define_method afterwards.

How to create a method that executes a previously given block in Ruby?

I have a class that was built for subclassing.
class A
def initialize(name)
end
def some
# to define in subclass
end
end
# usage
p A.new('foo').some
#=> nil
In my use case, I don't want to create a subclass since I need just one instance. Therefore, I'll change the initialize method to support the following usage.
p A.new('foo') { 'YEAH' }.some
#=> YEAH
How could I support the usage above?
BTW: I found the following solutions for a Ruby 1.8.7 project, but they look awkward to me.
class A
def singleton_class
class << self; self; end
end
def initialize(name, &block)
#name = name
self.singleton_class.send(:define_method, :some) { block.call } if block_given?
end
def some
# to define in subclass
end
end
You can store the block argument in an instance variable and call it later on:
class A
def initialize(name, &block)
#name = name
#block = block
end
def some
#block.call
end
end
A.new('foo') { 'YEAH' }.some
#=> "YEAH"
You can also pass arguments into the block:
class A
# ...
def some
#block.call(#name)
end
end
A.new('foo') { |s| s.upcase }.some
#=> "FOO"
Or instance_exec the block in the context of the receiver:
class A
# ...
def some
instance_exec(&#block)
end
end
Which allows you to bypass encapsulation:
A.new('foo') { #name.upcase }.some
#=> "FOO"

Custom Hook/Callback/Macro Methods

How do I create a Custom Hook Method in a Subclass?
No need to duplicate Rails, of course -- the simpler, the better.
My goal is to convert:
class SubClass
def do_this_method
first_validate_something
end
def do_that_method
first_validate_something
end
private
def first_validate_something; end
end
To:
class ActiveClass; end
class SubClass < ActiveClass
before_operations :first_validate_something, :do_this_method, :do_that_method
def do_this_method; end
def do_that_method; end
private
def first_validate_something; end
end
Example in Module: https://github.com/PragTob/after_do/blob/master/lib/after_do.rb
Rails #before_action: http://apidock.com/rails/v4.0.2/AbstractController/Callbacks/ClassMethods/before_action
Here's a solution that uses prepend. When you call before_operations for the first time it creates a new (empty) module and prepends it to your class. This means that when you call method foo on your class, it will look first for that method in the module.
The before_operations method then defines simple methods in this module that first invoke your 'before' method, and then use super to invoke the real implementation in your class.
class ActiveClass
def self.before_operations(before_method,*methods)
prepend( #active_wrapper=Module.new ) unless #active_wrapper
methods.each do |method_name|
#active_wrapper.send(:define_method,method_name) do |*args,&block|
send before_method
super(*args,&block)
end
end
end
end
class SubClass < ActiveClass
before_operations :first_validate_something, :do_this_method, :do_that_method
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18#/tmp.rb:31>}
If you want to make the idea by #SteveTurczyn work you must:
receive the args params in the block of define_method, not as arguments to it.
call before_operations AFTER your methods have been defined if you want to be able to alias them.
class ActiveClass
def self.before_operations(before_method, *methods)
methods.each do |meth|
raise "No method `#{meth}` defined in #{self}" unless method_defined?(meth)
orig_method = "_original_#{meth}"
alias_method orig_method, meth
define_method(meth) do |*args,&block|
send before_method
send orig_method, *args, &block
end
end
end
end
class SubClass < ActiveClass
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
before_operations :first_validate_something, :do_this_method, :do_that_method
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18#/tmp.rb:31>}
You can alias the original method to a different name (so :do_this_something becomes :original_do_this_something) and then define a new :do_this_something method that calls :first_validate_something and then the original version of the method Something like this...
class ActiveClass
def self.before_operations(before_method, *methods)
methods.each do |method|
alias_method "original_#{method.to_s}".to_sym, method
define_method(method, *args, &block) do
send before_method
send "original_#{method.to_s}", *args, &block
end
end
end
end
This is a way of writing the code that does not make use of aliases. It includes a class method validate that specifies the validator method and the methods that are to call the validator method. This method validate can be invoked multiple times to change the validator and validatees dynamically.
class ActiveClass
end
Place all the methods other than the validators in a subclass of ActiveClass named (say) MidClass.
class MidClass < ActiveClass
def do_this_method(v,a,b)
puts "this: v=#{v}, a=#{a}, b=#{b}"
end
def do_that_method(v,a,b)
puts "that: v=#{v}, a=#{a}, b=#{b}"
end
def yet_another_method(v,a,b)
puts "yet_another: v=#{v}, a=#{a}, b=#{b}"
end
end
MidClass.instance_methods(false)
#=> [:do_this_method, :do_that_method, :yet_another_method]
Place the validators, together with a class method validate, in a subclass of MidClass named (say) SubClass.
class SubClass < MidClass
def self.validate(validator, *validatees)
superclass.instance_methods(false).each do |m|
if validatees.include?(m)
define_method(m) do |v, *args|
send(validator, v)
super(v, *args)
end
else
define_method(m) do |v, *args|
super(v, *args)
end
end
end
end
private
def validator1(v)
puts "valid1, v=#{v}"
end
def validator2(v)
puts "valid2, v=#{v}"
end
end
SubClass.methods(false)
#=> [:validate]
SubClass.private_instance_methods(false)
#=> [:validator1, :validator2]
The class method validate passes symbols for the validation method to use and the methods to be validated. Let's try it.
sc = SubClass.new
SubClass.validate(:validator1, :do_this_method, :do_that_method)
sc.do_this_method(1,2,3)
# valid1, v=1
# this: v=1, a=2, b=3
sc.do_that_method(1,2,3)
# valid1, v=1
# that: v=1, a=2, b=3
sc.yet_another_method(1,2,3)
# yet_another: v=1, a=2, b=3
Now change the validation.
SubClass.validate(:validator2, :do_that_method, :yet_another_method)
sc.do_this_method(1,2,3)
# this: v=1, a=2, b=3
sc.do_that_method(1,2,3)
# valid2, v=1
# that: v=1, a=2, b=3
sc.yet_another_method(1,2,3)
# valid2, v=1
# yet_another: v=1, a=2, b=3
When super is called without arguments from a normal method, all arguments and a block, if there is one, are passed to super. If the method was created with define_method, however, no arguments (and no block) are passed to super. In the latter case the arguments must be explicit.
I wanted to pass a block or proc on to super if there is one, but have been using the wrong secret sauce. I would welcome advice for doing that.

How can I intercept method call in ruby?

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

Ruby methods similar to attr_reader

I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class

Resources