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.
Related
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
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
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
I realize this perhaps a naive question but still I cant figure out how to call one method from another in a Ruby class.
i.e. In Ruby is it possible to do the following:
class A
def met1
end
def met2
met1 #call to previously defined method1
end
end
Thanks,
RM
Those aren't class methods, they are instance methods. You can call met1 from met2 in your example without a problem using an instance of the class:
class A
def met1
puts "In met1"
end
def met2
met1
end
end
var1 = A.new
var1.met2
Here is the equivalent using class methods which you create by prefixing the name of the method with its class name:
class A
def A.met1
puts "In met1"
end
def A.met2
met1
end
end
A.met2
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...