Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby Code not in Any Method

Tags:

ruby

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?

like image 918
pseudopeach Avatar asked Aug 21 '12 21:08

pseudopeach


2 Answers

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 stuff

How 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.

like image 192
Radi Avatar answered Nov 16 '22 00:11

Radi


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.

like image 22
cjhveal Avatar answered Nov 16 '22 00:11

cjhveal