Module method access restriction - ruby

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

Related

How to work through name collisions in ruby

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

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)

Defining a method visible only within a certain module/class

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 !!!

Issue with creating DSL syntax

I have the following code:
module A
def self.included(base)
base.extend(ClassMethods)
end
def foo
a = bar
puts a
end
def bar(str="qwe")
str
end
module ClassMethods
end
end
class B
include A
def bar(str="rty")
str
end
end
B.new.foo #=> "rty"
I wish class B to look like this:
class B
include A
bar "rty"
end
B.new.foo #=> rty
or
class B
include A
HelperOptions.bar "rty" # HelperOptions class should be in module A
end
B.new.foo #=> rty
I tried using define_method, class_eval, and initialize. How can be implemented syntax bar 'rty' or HelperOptions.bar 'rty' and what shall be done in module A?
If I got your question properly you want to define a class method A.bar that defines an instance method B#bar that returns it's argument, you can do it like this:
module A
def self.included(base)
base.extend(ClassMethods)
end
def foo
puts bar
end
module ClassMethods
def bar(str)
define_method(:bar) { str }
end
end
end
class B
include A
bar 'rty'
end
B.new.bar
# => "rty"
B.new.foo
# Output: rty

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

Resources