General Ruby question: In Ruby, I frequently see code that's inside a class, but not part of a method. For example:
class DooDad
attr_accessor :foo
end
or
class Teacher < ActiveRecord::Base
has_many :students
end
I think attr_accessor
and has_many
are methods getting invoked with the :foo
or :students
arguments, respectively, is that right? If so, when do these kinds of statements get executed. I tried this:
class DooDad
attr_accessor :foo
puts "I happened!"
@foo = 7
end
It doesn't seem to run these a part of the new
method:
dd = DooDad.new
dd.foo
outputs nil, and never spits out any puts
stuff
How exactly does all that work?
Methods like attr_accessor
and has_many
are often called "mimic methods" because they kinda look like ruby keywords (mimic them) but they're in fact, as you and others have correctly pointed out, method calls.
dd = DooDad.new
dd.foo
outputs nil, and never spits out any
puts
stuffHow exactly does all that work?
When you're inside of a class definition the implicit receiver of all method calls and "variable definitions" is self
, which in your case is DooDad
.
So when you're writing
class DooDad
@foo = 1
end
you're actually defining an instance variable on self
, that happens to be the class itself , since you're inside of that classes definition. (and outside of any other class, module or method definitions)
The method attr_accessor
on the other hand generates, with some help of metaprogramming, accessor methods for an instance variable of the objects that are instantiated from class DooDad
.
Back to your example:
class DooDad
attr_accessor :foo
puts "I happened!"
@foo = 7
end
With the stuff mentioned above, you should now understand that you're dealing with two different @foo variables, one for instances of class DooDad (i.e DooDad.new
), the other (the one you created by writing @foo = 7
) for the class DooDad itself!
When calling the new
method on a class, you create an instance of it.
dd = DooDad.new
#=> dd is now an object of class DooDad
dd.foo
#=> You just called the "getter" method for an instance variable foo of object dd, which was never defined before, that's why it's returning nil.
The puts "I happened!"
statement, just as the other two in fact, gets evaluated as soon as the class is loaded, but not when you call new
on it.
If you want the behaviour you described (doing stuff when calling new
), I suggest implementing an initialize()
method for DooDad
, which will get called when you call new
:
class DooDad
attr_accessor :foo
def initialize()
puts "I happened!"
@foo = 7
end
end
dd = DooDad.new
#=> outputs "I happened!" and sets dd.foo = 7
dd.foo
#=> 7
But why does @foo = 7
now set the instance variable of dd
and not DooDad
?
When you define a method with the keyword def
, you enter a new scope (you pass a scope gate). self
is now no longer the class, but instead an instance of that class, that you created with new
, just like dd
. So when you're writing @foo = 7
inside of a method definition, you're talking about variables for an instance of class DooDad
, not the class itself.
This post is probably way too long and might not even satisfy as an answer, but I hope it was somewhat comprehensive.
Methods like has_many
and attr_accessor
are actually methods on the Module or Class. You are absolutely right about them being normal methods, invoked with arguments just like any other. When a method is called directly in the class (outside of a method definition), it is being called on the class itself.
Here are the docs for attr_accessor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With