How do I use methods from two different namespaces?
class Bar
def self.configure &block
new.instance_eval &block
end
def method2
puts "from Bar"
end
end
class Foo
def method1
puts "from Foo"
end
def start
Bar.configure do
method1
method2
end
end
end
Foo.new.start
In the above example the method1 can't be called because it is not from the Bar scope. How do I make methods from both scopes callable at the same time?
The trick is to forward missing method calls to the instance of Foo:
class Bar
def self.configure(&block)
o = new
context = eval('self', block.binding)
class << o; self; end.send(:define_method, :method_missing) do |meth, *args|
context.send(meth, *args)
end
o.instance_eval &block
end
def method2
puts "from Bar"
end
end
class Foo
def method1
puts "from Foo"
end
def start
Bar.configure do
method1
method2
end
end
end
Foo.new.start #=> "from Foo"
#=> "from Bar"
Try this:
class Bar
def initialize(foo)
puts "init"
#f = foo
end
def self.configure(foo, &block)
new(foo).instance_eval &block
end
def method2
puts "from Bar"
end
end
class Foo
def method1
puts "from Foo"
end
def start
Bar.configure(self) do
#f.method1
method2
end
end
end
This makes #f a class level instance variable of Bar, which is set when you initialize an object of Bar using new(foo) in Bar.configure. The block being passed assumes the existence of #f, which contains a reference to the object of class Foo.
It's a convoluted way of doing this - I can't think of anything better though. It'd be interesting to know the use case.
Maybe, this is the easiest way. It doesn't need to modify Bar.
class Bar
def self.configure &block
new.instance_eval &block
end
def method2
puts "from Bar"
end
end
class Foo
def method1
puts "from Foo"
end
def start
foo = self # add
Bar.configure do
foo.method1 # modify
method2
end
end
end
Foo.new.start
I would simplify your code as follows:
class Bar
def self.configure &block
obj = new
block.call(obj)
obj
end
def method2
puts "from Bar"
end
end
class Foo
def method1
puts "from Foo"
end
def start
Bar.configure do |obj|
method1
obj.method2
end
end
end
Foo.new.start
Block logic is clean and implementation doesn't require context switching. You are using the standard ruby functionality of passing parameters to the block.
Related
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
I want to wrap all class methods for a module with logging, like this:
module SomeModule
def self.foo
puts "bar"
end
class << self
SomeModule.methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
SomeModule.foo
#-> Called method: foo
#-> bar
That works perfectly. But what if I wanted the wrapping to only happen when I called a method? How could I make this happen when you call
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
#???
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
#-> Called method: foo
#-> bar
I’m not going to ask what you want this for, but here it is:
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
eigenclass = class << self; self; end
methods(false).each do |method|
eigenclass.class_eval do
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
Be aware that this also adds “logging” to add_logging_to_all_methods, but only after invoking it, so if you only invoke it once, you should not see anything wrong.
What eigenclass means is the “instance” where you add this methods foo and add_logging_to_all_methods. By returning self inside the class << self; end block I’m getting that instance. Then I ask the block to be evaluated in the context of that instance, which does more or less the same as your previous method.
There may be an easier way to do it.
You can apply that on all classes:
ObjectSpace.each_object.select { |o| o.is_a? Class }.each do |klass|
klass.class_eval do
methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end rescue nil
end
Ah nevermind, just placing the whole class << self block in the method works okay.
Can someone explain why the last yielder throws a no block given?
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do
yield "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
Gives:
hello
test.rb:27:in `block in ': no block given (yield) (LocalJumpError)
from test.rb:30:in `'
A short introduction:
You don't need the Mod-instance, if you define ##foo outside initialize.
You don't need the Mod class to get the problem:
class Foo
def yielder
p 2
yield "hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
foo.class.send(:define_method,:yielder) do
p 1
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
You may shorten your example again:
class Foo
end
foo = Foo.new
foo.class.send(:define_method,:yielder) do
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
This is the same as:
class Foo
define_method(:yielder) do
yield "new hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
End of Introduction.
And now, I'm not sure if I understood correct what you want (and if I understand ruby correct ;) )
define_method accepts a block and use it as method body.
If the new method should receive a block on its own, you must define it in the interface of the definition and call it:
class Foo
define_method(:yielder) do | &prc |
prc.call("new hello")
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
Or the same logic in your example:
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do | &prc |
prc.call "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
To make the code more robust, I would recommend some checks with block_given?.
How I can get all instance method names in the baz method call, which are only present in the Bar module (without other instance methods of this class) ?
class Foo
include Bar
def a
end
def b
end
def baz
#HERE
end
end
class Foo
include Bar
def a
end
def b()
end
def baz
Bar.instance_methods(false)
end
end
puts Foo.new.baz
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)