Ruby Class with Static Method Calling a Private Method

Ruby class with static method calling a private method?

First off, static is not really part of the Ruby jargon.

Let's take a simple example:

class Bar
def self.foo
end
end

It defines the method foo on an explicit object, self, which in that scope returns the containing class Bar.
Yes, it can be defined a class method, but static does not really make sense in Ruby.

Then private would not work, because defining a method on an explicit object (e.g. def self.foo) bypasses the access qualifiers and makes the method public.

What you can do, is to use the class << self syntax to open the metaclass of the containing class, and define the methods there as instance methods:

class Foo
class << self

def bar
do_calc
end

def baz
do_calc
end

private

def do_calc
puts "calculating..."
end
end
end

This will give you what you need:

Foo.bar
calculating...

Foo.baz
calculating...

Foo.do_calc
NoMethodError: private method `do_calc' called for Foo:Class

Calling a private instance method from a class method in Ruby

Using private or protected really don't do that much in Ruby. You can call send on any object and use any method it has.

class Foo
def Foo.bar(my_instance, n)
my_instance.send(:plus, n)
end
end

How to create a private class method?

private doesn't seem to work if you are defining a method on an explicit object (in your case self). You can use private_class_method to define class methods as private (or like you described).

class Person
def self.get_name
persons_name
end

def self.persons_name
"Sam"
end

private_class_method :persons_name
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name

Alternatively (in ruby 2.1+), since a method definition returns a symbol of the method name, you can also use this as follows:

class Person
def self.get_name
persons_name
end

private_class_method def self.persons_name
"Sam"
end
end

puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name

Call private class method from private instance method

First let me try to explain why the code does not work

class MyModel < ActiveRecord::Base

def self.create_instance
model = MyModel.new
# in here, you are not inside of the instance scope, you are outside of the object
# so calling model.somemething can only access public method of the object.
model.init_some_dependencies
...
end
...

You could bypass private calling of the method with model.send :init_some_dependencies. But I think in this case there is probably better solution.

I would guess that init_some_dependencies probably contain more business / domain logic rather than persistence. That's why I would suggest to pull out this logic into a "Domain Object" (or some call it Service Object). Which is just a plain ruby object that contain domain logic.

This way you could separate persistence logic to ActiveRecord and the domain logic to that class. Hence you will not bloat the ActiveRecord Model. And you get the bonus of testing
the domain logic without the need of ActiveRecord. This will make your test faster.

You could create a file say `lib/MyModelDomain.rb'

class MyModelDomain
attr_accessor :my_model

def initialize(my_model)
@my_model = my_model
end

def init_some_dependencies
my_model.property = 'some value example'
end
end

Now you could use this object say something like this

class MyModel < ActiveRecord::Base

def self.create_instance
model = MyModel.new
domain = MyModelDomain.new(model)
domain.init_some_dependencies
domain.my_model
end

def initialize_instance
# do some other work
other_init

domain = MyModelDomain.new(self)
domain.init_some_dependencies
end
end

You might also want to move the initialize_instance if you think it's necessary

Some resource that go deep into this pattern:

  • http://railscasts.com/episodes/398-service-objects
  • https://www.destroyallsoftware.com/screencasts/catalog/extracting-domain-objects

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

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.

static and private method behavior when calling direct on object of child class sounds like overriding?

Polymorphism is not for static methods. Static methods are called with JVM instructions invokestatic, whereas polymorphism is achieved with invokevirtual. The calls to static methods are determined at compile time, and polymorphic methods are dynamically dispatched at runtime.

You can easily tweak your code so that A.staticMethod() is called, by just assigning new B() to a variable of type A.

public static void main(String[] args) {
new B().privateMethod();
A b = new B(); // change here.
b.staticMethod(); // A.staticMethod() is called here.
}

Call Private methods outside class definition

Something like this should work

module AllClassesMethods
def self.included(base)
base.class_eval do
do_z :param do
set_property 'a', :x, :y, false
set_property 'b', :x, :y, false
end
end
end
end

module OnlyBMethods
def self.included(base)
base.class_eval do
do_z :param do
set_property 'only for class B', :x, :y, true
end
end
end
end

class A
include ModuleB::PrivateMethods
include AllClassesMethods

def self.inherited(klass)
klass.include AllClassesMethods
end
end

class B < A
include OnlyBMethods
end

A and any class that inherits from A will include AllClassesMethods, running the code in its included method. It has to be explicitly included on each inherited class, or else the included method will only get called for the parent A. The class_eval block executes within the including class's context, so it's just like opening up the class in your class definition. Only B is including OnlyBMethods, and therefore is the only one triggerring the included implementation of both Modules.

There's another approach you could use. If you define a class method macro in an extended module, the class method will be executed within the class context, also giving you easy access to it's private methods (I say "easy" access because in Ruby you can always access an object's private methods from any context by using send)

module AllClassesMethods
def does_z
do_z :param do
set_property 'a', :x, :y, false
set_property 'b', :x, :y, false
end
end

def does_z_for_b
do_z :param do
set_property 'only for class B', :x, :y, true
end
end
end

class A
include ModuleB::PrivateMethods
extend AllClassesMethods

does_z

def self.inherited(klass)
klass.does_z
end
end

class B < A
does_z_for_b
end

What is the use in class/static methods in ruby?

Your example isn't a good one.

Class methods might deal with managing all instances that exist of a class, and instance methods deal with a single instance at a time.

class Book
def self.all_by_author(author)
# made up database call
database.find_all(:books, where: { author: author }).map do |book_data|
new book_data # Same as: Book.new(book_data)
end
end

def title
@title
end
end

books = Book.all_by_author('Jules Vern')
books[0].title #=> 'Journey to the Center of the Earth'

In this example we have a class named Book. It has a class method all_by_author. It queries some pretend database and returns an array of Book instances. The instance method title fetches the title of a single Book instance.

So the class method managing a collection of instances, and the instance method manages just that instance.


In general, if a method would operate on a group of instances, or is code related to that class but does not directly read or update a single instance, then it probably should be a class method.



Related Topics



Leave a reply



Submit