How to define instance method in ruby dynamically? - ruby

I want to dynamically create instance method of child class through class method of parent class.
class Foo
def self.add_fizz_method &body
# ??? (This is line 3)
end
end
class Bar < Foo
end
Bar.new.fizz #=> nil
class Bar
add_fizz_method do
p "i like turtles"
end
end
Bar.new.fizz #=> "i like turtles"
What to write on line #3?

use define_method like this:
class Foo
def self.add_fizz_method &block
define_method 'fizz', &block
end
end
class Bar < Foo; end
begin
Bar.new.fizz
rescue NoMethodError
puts 'method undefined'
end
Bar.add_fizz_method do
p 'i like turtles'
end
Bar.new.fizz
output:
method undefined
"i like turtles"

define_method 'fizz' do
puts 'fizz'
end
...or accepting a block
define_method 'fizz', &block

Related

In Ruby, what's the best way to execute a block around a child method?

I have a parent class:
class Base
def my_method
block_method do
# EXECUTE WHATEVER'S IN THE CHILD VERSION OF my_method
# HOW TO DO?
end
end
def block_method
original_foo = 'foo'
foo = 'CUSTOM FOO'
yield
foo = original_foo
end
end
and some child classes--currently five and there will be more:
class ChildA < Base
def my_method
puts 'apple'
puts 'aardvark'
end
end
class ChildE < Base
def my_method
puts 'eel'
puts 'elephant'
end
end
I want to change what a variable refers to just for the duration of each Child's #my_method.
My thought is to do this by wrapping the functionality of each Child's #my_method in a block. But I'd rather do that in the parent class than have to wrap each child class's #my_method in the exact same block.
Any insights on how I can do this?
If there's some opposite of super, I guess that would be one way to accomplish what I want to do.
You could give the idea of what you are calling "some opposite of super" using a module and prepend like so:
module MethodThing
# added to remove binding from class since
# #my_method relies on the existence of #foo and #foo=
def self.prepended(base)
base.attr_reader(:foo) unless base.method_defined?(:foo)
base.attr_writer(:foo) unless base.method_defined?(:foo=)
end
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
super
rescue NoMethodError
warn "(skipped) #{self.class}##{__method__} not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
Prepend the module on inheritance
class Base
def self.inherited(child)
child.prepend(MethodThing)
end
attr_accessor :foo
def initialize
#foo = 12
end
end
class ChildA < Base
def my_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
class ChildE < Base
end
Output:
ChildA.new.my_method
# Before: 12
# apple
# During: CUSTOM FOO
# aardvark
# After: 12
ChildE.new.my_method
# Before: 12
# (skipped) ChildE#my_method not defined
# After: 12
There are other strange ways to accomplish this with inheritance as well such as
class Base
class << self
attr_accessor :delegate_my_method
def method_added(method_name)
if method_name.to_s == "my_method" && self.name != "Base"
warn "#{self.name}#my_method has been overwritten use delegate_my_method instead"
end
end
end
attr_accessor :foo
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
method(self.class.delegate_my_method.to_s).()
rescue NameError, TypeError
warn "(skipped) #{self.class} method delegation not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
class ChildA < Base
self.delegate_my_method = :delegation_method
def delegation_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
I could probably keep going with stranger and stranger ways to solve this problem but I think these will get you where you need to go.
One option would be to define a private or nodoc method which the parent class can call and is defined in each of the children.
class Parent
def my_method
block_method do
my_method_behavior
end
end
def block_method
original_foo = #foo
#foo = 'CUSTOM FOO'
yield
#foo = original_foo
end
end
class Child1 < Parent
def my_method_behavior
puts #foo
end
end
class Child2 < Parent
def my_method_behavior
puts #foo
end
end

Creating a method that functions as both a class and instance method

Let's say I had a class and I wanted to be able to call the same method on the class itself and an instance of that class:
class Foo
def self.bar
puts 'hey this worked'
end
end
This lets me do the following:
Foo.bar #=> hey this worked
But I also want to be able to do:
Foo.new.bar #=> NoMethodError: undefined method `bar' for #<Foo:0x007fca00945120>
So now I modify my class to have an instance method for bar:
class Foo
def bar
puts 'hey this worked'
end
end
And now I can call bar on both the class and an instance of the class:
Foo.bar #=> hey this worked
Foo.new.bar #=> hey this worked
Now my class Foo is 'wet':
class Foo
def self.bar
puts 'hey this worked'
end
def bar
puts 'hey this worked'
end
end
Is there a way to avoid this redundancy?
Have one method call the other. Otherwise, no, there is no way to avoid this "redundancy", because there is no redundancy. There are two separate methods that happen to have the same name.
class Foo
def self.bar
puts 'hey this worked'
end
def bar
Foo.bar
end
end

understand self for attr_accessor class method

class Test
class << self
attr_accessor :some
def set_some
puts self.inspect
some = 'some_data'
end
def get_some
puts self.inspect
some
end
end
end
Test.set_some => Test
puts Test.get_some.inspect => Test nil
Here above I could find self as Test itself but not returning the some_data as output.
But while I modified in following way it returns expected output
class Test
class << self
attr_accessor :some
def set_some
puts self.inspect
self.some = 'some_data'
end
def get_some
puts self.inspect
self.some
end
end
end
Test.set_some => Test
puts Test.get_some.inspect => Test some_data
What is the differences?
EDIT
Now in the first example if I set as get some method as
Test.some = 'new_data'
puts Test.some.inspect #=> new_data
Test.set_some
puts Test.get_some.inspect => new_data
Now it made me much more confused.
some = :foo makes ruby think it should create a new local variable with name some. If you want to call some=(), you have to use an explicit reciever - as in self.some = :foo. I once lost a bet on that... :-/
It's (local) variable in the first example
In the first example some is a local variable.
In the second one, some is a method of self. Why? Because attr_accessor :some is the same as:
def some= (val)
#some = val
end
def some
return #some
end
So, you have created the getter and setter methods for the instance variable #some (it's an instance variable of the object Test, as every class is also an object of class Class).
in the first method
def set_some
puts self.inspect
some = 'some_data'
end
some is a local variable.. its not the same as #some that is a instance variable (in this case a class instance variable) so the value disappears when the method ends.
if you want to call the setter method some or set #some to something then do this
#some = 'some_data'
or
self.some = 'some_data'
in the second method
def get_some
puts self.inspect
self.some
end
your calling the method some. which returns the instace variable #some.. and since at this point #some has no value.. returns nil..
Example 1 with no method override and no local variable
class Foo
def initialize
#foo = 'foo'
end
def print_foo
print #foo
print self.foo
print foo
end
end
#foo, self.foo, and foo will access instance variable #foo within the instance method:
Foo.new.print_foo #=> foofoofoo
Example 2 with method override
class Foo
def initialize
#foo = 'foo'
end
def foo
return 'bar'
end
def print_foo
print #foo
print self.foo
print foo
end
end
#foo will access the instance variable, but self.foo and foo will call the foo override method:
Foo.new.print_foo #=> foobarbar
Example 3 with method override and local variable
class Foo
def initialize
#foo = 'foo'
end
def foo
return 'bar'
end
def print_foo
foo = 'baz'
print #foo
print self.foo
print foo
end
end
#foo accesses instance variable, self.foo accesses override method, and foo accesses local variable:
Foo.new.print_foo #=> foobarbaz

Calling Super Methods in Ruby

I am trying to define some classes in Ruby that have an inheritance hierarchy, but I want to use one of the methods in the base class in the derived class. The twist is that I don't want to call the exact method I'm in, I want to call a different one. The following doesn't work, but it's what I want to do (basically).
class A
def foo
puts 'A::foo'
end
end
class B < A
def foo
puts 'B::foo'
end
def bar
super.foo
end
end
Probably, this is what you want?
class A
def foo
puts 'A::foo'
end
end
class B < A
alias bar :foo
def foo
puts 'B::foo'
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo
A more general solution.
class A
def foo
puts "A::foo"
end
end
class B < A
def foo
puts "B::foo"
end
def bar
# slightly oddly ancestors includes the class itself
puts self.class.ancestors[1].instance_method(:foo).bind(self).call
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo

Calling a Protected Superclass Class Method in Ruby

I want to call a protected superclass class method from an instance method in the base class.
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class B < A
def bar
puts "In bar"
# call A::foo
end
end
What's the best way to do this?
... 2.67 years later ...
A simpler way to solve this is with class_eval
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class B < A
def bar
self.class.class_eval { foo }
end
end
B.new.bar # prints "In foo"
Override the method in B, calling super:
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class B < A
def self.foo
super
end
def bar
puts "In bar"
# call A::foo
self.class.foo
end
end
>> B.foo
=> In foo
>> B.new.bar
=> In bar
=> In foo
So far, the only solution I've found is to define a class method in the subclass that calls the class method in the superclass. Then I can call this method in the subclass' instance method.
class A
class << self
protected
def foo
puts "In foo"
end
end
end
class B < A
def self.call_foo
puts "In call_foo"
A::foo
end
def bar
puts "In bar"
self.class.call_foo
end
end
Is this really necessary?
I'd probably just make A.foo public. Otherwise send will do it, since it bypasses access controls:
A.send(:foo)

Resources