Djunzu
Djunzu

Reputation: 498

How to allow only one class to access a method from another class?

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

Answers (3)

Tiago Lopo
Tiago Lopo

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

developer_hatch
developer_hatch

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

Sergio Tulentsev
Sergio Tulentsev

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

Related Questions