Why is Kernel method looked up only when `send` is used? - ruby

I should be able to call Kernel methods on every object, and method format is defined on Kernel. Why is method_missing invoked on Kernel with the third example?
class A
def method_missing(meth, *args, &block)
if meth == :foo
puts 'ok'
elsif meth == :format
puts 'ok'
end
end
end
a = A.new
a.foo # => ok
a.send(:foo) # => ok
a.format # => ok
a.send(:format) # => too few arguments (ArgumentError)

That is because Kernel#format is a private method. When you call it using send, which means you are calling it without an explicit receiver, the defined method is called, and argument error is raised. When you call it with an explicit receiver, the method is not found because the defined one is private, and so method_missing is invoked.

Related

How to call any instance method in ruby object without instantiating it?

I am creating a helper module to initialize the object before calling its methods
module Initialized
extend ActiveSupport::Concern
class_methods do
def run(*args)
new(*args).run
end
def call(*args)
new(*args).call
end
def execute(*args)
new(*args).create
end
end
end
So instead of defining run, call, and execute in my helper module I need to receive any method name and check if it exists on the main class after initializing it, then call the requested instance method if exists in the main class or raise an error if not
I would say my targeted code would be something like this
module Initialized
extend ActiveSupport::Concern
class_methods do
def _(*args, methodname)
new(*args).try(:send, "#{methodname}") || raise 'Method not exist'
end
end
end
Sample usage would be
class MyClass
include Initialized
def initialize(param1)
#param1 = param1
end
def call
puts "call '#{#param1}'"
end
end
then calling
MyClass.call('method param')
I found these links but couldn't find my answer yet:
meta-dynamic-generic-programming-in-ruby
ruby-module-that-delegates-methods-to-an-object
template-methods-in-ruby
Despite the fact method_missing would do the job, I'd probably avoid it in this case in favor of a more explicit delegation. Simple and dirty example:
module InstanceDelegator
def delegate_to_instance(*methods)
methods.each do |method_name|
define_singleton_method method_name do |*args|
new(*args).public_send(method_name)
end
end
end
end
class Foo
extend InstanceDelegator
delegate_to_instance :bar # <- define explicitly which instance methods
# should be mirrored by the class ones
def bar
puts "bar is called"
end
def baz
puts "baz is called"
end
end
# and then
Foo.bar # => bar is called
Foo.baz # NoMethodError ...
# reopening works too
class Foo
delegate_to_instance :baz
end
Foo.baz # baz is called
Pros:
you don't need to redefine method_missing (less magic -> less pain when you debug the code)
you control precisely which instance methods to be wrapped with the class level "shorthand" (fewer chances to delegate something you don't want to - more robust code)
(minor) no need to raise NoMethodError explicitly - you can fully rely on the core dispatching as it is...
I found another solution instead of using a module,
I can use the class method self.method_missing
def self.method_missing(method_name, *args, &block)
obj = new(*args)
raise NoMethodError, "undefined method `#{method_name}' for #{self}:Class" unless obj.respond_to?(method_name)
obj.send(method_name, &block)
end
But the limitation is that I have to copy it into every class whenever I need to use this feature

How to fix "NoMethodError" when method_missing is defined for given class?

I have a class called RubyCsvRow, which holds a row of a CSV file, in a hash. I am using method_missing to allow any column to be used as a function to return the value of the row at that column. However, I get a method_missing error when I run I attempt to use it.
I wasn't sure exactly what was happening, so I replaced the call to one with a call to class.
m = RubyCsv.new
m.each{|row| puts row.class}
I edited the method missing in RubyCsvRow so that I could see what happens when it prints and see the name of the missing method:
def self.method_missing(name, *args, &block)
puts "Called Method Missing"
puts name.to_s
end
The return only confused me more.
Called Method Missing
to_ary
RubyCsvRow
Called Method Missing
to_ary
RubyCsvRow
It calls method missing. I don't know why it prints name as to_ary, which when I searched I found this, but I am not sure when it is being implicitly converted or why.
I searched around and have looked at these links. The labels where why I thought they didn't fit.
I have my private variable defined as a :attr_accesssor
Mine is a method of a class and I am using it like one
I am calling my method after defining it
I am not sure about this one. I am already converting my symbol to_s, but I had trouble determining if this fit
Why I decided to format my each method in RubyCsv the way I did
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
def self.method_missing(name, *args, &block)
if #values.key?(name.to_s)
#values[name.to_s]
else
puts "No Column with name: ", name.to_s, " found!"
end
end
def to_s
self.inspect
end
end
r = RubyCsvRow.new({"one" => "dog", "two" => "cat" })
puts r.one
RubyCsvRow is used in RubyCsv's each, but I get the same error with just this code. I can post the RubyCsv code, but this is the minimum code to reproduce the error.
I get a NoMethodError for one instead of printing dog.
Try to use def method_missing instead of self. You call method on instance not class it self.
If you define method with self you define the method as class not instance. In your code you create new instance of RubyCsvRow Class and you need to define method_missing as instace method.
Modify code here:
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
# Define method missing on instance
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# return my error message
"No Column with name: #{name} found!"
end
def to_s
inspect
end
end
r = RubyCsvRow.new({ "one" => "dog", "two" => "cat" })
puts r.one
# => dog
puts r.test
# => "No Column with name: test found!"
BTW: If you need the original error message use super in method_missing method
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# call original method
super
end

Monkey patching: Does define_method take precedence over bind?

In the below code snippet, I monkey patch Foo#bar with a define_method block. After I initialize a new instance of Foo, I overwrite it with a bind call to the parent class bar method, but when I call the method the block defined by the define_method block is called. Why doesn't the bind call change the behavior of the method?
class OriginalFoo
def bar
puts 'in OriginalFoo!'
end
end
class Foo < OriginalFoo
def bar
puts 'in Foo'
end
end
class Foo < OriginalFoo
old_bar = instance_method(:bar)
define_method(:bar) do
puts 'in define_method block'
old_bar.bind(self).call
end
end
foo_instance = Foo.new # => #<Foo:0x00007fe3ff037038>
OriginalFoo.instance_method(:bar).bind(foo_instance) # => #<Method: OriginalFoo#bar>
foo_instance.bar
# >> in define_method block
# >> in Foo
You're misunderstanding how UnboundMethod#bind works. You call Module#instance_method to get an UnboundMethod (i.e. a method without a self):
OriginalFoo.instance_method(:bar)
# #<UnboundMethod: ... >
Then you call UnboundMethod#bind to attach a self to that method, that returns a Method instance:
m = OriginalFoo.instance_method(:bar).bind(foo_instance)
# => #<Method: ...>
But that won't alter the method in foo_instance, all it does is make self your foo_instance when (or if) you said m.call: um.bind(obj) doesn't do anything to obj, it just gives you um as a Method that has obj as its self.

Object that accepts all method names and prints them out

I would like to create an object that accepts method names and prints them out. I should be able to call any method on it. For example,
obj.hello("was")
# => called hello with argument 'was'
obj.ok(["df", 1])
# => called ok with argument ["df", 1]
I don't want to define hello or ok in advance.
Is that possible?
Easy:
class Noop
def method_missing(m, *args)
puts "#{m} #{args.inspect}"
end
end
Noop.new.foo
# => foo []
Noop.new.bar(1,2,3)
# => bar [1, 2, 3]
method_missing is called on every Ruby object when you call a method that does not exist. It usually ends up being handled by Object (the superclass of everything) which raises NoMethodError.
Note that this does not apply to the methods provided by its ancestors (Class, Module, Object, Kernel, BasicObject) which you can inspect by:
class Noop
puts self.instance_methods.inspect
puts self.methods.inspect
def method_missing(m, *args)
puts "#{m} #{args.inspect}"
end
end

How to run a method right after method dispatch and before the method is called?

When I have the following:
class Foo
def bar
puts "#{__method__} was called and found within #{self}"
end
def method_missing(meth, *args, &blk)
puts "#{meth} was called and was not found within #{self}"
end
end
foo = Foo.new
foo.bar
# => bar was called and found within #<Foo:0x100138a98>
foo.baz
# => baz was called and was not found within #<Foo:0x100138a98>
I assume that when the method is found, the method dispatch looks a bit like so:
foo.bar was asked to be called
Search methods defined within #<Foo:0x100138a98>
Method `bar` found
Call the `bar` method
And for methods not found:
foo.baz was asked to be called
Search methods defined within #<Foo:0x100138a98>
Method `baz` not found
Search methods defined within the parent of #<Foo:0x100138a98>
Method `baz` not found
And so on until it hits the parent that has no parent
Loop back around and see if #<Foo:0x100138a98> has a `method_missing` method defined
Method `method_missing` found
Call the `method_missing` method
I would like to step in like so:
foo.bar was asked to be called
Search methods defined within #<Foo:0x100138a98> to see it has a `method_dispatched` method
Method `method_dispatched` found
Calling `method_dispatched`
Search methods defined within #<Foo:0x100138a98>
...
This would allow developers to do something like below:
class Foo
def bar
puts "#{__method__} was called and found within #{self}"
end
def method_missing(meth, *args, &blk)
puts "#{meth} was called and was not found within #{self}"
end
def method_dispatched(meth, *args, &blk)
puts "#{meth} was called within #{self}..."
puts "continuing with the method dispatch..."
end
end
foo = Foo.new
foo.bar
# => bar was called within #<Foo:0x100138a98>...
# => continuing with the method dispatch...
# => bar was called and found within #<Foo:0x100138a98>
foo.baz
# => bar was called within #<Foo:0x100138a98>...
# => continuing with the method dispatch...
# => baz was called and was not found within #<Foo:0x100138a98>
This brings me to the question..
Is this possible?
I'm not aware of a callback for what you're looking for. I would read up on Ruby Delegators for a possible solution that's more elegant than what I've sketched below.
You can wrap the object and intercept on method_missing.
class A
def foo
puts "hi there, I'm A"
end
end
maybe have B inherit A?
class B
def initialize
#a = A.new
end
def method_missing(m, *args, &block)
puts "hi there, I'm in B"
#a.send(m, *args, &block) if #a.respond_to? m
puts "all done"
end
end
Sort of a Ruby beginner here, but I have some working solution. So please comment on the code if you think something is not ruby-esque.
The idea is to alias the methods that you have and undef_method the original method. This will create a alias for all your instance methods. You can then have a method_missing that can call method_dispatched and then the actual method.
class Test
def foo
"foo"
end
def method_dispatched(meth, *args, &blk)
puts "#{meth} was called within #{self}..."
puts "continuing with the method dispatch..."
end
def method_missing(name, *args, &block)
method_dispatched(name, args, block) #Calls your standard dispatcher
if (respond_to?('_' + name.to_s)) # Check if we have a aliased method
send('_' + name.to_s)
else
"undef" #Let the caller know that we cant handle this.
end
end
instance_methods(false).each do |meth|
if meth != 'method_missing' and meth != 'method_dispatched'
alias_method "_#{meth}", meth
remove_method(meth)
end
end
end
t = Test.new
puts t.foo()
puts t.undefined_method()

Resources