Let's say I have two classes. One class, "parent", has many of another class "child". This is not inheritance, I don't want parent methods to act on child objects. What I want is the child object to be able to reference the parent object, get variables from it (child.parent.var) and call parent methods that modify the parent (child.parent.update).
I'd like one object (which could be thought of as a child-but-not-child-because-this-isn't-inheritance) to be passed a reference to another object when it is initialized. I'd compare this to a parent child relationship in a database where we store info on the parent so we don't have to duplicate it onto every child.
Example:
class Parent
attr_accessor :var
def initialize(num)
#var = num
end
def increase
#var += 1
end
end
class Child
attr_accessor :var, :parent
def initialize(parent, num)
#parent = parent
#var = num
end
def sum
#parent.increase
#parent.var + var
end
end
parent1 = Parent.new(1)
child1 = Child.new(parent1, 2)
child2 = Child.new(parent1, 3)
child1.parent.increase # update the parent variable
child2.parent.var # get the parent variable
The above code does work, but is there a better (more concise, or more ruby-esq) way to achieve this?
Thanks so much for your help/thoughts.
This is basically how it's supposed to be done :) There are a couple of possible improvements though, depending on what you actually want to achieve.
Right now, your Child instances expose access to the parent on their external interface (via the public parent accessor). This is often a violation of the Law of Demeter which states that objects should only talk to their direct neighbors. In this sense, the parent is a stranger when accessed though the child object.
You could improve your design by hiding the parent object:
class Child
extend Forwardable
def_delegator :#parent, :var, :parent_var
def_delegator :#parent, :increase
attr_accessor :var
def initialize(parent, num)
#parent = parent
#var = num
end
def sum
#parent.increase
#parent.var + var
end
end
Here, we use Ruby's Forwardable module to provide access to some methods of the parent from the client. This makes these methods part of the single public interface of your Child class.
parent = Parent.new(1)
child = Child.new(parent, 2)
child.var
# => 2
child.parent_var
# => 1
child.increase
# => 2
parent.var
# => 2
# ^^^^ the increase method was called on the parent object
From the outside, it doesn't matter that the methods are forwarded and you can later change this without affecting your external interface at all.
A second improvement could be to extend your Parent class to generate children directly:
class Parent
# ...
def child(num)
Child.new(self, num)
end
end
This is usually called a Factory Method, i.e. a method which builds other objects. With this, you can hide the complexity of building your Child objects and attaching them to your parent.
You can call it like
parent = Parent.new(1)
child = parent.child(2)
Related
I found the below working solutions (link 1, link 2) which call the grandparent method but without any parameters. Does anyone know how to call the grandparent method with parameters?
class GrandParent
def fun(word)
puts word
end
end
class Parent < GrandParent
def fun(word)
puts word + 'end'
end
end
class Child < Parent
def fun(word)
GrandParent.instance_method(:fun).bind(self).call
end
end
You can pass parameters directly to call like this:
class Child < Parent
def fun(word)
GrandParent.instance_method(:fun).bind(self).call(param1, param2)
end
end
call accepts parameters
GrandParent.instance_method(:fun).bind(self).call word
I don't know your use case, but this is a Really Bad Idea™. It creates unnecessary dependencies directly between Child and GrandParent such that all kinds of normally reasonable refactoring would result in a crash e.g. moving fun to only be implemented in Parent, changing Parent to subclass a different but similar parent, etc.
If a child inherits from the parent, it inherits the parents methods as well.
So how come an inherited method can't access the child's constants?
Example -
class Parent
def my_method
puts "Value of FOO is #{FOO}"
end
end
class Child < Parent
FOO = "bar"
end
Child.new.my_method #=> NameError: uninitialized constant Parent::FOO
Doesn't the inherited method run "inside" the child class?
How can I get around this?
No it doesn't. When you call Child.new.my_method it looks for the FOO constant in Parent and upwards.
I'm not going to question your approach and just provide you with a way to do what you want:
class Parent
def self.inherited(other)
other.define_singleton_method(:my_method) do
puts "Value of FOO is #{other.const_get(:FOO)}"
end
end
end
Now whenever you inherit from Parent the inherited hook will define a method my_method on the subclass. Notice that the scope of the block in the inherited method is still the class level scope of Parent. That's why I'm directly referencing the subclass (other) to get the constant.
A way around this would be to use other.instance_exec { ... }.
Update
#maxpleaner pointed out (with the explanation provided by #CarySwoveland) that you could also retrieve the class via self and then retrieve the constant from it.
I.e., just do
class Parent
def my_method
puts "Values of FOO is #{self.class.const_get(:FOO)}"
end
end
That works because when calling child = Child.new; child.my_method the value of self within my_method will be child. And the class of child is obviously Child which knows the constant FOO.
class Parent
def punish!
end
end
class Mom < Parent
end
class Dad < Parent
end
If I have an instance of Dad but want to make it an instance of Mom, is this possible in Ruby?
If by "swap" you mean substitute in the context of a variable:
parent = Mom.new
parent.punish!
parent = Dad.new
parent.punish!
You can reassign a variable at any time. What you can't do is pervert an instance of object from one class into another. Once created it's basically stuck in that class.
The same principle here applies to object attributes and other places where an object reference might be saved.
How can I get child to ignore what its parent thinks is fun and go straight to the grandparent's idea of fun?
Child still inherits from the parent, but it just doesn't agree with a couple methods.
Calling the method of the super class of the super class?
Also, is it considered poor design if I'm in a situation where the child doesn't agree with its parents but agrees with the parent's parents?
class Grandparent
def fun
#do stuff
end
end
class Parent < Grandparent
def fun
super
#parent does some stuff
end
def new_business
#unrelated to my parent
end
end
class Child < Parent
def fun
super
#child also does some stuff
end
def inherit_new_business
new_business
#stuff
end
end
It's generally easier in Ruby to get this kind of behavior through composition rather than inheritance. To accomplish that Modules are included that contain the specific behaviors you wish a class to have.
But if you absolutely have to use inheritance you can do this:
class Child < Parent
def fun
GrandParent.instance_method(:fun).bind(self).call
# fun child stuff
end
end
This will do exactly what it says. Grab the instance method fun from the GrandParent class, attach it to the current instance object self and call it.
In Ruby, is it possible to go about getting a child's constant when I've called a parent's method through the child?
Example:
class Tester
class Parent
def go
EWOCK
end
end
class Child < Parent
EWOCK = "EWOCKS rule"
end
end
Then call Tester::Child.new.go and desire "EWOCKS rule" to come back?
[Edit 3/31]
Wow I'm REALLY sorry guys. I completely screwed up the explanation.
Parent should have been Child and Child should have inherited from base.
The call should have been to Child and not Parent
Again, many apologies and thanks to those who replied attempting to understand my horrid writeup.
It is fixed now.
EDIT: this answer is correct, although Wayne's is the more ruby-ish way to approach the problem.
Yes it is.
Your implementation will not work, because the parent tries to resolve EWOK locally. Parent doesn't have EWOK defined. However, you can tell Ruby to look specifically at the class of the actual instance the method was called on, to get EWOK.
this will work:
class Parent
def go
self.class::EWOK
end
end
class Child < Parent
EWOK = "Ewoks Rule"
end
class Child2 < Parent
EWOK = "Ewoks are ok, I guess"
end
bob = Child.new
bob.go # => "Ewoks Rule"
joe = Child2.new
joe.go # => "Ewoks are ok, I guess"
what's going on here:
in Parent's 'go', "self" will refer to the instance of the object that 'go' is actually being called on. i.e., bob (a Child), or joe (a Child2). self.class gets the actual class of that instance - Child in the case of bob, or Child2 in the case of joe.
then, self.class::EWOK will retrieve EWOK from the correct class.
For a parent class to have access to a constant defined in a child class, wrap that constant in a method. Then the normal inheritance rules apply:
class Parent
def ewok
"Ewoks are lame"
end
end
class Child < Parent
def ewok
"Ewoks rule"
end
end
p Parent.new.ewok # Ewoks are lame
p Child.new.ewok # Ewoks rule
If the constant is expensive to initialize (a large hash, for example), the define it in a constant, but access it via a method:
class Parent
EWOK = {
# Enormous hash...
}
def ewok
EWOK
end
end