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.
Related
coming from the .net world and making my very first steps in Ruby. This should be pretty straightforward but still I am missing something:
class TestClass
def initialize(foo_class)
puts foo_class.number
end
end
class FooClass
#number = 3
attr_accessor :number
end
foo_class = FooClass.new
test_class = TestClass.new(foo_class)
I would expect the number 3 to show up in the output but instead it's null.
But I have defined #number with just one # to make sure it's an instance variable, so it should be attached to the instance I am passing to the constructor of TestClass. What am I missing?
Instance variables belong to instances. That’s why they are called instance variables. You have two instance variables here, both of which are called #number, which belong to two different instances.
One belongs to FooClass. This one is initialized to 3. Another one belongs to foo_class. This one is never assigned to, hence it is uninitialized and thus evaluates to nil.
If you want it to have a specific value, you need to actually assign it one: you defined an attribute writer method, but you never call it:
foo_class.number = 4
Now, the instance variable #number of foo_class is initialized to 4.
If you don't want to force your users to remember to initialize the instance variable themselves, you can use an initializer method. Note that initializer methods are nothing special, they are methods just like any other method.
However, most objects are created by calling the Class#new factory method, which looks something like this:
class Class
def new(...)
obj = allocate
obj.initialize(...)
obj
end
end
As you can see, the default implementation of Class#new calls an instance method called initialize on the newly created instance, which means you can customize the initialization by overriding #initialize:
class FooClass
#number = 3
attr_accessor :number
def initialize
self.number = 4
end
end
Now, it is guaranteed that the instance variable #number of any instance of FooClass will always be initialized to 4 (at least when users use the default Class#new factory method).
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)
I want to store newly created Person instance inside the class variable objects, but not sure how to reference the current instance from the constructor.
class Person
##objects = {}
def initialize(key)
##objects[key] = something
end
Ideally, the result is to be able to access the dictionary of Person objects through Person.objects
Simply, in constructor function, self will refer to the current instance.
class Person
##objects = {}
def initialize(key)
##objects[key] = self
puts self # it will print the id of the current instance
end
end
Same way, if you write self in a class method, it will refer to the the class.
But from your question, you seem to be doing something like Person.objects, and it won't work, and will output the following line:
NoMethodError: undefined method `objects' for Person:Class
So, you need to write a class method for it to let the outside world access objects.
def self.objects
##objects
end
Well, there are other ways as well to access the class variables, please have a look at this question.
I'm new to programming and am trying to figure out the purpose of "initialize" in creating a class.
Here's an example:
class Person
def initialize(name)
#name = name
#pet = nil
#home = 'NYC'
end
end
So initializing is to create a bunch of attributes that I can pull out directly by saying Person.name and Person.pet and Person.home right? Is "initialize" just to compact a bunch of variables into one place? Would I accomplish the same thing doing this:
class Person
pet = nil
home = 'NYC'
#not so sure how to replicate the #name here.
end
Wouldn't I be able to access the values with Person.pet and Person.home the same way as I would in the first code?
This is a little tricky in Ruby (as opposed to, say, Java) since both classes and instances of classes are actual objects at runtime. As such, a class has its own set of variables, and each instance of that class also gets its own set of variables (distinct from the class's variables).
When you say
class Person
pet = nil
end
You're setting a variable, pet, which is local only to the class object called Person.
The way to manipulate the variables of an instance of a class is to use the variables in methods:
class Person
def initialize
pet = nil
end
end
Here, pet refers to a local variable of a given instance of Person. Of course, this pet variable is pretty useless as defined, since it's just a local variable that goes away after the initialize function completes. The way to make this variable persist for the lifetime of the instance is to make it an instance variable, which you accomplish by prefixing it with a #. And thus we arrive at your first initialize:
class Person
def initialize
#pet = nil
# And so on
end
end
So, as to why you need initialize. Since the only way to set the instance variables of instances of Person is within methods of Person, this initialization needs to be in some method. initialize is just the convenient name for a method which is automatically called when your instance is first created.
Initialize is a method usually referred as an object constructor. It is used when you call Person.new("Bob") and it will give you an instance of that Person object. The # symbol you see before the variables in the initialize makes the variable an instance variable meaning that variable will only be accessed once you have an instance of that object and it will stay there for the lifetime of that instance.
For example
person = Person.new("Bob")
person.name #Will output Bob
person.home #Will output NYC
Classes are objects and doing this:
class Person
pet = nil
home = 'NYC'
end
is just creating local variables pet and home and will be outside of the scope of the class. This means calling Person.pet and Person.home will just give you an error. I would suggest do a little reading on Object Oriented Programming (OOP) and if you have any more questions throw them in stackoverflow :D
I've come across the following example from this tutorial:
class Song
##plays = 0
def initialize(name, artist, duration)
#name = name
#artist = artist
#duration = duration
#plays = 0
end
def play
#plays += 1
##plays += 1
"This song: ##plays plays. Total ###plays plays."
end
end
s1 = Song.new("Song1", "Artist1", 234) # test songs
s2 = Song.new("Song2", "Artist2", 345)
puts s1.play
puts s2.play
puts s1.play
puts s1.play
Is ##plays politely accessible only inside the class Song? This commentary brings up the point of not recommending the use of class variables. Is it b/c they are often not required in common-day use, and create a lot of debugging headaches when used?
Class variables are never really required. But the reason isn't that they're shared state. I mean, it's good to avoid shared state where you can, but that's not the real problem here.
The reason they're recommended against is, as shown in that article, they are really confusing. In particular, a class's class variables are shared by its subclasses and instances of its subclasses. For example:
class Parent
end
class Child1 < Parent
##class_var = "Child1's"
end
class Child2 < Parent
##class_var = "Child2's"
end
With this code, Child1 and its instances will all see a class variable named ##class_var with the value "Child1's" and Child2 and its instances will all see a class variable named ##class_var with the value "Child2's". But suppose later on we reopen Parent and write this:
class Parent
##class_var = "Parent's"
end
Now Parent and the instances it creates will all see a class variable named ##class_var with the value "Parent's". But that's not all. Now that the parent class has this variable, Child1 and Child2 suddenly share the variable, so all of the ##class_vars have the value "Parent's". And if you reassign the variable in Child1, it's still shared, so all of the classes get updated. How confusing!
Instead of class variables, you can just use instance variables of the class, like this:
class Parent
#class_var = "Parent's"
def self.class_var
#class_var
end
end
class Child1 < Parent
#class_var = "Child1's"
end
class Child2 < Parent
#class_var = "Child2's"
end
Now, Parent.class_var will return "Parent's", Child1.class_var will return "Child1's" and Child2.class_var will return "Child2's" — just like you expect.
A class variable is a variable that is shared among all instances of a class. This means only one variable value exists for all objects instantiated from this class. This means that if one object instance changes the value of the variable, that new value will essentially change for all other object instances. Another way of thinking of thinking of class variables is as global variables within the context of a single class.
##plays #is a class variable
#plays #is an instance variable
$plays #is a global variable accessed outside a class
So in your example you created a class variable ##plays to calculate the total number of songs played for all songs. Since it is a class variable, it cannot be accessed outside the class alone. If you wanted to access the total number of plays you can use a global variable. They start with a dollar sign $plays (in your case). I warn you, you should stay away from using global variables as they are problematic for numerous reasons. One thing you may consider is to create a method that pushes all song instances into an array. You can then sum all plays across all songs through iterators. Way more secure, way less prone to programmer error.
Edit:
Here are why global variables are bad
Are global variables bad?
The ## variable will a class variable. This is generally bad practice. In your code its redundant because #plays == ##plays (unless you set ##plays elsewhere in your code (bad practice))
Actually now that I look at it, they aren't really the same. #plays keeps a count of how many times an individual song has been played, and ##plays will keep a count of all songs. Still, its likely bad practice to use ##plays. Usually, you'd have a parent class like "Player" that is managing all the songs. There should be an instance variable called #total_plays in the "Player" class.