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
Related
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
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.
I was experimenting with making a DSL and ran across something that confuses me. In my call method I wanted to set an initial value for #mymethod before evaulating the block. It works if I assign to the variable directly:
class Test
class << self
attr_accessor :mymethod
end
def self.call(&block)
#mymethod="foo"
class_eval &block
end
end
Test.call do
puts "mymethod returned: #{mymethod}"
mymethod = "bar"
puts "mymethod is now: #{mymethod}"
end
Which returns:
[1] pry(main)> load 'test.rb'
mymethod returned: foo
mymethod is now: bar
=> true
But I feel like this should work and it doesn't. The only thing that has changed is the # has been removed from the assignment to mymethod so I think it should be using the mymethod= method created by attr_accessor:
class Test
class << self
attr_accessor :mymethod
end
def self.call(&block)
mymethod="foo"
class_eval &block
end
end
Test.call do
puts "mymethod returned: #{mymethod}"
mymethod = "bar"
puts "mymethod is now: #{mymethod}"
end
However the assignment to mymethod from within call fails while the same assignment inside the block succeeds:
[1] pry(main)> load 'test.rb'
mymethod returned:
mymethod is now: bar
=> true
What's going on here? Can someone explain to my why the assignment would fail inside the call method?
in your case, mymethod="foo" will define mymethod local variable
rather than call mymethod= method.
use self.mymethod="foo" instead
I am trying to find a way that I can override a method, do something, and then revert without leaving any artifacts around.
I have implemented this using mocha but obviously this is not going to fly in a production app. Notice the new method has parameters and the old one does not.
Example as follows
require 'rubygems'
require 'mocha'
class Example
def to_something
self.stubs(:attribs => other(1))
r = attribs_caller
self.unstub(:attribs)
r
end
def other(int)
{"other" => int }
end
def attribs_caller
attribs
end
def attribs
{"this" => 1 }
end
end
a1 = Example.new
puts a1.attribs_caller #=> this1
puts a1.to_something #=> other1
puts a1.attribs_caller #=> this1
class String
alias orig_reverse reverse
def reverse(n)
'fooled you. '*n
end
end
puts "ab".reverse(2)
#=> fooled you fooled you
# clean up:
class String
alias reverse orig_reverse
remove_method(:orig_reverse)
end
puts "ab".reverse #=> ba
Another way to do that, without creating an extra method, is this:
class Foo
def bar
:old_method
end
end
Foo.new.bar # => :old_method
$old_method = Foo.new.method(:bar)
class Foo
def bar
:new_method
end
end
Foo.new.bar # => :new_method
class Foo
define_method($old_method.name, &$old_method)
end
Foo.new.bar # => :old_method
I think that this is better than using an alias method. In Ruby methods are, also, objects. I just take the reference of the object before destructing the association of the object (the method) with the class. After I add the same method. It also works if you use the undef keyword to remove the method from the class. The bad point is that you have to have an object of the class to take the reference of the method.
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