Dynamically create a public method in a private method - ruby

I have code in a class that dynamically creates methods using define_method. I want to split it into two parts to make the code more understandable. I need to put a part of the code into a private block.
Here is how my code looks:
class Foo
["bar", "baz"].each do |method|
create_method(method)
end
private
def create_method(name)
define_method(name) do
puts "HELL"
end
end
end
Foo.new.bar
`create_method' for Foo:Class (NoMethodError)
I don't understand why it doesn't work.

There are several problems with your code. The error you are getting has absolutely nothing to do with private or public at all. The error message says that the method create_method cannot be found. There are two reasons for that:
You are calling it before it is defined. You need to move the call to create_method after its definition.
create_method is defined as an instance method, i.e. for calling it on instances of Foo, but you are calling it on Foo itself. You have to define it as a method somewhere in Foo's class (i.e. Class), one of its ancestors (e.g. Module), or Foo's singleton class.
I will define it as a singleton method of Foo here, but if the method really is as generic as you have showed in your example, then it probably rather belongs in Module instead.
class Foo
class << self
private
def create_method(name)
define_method(name) do
puts "HELL"
end
end
end
["bar", "baz"].each do |method|
create_method(method)
end
end
Foo.new.bar
# HELL

Not sure what went wrong for you, but this seemed to work for me:
#!/usr/bin/env ruby
class C
def initialize
create_method
end
def create_method
private_create_method
end
private
def private_create_method
class << self
define_method(:foo) { puts "I am a public method." }
end
end
end
C.new.foo # outputs: I am a public method.

Use:
MyClass.instance_eval { public :public_method }
Or run:
public :public_method
from within the MyClass class.

If you replace private_create_method with
def private_create_method
self.class.send(:define_method, :foo) { puts "I am a public method." }
end
then
C.new.foo
I am a public method

Related

ruby private class method helper

Hi I am trying to create a helper for mass defining ruby methods as private class methods. In general one can define a method as a private class method by using private_class_method key work. But I would like to create a helper in the following style:
class Person
define_private_class_methods do
def method_one
end
def method_two
end
end
end
The way I planned to dynamically define this is in the following way, which is not at all working:
class Object
def self.define_private_class_methods &block
instance_eval do
private
&block
end
end
end
any ideas where I might be going wrong?
$ cat /tmp/a.rb
class Object
def self.define_private_class_methods &cb
existing = methods(false)
instance_eval &cb
(methods(false) - existing).each { |m| singleton_class.send :private, m }
end
end
class Person
define_private_class_methods do
def method_one
puts "¡Yay!"
end
end
end
Person.send(:method_one)
Person.public_send(:method_one)
$ ruby /tmp/a.rb
¡Yay!
/tmp/a.rb:18:in `public_send': private method `method_one'
called for Person:Class (NoMethodError)
Did you mean? method
from /tmp/a.rb:18:in `<main>'
Please note, that it’s hard to understand, what you are trying to achieve and possibly there is better, cleaner and more robust way to achieve this functionality.
Similar, yet different (and semantically more correct IMHO) to #mudasobwa's answer:
class Class
def define_private_class_methods(&definition)
class_methods_prior = methods
singleton_class.class_eval(&definition)
(methods - class_methods_prior).each do |method_name|
private_class_method method_name
end
end
end
class Person
define_private_class_methods do
def method_one
1
end
end
end
Person.method_one # !> NoMethodError: private method `method_one' called for Person:Class
Person.send :method_one # => 1
Note: It will not change the accessibility of a class method that you are currently overwriting.
You could define the methods in an anonymous module by passing the block to Module.new, make each instance method in the module private and extend your class with the module:
class Class
def define_private_class_methods(&block)
mod = Module.new(&block)
mod.instance_methods.each { |m| mod.send(:private, m) }
extend(mod)
end
end
This has the desired result:
class Person
define_private_class_methods do
def method_one
123
end
end
end
Person.send(:method_one)
#=> 123
Person.method_one
#=> private method `method_one' called for Person:Class (NoMethodError)
... and as a bonus, it also gives you a super method: (probably of little use)
class Person
def self.method_one
super * 2
end
end
Person.method_one
#=> 456
Of course, you don't have to use extend, you could just as well define the methods manually:
class Class
def define_private_class_methods(&block)
mod = Module.new(&block)
mod.instance_methods.each do |m|
define_singleton_method(m, mod.instance_method(m))
private_class_method(m)
end
end
end
The essential component is the anonymous module, so you have a (temporary) container to define the methods in.

Deep into Ruby class_eval and instance_eval

class_eval and instance_eval are quite predictable in such cases like defining methods. I also understand the difference between class's instance and class's singleton (aka eigenclass).
BUT
I cannot figure out the only thing like following:
Let's say, for some strage purposes, we want make existing class to be singleton.
class A; end
class B; end
A.class_eval do
private :new
end
B.instance_eval do
private :new
end
in both cases got
NameError: undefined method 'new' for class
Did you mean? new
yes, I mean exactly this method.
Moreover, these two variants give the same result, like self points at class object in both cases
A.class_eval do
class << self
private :new
end
end
A.new
=> NoMethodError: private method 'new' called for A:Class
B.instance_eval do
class << self
private :new
end
end
B.new
=> NoMethodError: private method 'new' called for B:Class
How come? Can anybody shed the light on this?
Lets take a peek into what self is here:
class A
puts self.inspect
class << self
puts self.inspect
end
end
A.class_eval {
puts self.inspect
class << self
puts self.inspect
end
}
A.instance_eval{
puts self.inspect
class << self
puts self.inspect
end
}
We get the following output:
A
#<Class:A>
A
#<Class:A>
A
#<Class:A>
The class_eval method is defined for modules (and thus classes) and evaluates within the context of the module (class). The instance_eval method evaluates within the context of a BasicObject. It seems that in these cases the two (three actually) are the same thing.
However, I know for a fact that if methods are created inside the eval block that class_eval creates instance methods and instance_eval creates class methods. There is already an excellent posting for that observation:

Ruby class with static method calling a private method?

I have a class with a number of static methods. Each one has to call a common method, but I'm trying not to expose this latter method. Making it private would only allow access from an own instance of the class? Protected does not seem like it would solve the problem here either.
How do I hide do_calc from being called externally in a static context? (Leaving it available to be called from the first two static methods.)
class Foo
def self.bar
do_calc()
end
def self.baz
do_calc()
end
def self.do_calc
end
end
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
You can define a private class method with private_class_method like this:
class Foo
def self.bar
do_calc
end
def self.baz
do_calc
end
def self.do_calc
#...
end
private_class_method :do_calc
end
Or as of Ruby 2.1:
class Foo
def self.bar
do_calc
end
private_class_method def self.do_calc
#...
end
end

Can the Ruby 2.1 shorthand for private/protected be used for class methods?

In Ruby 2.1, def now returns a symbol
[1] pry(main)> def foo; end
=> :foo
One cool use case of this is that because private and protected are methods that take a symbol and make the method private, you can now create a private method like so:
private def foo
end
However, I can't get this to work with class methods. This code:
protected def self.baz
end
will error with: protected': undefined method 'baz' for class 'User' (NameError)".
Is there a way to get that working?
You can achieve that by using singleton class of your class:
class Foo
def self.baz
...
end
class << self
private :baz
end
end
or in a single attempt:
class Foo
class << self
private def baz
...
end
end
end
So, everything executed in a class << self block will be applied on a class level. Resulting in a private/protected class methods.
private is a method used to mark instance methods as private. The equivalent for class methods is private_class_method so the equivalent idiom would be the somewhat unwieldy and redundant:
private_class_method def self.foo
#...
end

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

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
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.
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
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.
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.
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
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.
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...

Resources