Some background: I have an external library that uses explicit type checking instead of duck-typing in one of its methods. Something like:
def a_method(value)
case value
when Array then 'an Array'
when Hash then 'a Hash'
when Foo then 'a Foo'
end
end
Foo is defined in the library. I would like to pass another object to this method that should be treated like a Foo. Therefore, I'm subclassing Foo:
class Bar < Foo
end
which works just fine:
bar = Bar.new
a_method(bar)
#=> 'a Foo'
Unfortunately, Foo implements several methods that will break the way Bar is supposed to work, including method_missing and respond_to?. For example:
class Foo
def respond_to?(method_name)
false
end
end
Because Foo is Bar's superclass, Foo#respond_to? is invoked when calling Bar#respond_to?:
class Bar < Foo
def hello
end
end
bar = Bar.new
bar.respond_to?(:hello) #=> false
bar.method(:respond_to?) #=> #<Method: Bar(Foo)#respond_to?>
I would like to remove or bypass Foo's method in this case (i.e. from within Bar) so that:
bar.respond_to?(:hello) #=> true
bar.method(:respond_to?) #=> #<Method: Bar(Kernel)#respond_to?>
just as if Foo#respond_to? did not exist.
Any suggestions?
Suppose you had this class structure:
class A
def respond_to?(meth)
"A"
end
def cat
end
def tap
puts "tap in A"
end
end
class B < A
def respond_to?(meth)
"B"
end
end
class C < B
def respond_to?(meth)
"C"
end
end
class D < C
end
We have:
D.ancestors
#=> [D, C, B, A, Object, Kernel, BasicObject]
Further, you want:
D.new.respond_to?(:cat)
#=> true
If we write:
class D < C
def respond_to?(meth)
method(__method__).super_method.call(meth)
end
end
then:
D.new.respond_to?(:cat)
#=> C
This is not surprising, since it's the same as:
class D < C
def respond_to?(meth)
super
end
end
D.new.respond_to?(:cat)
#=> C
Now try:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.call(meth)
end
end
then:
D.new.respond_to?(:cat)
#=> B
so we have skipped over C. Now redefine D#respond_to? as follows:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.super_method.call(meth)
end
end
D.new.respond_to?(:cat)
#=> A
Once more:
class D < C
def respond_to?(meth)
method(__method__).super_method.super_method.super_method.super_method.call(meth)
end
end
D.new.respond_to?(:cat)
#=> True (invokes Kernel#respond_to?)
Therefore, you could do the following:
module JumpOver
def jump_over(over_mod, meth)
m = instance_method(meth)
loop do
m = m.super_method
break if m.owner > over_mod
end
define_method(meth, m)
end
end
class D
extend JumpOver
jump_over(A, :respond_to?)
jump_over(A, :tap)
end
D.methods.include?(:jump)
#=> true
D.instance_methods(false)
#=> [:respond_to?, :tap]
d = D.new
#=> #<D:0x007ff263161b58>
d.respond_to? :cat
#=> true
d.respond_to? :dog
#=> false
d.tap { |o| puts 'hi' }
# 'hi'
#=> #<D:0x007ff263161b58>
I don't understand the problem. Just implement a respond_to? in Bar and don't call super.
class Foo
def respond_to?(method)
puts 'in foo'
false
end
end
class Bar < Foo
def respond_to?(method)
puts 'in bar'
true
end
end
bar = Bar.new
bar.respond_to?(:quux) # => true
# >> in bar
This is, of course, a violation of LSP, but you specifically asked for it, so... :)
Ruby's method lookup seems to be "hard-coded". I couldn't find a way to alter it from within Ruby.
Based on Sergio Tulentsev's suggestion to re-implement the methods, however, I came up with a helper method to replace / overwrite every superclass (instance) method with its "super" method (or undefine it if there is none):
def self.revert_superclass_methods
superclass.instance_methods(false).each do |method|
super_method = instance_method(method).super_method
if super_method
puts "reverting #{self}##{method} to #{super_method}"
define_method(method, super_method)
else
puts "undefining #{self}##{method}"
undef_method(method)
end
end
end
This has basically the same effect for my purposes. Example usage:
module M
def foo ; 'M#foo' ; end
end
class A
include M
def to_s ; 'A#to_s' ; end
def foo ; 'A#foo' ; end
def bar ; 'A#bar' ; end
end
class B < A
def self.revert_superclass_methods
# ...
end
end
b = B.new
b.to_s #=> "A#to_s"
b.foo #=> "A#foo"
b.bar #=> "A#bar"
B.revert_superclass_methods
# reverting B#to_s to #<UnboundMethod: Object(Kernel)#to_s>
# reverting B#foo to #<UnboundMethod: Object(M)#foo>
# undefining B#bar
b.to_s #=> "#<B:0x007fb389987490>"
b.foo #=> "M#foo"
b.bar #=> undefined method `bar' for #<B:0x007fb389987490> (NoMethodError)
Related
Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call
Is there a concise way to limit method visibility within the module while including it? In other words, I'd like to limit polluting the class with helper methods only used in the included module.
module Bar
def call
hide_me
end
private
# make this method only callable within this module
def hide_me
'visible'
end
end
class Foo
include Bar
def unrelated_method
hide_me
end
end
# that's ok
Foo.new.call #=> 'visible'
# that's not
Foo.new.unrelated_method #=> 'visible'
I'm ok with calling it via Bar.instance_method(:hide_me).bind(self).call, I just don't want to worry about accessing or redefining a helper method from some module.
You can wrap a class into the module and use private methods within the class, like so:
module Bar
def call
BarClass.new.call
end
class BarClass
def call
hide_me
end
private
def hide_me
puts "invisible"
end
end
end
class Foo
include Bar
def call_bar
call
end
def this_method_fails
hide_me
end
end
One can do what you want by including the module to the class and then undefining the unwanted included methods.
First construct a module.
module M
def cat
puts "meow"
end
def dog
puts "woof"
end
def owl
puts "who?"
end
private
def frog
puts "ribbit"
end
def pig
puts "oink"
end
end
Confirm the methods now exist.
M.instance_methods(false)
#=> [:cat, :dog, :owl]
M.private_instance_methods(false)
#=> [:frog, :pig]
Create the class, including the module M.
class C
def froggie
frog
end
def cat
puts "meow-meow"
end
include M
end
Check the instance methods.
C.instance_methods & [:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog, :owl]
C.private_instance_methods & [:frog, :pig]
#=> [:frog, :pig]
and confirm :cat is owned by C and not by M.
C.instance_method(:cat).owner
#=> C
Now use the method Module#undef_method to undefine the unwanted methods from the module.
class C
[:cat, :owl, :pig].each { |m|
undef_method(m) unless instance_method(m).owner == self }
end
The unless... clause is needed so that the instance method :cat defined in C is not undefined.
Confirm the methods were undefined.
C.instance_methods & [[:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog]
C.private_instance_methods & [:frog, :pig]
#=> [:frog]
Execute the methods.
c = C.new
c.cat
#=> "meow-meow"
c.dog
#=> "woof"
c.froggie
#=> "ribbit"
I want to execute a method of instance ofA on instance of B, where A and B are not related, independent classes.
a = A.new
b = B.new
b.<method_of_a>
Insane, senseless way:
class A
def a_method
'I am instance of A'
end
end
class B
def method_missing(method_name)
if method_name.to_s =~ /a_method/
A.instance_method(method_name).bind(self).call
else
super
end
end
end
B.new.a_method
#=> "I am instance of A"
Sane, idiomatic way:
module CommonMethods
def common_method
'I am available for all includers'
end
end
class A
include CommonMethods
end
class B
include CommonMethods
end
a = A.new
b = B.new
a.common_method
#=> "I am available for all includers"
b.common_method
#=> "I am available for all includers"
class A
def yay!
puts "¡YAY!"
end
end
b = A.new
A.instance_method(:yay!).bind(b).()
#⇒ "¡YAY!"
I am using the following code to enforce context of DSL nested constructs. What are the other ways of achieving the same functionality?
def a &block
p "a"
def b &block
p "b"
def c &block
p "c"
instance_eval &block
end
instance_eval &block
undef :c
end
instance_eval &block
undef :b
end
# Works
a do
b do
c do
end
end
end
# Doesn't Work
b do
end
c do
end
Source
You asked about other ways, not the best way. So here's some examples :
Example A
class A
def initialize
p "a"
end
def b &block
B.new.instance_eval &block
end
end
class B
def initialize
p "b"
end
def c &block
C.new.instance_eval &block
end
end
class C
def initialize
p "c"
end
end
def a &block
A.new.instance_eval &block
end
Example B
A bit shorter :
def a &block
p "a"
A.new.instance_eval &block
end
class A
def b &block
p "b"
B.new.instance_eval &block
end
class B
def c &block
p "c"
C.new.instance_eval &block
end
class C
end
end
end
Example C
If you don't plan to have a d method for an A::B::C object :
def a &block
p "a"
A.new.instance_eval &block
end
class A
def b &block
p "b"
B.new.instance_eval &block
end
class B
def c &block
p "c"
instance_eval &block
end
end
end
Example D
This was a fun one :
def new_class_and_method(klass_name, next_klass=Object)
dynamic_klass = Class.new do
define_method(next_klass.name.downcase){|&block| p next_klass.name.downcase; next_klass.new.instance_eval &block}
end
Object.const_set(klass_name, dynamic_klass)
end
new_class_and_method("A", new_class_and_method("B", new_class_and_method("C")))
def a &block
p "a"
A.new.instance_eval &block
end
Example E
I dare say this doesn't look half bad:
def new_method_and_class(x)
define_method(x) do |&block|
p x
self.class.const_get(x.capitalize).new.instance_eval &block
end
self.const_set(x.capitalize, Class.new)
end
["a", "b", "c"].inject(Object){|klass,x| klass.instance_eval{new_method_and_class(x)} }
Example F
A bit more robust :
def new_method_and_class(x, parent_klass = Object)
parent_klass.class_eval do
define_method(x) do |&block|
p x
parent_klass.const_get(x.capitalize).new.instance_eval &block if block
end
end
parent_klass.const_set(x.capitalize, Class.new)
end
["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }
Explanation
Example B
In example B, we first define :
an a() method
an A class
both are defined in main, because we want a() to be available directly.
a() method doesn't do much expect printing "a" and passing a block to an instance of A.
Then comes b() method. We don't want it to be available from main, so we define it inside A class. We want to continue with the nested methods, so we define a B class, which is also defined inside A. The B class is actually a A::B class. The A::B#b() method also prints "b", and passes a block to an instance of B.
We continue with A::B::C inside of A::B, just like we did with A::B and A.
Example F
Example F is basically like Example B, but written dynamically.
In example B, we defined an x method and an X class in every step, with the exact same structure. It should be possible to avoid code repetition with a method called new_method_and_class(x) which uses define_method, const_set and Class.new :
new_method_and_class("a") # <- Object#a() and A are now defined
a do
puts self.inspect
end
#=> "a"
# <A:0x00000000e58bc0>
Now, we want to define a b() method and a B class, but they shouldn't be in main. new_method_and_class("b") wouldn't do. So we pass an extra parameter, called parent_klass, which defaults to Object :
parent_klass = new_method_and_class("a")
new_method_and_class("b", parent_klass)
a do
b do
puts self.inspect
end
end
# => "a"
# "b"
# <A::B:0x00000000daf368>
b do
puts "Not defined"
end
# => in `<main>': undefined method `b' for main:Object (NoMethodError)
To define the c method, we just add another line :
parent_klass = new_method_and_class("a")
parent_klass = new_method_and_class("b", parent_klass)
parent_klass = new_method_and_class("c", parent_klass)
And so on and so on.
To avoid code repetition, we can use inject with the parent_klass as accumulator value :
["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }
Bonus - Example G
Here's a modified code from Example F which works with a basic tree structure.
# http://stackoverflow.com/questions/40641273/ruby-dsl-nested-constructs/40641743#40641743
def new_method_and_class(x, parent_klass = Object)
parent_klass.class_eval do
define_method(x) do |&block|
p x.to_s
parent_klass.const_get(x.capitalize).new.instance_eval &block if block
end
end
parent_klass.const_set(x.capitalize, Class.new)
end
def create_dsl(branch,parent_klass = Object)
case branch
when Symbol, String
new_method_and_class(branch,parent_klass)
when Array
branch.each do |child|
create_dsl(child, parent_klass)
end
when Hash
branch.each do |key, value|
create_dsl(value, new_method_and_class(key,parent_klass))
end
end
end
methods_tree = {
:a => {
:b => [
:c,
:d
],
:e => :f,
:g => nil
}
}
create_dsl(methods_tree)
a do
b do
c do
puts self.inspect
end
d do
end
end
e do
f do
end
end
g do
puts self.inspect
end
end
# =>
# "a"
# "b"
# "c"
# #<A::B::C:0x0000000243dfa8>
# "d"
# "e"
# "f"
# "g"
# #<A::G:0x0000000243d918>
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