From within the method definition, is there a way to tell if the method method_missing was explicitly called or was called as a hook method?
With the method initialize, it is possible to tell if it was called explicitly or was called as a hook by doing the following:
class A
def initialize
puts caller
end
end
When called as a hook, the method initialize has a line with new:
a = A.new
# => ...:in `new'
# => ...
# => ...:in `<main>'
When called explicitly, it does not have such line (unless it is explicitly called from new):
a.send(:initialize)
# => ...
# => ...:in `<main>'
But when I do a similar thing with method_missing, I cannot distinguish the two cases:
class A
def method_missing *;
puts caller
end
end
a.foo
# => ...
# => ...:in `<main>'
a.method_missing
# => ...
# => ...:in `<main>'
You could check the first argument:
class A
def method_missing(name = nil, *)
if name
puts "called via `#{name}'"
else
puts "called directly"
end
end
end
A.new.foo
#=> called via `foo'
A.new.method_missing
#=> called directly
Unlike initialize, which is invoked explicitly via the Class#new, the BasicObject#method_missing is invoked by the Ruby interpreter:
Invoked by Ruby when obj is sent a message it cannot handle. symbol is
the symbol for the method called, and args are any arguments that were
passed to it. By default, the interpreter raises an error when this
method is called. However, it is possible to override the method to
provide more dynamic behavior.
Kernel#caller will not include those locations in the execution stack trace from where Ruby interpreter invoked the method_missing if that's what you are looking for.
Related
I'm trying to define a macro that wraps another method, then pass the instance to the macro. Here's a contrived example...
module Wrapper
def wrap(method_name, options: {})
class_eval do
new_method = "_wrap_#{method_name}"
alias_method new_method, method_name
define_method method_name do |*args, &block|
# binding.pry
puts "Options... #{options}"
send new_method, *args, &block
end
end
end
end
class Thing
extend Wrapper
class << self
extend Wrapper
def bar
puts 'Bar!'
end
wrap :bar, options: -> { bar_options }
private
def bar_options
{ bar: 2 }
end
end
def foo
puts 'Foo!'
end
wrap :foo, options: -> { foo_options }
private
def foo_options
{ foo: 1 }
end
end
So, I know I have access to the instance from within define_method, but I don't think I should have to create / rename / alias methods within my classes to conform to a module - I'd like to pass the config in, so to speak. I feel like instance_exec/eval is my friend, here, but I can't seem to get the incantation correct. I tried with passing a block to the code as well, but yield behaved the same as the proc. Maybe binding, but I can't wrap my head around that at all, for some reason.
This is from within the define_method call...
> Thing.new.foo
=> Options... #<Proc:0x00007fcb0782cfd8#(irb):24>
Foo!
> self.class
=> Thing
> self
=> #<Thing:0x00007fdfb78b88e8>
> options
=> #<Proc:0x00007fcb0782cfd8#(irb):24>
> options.call
# NameError: undefined local variable or method `foo_options` for Thing:Class
> foo_options
=> {:foo=>1}
> self.class.instance_eval &options
# NameError: undefined local variable or method `foo_options` for Thing:Class
> self.class.instance_exec &options
# NameError: undefined local variable or method `foo_options` for Thing:Class
I understand how a proc captures scope to use for later, so I can see how the use of a proc/lambda is incorrect here. When the class loads, the wrap method "wraps" the method and captures foo_options at the class level to be called for later - which doesn't exist at the class level. Calling options: foo_options does the same thing, just blows up on load.
Any bit helps... Thanks!
I was so close... While within define_method, I have access to the instance, and can call instance_exec on self, instead of self.class! Also, there is a newer, preferred approach to wrapping methods since ruby 2.0.
module Wrapper
def wrap(method_name, options: {})
proxy = Module.new
proxy.define_method(method_name) do |*args, &block|
options = instance_exec(&options) if options.is_a?(Proc)
target = is_a?(Module) ? "#{self}." : "#{self.class}#"
puts "#{target}#{method_name} is about to be called. `wrap` options #{options}"
super *args, &block
end
self.prepend proxy
end
end
Output:
> Thing.new.foo
Thing#foo is about to be called. `wrap` options {:foo=>1}
Foo!
=> nil
> Thing.bar
Thing.bar is about to be called. `wrap` options {:bar=>2}
Bar!
=> nil
This is much cleaner than the "old way" that Sergio mentions in the comments. This Question helped me out!
Another great benefit of this approach is that you can define the macro at the top of the file (where they belong, arguably).
Is there a way to specify a class method such that when the object is used as if it were a function, that method is called? Something like this:
class MyClass
def some_magic_method(*args)
# stuff happens
end
end
# create object
myob = MyClass.new
# implicitly call some_magic_method
myob 'x'
You could write a command class and make use of a ruby shortcut
class MyClass
def self.call(text)
puts text
end
end
MyClass.('x')
Here MyClass.() defaults to the call class method.
As mentioned by #CarySwoveland in the comments you can use method_missing. A basic example is as follows:
class MyClass
def method_missing(method_name, *args)
if method_name.match?(/[xyz]/)
send(:magic_method, args.first)
else
super
end
end
def magic_method(a)
a = 'none' if a.nil?
"xyz-magic method; argument(s): #{a}"
end
end
myob = MyClass.new
myob.x #=> "xyz-magic method; argument(s): none"
myob.x(1) #=> "xyz-magic method; argument(s): 1"
myob.y #=> "xyz-magic method; argument(s): none"
myob.z #=> "xyz-magic method; argument(s): none"
This captures all methods named x, y or z. Our else branch sends all other undefined methods to the original method_missing:
myob.v #=> test.rb:7:in `method_missing': undefined method `v' for
#<MyClass:0x000000021914f8> (NoMethodError)
#from test.rb:25:in `<main>'
What methods you capture is up to you and is determined by the regex /[xyz]/ in this case.
Key methods: BasicObject#method_missing, Object#send. For further info check out this question, read Eloquent Ruby by Russ Olsen (from which this answer references)
You meant to invoke some class' instance method when the object is invoked as a function. This is already supported: instance method call gets called when you "invoke" an object via the functional invocation method () (for more details, see here How do I reference a function in Ruby?).
class C
def call(x)
puts "Called with #{x}"
end
end
obj = C.new
obj.(88) # Called with 88 => nil
obj (88) # NoMethodError: undefined method `obj' for main:Object
If you do want the latter syntax, a horrible trick is the following one (but works only at the top-level, unless you carry along the bindings):
module Kernel
def method_missing(name,*args)
obj = begin
TOPLEVEL_BINDING.local_variable_get(name)
rescue
nil
end
return super if obj.nil?
obj.send :call, *args
end
end
obj = C.new
obj 88 # Called with OK => nil
This example also wants to communicate that you should always keep in mind
who is the receiver of your method calls, and what syntaxes are available for calling methods (especially when you leave out dots and parentheses).
class D
def obj; C.new end
def f
#(obj) 88 # BAD
(obj).(88)
#obj() 88 # BAD
obj().(88)
end
end
The point is that you do not actually have functions, but methods that get called on objects. If you omit the receiver of a method call, the receiver defaults to self, the current object. But in your example, myob does not appear as an explicit receiver (since there is not following dot as in myob.), hence the current object is looked for a method myob.
I'm able to get access to a Ruby method's arguments using the TracePoint API:
def foo(foo_arg)
end
trace = TracePoint.trace(:call, :c_call) do |tp|
tp.disable
case tp.method_id
when :foo, :sub
method = eval("method(:#{tp.method_id})", tp.binding)
method.parameters.each do |p|
puts "#{p.last}: #{tp.binding.local_variable_get(p.last)}"
end
end
tp.enable
end
trace.enable
foo(10)
# => foo_arg: 10
However when I try this with a c method call, I get an error.
"foo".sub(/(f)/) { $1.upcase }
script.rb:20:in `method': undefined method `sub' for class `Object' (NameError)
from script.rb:20:in `<main>'
from script.rb:8:in `eval'
from script.rb:8:in `block in <main>'
from script.rb:20:in `<main>'
This looks like it happens because of a discrepancy between the binding returned when using a C method call and regular Ruby method call.
In the Ruby case tp.self is equal to tp.binding.eval("self") is main however in the C case tp.self is "foo" and tp.binding.eval("self") is main. Is there a way to get the arguments passed into a method using TracePoint for both Ruby and C defined methods?
As you point in your question and as it documented in ruby documentation, tp.self returns a traced object, which have a method method you are looking for.
I think you should use
method = tp.self.method(tp.method_id)
instead of
method = eval("method(:#{tp.method_id})", tp.binding)
UPDATE. Some explanation regarding your last paragraph in question. tp.self in first case (when you call foo) is point to main, because you define foo method in main context and it points to String object in second case because sub is defined there. But tp.binding.eval("self") returns main in both cases because it returns a calling context (not a 'define' context as you expect) and in both cases it is main.
UPDATE (in reply to comment) I think that the only way to do this is to monkey patch sub and all other methods that you are interesting for. Code example:
class String
alias_method :old_sub, :sub
def sub(*args, &block)
old_sub(*args, &block)
end
end
trace = TracePoint.trace(:call, :c_call) do |tp|
tp.disable
case tp.method_id
when :sub
method = tp.self.method(tp.method_id)
puts method.parameters.inspect
end
tp.enable
end
trace.enable
"foo".sub(/(f)/) { |s| s.upcase }
One big drawback is that you can't use $1, $2, ... vars in your original blocks. As pointed here where is no way to make it works. However you can still use block parameters (s in my example).
I am trying to disable the unused method in gem that is present in my rails app. Is it possible?
You can use remove_method http://apidock.com/ruby/Module/remove_method
I'm curious why you want to do this though.
You can override the method and let it behave differently or the below ruby built in approach:
undef_method http://ruby-doc.org/core-2.0.0/Module.html#method-i-undef_method
or
remove_method http://ruby-doc.org/core-2.0.0/Module.html#remove_method-method
If you want to remove method from the particular class (not from the ancestors) you should use remove_method.
If also you want to search superclasses and mixed-in modules for a receiver then use undef_method.
There's also undef keyword which acts similar as remove_method but probably a bit faster. It receives method name (not symbol or string).
Usage:
class Parent
def foo; end
def baz; end
end
class Child < Parent
def bar; end
end
Child.send :remove_method, :bar # I use send for space-economy.
# You should reopen the class
Child.new.bar # => NoMethodError
Child.send :remove_method, :foo # NameError: method `foo' not defined in Child
Child.send :undef_method, :foo
Child.new.foo # => NoMethodError
Parent.class_eval { undef baz }
Parent.new.baz # => NoMethodError
I'm confused when to use each of this methods.
From respond_to? documentation:
Returns true if obj responds to the given method. Private methods
are included in the search only if the optional second parameter
evaluates to true.
If the method is not implemented, as Process.fork on Windows,
File.lchmod on GNU/Linux, etc., false is returned.
If the method is not defined, respond_to_missing? method is called and
the result is returned.
And respond_to_missing?:
Hook method to return whether the obj can respond to id method or
not.
See #respond_to?.
Both methods takes 2 arguments.
Both methods seems to the same thing(check if some object respond to given method) so why we should use(have) both?
Defining 'resond_to_missing?` gives you ability to take methods:
class A
def method_missing name, *args, &block
if name == :meth1
puts 'YES!'
else
raise NoMethodError
end
end
def respond_to_missing? name, flag = true
if name == :meth1
true
else
false
end
end
end
[65] pry(main)> A.new.method :meth1
# => #<Method: A#meth1>
Why respond_to? couldn't do this?
What I guess:
respond_to? checks if method is in:
Current object.
Parent object.
Included modules.
respond_to_missing? checks if method is:
Defined via method_missing:
Via array of possible methods:
def method_missing name, *args, &block
arr = [:a, :b, :c]
if arr.include? name
puts name
else
raise NoMethodError
end
end
Delegating it to different object:
class A
def initialize name
#str = String name
end
def method_missing name, *args, &block
#str.send name, *args, &block
end
end
2 . Other way that I'm not aware of.
Where should both be defined/used(my guessing too):
Starting from 1.9.3(as fair I remember) define only respond_to_missing? but use only respond_to?
Last questions:
Am I right? Did I missed something? Correct everything that is bad and/or answer questions asked in this question.
respond_to_missing? is supposed to be updated when you make available additional methods using the method missing technique. This will cause the Ruby interpreter to better understand the existence of the new method.
In fact, without using respond_to_missing?, you can't get the method using method.
Marc-André posted a great article about the respond_to_missing?.
In order for respond_to? to return true, one can specialize it, as follows:
class StereoPlayer
# def method_missing ...
# ...
# end
def respond_to?(method, *)
method.to_s =~ /play_(\w+)/ || super
end
end
p.respond_to? :play_some_Beethoven # => true
This is better, but it still doesn’t make play_some_Beethoven behave exactly like a method. Indeed:
p.method :play_some_Beethoven
# => NameError: undefined method `play_some_Beethoven'
# for class `StereoPlayer'
Ruby 1.9.2 introduces respond_to_missing? that provides for a clean solution to the problem. Instead of specializing respond_to? one specializes respond_to_missing?. Here’s a full example:
class StereoPlayer
# def method_missing ...
# ...
# end
def respond_to_missing?(method, *)
method =~ /play_(\w+)/ || super
end
end
p = StereoPlayer.new
p.play_some_Beethoven # => "Here's some_Beethoven"
p.respond_to? :play_some_Beethoven # => true
m = p.method(:play_some_Beethoven) # => #<Method: StereoPlayer#play_some_Beethoven>
# m acts like any other method:
m.call # => "Here's some_Beethoven"
m == p.method(:play_some_Beethoven) # => true
m.name # => :play_some_Beethoven
StereoPlayer.send :define_method, :ludwig, m
p.ludwig # => "Here's some_Beethoven"
See also Always Define respond_to_missing? When Overriding method_missing.