Defining a method visible only within a certain module/class - ruby

Is there a way to define a method foo on module/class A so that it will be visible only from within module/class B, or its descendants? The following illustrates this situation:
A.new.foo # => undefined
class B
A.new.foo # => defined
def bar
A.new.foo # => defined
end
def self.baz
A.new.foo # => defined
end
end
class C < B
A.new.foo # => defined
def bar
A.new.foo # => defined
end
def self.baz
A.new.foo # => defined
end
end
I intuitively felt refinement was close in spirit, but it does not seem to do what I want.

This works. ^_^
class A
private
def foo
"THE FOO !!!"
end
end
class B < A
public :foo
def initialize
#foo = self.foo
end
end
puts "A.new.foo #{ A.new.foo rescue '... sorry, no.' }"
=> A.new.foo ... sorry, no.
puts "B.new.foo #{ B.new.foo rescue '... sorry, no.' }"
=> B.new.foo THE FOO !!!
If you want to use the A.new.foo within all sub classes by still using the A class name then you should use the following.
class A
private
def foo
"THE FOO !!!"
end
end
class B
class A < A
public :foo
end
attr_reader :c, :d
def c
A.new.foo
end
def d
A.new.foo
end
end
puts "A.new.foo #{ A.new.foo rescue '... sorry, no.' }"
=> A.new.foo ... sorry, no.
puts B.new.c
=> THE FOO !!!
puts B.new.d
=> THE FOO !!!

Related

Why does using class_eval cause toplevel class variable to be set?

I wrote some code to learn about the class_eval method. My understanding is that Foo.class_eval will set self to "Foo" and evaluate the block below in that context, just as saying "class Foo" will do. However, the code below shows that setting a class variable using class_eval will assign the class variable at the toplevel: to "Object". Why does this happen?
class Foo
end
class Foo
puts self
# => Foo
##class_var = "hello"
end
puts Object.class_variables
# => []
Foo.class_eval do
puts self
# => Foo
##class_var = "hi"
# => warning: class variable access from toplevel
end
puts Object.class_variables
# => ##class_var
As pointed out in comments a single # would do what you want, now if you were running the code from inside another class ## would work but it would set a class variable for both Foo, and whatever class you running from:
class Foo
puts self
# => Foo
##class_var = "hello"
end
class Bar
def self.main
puts Object.class_variables
# => []
Foo.class_eval do
puts self
# => Foo
##class_var = "hi"
end
end
end
Bar.main
p [Bar.class_variables]
#[[:##class_var]]
p [Foo.class_variables]
#[[:##class_var]]
That has to do with proc or block scope, I was expecting it to set only ##class_var only on Bar class but it actually sets on both, weird.

Module method access restriction

I have class C that inherits class A and includes module B. A has a method baz.
class A
def baz; 5 end
end
module B
def foobaz; baz end
end
class C < A
include B
def barbaz; baz end
end
I want to disallow B instance from (indirectly) calling baz, but allow C instance to call baz.
B.new.foobaz # => error
C.new.foobaz # => no error
C.new.barbaz # => no error
How would I do this?
Maybe this is what you wanted to do ?
class A
def baz; 5 end
end
module B
module_function
def foobaz
self.respond_to(:baz) ? baz : "No baz defined :("
end
end
class C < A
include B
def barbaz; baz end
end
B.foobaz # => "No baz defined :("
C.new.foobaz # => 5
C.new.barbaz # => 5
My option:
class A
private def baz; 5 end
end
module B
def foobaz; self.baz end
end
class C < A
include B
def barbaz; baz end
end
C.new.foobaz # => private method `baz' called
C.new.barbaz # => no error

How to remove or bypass a superclass method in Ruby?

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)

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