Reputation: 498
I would like to have something like this:
class A
def only_B_can_call_me
'called_by_B'
end
end
class B
def do_stuff(a)
a.only_B_can_call_me
end
end
class C
def do_stuff(a)
a.only_B_can_call_me # how to forbid it?
end
end
B.new.do_stuff(A.new) # => 'called_by_B'
C.new.do_stuff(A.new) # => it should not be allowed!!! but how to do it?
One way to do this is to make only_B_can_call_me
a private method and use a.send(:only_B_can_call_me)
inside B. OK, it works. But I could be tempted in doing the same thing inside C... so, I think it is not a good approach. Is there any other way to do this? (Allow a method be accessed just by instances of a specific class.)
(I know that ultimately it is always possible to access any method from anywhere using send
. But I want to keep myself away from send
in this case.)
Upvotes: 1
Views: 386
Reputation: 7959
Sergio's answer is correct, however if you really need a hack take a look at the code below:
class CallerClass
def self.get (c)
line = c.first.scan(/^.*:(.*):.*/).first.first.to_i
file = c.first.scan(/^(.*?):/).first.first
func = c.first.scan(/:in `(.*)?'/).first.first
fc = File.readlines(file)
caller_class = nil
caller_class = '<main>' if func == '<main>'
line.downto(0) do |it|
break if caller_class
caller_class = fc[it].scan(/^\s*class\s+(.*)\s+/).first.first if fc[it] =~ /^\s*class\s+(.*)\s+/
end
caller_class
end
end
class A
def only_B_can_call_me
caller_class = CallerClass.get(caller)
raise "'#{caller_class}' is not an allowed caller" unless caller_class == 'B'
'called_by_B'
end
end
class B
def do_stuff
A.new.only_B_can_call_me
end
end
class C
def do_stuff
A.new.only_B_can_call_me
end
end
B.new.do_stuff #called_by_B
C.new.do_stuff #Raises exception
OBS. This is a fragile parsing of ruby code using regex, It's a HACK you've been warned!!
Upvotes: 0
Reputation: 16224
This is the best I could do with help of @Sergio Tulentsev:
class A
def only_B_can_call_me(b)
return unless b.class == B # here you are asking
'called_by_B'
end
end
class B
def do_stuff(a)
a.only_B_can_call_me(self) # how to forbid it? ask if self is B
end
end
class C
def do_stuff(a)
a.only_B_can_call_me(self) # how to forbid it? ask if self is B
end
end
Other way is using subclass:
class A
def only_B_can_call_me
'called_by_B'
end
end
class B < A
def do_stuff
self.only_B_can_call_me
end
end
class C
def do_stuff
self.only_B_can_call_me # how to forbid it?, like this C hasn't the method, B does
end
end
puts(B.new.do_stuff) # => 'called_by_B'
puts(C.new.do_stuff) # => it should not be allowed!!! but how to do it?
Upvotes: 0
Reputation: 230346
There are no clear solutions. If B can do it, so can C. Unlike some other languages, ruby doesn't have "internal" or "package" visibility modifier, which could help you if A and B were in the same "package", but C was external. If the method is private, even B has to use send
. If it's public, C can just call it. B is not a subclass of A in your example, so protected
modifier doesn't apply.
One dirty-dirty way would be to check caller
in only_B_can_call_me
. It returns the entire callstack. So you can check if it is, indeed, B or reject otherwise. But this is super-fragile and totally not recommended.
Upvotes: 4