I understand that
def a(&block)
block.call(self)
end
and
def a()
yield self
end
lead to the same result, if I assume that there is such a block a {}. My question is - since I stumbled over some code like that, whether it makes any difference or if there is any advantage of having (if I do not use the variable/reference block otherwise):
def a(&block)
yield self
end
This is a concrete case where i do not understand the use of &block:
def rule(code, name, &block)
#rules = [] if #rules.nil?
#rules << Rule.new(code, name)
yield self
end
The only advantage I can think of is for introspection:
def foo; end
def bar(&blk); end
method(:foo).parameters #=> []
method(:bar).parameters #=> [[:block, :blk]]
IDEs and documentation generators could take advantage of this. However, it does not affect Ruby's argument passing. When calling a method, you can pass or omit a block, regardless of whether it is declared or invoked.
The main difference between
def pass_block
yield
end
pass_block { 'hi' } #=> 'hi'
and
def pass_proc(&blk)
blk.call
end
pass_proc { 'hi' } #=> 'hi'
is that, blk, an instance of Proc, is an object and therefore can be passed to other methods. By contrast, blocks are not objects and therefore cannot be passed around.
def pass_proc(&blk)
puts "blk.is_a?(Proc)=#{blk.is_a?(Proc)}"
receive_proc(blk)
end
def receive_proc(proc)
proc.call
end
pass_proc { 'ho' }
blk.is_a?(Proc)=true
#=> "ho"
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'm wondering if you can use a defined method into another defined method
like for example
def method1(example)
funtion1
end
def method2(example)
funtion2
end
Like how can you use method1 to method2
def method_1(arg)
arg.call
end
def method_2
puts 'hi'
end
method_1(method(:method_2)) #=> should print 'hi'
You can't pass a method to a method. However you can pass a proc to a method.
Define a proc like so
proc = Proc.new {|x| puts x}
I want to intercept method calls on a ruby-class and being able to do something before and after the actual execution of the method. I tried the following code, but get the error:
MethodInterception.rb:16:in before_filter': (eval):2:inalias_method': undefined method
say_hello' for classHomeWork'
(NameError)
from (eval):2:in `before_filter'
Can anybody help me to do it right?
class MethodInterception
def self.before_filter(method)
puts "before filter called"
method = method.to_s
eval_string = "
alias_method :old_#{method}, :#{method}
def #{method}(*args)
puts 'going to call former method'
old_#{method}(*args)
puts 'former method called'
end
"
puts "going to call #{eval_string}"
eval(eval_string)
puts "return"
end
end
class HomeWork < MethodInterception
before_filter(:say_hello)
def say_hello
puts "say hello"
end
end
I just came up with this:
module MethodInterception
def method_added(meth)
return unless (#intercepted_methods ||= []).include?(meth) && !#recursing
#recursing = true # protect against infinite recursion
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).call(*args, &block)
puts 'after'
end
#recursing = nil
end
def before_filter(meth)
(#intercepted_methods ||= []) << meth
end
end
Use it like so:
class HomeWork
extend MethodInterception
before_filter(:say_hello)
def say_hello
puts "say hello"
end
end
Works:
HomeWork.new.say_hello
# before
# say hello
# after
The basic problem in your code was that you renamed the method in your before_filter method, but then in your client code, you called before_filter before the method was actually defined, thus resulting in an attempt to rename a method which doesn't exist.
The solution is simple: Don't Do That™!
Well, okay, maybe not so simple. You could simply force your clients to always call before_filter after they have defined their methods. However, that is bad API design.
So, you have to somehow arrange for your code to defer the wrapping of the method until it actually exists. And that's what I did: instead of redefining the method inside the before_filter method, I only record the fact that it is to be redefined later. Then, I do the actual redefining in the method_added hook.
There is a tiny problem in this, because if you add a method inside of method_added, then of course it will immediately get called again and add the method again, which will lead to it being called again, and so on. So, I need to guard against recursion.
Note that this solution actually also enforces an ordering on the client: while the OP's version only works if you call before_filter after defining the method, my version only works if you call it before. However, it is trivially easy to extend so that it doen't suffer from that problem.
Note also that I made some additional changes that are unrelated to the problem, but that I think are more Rubyish:
use a mixin instead of a class: inheritance is a very valuable resource in Ruby, because you can only inherit from one class. Mixins, however, are cheap: you can mix in as many as you want. Besides: can you really say that Homework IS-A MethodInterception?
use Module#define_method instead of eval: eval is evil. 'Nuff said. (There was absolutely no reason whatsoever to use eval in the first place, in the OP's code.)
use the method wrapping technique instead of alias_method: the alias_method chain technique pollutes the namespace with useless old_foo and old_bar methods. I like my namespaces clean.
I just fixed some of the limitations I mentioned above, and added a few more features, but am too lazy to rewrite my explanations, so I repost the modified version here:
module MethodInterception
def before_filter(*meths)
return #wrap_next_method = true if meths.empty?
meths.delete_if {|meth| wrap(meth) if method_defined?(meth) }
#intercepted_methods += meths
end
private
def wrap(meth)
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).(*args, &block)
puts 'after'
end
end
def method_added(meth)
return super unless #intercepted_methods.include?(meth) || #wrap_next_method
return super if #recursing == meth
#recursing = meth # protect against infinite recursion
wrap(meth)
#recursing = nil
#wrap_next_method = false
super
end
def self.extended(klass)
klass.instance_variable_set(:#intercepted_methods, [])
klass.instance_variable_set(:#recursing, false)
klass.instance_variable_set(:#wrap_next_method, false)
end
end
class HomeWork
extend MethodInterception
def say_hello
puts 'say hello'
end
before_filter(:say_hello, :say_goodbye)
def say_goodbye
puts 'say goodbye'
end
before_filter
def say_ahh
puts 'ahh'
end
end
(h = HomeWork.new).say_hello
h.say_goodbye
h.say_ahh
Less code was changed from original. I modified only 2 line.
class MethodInterception
def self.before_filter(method)
puts "before filter called"
method = method.to_s
eval_string = "
alias_method :old_#{method}, :#{method}
def #{method}(*args)
puts 'going to call former method'
old_#{method}(*args)
puts 'former method called'
end
"
puts "going to call #{eval_string}"
class_eval(eval_string) # <= modified
puts "return"
end
end
class HomeWork < MethodInterception
def say_hello
puts "say hello"
end
before_filter(:say_hello) # <= change the called order
end
This works well.
HomeWork.new.say_hello
#=> going to call former method
#=> say hello
#=> former method called
Jörg W Mittag's solution is pretty nice. If you want something more robust (read well tested) the best resource would be the rails callbacks module.
How would I use the parameter value as the instance variable name of an object?
This is the object
Class MyClass
def initialize(ex,ey)
#myvar = ex
#myothervar = ey
end
end
I have the following method
def test(element)
instanceofMyClass.element #this obviously doesnt work
end
How can I have the test method return either myvar or myothervar value depending on the element parameter. I don't want to write an if condition though, I want to pass myvar or myother var via element to the object instance if possible.
def test(element)
instanceofMyClass.send(element.to_sym)
end
You'll get a missing method error if instanceofMyClass doesn't respond to element.
def test(element)
instanceofmyclass.instance_variable_get element
end
test :#myvar # => ex
test :#myothervar # => ey
I like the simplicity of send(), though one bad thing with it is that it can be used to access privates. The issue is still remains solution below, but at least then it's explicitly specified, and reader can see which methods are to be forwarded. The first one just uses delegation, while the second one uses more dynamic way to define methods on the fly.
require 'forwardable'
class A
extend Forwardable
def_delegators :#myinstance, :foo, :bar
class B
def foo
puts 'foo called'
end
def bar
puts 'bar called'
end
def quux
puts 'quux called'
end
def bif
puts 'bif called'
end
end
def initialize
#myinstance = B.new
end
%i(quux bif).each do |meth| # note that only A#quux and A#bif are defined dynamically
define_method meth do |*args_but_we_do_not_have_any|
#myinstance.send(meth)
end
end
end
a = A.new
a.foo
a.bar
a.quux
a.bif