Ruby: define_method vs. def - ruby

As a programming exercise, I've written a Ruby snippet that creates a class, instantiates two objects from that class, monkeypatches one object, and relies on method_missing to monkeypatch the other one.
Here's the deal. This works as intended:
class Monkey
def chatter
puts "I am a chattering monkey!"
end
def method_missing(m)
puts "No #{m}, so I'll make one..."
def screech
puts "This is the new screech."
end
end
end
m1 = Monkey.new
m2 = Monkey.new
m1.chatter
m2.chatter
def m1.screech
puts "Aaaaaargh!"
end
m1.screech
m2.screech
m2.screech
m1.screech
m2.screech
You'll notice that I have a parameter for method_missing. I did this because I was hoping to use define_method to dynamically create missing methods with the appropriate name. However, it doesn't work. In fact, even using define_method with a static name like so:
def method_missing(m)
puts "No #{m}, so I'll make one..."
define_method(:screech) do
puts "This is the new screech."
end
end
Ends with the following result:
ArgumentError: wrong number of arguments (2 for 1)
method method_missing in untitled document at line 9
method method_missing in untitled document at line 9
at top level in untitled document at line 26
Program exited.
What makes the error message more bewildering is that I only have one argument for method_missing...

define_method is a (private) method of the object Class. You are calling it from an instance. There is no instance method called define_method, so it recurses to your method_missing, this time with :define_method (the name of the missing method), and :screech (the sole argument you passed to define_method).
Try this instead (to define the new method on all Monkey objects):
def method_missing(m)
puts "No #{m}, so I'll make one..."
self.class.send(:define_method, :screech) do
puts "This is the new screech."
end
end
Or this (to define it only on the object it is called upon, using the object's "eigenclass"):
def method_missing(m)
puts "No #{m}, so I'll make one..."
class << self
define_method(:screech) do
puts "This is the new screech."
end
end
end

self.class.define_method(:screech) doesn't work,because define_method is private method
you can do that
class << self
public :define_method
end
def method_missing(m)
puts "No #{m}, so I'll make one..."
Monkey.define_method(:screech) do
puts "This is the new screech."
end

def method_missing(m)
self.class.class_exec do
define_method(:screech) {puts "This is the new screech."}
end
end
screech method will be available for all Monkey objects.

Related

Variables in class methods

could you please explain me why the class variable cannot be accessed by attribute_accessors?
As i am trying here to have the list of all methods of all subclasses in one array it works a little different. It created array #method_names in every subclass with specific methods for every class ... so i do need to do a loop through subclasses.
What kind of variable/attribute is #method_names?
Thanks!
module First
class First_class
class << self
def info
puts "First_class method info."
puts #subclasses
puts #method_names
end
def inherited(subclass)
puts "#{subclass} located ..."
subclasses << subclass
end
def subclasses
#subclasses ||= []
end
def method_added(method_name)
puts "Method located #{method_name} ..."
method_names << method_name
end
def method_names
#method_names ||= []
end
end
def initialize
puts "Instance of First_class is created."
end
def first_method
end
end
class Second_class < First_class
def self.info
puts "Second_class method info."
puts #subclasses
puts #method_names
end
def second_method
end
def initialize
puts "Instance of Second_class is created."
end
end
class Third_class < First_class
def third_method
end
def initialize
puts "Instance of Third_class is created."
end
end
end
First::First_class.subclasses.each {
|subclass| puts subclass
subclass.method_names.each {
|methodn| puts methodn
}
}
#################UPDATE#########
Ok, maybe I put the question incorrectly.
Basically what is the difference for ##method_names(class variable) and #method_names (instance variable) if i do not create the instance of object? After inserting more inputs into #method_names it still inserts into the same object_id. So what is benefit of ##method_names?
updated to answer updated question.
Classes in ruby can have class variables. However if you modify the class level variable, ALL instances will be modified. This is not recommended but will illustrate the point. But also see this answer
class Foo
##bar = 'bar'
attr_accessor :bar
def initialize
#bar = 'bar'
end
def class_bar
##bar
end
def change_class_bar string
raise ArgumentError unless string.is_a?(String)
##bar = string
end
end
a = Foo.new
b = Foo.new
# change the class variable ##bar
b.change_class_bar 'wtf?'
# see both instances are changed because objects are passed by referrence
print 'a.class_bar is: '
puts a.class_bar
print 'b.class_bar is: '
puts b.class_bar
# change one instance only
a.bar = 'only a has changed'
print 'a.bar is: '
puts a.bar
print 'b.bar is still: '
puts b.bar
run this and you should get output:
a.class_bar is: wtf?
b.class_bar is: wtf?
a.bar is: only a has changed
b.bar is still: bar
original answer left here
#method_names is an instance variable of an instance of the class from which it was instantiated. However it cannot be accessed for read/write unless those attributes are defined with getter or setter methods defined.
ff = First::First_class.new
Instance of First_class is created.
=> #<First::First_class:0x00007fde5a6867b8>
ff.method_names
NoMethodError: undefined method `method_names' for #<First::First_class:0x00007fde5a6867b8>
Did you mean? methods
Now if you call ff.methods you will see all methods defined through standard Ruby inheritance.
As a side note, class names in Ruby conventionally use PascalCase see PascalCase. Mixed_case is discouraged.

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

Calling original method from a redefined one

My question is based on an answer to the topic “redefining a single ruby method on a single instance with a lambda”.
How can I redefine a method and from within the new method call the original definition? Other instances of some_object's class should not become affected.
def some_object.some_method
# call original `some_object.some_method` here
do_something_else
end
If some_object.some_method is not a singleton method, then you can just call super in your redefined method.
def some_object.some_method
super
do_something_else
end
If some_object.some_method is a singleton method, then
Ruby >= 2.0.0
You can define that method in a module
module SomeModule
def some_method
super
do_something_else
end
end
And then prepend it to the singleton class of the object
some_object.singleton_class.prepend(SomeModule)
Ruby < 2.0.0
You have to make an alias then redefine, since there is no Module#prepend.
class << some_object # open the singleton class of some_object
alias some_method_original some_method
def some_method
some_method_original
do_something_else
end
end
class Klass
def greeting
puts "hiya"
end
def add_personal_greeting(str)
define_singleton_method(:greeting) do
super
puts str
end
end
end
Bob gets a handle and tries the standard greeting.
bob = Klass.new
#=> #<Klass:0x007fa66b084ad0>
bob.greeting
# hiya
He finds that too impersonal so he decides to add "I'm Bob" after the greeting. He does that by defining a method greeting on his singleton class that calls Klass's instance method greeting and then adds another line to the greeting.
bob.add_personal_greeting("I'm Bob")
He tries it.
bob.greeting
# hiya
# I'm Bob
Much better. Note
bob.singleton_class.superclass
#=> Klass
Meanwhile, Lucy tries the standard greeting.
lucy = Klass.new
#=> #<Klass:0x007fa66a9ed050>
lucy.greeting
# hiya
She's not wild about it, but it will do.
Bob decides to change his greeting.
bob.add_personal_greeting("I'm Robert")
and tries it.
bob.greeting
# hiya
# I'm Robert

Instance variable in a class method

I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).

checking if a method is defined on the class

How do I check if a method is defined on some class directly, not by inheritance or by inclusion/extension? I want something like 'foo?' in the following:
class A
def a; end
end
module B
def b; end
end
class C < A
include B
def c; end
end
C.foo?(:a) #=> false
C.foo?(:b) #=> false
C.foo?(:c) #=> true
Use this:
C.instance_methods(false).include?(:a)
C.instance_methods(false).include?(:b)
C.instance_methods(false).include?(:c)
The method instance_methods return an Array of methods that an instance of this class would have. Passing false as first parameter returns only methods of this class, not methods of super classes.
So C.instance_methods(false) returns the list of methods defined by C.
Then you just have to check if that method is in the returned Array (this is what the include? calls do).
See docs
For objects you can use Object.respond_to?.
Returns true if obj responds to the given method.
For classes take a look at Module.instance_methods
Returns an array containing the names of the public and protected instance methods in the receiver.
Not exactly an answer to the question, but if you're reading this question, you might be interested in this, which uses .instance_methods(false)
class Object
# This is more or less how Ruby does method lookup internally
def who_responds_to?(method, klass_ancestors = nil)
if klass_ancestors.nil?
return who_responds_to?(method, self.class.ancestors)
end
if klass_ancestors.empty?
return nil
end
if klass_ancestors[0].instance_methods(false).include?(method)
return klass_ancestors[0]
end
klass_ancestors.shift
who_responds_to?(method, klass_ancestors)
end
end
For example
class Person
end
module Drummer
def drum
end
end
module Snowboarder
def jump
end
end
module Engineer
def code
end
end
class Bob < Person
include Drummer
include Snowboarder
include Engineer
def name
end
end
puts "who responds to name"
puts bob.who_responds_to?(:name)
puts "\n"
puts "who responds to code"
puts bob.who_responds_to?(:code)
puts "\n"
puts "who responds to jump"
puts bob.who_responds_to?(:jump)
puts "\n"
puts "who responds to drum"
puts bob.who_responds_to?(:drum)
puts "\n"
puts "who responds to dance"
puts bob.who_responds_to?(:dance)
yields
who responds to name
Bob
who responds to code
Engineer
who responds to jump
Snowboarder
who responds to drum
Drummer
who responds to dance
[this line intentionally blank because return value is nil]

Resources