Getting the following error when trying to call a private method inside the same class:
undefined local variable or method a_private_method' for AClass (NameError)`
Here the class:
class AClass
def self.public_method
file = a_private_method
end
private
def a_private_method
do something
end
end
You are trying to call an instance method from a class method, this does of course not work.
Try this
class AClass
class << self
def public_method
file = a_private_method
end
private
def a_private_method
# do something
end
end
end
You can also use self as the receiver similar to what you already did but pleases be aware that moving the method to the private part of your class does not work in this case. You could use private_class_method though.
class AClass
def self.public_method
file = a_private_method
end
def self.a_private_method
# do something
end
private_class_method :a_private_method
end
end
See https://jakeyesbeck.com/2016/01/24/ruby-private-class-methods and https://dev.to/adamlombard/ruby-class-methods-vs-instance-methods-4aje.
Related
I have the following class
class EvaluateService
def initialize
end
def get_url
end
def self.evaluate_service
#instance ||= new
end
end
class CheckController < ApplicationController
def index
get_url = EvaluateService.get_url
end
end
The problem here is that i know that i can do evaluate_service = EvaluateService.new and use the object evaluate_service.get_url and it will work fine but i also know that some frown upon the idea of initializing the service object this way and rather there is a way of initializing it via a call, send method in the service class.
Just wondering how do i do this?
I think what you're looking for is something like:
class Evaluate
def initialize(foo)
#foo = foo
end
def self.call(foo)
new(foo).call
end
def call
url
end
private
def url
# Implement me
end
end
Now you can do this in your controller:
class CheckController < ApplicationController
def index
#url = Evaluate.call(params)
end
end
The reason some prefer #call as the entry point is that it's polymorphic with lambdas. That is, anywhere you could use a lambda, you can substitute it for an instance of Evaluate, and vice versa.
There are various ways to approach this.
If the methods in EvaluateService don't need state, you could just use class methods, e.g.:
class EvaluateService
def self.get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.get_url
end
end
In this scenario, EvaluateService should probably be a module.
If you want a single global EvaluateService instance, there's Singleton:
class EvaluateService
include Singleton
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.instance.get_url
end
end
But global objects can be tricky.
Or you could use a helper method in your controller that creates a service instance (as needed) and memoizes it:
class EvaluateService
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = evaluate_service.get_url
end
private
def evaluate_service
#evaluate_service ||= EvaluateService.new
end
end
Maybe even move it up to your ApplicationController.
I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end
Given a Class in Ruby:
class MyClass
def self.my_class_method
puts "class method"
end
private
def my_method
puts "regular method"
end
private_class_method :my_class_method
end
To access private methods I can call .send(:my_method) on the Class Object, but how does that work for class methods?
You should do:
class MyClass
def self.my_class_method
puts "class method"
end
private
def my_method
puts "regular method"
end
private_class_method :my_class_method
end
# to call class method
MyClass.send :my_class_method # => class method
# to call instance method
MyClass.new.send :my_method # => regular method
In Ruby, class(s) are also objects, so you can call the #send method on the class also.
In Ruby, you can define private class methods as
class MyClass
class << self
private
def my_class_method
puts "class method"
end
end
end
Or using thhis macro like method: private_class_method
First off, MyClass.send(:my_method) would not work. You have to send it to an instance: MyClass.new.send(:my_method).
Then, your my_class_method is not really private.
Ruby's semantic of private are somewhat different from what you might be used to in other languages. Since Ruby allows you to bypass encapsulation if you choose to, private just means that a method can only be called implicitly, without sending a message to an actual object.
For example:
class Example
def test
'foobar'
end
def hello
puts test # implicit receiver
puts self.test # explicit receiver
end
end
This is all good, but why is this important for your question?
Because you're declaring my_class_method explicitly on self. Doing so bypasses the private modifier, and the method is public. This means that you can just call it with:
MyClass.my_class_method
If you really need private class methods, then you can define them on the metaclass:
class MyClass
class << self
private
def my_class_method
puts "class method"
end
end
private
def my_method
puts "regular method"
end
end
This will make my_class_method actually private, and force you to invoke it with any of these:
MyClass.send :my_class_method
MyClass.class_exec { my_class_method }
MyClass.class_eval { my_class_method }
MyClass.class_eval "my_class_method"
Just for reference, that's not how you create a private class method.
class A
private
def self.foo
"foo"
end
end
A.foo # => "foo"
To create a private class method, you need to use private_class_method.
class A
def self.foo
"foo"
end
private_class_method :foo
end
A.foo # => private method `foo' called for A:Class
There are no such things as class methods. Class methods are just singleton methods of the class. But there are no such things as singleton methods either. Singleton methods are just instance methods of the singleton class. So, class methods are just instance methods of the class's singleton class.
Since there is no such thing as a class method, only instance methods, you already know what to do:
To access private methods I can call .send(:my_method) on the Class Object, but how does that work for class methods?
Just started to learn Ruby. I'm confused with Ruby's private keyword.
Let's say I have code like this
private
def greeting
random_response :greeting
end
def farewell
random_response :farewell
end
Does private only applied to the #greeting or both - #greeting and #farewell?
It's fairly standard to put private/protected methods at the bottom of the file. Everything after private will become a private method.
class MyClass
def a_public_method
end
private
def a_private_method
end
def another_private_method
end
protected
def a_protected_method
end
public
def another_public_method
end
end
As you can see in this example, if you really need to you can go back to declaring public methods by using the public keyword.
It can also be easier to see where the scope changes by indenting your private/public methods another level, to see visually that they are grouped under the private section etc.
You also have the option to only declare one-off private methods like this:
class MyClass
def a_public_method
end
def a_private_method
end
def another_private_method
end
private :a_private_method, :another_private_method
end
Using the private module method to declare only single methods as private, but frankly unless you're always doing it right after each method declaration it can be a bit confusing that way to find the private methods. I just prefer to stick them at the bottom :)
In Ruby 2.1 method definitions return their name, so you can call private on the class passing the function definition. You can also pass the method name to private. Anything defined after private without any arguments will be a private method.
This leaves you with three different methods of declaring a private method:
class MyClass
def public_method
end
private def private_method
end
def other_private_method
end
private :other_private_method
private
def third_private_method
end
end
It applies to everything under private i.e greeting and farewell
To make either of them private you can make greeting alone private as below:
def greeting
random_response :greeting
end
private :greeting
def farewell
radnom_response :farewell
end
Documentation is available at Module#private
In Ruby 2.1 you can mark single methods as private also like this (space is optional):
class MyClass
private \
def private_method
'private'
end
private\
def private_method2
'private2'
end
def public_method
p private_method
'public'
end
end
t = MyClass.new
puts t.public_method # will work
puts t.private_method # Error: private method `private_method'
# called for #<MyClass:0x2d57228> (NoMethodError)
I am trying to do a custom active record macro. But it right now seems impossible set an instance variable from within it's block.. here is what i am trying to do.
module ActiveRecord
class Base
def self.included(base)
base.class.send(:define_method, :my_macro) do |args|
# instance_variable_set for the model instance that has called this
# macro using args
end
end
end
end
i have tried class_eval, instance_eval.. but nothing seems to work or i don't how to use them.
Thanks in advance.
Edit: Let me try to explain better. I have a class method. An instance of the class calls this method. Now, this class method should instruct the instance to set an instance variable for itself.
Edit- this is how i want o use the macro
class MyModel < ActiveRecord::Base
my_macro(*args)
def after_initialize
# use the value set in the macro as #instance variable
end
end
Is this what you are thinking of:
class DynamicAdd
def add_method
self.class_eval do
attr_accessor :some_method
end
end
end
You can then do the following:
k = DynamicAdd.new
k.some_method = "hi"
should result in an undefined method error.
But,
k = DynamicAdd.new
k.add_method
k.some_method = "hi"
should work.
You can use this same format to define other types of methods besides attr_accessors as well:
class DynamicAdd
def add_method
self.class_eval do
def some_method
return "hi"
end
end
end
end
Hm.. Isn't included() a Module method? I don't think you can use that in a class like you have written. If you want to create a class method you can do
class Base
def self.my_method
end
or
class Base
class << self
def my_method
end
end
If all you want to do is to add an instance variable to an existing object, then you can use #instance_variable_set
class Base
class << self
def my_method(instance_of_base, value)
instance_of_base.instance_variable_set "#x", value
end
end
end
a = Base.new
a.class.send(:my_method, *[a,4])