I started to touch the meta-programming in Ruby
If I want to trace all the details in meta-programming
Like to lookup m_tbl methods_table in a specific object,
I mean if there is a test method and defined in class B
A < B
B < C
What's the convenient way to know the method is defined in class B
Any good methods or tools to share about discovery the meta-programing in Ruby.
To look up all the relation between object's hierachy relation in quick way.
Thanks !
Use method method:
class C
def c
end
end
class B < C
def b
end
end
class A < B
def a
end
end
a = A.new
a.method(:b).owner
# => B
Related
Say we have a class that we cannot change,
class C
def foo
super
puts "Low!"
end
end
We'll need to dynamically define the method foo in something that we'll be able to inject into C's ancestry chain. The behavior of super must be specific to a given object, not class-wide. We'll be able to enclose that logic into an anonymous module (let's name it for now):
module M
def foo
puts "High!"
end
end
Extending an instance of C with the module:
c = C.new
c.extend(M)
c.foo
# High!
will not work since we've put the method from the module before the method we've defined in the class. Looking at our object's ancestors
c.singleton_class.ancestors
# => [#<Class:#<C:0x00005652be630b20>>, M, C, ...]
I came up with an ugly workaround, which is redefining the methods from our class in our singleton_class, i.e.
c.define_singleton_method(:foo, c.class.instance_method(:foo))
c.foo
# High!
# Low!
While this works (does it work? I've tested it for a bit and it seems to, but I'm no longer certain), I wonder whether I'm missing something obvious and there's an easier way to dynamically define a "super" method for an instance of a class.
To be clear, we want to be able to extend another instance of C with another module, i.e.
C.new.extend(Module.new do
def foo
puts "Medium!"
end
end).foo
# Medium!
# Low!
and have its output not tainted by other instances.
Now that I understand you're trying to work around an issue in some third-party code, I can suggest a more reasonable solution. In the code below, I'm thinking that B and C are defined by the gem and you don't want to change their source code, but you want to inject some code into the place where C#foo calls B#foo.
class B
def foo
puts "Highest!"
end
end
class C < B
def foo
super
puts "Low!"
end
end
module CE
def foo
super
foo_injected
end
end
C.include(CE)
module M
def foo_injected
puts "High!"
end
end
c = C.new
c.extend(M)
p c.singleton_class.ancestors
c.foo
The output is:
[#<Class:#<C:0x000055ce443366a8>>, M, C, CE, B, Object, Kernel, BasicObject]
Highest!
High!
Low!
I can't figure out how to initialize a class from a parent class variable. I'm trying to accomplish this:
x = A::B.new('my string')
myObject = x::C.new(**params)
and the program I'm calling is organized similar to:
module A
...<stuff>...
class B
...<stuff>....
class C < B
...<stuff>...
end
end
end
I want to initialize the class C after initializing the parent class B. That way, I can have access to class B's variables and methods from class C.
When I try to execute my program, I get:
"#<A::B:0x...............>" is not a class/module (TypeError)
Can anyone point me in the right direction for initializing nested classes like this?
You can't construct an instance from an instance - the whole module hierarchy is only applicable to modules and classes. Since x is neither, the parser has no idea what :: is supposed to do.
The idea behind nested classes is that they are namespaced within the parent class, no more, no less. In effect, class B acts like a module prefix to class C, but there's no inherent relationship between them (by being nested alone; you do inherit C from B in your example, which is what gives you the relationship), and certainly not between an instance of B and the class C
Instead, I would recommend constructing a C directly with 'my string', then calling #super in the initialize method, like so:
myObject = A::B::C.new('my string', **params)
and in the implementation:
module A
class B
def initialize(some_string)
#some_string = some_string
end
class C < B
def initialize(str, params)
super(str)
...
end
end
end
end
As the error message suggests, the namespace operator :: can only be applied to a class/module. If x were the class A::B, then x::C should work. It does not make sense to apply ::C on an instance of A::B.
You need to apply the :: to x's class, not x itself. So you just need a call to the class method:
x = A::B.new('my string')
myObject = x.class::C.new(**params)
For example, this sort of thing:
module A
class B
class C
end
end
end
pancakes = A::B.new
eggs = pancakes.class::C.new
puts pancakes.inspect
puts eggs.inspect
gives you something like this:
#<A::B:0x007faea90b6a58>
#<A::B::C:0x007faea90b6a30>
I have module
# File 1.rb
module Search
class A
attr_accessor :results
def find_results
self.results = [somethings]
end
def do_something_with_results
b = B.new.do_something
b.response #=> something
c = C.new.use_b_response_do_something
return c.did_something
end
end
end
# File 2.rb
module Search
class B
end
class C
end
end
I have module Search with classes A, B, C
Class A does something and brings back data which needs to be shared with Classes B and C to do something with that data (refine it, send it somewhere, return true or false).
How can I share this information between the classes? I have been doing this which I think is wrong
def do_something
b = B.new.doing_something
c = C.new.something_else(b.some_attr)
end
which is not efficient
I don't see any issues in your code. You seem to be doing things as it should be done. Using methods to get and pass data around.
One thing that could make your code better is passing b to c instead.
def do_something
b = B.new.doing_something
c = C.new(b).something_else
end
But it depends on your use case...
def class A
def a
raise "hi" #can't be reached
end
class B
def b
a() #doesn't find method a.
end
end
end
I want to invoke a from b and raise the exception. How can I?
Ruby doesn't have nested classes.
The only way to inherit behavior is, well, via inheritance.
If you want your code to work, you need to use a language which supports nested classes. While this is an incredibly neat and powerful feature, I unfortunately know of only two languages that have nested classes:
BETA, the language which introduced nested classes (and its successor gbeta)
Newspeak
I don't know of any other.
Java has a construct called nested classes, but they have some unfortunate design limitations.
In your example above, it's not the class B that is nested inside A, it is the constant B that is nested inside A. Think about this:
C = A::B
Now, the class is available under two names: A::B and C. It should be immediately obvious that C is global and not nested inside A. (Well, actually, C is nested inside Object, because there aren't really global constants either, but that's beside the point.) But since C and A::B are the same class, it obviously cannot be both nested and not nested. The only logical conclusion is that the class itself isn't nested.
The defining feature of nested classes is that method lookup goes along two dimensions: up the inheritance chain, and outwards through the nesting. Ruby, like 99.9% of all OO languages, only supports the former. (In some sense, nested classes inherit not only the features of their superclass, but also the features of their surrounding class.)
This is just for the lulz:
class A
def a
puts "hello from a"
end
class B
def b
Module.nesting[1].new.a()
end
end
end
I typically do something like this:
class A
def a
puts "hi"
end
def createB
B.new self
end
class B
def initialize(parent)
#parent=parent
end
def b
#parent.a
end
end
end
A.new.createB.b
If you want then nested class to extend the outer class, then do so:
class Outer
class Inner < Outer
def use_outer_method
outer_method("hi mom!")
end
end
def outer_method(foo)
puts foo
end
end
foo = Outer::Inner.new
foo.use_outer_method #<= "hi mom"
foo.outer_method("hi dad!") #<= "hi dad"
Well depending on your circumstances there is actually a solution, a pretty easy one at that. Ruby allows the catching of method calls that aren't captured by the object. So for your example you could do:
def class A
def a
raise "hi" #can't be reached
end
class B
def initialize()
#parent = A.new
end
def b
a() #does find method a.
end
def method_missing(*args)
if #parent.respond_to?(method)
#parent.send(*args)
else
super
end
end
end
end
So then if you do this:
A::B.new().b
you get:
!! #<RuntimeError: hi>
It is probably an easier way to make something like a SubController that only handles certain activities, but can easily call basic controller methods (You would want to send in the parent controller as an argument in the initializer though).
Obviously this should be used sparingly, and it can really get confusing if you use it all over the place, but it can be really great to simplify your code.
You can use methods like module_parent, module_parent_name, module_parents from ActiveSupport to get outer modules, eg.:
class A
def self.a; puts 'a!' end
class B
def self.b; module_parent.a end # use `parent` if rails < 6.0
end
end
A::B.b #=> a!
Was a supposed to be a class method for class A?
class A
def self.a
raise "hi"
end
class B
def b
A::a
end
end
end
A::B.new.b
If you want to keep it as an instance method, you'll obviously have call to it on an instance, like for example A.new.a.
Say, I have the following 2 classes:
class A
def a_method
end
end
class B < A
end
Is it possible to detect from within (an instance of) class B that method a_method is only defined in the superclass, thus not being overridden in B?
Update: the solution
While I have marked the answer of Chuck as "accepted", later Paolo Perrota made me realize that the solution can apparently be simpler, and it will probably work with earlier versions of Ruby, too.
Detecting if "a_method" is overridden in B:
B.instance_methods(false).include?("a_method")
And for class methods we use singleton_methods similarly:
B.singleton_methods(false).include?("a_class_method")
If you're using Ruby 1.8.7 or above, it's easy with Method#owner/UnboundMethod#owner.
class Module
def implements_instance_method(method_name)
instance_method(method_name).owner == self
rescue NameError
false
end
end
class A
def m1; end
def m2; end
end
class B < A
def m1; end
def m3; end
end
obj = B.new
methods_in_class = obj.class.instance_methods(false) # => ["m1", "m3"]
methods_in_superclass = obj.class.superclass.instance_methods(false) # => ["m2", "m1"]
methods_in_superclass - methods_in_class # => ["m2"]
you can always to the following and see if its defined there:
a = A.new
a.methods.include?(:method)
Given an object b which is an instance of B, you can test to see whether b's immediate superclass has a_method:
b.class.superclass.instance_methods.include? 'a_method'
Notice that the test is against the method name, not a symbol or a method object.
"thus not being overridden in B" - Just knowing that the method is only defined in A is difficult because you can define the method on an individual instances of A and B... so I think it's going to be difficult to test that a_method is only defined on A, because you'd have to round up all the subclasses and subinstances in the system and test them...