I would like to dynamically determine the class the current method was defined in.
Here's a static example of what I'm trying to do:
class A
def foo
puts "I was defined in A"
end
end
class B < A
def foo
puts "I was defined in B"
super
end
end
A.new.foo
# I was defined in A
B.new.foo
# I was defined in B
# I was defined in A <- this is the tricky one
How can I replace A and B in the strings above with a dynamic expression?
Apparently, #{self.class} does not work. (it would print I was defined in B twice for B)
I suspect that the answer is "you can't", but maybe I'm overlooking something.
What about this?
class A
def foo
puts "I was defined in #{Module.nesting.first}"
end
end
class B < A
def foo
puts "I was defined in #{Module.nesting.first}"
super
end
end
Corrected following WandMaker's suggestion.
You could use Module.nesting.first.
However, note that this works purely lexically, the same way constants resolution works, so it won't cut it if you have more dynamic needs:
Foo = Class.new do
def foo
Module.nesting
end
end
Foo.new.foo # => []
I have this nagging feeling that if you could do this, it would violate object-orientated encapsulation, although I can't quite place my finger on exactly why. So, it shouldn't come as a surprise that it's hard.
I can see a way if you are open to modifying the method definitions:
class A
this = self
define_method(:foo) do
puts "I was defined in #{this}"
end
end
class B < A
this = self
define_method(:foo) do
puts "I was defined in #{this}"
super()
end
end
A.new.foo
# I was defined in A
B.new.foo
# I was defined in B
# I was defined in A
Related
I was under the impression that refinements fell outside the usual inheritance scheme in Ruby; that overriding a method within a refinement replaced the original method for all code using the refinement.
But then, I tried this experiment with super, and it appears that the overridden method gets called:
class MyClass
def my_instance_method
puts "MyClass#my_instance_method"
end
end
module MyRefinement
refine(MyClass) do
def my_instance_method
puts "MyClass#my_instance_method in MyRefinement"
super
end
end
end
using MyRefinement
MyClass.new.my_instance_method
The above code outputs:
MyClass#my_instance_method in MyRefinement
MyClass#my_instance_method
My question is, how? Is the refinement inserted into the class hierarchy in some way?
Based on the documentation, the method lookup for a refinement's built in behaviour is the same as you have observed.
Your assumption was correct that it is not typical inheritance, that can be seen by invoking superclass
class C
def foo
puts "C#foo"
end
end
module M
refine C do
def foo
puts "C#foo in M"
puts "class: #{self.class}"
puts "superclass: #{self.class.superclass}"
super
end
end
end
using M
x = C.new
x.foo
The output:
C#foo in M
class: C
superclass: Object
C#foo
I have the following program.
module C
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def test_for
class_eval <<-DEFINECLASSMETHODS
def self.my_method(param_a)
puts "SELF is: #{self.inspect}"
puts param_a
puts "#{param_a}"
end
DEFINECLASSMETHODS
end
end
end
class A
include C
end
class B < A
test_for
end
when I run B.new.my_method("aaa"), I got this error
NameError: undefined local variable or method `param_a' for B:Class
I am quite confused.
I define param_a as a local variable in class method my_method,
puts param_a
runs good, and will output the "aaa".
however,
puts "#{param_a}"
output that error.
why?
Can anyone explain this?
You get that error because the #{} doesn't interpolate param_a into the string passed to puts - it interpolates it into the string passed to class_eval. It will work when you escape it, i.e.
puts "\#{param_a}"
You can also disable interpolation inside the heredoc by using <<-'DEFINECLASSMETHODS' instead of <<-DEFINECLASSMETHODS. This will also allow you to use other meta characters without having to escape them.
Try using "class_eval do; end" instead, like this:
def test_for
class_eval do
def self.my_method(param_a)
puts "SELF is: #{self.inspect}"
puts param_a
puts "#{param_a}"
end
end
end
This way, no code escaping is necessary.
Those are some majorly complex hoops you are jumping through, to achieve basically this:
module C
def test_for
define_singleton_method :my_method do |param_a|
puts "SELF is: #{inspect}"
p param_a
end
end
end
class A
extend C
end
class B < A
test_for
end
B.my_method 'foo'
# => SELF is: B
# => "foo"
EDIT: I just realized that the solution above is still much more complicated than it needs to be. In fact, we do not need any metaprogramming at all:
module C
module D
def my_method(param_a)
puts "SELF is: #{inspect}"
p param_a
end
end
def test_for
extend D
end
end
class A
extend C
end
class B < A
test_for
end
B.my_method 'foo'
# => SELF is: B
# => "foo"
I'm looking for a way of making a method "personal" - note NOT PRIVATE to a class
here is an example - by "personal" I mean the behaviour of method "foo"
class A
def foo
"foo"
end
end
class B < A
def foo
"bar"
end
end
class C < B
end
a=A.new; b=B.new;c=C.new
I'm looking for a way of producing the following behaviour
a.foo #=> "foo"
b.foo #=> "bar"
c.foo #=> "foo" (ultimate base class method called)
Instead of creating 'personal' methods, change your inheritance structure.
It appears that you want the C class to have only some of the same functionality of the B class while not making changes to the A class.
class A
def foo
"foo"
end
end
class BnC < A
end
class B < BnC
def foo
"bar"
end
end
class C < BnC
end
a=A.new; b=B.new;c=C.new
There's no standard way of doing this. It circumvents how inheritance works. You could implement B's method to do the logic like this:
def foo
instance_of?(B) ? "bar" : super
end
And you could of course define a method on Class that would do this for you similar to public and private.
class Class
def personal(*syms)
special_class = self
syms.each do |sym|
orig = instance_method(sym)
define_method(sym) {|*args| instance_of?(special_class) ? orig.bind(self).call(*args) : super}
end
end
end
Then you can personal :foo in B just like you'd private :foo.
(This isn't at all optimized and I didn't implement the zero-argument behavior that public and private have because frankly it's a huge PITA to do right and even then it's a hack.)
Seems like it could be confusing, but here's one option:
class A
def foo
"foo"
end
end
class B < A
def initialize #when constructing, add the new foo method to each instance
def self.foo
"bar"
end
end
end
class C < B
def initialize #when constructing, do nothing
end
end
More generally, using a similar approach, you can always add a method to a given instance, which of course has no effect on inherited classes or indeed on other instances of the same class.
If you give us specifics of what you're ultimately trying to accomplish we can probably be more helpful.
Answering this is a bit tricky since I don't really see what you want to accomplish in practice, but you could try something like
class C < B
def foo
self.class.ancestors[-3].instance_method(:foo).bind(self).call
end
end
(The ancestors[-3] assumes that A inherits from Object and Kernel and your intent was to access the method from the topmost non-builtin class. Of course you could substitute self.class.ancestors[-3] with just A, or figure out the class from the Array ancestors yourself, etc.)
In practice it would be simpler to alias the original in class B if you can modify it (i.e. alias :foo_from_A :foo in class B < A before the new def foo, then you can call foo_from_A in C). Or just redefine what you want in C. Or design the whole class hierarchy differently.
You can write a shortcut function to handle personalizing methods.
def personalize(*methodNames)
old_init = instance_method(:initialize)
klass = self
modul = Module.new {
methodNames.each { |m|
define_method(m, klass.instance_method(m)) if klass.method_defined?(m)
}
}
methodNames.each { |m|
remove_method(m) if method_defined?(m)
}
define_method(:initialize) { |*args|
# I don't like having to check instance_of?, but this is the only way I
# could thing of preventing the extension of child classes. At least it only
# has to happen once, during initialization.
extend modul if instance_of?(klass)
old_init.bind(self).call(*args)
}
self
end
class A
def foo
"foo"
end
end
class B < A
def foo
"bar"
end
def bam
'bug-AWWK!'
end
personalize :foo, :bam, :nometh
end
class C < B
end
a=A.new; b=B.new; c=C.new
a.foo #=> "foo"
b.foo #=> "bar"
b.bam #=> "bug-AWWK!"
c.foo #=> "foo"
C.instance_method(:foo) # => #<UnboundMethod: C(A)#foo>
c.bam #throws NoMethodError
Sometimes you don't really want an "is a" (inheritance) relationship. Sometimes what you want is "quacks like a." Sharing code among "quacks like a" classes is easily done by using modules to "mix in" methods:
#!/usr/bin/ruby1.8
module BasicFoo
def foo
"foo"
end
end
class A
include BasicFoo
end
class B
def foo
"bar"
end
end
class C
include BasicFoo
end
p A.new.foo # => "foo"
p B.new.foo # => "bar"
p C.new.foo # => "foo"
obj = SomeObject.new
def obj.new_method
"do some things"
end
puts obj.new_method
> "do some things"
This works ok. However, I need to do same thing inside an existing method:
def some_random_method
def obj.new_method
"do some things"
end
end
Works ok as well, but having a method inside a method looks pretty horrible. The question is, is there any alternate way of adding such a method?
In ruby 1.9+, there's a better way of doing this using define_singleton_method, as follows:
obj = SomeObject.new
obj.define_singleton_method(:new_method) do
"do some things"
end
Use a Mixin.
module AdditionalMethods
def new_method
"do some things"
end
end
obj = SomeObject.new
obj.extend(AdditionalMethods)
puts obj.new_method
> "do some things"
There are several ways to achieve this, and they are all related to the singleton class:
You can use class << idiom to open the singleton class definition:
obj = Object.new
class << obj
def my_new_method
...
end
end
Or you can use define_singleton_method on the obj:
obj = Object.new
obj.define_singleton_method(:my_new_method) do
...
end
You can also use define_method from the singleton class:
obj = Object.new
obj.singleton_class.define_method(:my_new_method) do
...
end
Or you can use def directly:
obj = Object.new
def obj.my_new_method
...
end
Pay attention to example 3, I think the concept of a singleton class becomes clearer on that one. There is a difference between these two examples:
a = Object.new
b = Object.new
# -- defining a new method in the object's "class" --
a.class.define_method(:abc) do
puts "hello abc"
end
a.abc # prints "hello abc"
b.abc # also prints "hello abc"
# -- defining a new method in the object's "singleton class" --
a.singleton_class.define_method(:bcd) do
puts "hello bcd"
end
a.bcd # prints "hello bcd"
b.bcd # error undefined method
This is because every object has its own singleton class:
a = Object.new
b = Object.new
p a.class # prints "Object"
p a.singleton_class # prints "#<Class:#<Object:0x000055ebc0b84438>>"
p b.class # also prints "Object"
p b.singleton_class # prints "#<Class:#<Object:0x000055ebc0b84410>>" (a different reference address)
Just an interesting point to note:
if you had instead gone:
def my_method
def my_other_method; end
end
Then my_other_method would actually be defined on the CLASS of the object not withstanding that the receiver ofmy_method is an instance.
However if you go (as you did):
def my_method
def self.my_other_method; end
end
Then my_other_method is defined on the eigenclass of the instance.
Not directly relevant to your question but kind of interesting nonetheless ;)
You can use modules.
module ObjSingletonMethods
def new_method
"do some things"
end
end
obj.extend ObjSingletonMethods
puts obj.new_method # => do some things
Now if you need to add more methods to that object, you just need to implement the methods in the module and you are done.
Use instance_eval:
obj = SomeObject.new
obj.instance_eval do
def new_method
puts 'do something new'
end
end
obj.new_method
> "do something new"
class Some
end
obj = Some.new
class << obj
def hello
puts 'hello'
end
end
obj.hello
obj2 = Some.new
obj2.hello # error
Syntax class << obj means that we are opening definition of the class for an object. As you probably know we can define Ruby class methods using syntax like this:
class Math
class << self
def cos(x)
...
end
def sin(x)
...
end
end
end
Then we can use those methods like this:
Math.cos(1)
In Ruby, everything is an object - even classes. self here is an object of Math class itself (you can access that object with Math.class). So syntax class << self means we are opening class for Math class object. Yes, it means that Math class has class too (Math.class.class).
Another way to use a Mixin
obj = SomeObject.new
class << obj
include AnotherModule
end
This includes all of the methods from AnotherModule into the current object.
I need to define the constant in the module that use the method from the class that includes this module:
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
But the compiler gives the error on the 4th line.
Is there any other way to define the constant?
The more idiomatic way to achieve this in Ruby is:
module B
def self.included(klass)
klass.class_eval <<-ruby_eval
CONST = find
ruby_eval
# note that the block form of class_eval won't work
# because you can't assign a constant inside a method
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
What you were doing (class << base) actually puts you into the context of A's metaclass, not A itself. The find method is on A itself, not its metaclass. The thing to keep in mind is that classes are themselves objects, and so have their own metaclasses.
To try to make it clearer:
class Human
def parent
# this method is on the Human class and available
# to all instances of Human.
end
class << self
def build
# this method is on the Human metaclass, and
# available to its instance, Human itself.
end
# the "self" here is Human's metaclass, so build
# cannot be called.
end
def self.build
# exactly the same as the above
end
build # the "self" here is Human itself, so build can
# be called
end
Not sure if that helps, but if you don't understand it, you can still use the class_eval idiom above.
In your specific case.
module B
def self.included(base)
base.const_set("CONST", base.find)
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
Despite it works, it's a little bit messy. Are you sure you can't follow a different way to achieve your goal?
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
class << self
def self.find
"AAA"
end
end
include B
end
then the compiler error is fixed, pls try.