Reputation: 1639
I am new to ruby and programming in general and am trying to grasp a few key concepts. Given I have a class Dog, with the below characteristics.
class Dog
attr_accessor :type, :popularity, :total
def initialize(type = nil)
@type = type
end
def total_dogs
Dog.count
end
def total
Dog.where(:type => self.type).size
end
def popularity
total.to_f/total_dogs
end
end
What I am trying to understand, is how ruby persists attributes to an instance via getter/setter methods. Its clear to me that if I instantiate a new instance and then save attributes to that instance, those attributes are tied to that instance because if I look at the object the attributes appear as such:
@dog = Dog.new
@dog
=> #<Dog:0x007fa8689ea238 @type=nil>
Its easy for me to understand that as I pass the @dog object around its always going to have the @type attribute as nil. However, the situation I am having trouble understanding is if I pass this @dog object to another class. Like if I did:
Owner.new(@dog)
When I am in the owner class and I call @dog.popularity how does it know the value of popularity for that instance? At runtime are all methods processed and then that instance just always is tied to the value at the time? Apologies if this makes no sense or I am way off.
Upvotes: 1
Views: 8158
Reputation: 33732
Niel has a great answer for this, I just want to add something to it.
Counting Dogs :)
You need a class variable to do this..
class Dog
@@count = 0 # this is a class variable; all objects created by this class share it
def initialize
@@count += 1 # when we create a new Dog, we increment the count
end
def total
@@count
end
end
There is another way to do this with "instance variables of the Class object" but that's a bit of an advanced topic.
Accessing Instance Variables
In Ruby, variables are really just references to objects / instances.
> x = 1
=> 1
> x.class
=> Fixnum
> 1.instance_variables
=> []
x is a reference to the object '1', which is an instance of class Fixnum. The '1' object is an instance of Fixnum which does not contain any instance variables. It is not different in any way from a reference to a new "Dog" instance.
Similarly, you can say x = Dog.new
, then x is a reference to an instance of class Dog.
class Dog
attr_accessor :legs # this defines the 'legs' and 'legs=' methods!
end
x = Dog.new
x.instance_variables
=> [] # if you would assign legs=4 during "initialize", then it would show up here
x.legs = 4 # this is really a method call(!) to the 'legs' method
x.instance_variables # get created when they are first assigned a value
=> [:legs]
It does not matter if you pass such a reference to a method call, or to another class or just evaluate it by itself - Ruby knows it's an object reference, and looks inside the object and it's inheritance chain on how to resolve things.
Resolving Method Names
That was only the partial truth :) When interpreting x.legs
, Ruby checks if there is a method in the class-inheritance chain of the object, which responds to that name 'legs'.
It is not magically accessing the instance variable with the same name!
We can define a method 'legs' by doing "attr_reader :legs" or "attr_accessor :legs", or by defining the method ourselves.
class Dog
def legs
4 # most dogs have 4 legs, we don't need a variable for that
end
end
x.legs # this is a method call! it is not directly accessing a :legs instance variable!
=> 4
x.instance_variables
=> [] # there is no instance variable with name ":legs"
and if we try to implement it as a method and an instance variable, this happens: :)
class Dog
attr_accessor :legs # this creates "def legs" and "def legs=" methods behind the scenes
def legs # here we explicitly override the "def legs" method from the line above.
4
end
end
x = Dog.new
x.legs # that's the method call we implemented explicitly
=> 4
x.legs = 3 # we can still assign something to the instance_variable via legs=
=> 3
x.legs # the last definition of a method overrides previous definitions
# e.g. it overrides the automatically generated "legs" method
=> 4
attr_accessor :legs
is just a short hand notation for doing this:
class Dog
def legs
@legs
end
def legs=(value)
@legs = value
end
end
there is no magic way instance variable get automatically accessed. They are always accessed through a method, which can be overridden later.
I hope that makes sense to you
Upvotes: 2
Reputation: 20639
Both "type" and "popularity" are methods on "dog" instances. Their definition is as follows:
class Dog
# getter
def type
@type
end
def popularity
total.to_f/total_dogs
end
end
This is roughly equivalent to:
class Dog
attr_accessor :type
def popularity
total.to_f/total_dogs
end
end
Note that attr_accessor is just a shortcut for defining a getter method. If you define a method yourself it is pointless to use attr_accessor:
class Dog
attr_accessor :popularity
# this will override getter defined by attr_accessor
def popularity
total.to_f/total_dogs
end
end
Back to your question: @dog.type invokes type method on @dog which returns its instance variable; @dog.popularity invokes popularity method on @dog which does calculations (as defined by you) on the fly and returns the result. No magic here!
Upvotes: 0
Reputation: 23483
When you create an object, you don't need to use the @
symbol. The variable is the object. So if you have multiple dogs, you'd do:
myDog = Dog.new(brown)
yourDog = Dog.new(white)
From there, you can say:
yourDog.type #white
myDog.type #brown
What you would NOT do is:
@dog = Dog.new #myDog
@dog = Dog.new #yourDog
If you need multiple versions of an object, you just give them different names. So if you create multiple dogs and pass them to other objects, they will work. For example:
Say your owner class is:
Class Owner
def initialize(pet)
puts "my pet is #{pet.type}"
end
Then using the instance variable would be:
me = Owner.new(myDog) #my pet is brown
you = Owner.new(yourDog) #my pet is white
Upvotes: 0
Reputation: 27207
When you do
@dog = Dog.new
You do two spearate things
1) Create an instance variable @dog for whatever object your code is currently inside
2) Instantiate a new instance of Dog (with all its methods and attributes) and assign a reference to it to @dog
@dog is a variable, that just happens to point at the Dog instance ("instance of class" generally same meaning as "object") you created at that point. You can set other variables to point to the same instance, and in Ruby this is generally how you pass data around. Objects contain instance variables, and those instance variables point to yet more objects.
Using the assignment operator (i.e "=") you can point a variable at any other object.
To answer your questions in turn:
When I am in the owner class and I call @dog.popularity how does it know the value of popularity for that instance?
You have to be careful in Ruby (and OO languages in general) to differentiate between class and object in your descriptions and questions. Ruby I'm assuming you are referring to a line of code in the Owner class, and that you intend it to work with an owner object. I'd also assume that @dog is an attribute you have added to Owner.
In which case, Ruby knows because @dog points to the Dog object that you added to owner. Each Dog object has its own copy of all of Dog's instance variables. You do need to take care in Ruby though, because variables point to objects, that you aren't simply passing in the same Dog object to all the owners (i.e. they all effectively share a single dog). So you need to understand when you are creating new instances (via new) and when you are simply handling existing references.
At runtime are all methods processed and then that instance just always is tied to the value at the time?
No. At runtime, basic Ruby will only perform the assignments that you have coded. Instance variables may not even exist until the code that assigns them has been run. If you use attr_reader etc methods, then the variables will at least exist (but will be nil unless you assign something during initialize)
Upvotes: 6