Nested classes in Ruby - ruby

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>

Related

How to get a class name given that self could be a class or an instance?

I have a module that provides logging functionality. Some classes extend it, others include it. As part of the logging, I pass in a class name.
If I do this:
global_logger.call(level, self, msg)
It could log either:
WARN -- Some::Class: some msg
OR
WARN -- #<Some::OtherClass:0x00007fdc04907710>: some msg
based on if the module was extended or included. I can call self.class instead, but then the the other one turns into Class.
Is there a way to get a class name(without the #<...:0x00007fdc04907710>), given that you don't know if self is a class or an instance?
There are a number of things you could do, but one of the easiest is probably to see if the passed object is a class before trying to extract other information about it. For example:
Object.kind_of? Class #=> true
Object.new.kind_of? Class #=> false
You can then decide what methods are appropriate to call based on whether or not it's a class. For example:
p Object.new.class.name #=> "Object"
P Object.name #=> "Object"
We know that, for a given module M, you can determine if a class C has included M by writing
C.included_modules.include?(M)
To determine if a class C has extended M you may execute
C.singleton_class.included_modules.include?(M)
because extending a module to a class is equivalent to including the same module to the class' singleton class.
See Module#included_modules and Object#singleton_class.
Here is an example.
module M
def m; end
end
class C
extend M
end
a = C.singleton_class.included_modules
#=> [M, Kernel]
a.include?(M)
#=> true

Ruby Inheritance Puzzle: How to Properly Call self.class.new in Parent Method

I have a library that I would use in an app by using a class that wraps the library object in a new class by inheriting from it and adding a few instance variables. I can change the library code if need be. Here is the problem:
class A
def process_it
# Make a new instance
aa = self.class.new
do_something_to(aa)
end
def do_something_to(item)
item
end
end
class B < A
def initialize(extra = "Default extra")
#extra = extra
super()
end
def extra
#extra
end
end
# I want B to inherit A's methods, like #process_it but:
b = B.new("Non-default extra")
puts b.process_it.extra => Default extra
The output should have been "Non-default extra" and the problem is that, in the parent class I call self.class.new but can pass no parameter to it to set #extra. In the call, self.class is B, the inherited class, but when I write the parent class library, A, I cannot predict what, if any parameters, should be passed to self.class.new. Indeed, I might have class C < A with different parameters for initialize.
Is there a proper way to write the code in library A to instantiate a new instance of the self class that takes possible parameters into account?
Will #dup work for you? Instead of aa = self.class.new, change it to aa = self.dup

Why module included outside the class adds instance methods to class's objects

I am experimenting with the Ruby include keyword as shown below:
module A
def show
puts "working"
end
end
include A
class E
end
class D
end
e = E.new
d = D.new
e.show
d.show
o = Object.new
puts o.respond_to?("show")
******************************output****************
working
working
true
I was expecting output to be undefined method but it's giving me the proper output. I have also observed that the show method defined in module A is becoming an instance method of Object.
Why are these methods becoming instance methods of the class Object?
Please help in understanding this concept.
Because instances of class Class inherit from Object.
Thus, modules, included into Object are available to instances of Class's instances (instances of your E and D classes).
class A
end
module B
def test; :hi end
end
#=> test
include B
#=> Object
A.new.test
#=> :hi
Object.new.test
#=> :hi
Having include B written in the top-level means include'ing B into Object.
include B is the outermost context is equivalent to:
class Object
include B
end
The only class, whose instances do not share module B's methods is BasicObject.

How to trace the singleton class and m_tbl in Ruby

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

How can a nested class access a method in the outer class in Ruby?

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.

Resources