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.
I want to pass a class method to another function, how can I do that?
class A
def A.test(data)
puts data
end
end
def ps(fun)
fun(3)
end
ps(A.test)
You have to use the right getter to receive the method object:
class A
def A.test(data)
puts data
end
end
def ps(fun)
fun.call(3)
end
ps(A.method(:test)) #=> 3
The method method returns the method-object, which can be executed with call.
You can pass just the method_name to ps and have ps send the method_name message to the A class (and always the A class) using this:
class A
def A.test(data)
puts data
end
end
def ps(method_name)
A.send(method_name, 3)
end
ps(:test) # => 3
Or you can pass both the A class and the method_name to ps using this:
class A
def A.test(data)
puts data
end
end
def ps(klass, method_name)
klass.send(method_name, 3)
end
ps(A, :test) # => 3
instance_eval method change self in its block, eg:
class D; end
d = D.new
d.instance_eval do
puts self # print something like #<D:0x8a6d9f4>, not 'main'!
end
If we define a method ourself(or any other methods(other than instance_eval) which takes a block), when print self, we will get 'main', which is different from instance_eval method.eg:
[1].each do |e|
puts self # print 'main'
end
How can i define a method(which takes a block) like instance_eval?
Thanks in advance.
You can write a method that accepts a proc argument, and then pass that as a proc argument to instance_eval.
class Foo
def bar(&b)
# Do something here first.
instance_eval &b
# Do something else here afterward, call it again, etc.
end
end
Foo.new.bar { puts self }
Yields
#<Foo:0x100329f00>
It's obvious:
class Object
def your_method(*args, &block)
instance_eval &block
end
end
receiver = Object.new
receiver.your_method do
puts self #=> it will print the self of receiver
end
I want to use the combination method with a custom class.
If my class looks like this...
class MyClass
def initialize
#data = []
end
def to_a
#data
end
end
I could call this...
myobj = MyClass.new
myobj.to_a.combination(2) {|a,b| puts "#{a} #{b}" }
But I'd much rather have this...
myobj.combination {|a,b| puts "#{a} #{b}" }
I've tried to write a class method to wrap the combination method, passing the block. But it's not working.
def combination(&block)
#data.to_a.combination(2) block.call
end
Also, does anyone know why combination is in the Array class and not Enumerable? I'd have thought it would have been more useful there.
The block is a special type of parameter to Array#combination (much like you've got it in your own definition). The correct invocation is:
def combination(&block)
#data.to_a.combination(2, &block)
end
Try this:
def combination(&block)
#data.to_a.combination(2) { block }
end
I'm trying to figure out how to dynamically create methods
class MyClass
def initialize(dynamic_methods)
#arr = Array.new(dynamic_methods)
#arr.each { |m|
self.class.class_eval do
def m(*value)
puts value
end
end
}
end
end
tmp = MyClass.new ['method1', 'method2', 'method3']
Unfortunately this only creates the method m but I need to create methods based on the value of m, ideas?
There are two accepted ways:
Use define_method:
#arr.each do |method|
self.class.class_eval do
define_method method do |*arguments|
puts arguments
end
end
end
Use class_eval with a string argument:
#arr.each do |method|
self.class.class_eval <<-EVAL
def #{method}(*arguments)
puts arguments
end
EVAL
end
The first option converts a closure to a method, the second option evaluates a string (heredoc) and uses regular method binding. The second option has a very slight performance advantage when invoking the methods. The first option is (arguably) a little more readable.
define_method(m) do |*values|
puts value
end