Are There Good Reasons for 'Private' to Work the Way It Does in Ruby

Are there good reasons for 'private' to work the way it does in Ruby?

You might find it helpful to read ruby's definition of public, private and protected. (Skip to Access Control)

Ruby's private is analogous to Java's protected. There is no Ruby equivalent of Java's private. EDIT: This solution now provides a method of faking it Java's ideal of private in Ruby objects.

Private is defined as methods/variables that can only be called implicitly. This is why statements 2 and 3 fail. In other words, private limits methods/variables to the context of a class or subclass in which they are defined. Inheritance passes private methods to the subclasses and can therefore be accessed with an implicit self. (Explaining why statement 6 works.)

I think you're looking for something closer to protected. Which behaves similarly to Java accessors that are not given a visibility (eg: public, private, protected)
By changing the private in Spy to protected all 6 of your statements work.
Protected methods can be called by any instance of the defining class or their subclasses. Either explicitly or implicitly called on self are valid statements for protected methods so long as the caller is either the class of the object responding to the call, or inherits from it.

class Person
private
attr_reader :weight
end

class Spy < Person
protected
attr_accessor :code
public
def test
code #(1) OK: you can call a private method in self
Spy.new.code #(2) OK: Calling protected method on another instance from same class family or a descendant.
self.code #(3) OK: Calling protected method on with explicit self is allowed with protected
code="xyz" #(4) Ok, it runs, but it actually creates a local variable!!!
self.code="z" #(5) OK! This is the only case where explicit 'self' is ok
weight #(6) OK! You can call a private method defined in a base class
end
end

s = Spy.new
s.test # succeeds
s.code #(7) Error: Calling protected method outside of the class or its descendants.

As for statement 4. You are correct in assuming this is to avoid ambiguity. It's more a safeguard to the potential harm of ruby's dynamic nature. It ensures that you cannot override accessors by opening up the class again later. A situation that can arise, for example by eval'ing tainted code.

I can only speculate on he design decisions that led to these behaviours. For most of it I feel it comes down to the dynamic nature of the language.

P.S. If you really want to give things the java definition of private. Only available to the class in which it's defined, not even subclasses. You could add an self.inherited method to your classes to remove references to the methods you want to limit access to.

Making the weight attribute inaccessible from subclasses:

class Person
private
attr_reader :weight

def initialize
@weight = 5
end

def self.inherited(subclass)
subclass.send :undef_method, :weight
end
end

class Spy < Person
private
attr_accessor :code
public
def test
weight
end
end

Person.new.send(:weight) # => 5
Spy.new.send(:weight) #=> Unhelpful undefined method error

It may make more sense to replace the undef_method call to something like this:

  def self.inherited(subclass)
subclass.class_eval %{
def weight
raise "Private method called from subclass. Access Denied"
end
}
end

Which provides a much more helpful error and the same functionality.

The send is necessary to get around calling private methods for other classes. Only used to prove that things are actually working.

Which in hindsight, makes private and protected useless. If you're really serious about protecting your methods you will have to override send to block them. The following code does that based on the private_methods of the object:

def send_that_blocks_private_methods(method, *args)
if private_methods.include?(method.to_s)
raise "Private method #{method} cannot called be called with send."
else
send_that_allows_private_methods(method, *args)
end
end

alias_method :send_that_allows_private_methods, :send
alias_method :send, :send_that_blocks_private_methods
private :send_that_allows_private_methods

You could specify a class_variable of private_methods you want to block access to instead of denying access to all private methods. You could also make send private, but there are legitimate uses of calling send from outside an object.

Rails: what are the main reasons for making methods private?

As you say there may be no external reason to make it private. However, it also prevents you — or somebody else using your code — from accidentally making use of the method where you're not supposed to.

See it as a sanity check on your own future behaviour if you will.

Why does Ruby have both private and protected methods?

protected methods can be called by any instance of the defining class or its subclasses.

private methods can be called only from within the calling object. You cannot access another instance's private methods directly.

Here is a quick practical example:

def compare_to(x)
self.some_method <=> x.some_method
end

some_method cannot be private here. It must be protected because you need it to support explicit receivers. Your typical internal helper methods can usually be private since they never need to be called like this.

It is important to note that this is different from the way Java or C++ works. private in Ruby is similar to protected in Java/C++ in that subclasses have access to the method. In Ruby, there is no way to restrict access to a method from its subclasses like you can with private in Java.

Visibility in Ruby is largely a "recommendation" anyways since you can always gain access to a method using send:

irb(main):001:0> class A
irb(main):002:1> private
irb(main):003:1> def not_so_private_method
irb(main):004:2> puts "Hello World"
irb(main):005:2> end
irb(main):006:1> end
=> nil

irb(main):007:0> foo = A.new
=> #<A:0x31688f>

irb(main):009:0> foo.send :not_so_private_method
Hello World
=> nil

When to use private method in Rails?

In the Rails ActionController context, public methods of a controller class are exposed to web server through Rails routes. You can define a route to the public methods of the class and use them as controller actions.

However you can not define routes to private methods. They are designed as internal helper methods and there is no way to expose them to web server.

This leads to a basic convention in your controllers: Define each of your controller action as a public method, define routes for each of them and ether define views corresponding the actions or chain each action to another action or view. Use private methods or other classes for your helper methods or other components.

Of course these are conventions. You can make all your methods in controllers public if you're sure that no one would define routes to these methods or exposing them to the clients won't be harmful (as exposing sensitive information, creating vulnerability or just looking silly).

Is Ruby private method accessible in sub class?

Here's a brief explanation from this source:

  1. Public methods can be called by anyone---there is no access control. Methods are public by default (except for initialize, which is always private).
  2. Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family.
  3. Private methods cannot be called with an explicit receiver. Because you cannot specify an object when using them, private methods can be called only in the defining class and by direct descendants within that same object.

This answer from a similar question expands on the topic in more detail: https://stackoverflow.com/a/1565640/814591

Understanding private methods in Ruby

Here's the short and the long of it. What private means in Ruby is a method cannot be called with an explicit receivers, e.g. some_instance.private_method(value). So even though the implicit receiver is self, in your example you explicitly use self so the private methods are not accessible.

Think of it this way, would you expect to be able to call a private method using a variable that you have assigned to an instance of a class? No. Self is a variable so it has to follow the same rules. However when you just call the method inside the instance then it works as expected because you aren't explicitly declaring the receiver.

Ruby being what it is you actually can call private methods using instance_eval:

class Foo
private
def bar(value)
puts "value = #{value}"
end
end

f = Foo.new
begin
f.bar("This won't work")
rescue Exception=>e
puts "That didn't work: #{e}"
end
f.instance_eval{ bar("But this does") }

Hope that's a little more clear.

-- edit --

I'm assuming you knew this will work:

class Foo
def public_m
private_m # Removed self.
end
private
def private_m
puts 'Hello'
end
end

Foo.new.public_m

Understanding private methods in Ruby

Here's the short and the long of it. What private means in Ruby is a method cannot be called with an explicit receivers, e.g. some_instance.private_method(value). So even though the implicit receiver is self, in your example you explicitly use self so the private methods are not accessible.

Think of it this way, would you expect to be able to call a private method using a variable that you have assigned to an instance of a class? No. Self is a variable so it has to follow the same rules. However when you just call the method inside the instance then it works as expected because you aren't explicitly declaring the receiver.

Ruby being what it is you actually can call private methods using instance_eval:

class Foo
private
def bar(value)
puts "value = #{value}"
end
end

f = Foo.new
begin
f.bar("This won't work")
rescue Exception=>e
puts "That didn't work: #{e}"
end
f.instance_eval{ bar("But this does") }

Hope that's a little more clear.

-- edit --

I'm assuming you knew this will work:

class Foo
def public_m
private_m # Removed self.
end
private
def private_m
puts 'Hello'
end
end

Foo.new.public_m

What actually occurs when stating private/protected in Ruby?

Both are method calls. Quoting from docs:

Each function can be used in two different ways.

  1. If used with no arguments, the three functions set the default access control of subsequently defined methods.
  2. With arguments, the functions set the access control of the named methods and constants.

See documentation here:

  1. Module.private
  2. Access Control

You were looking for how the Module.private method comes into existence. Here is where that happens. And here is some more information about it. You would have to read more into it, starting from rb_define_private_method defined in class.c.

Hope that helps!

Why can a private class method be explicitly invoked in Ruby?

private only affects instance methods. To make a private class method, use private_class_method:

class A
private_class_method def self.test
puts "hello,world!"
end
end

or

class A
def self.test
puts "hello,world!"
end
private_class_method :test
end

EDIT: Yet another way to do it is to define methods on the metaclass - they will behave as class methods.

class A
class << self
private
def test
puts "hello,world!"
end
end
end

Unfortunately, there is no such thing as protected_class_method - but this last option gives us a hint on how to do it:

class A
class << self
protected
def test
puts "hello,world!"
end
end
end

but note that it can be only called from class methods of the descendant classes:

class B < A
def self.test_class
A.test
end
def test_instance
A.test
end
end

B.test_class
# => hello,world!
B.new.test_instance
# => `test_instance': protected method `test' called for A:Class (NoMethodError)

Where to place private methods in Ruby?

The best practice in my point of view is to go sequentially and declare your methods without keeping private in point of view.

At the end, you can make make any method private by just adding: private :xmethod

Example:

class Example
def xmethod
end

def ymethod
end

def zmethod
end

private :xmethod, :zmethod

end

Does this justify your question?



Related Topics



Leave a reply



Submit