Is there a way to call a private Class method from an instance in Ruby?
Asked Answered
A

8

18

Other than self.class.send :method, args..., of course. I'd like to make a rather complex method available at both the class and instance level without duplicating the code.


UPDATE:

@Jonathan Branam: that was my assumption, but I wanted to make sure nobody else had found a way around. Visibility in Ruby is very different from that in Java. You're also quite right that private doesn't work on class methods, though this will declare a private class method:

class Foo
  class <<self
    private
    def bar
      puts 'bar'
    end
  end
end

Foo.bar
# => NoMethodError: private method 'bar' called for Foo:Class
Antung answered 21/8, 2008 at 18:2 Comment(0)
K
12

Here is a code snippet to go along with the question. Using "private" in a class definition does not apply to class methods. You need to use "private_class_method" as in the following example.

class Foo
  def self.private_bar
    # Complex logic goes here
    puts "hi"
  end
  private_class_method :private_bar
  class <<self
    private
    def another_private_bar
      puts "bar"
    end
  end
  public
  def instance_bar
    self.class.private_bar
  end
  def instance_bar2
    self.class.another_private_bar
  end
end

f=Foo.new
f=instance_bar # NoMethodError: private method `private_bar' called for Foo:Class
f=instance_bar2 # NoMethodError: private method `another_private_bar' called for Foo:Class

I don't see a way to get around this. The documentation says that you cannot specify the receive of a private method. Also you can only access a private method from the same instance. The class Foo is a different object than a given instance of Foo.

Don't take my answer as final. I'm certainly not an expert, but I wanted to provide a code snippet so that others who attempt to answer will have properly private class methods.

Krp answered 21/8, 2008 at 18:42 Comment(1)
This would be good as a part of the question, not the answer.Ossuary
K
8

Let me contribute to this list of more or less strange solutions and non-solutions:

puts RUBY_VERSION # => 2.1.2

class C
  class << self
    private def foo
      'Je suis foo'
    end
  end

  private define_method :foo, &method(:foo)

  def bar
    foo
  end
end

puts C.new.bar # => Je suis foo
puts C.new.foo # => NoMethodError
Keddah answered 14/6, 2014 at 20:33 Comment(4)
As the original post requests, it should call the class method. This isn't good as it's copying the method into the class's set of instance methods. If you, for example, monkey patch the class method C::foo, you'd suddenly have mismatching behavior between the class level ::foo and instance level #foo.Audy
@ChadM, nobody said monkey patching made sense, even less of a private method. If you monkey patch, you must know what you're doing and take the inconveniences. One doesn't optimize code for future monkey patching.Bobker
@akostadinov, yes, I would advise me of 2016 similar sentiments and would change my decision if Stackoverflow would allow it.Audy
@Bobker That said, I still wouldn't use this method. Still better to just use send and actually call the original code than duplicate it.Audy
W
3

Nowadays you don't need the helper methods anymore. You can simply inline them with your method definition. This should feel very familiar to the Java folks:

class MyClass

  private_class_method def self.my_private_method
    puts "private class method"
  end

  private def my_private_method
    puts "private instance method"
  end

end

And no, you cannot call a private class method from an instance method. However, you could instead implement the the private class method as public class method in a private nested class instead, using the private_constant helper method. See this blogpost for more detail.

Wigeon answered 10/3, 2016 at 16:18 Comment(1)
Since which ruby version exactly?Ossuary
C
1

If your method is merely a utility function (that is, it doesn't rely on any instance variables), you could put the method into a module and include and extend the class so that it's available as both a private class method and a private instance method.

Celik answered 17/12, 2011 at 7:13 Comment(2)
I always confuse these too. Example would be helpful.Ossuary
Also would be good to be sure that the methods that you borrowed are private on the instance.Ossuary
O
0

This is the way to play with "real" private class methods.

class Foo
  def self.private_bar
    # Complex logic goes here
    puts "hi"
  end
  private_class_method :private_bar
  class <<self
    private
    def another_private_bar
      puts "bar"
    end
  end
  public
  def instance_bar
    self.class.private_bar
  end
  def instance_bar2
    self.class.another_private_bar
  end
  def calling_private_method
    Foo.send :another_private_bar
    self.class.send :private_bar
  end
end
f=Foo.new
f.send :calling_private_method 
 # "bar"
 # "hi"
Foo.send :another_private_bar
# "bar"

cheers

Olympus answered 4/7, 2012 at 5:15 Comment(3)
Getting an error for the first example: 1.9.3p327 :078 > f=Foo.new => #<Foo:0x0000000293baa0> 1.9.3p327 :079 > f.class.send :calling_private_method NoMethodError: undefined method `calling_private_method' for Foo:Class from (irb):79 from ~/.rvm/rubies/ruby-1.9.3-p327/bin/irb:16:in `<main>'Louvar
Yeah, this does not work (or perhaps no longer works).Superintendency
@metakungfu the part I (still) take issue with is calling a private class method from any instance method. From within the instance method, self.class.some_private_method will throw an error. It bothers me that, unless we use reflection, private class methods can only be used by other class methods.Superintendency
M
0

This is probably the most "native vanilla Ruby" way:

class Foo
  module PrivateStatic # like Java
    private def foo
      'foo'
    end
  end
  extend PrivateStatic
  include PrivateStatic

  def self.static_public_call
    "static public #{foo}"
  end

  def public_call
    "instance public #{foo}"
  end
end

Foo.static_public_call # 'static public foo'
Foo.new.public_call # 'instance public foo'
Foo.foo # NoMethodError: private method `foo' called for Foo:Class
Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x00007fa154d13f10>

With some Ruby metaprogramming, you could even make it look like:

class Foo
  def self.foo
    'foo'
  end

  extend PrivateStatic
  private_static :foo
end

Ruby's metaprogramming is quite powerful, so you could technically implement any scoping rules you might want. That being said, I'd still prefer the clarity and minimal surprise of the first variant.

Militarism answered 3/4, 2019 at 2:43 Comment(1)
Wouldn't this method have a direct access to instance attributes? I.e. it ruins the purpose of a wish to define it outside of the instance class.Ossuary
U
0

The prior examples (no longer?) work. You cannot call the private class method directly; even from within the same class. Instead, you need to use send.

class Foo
  def self.a_method
    puts "uses #{private_method} from class method"
  end

  def a_method
    # normally this would be done with an alias or include+extend a module or calling self.class.a_method
    puts "uses #{private_method} from instance method"
  end

  class << self
    private

    def private_method
      "private class method"
    end
  end

  private

  def private_method
    self.class.send(:private_method)
  end
end

Foo.a_method => 'uses private class method from class method'
Foo.new.a_method => 'uses private class method from instance method'
Unhitch answered 2/8, 2023 at 17:25 Comment(0)
S
-1

Unless I'm misunderstanding, don't you just need something like this:

class Foo
    private
    def Foo.bar
        # Complex logic goes here
        puts "hi"
    end

    public
    def bar
        Foo.bar
    end
end

Of course you could change the second definition to use your self.class.send approach if you wanted to avoid hardcoding the class name...

Sherborne answered 21/8, 2008 at 18:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.