0

Note: This was the best title I could think of that wouldn't make this question seem like a dup. Please feel free to edit if it does not capture the point of the question

So I've been reading Advanced Rails and I had a question that wasn't fully answered by these posts:

In my understanding, within a class definition, self refers to the object referenced by the pointer klass, so for the example of an eigenclass:

class A
  class << self
    def to.s
      "Woot"
    end
  end 
end

is the exact same as:

class << A
  def to.s
    "Woot"
  end
end 

This is because as you open up the class, ruby is creating a Class:A (virtual) class and assigning A's klass pointer to it. To my understanding this means that within the context of this class definition self == A

My question is (assuming my understanding as I've described it above is accurate) why then in an ActiveRecord::Base subclass in Rails does the use of self seem to be merely a disambiguation of the instance and not the class itself?

For example, if I have a class: A < ActiveRecord::Base with an attribute name, I cannot call A.name = "blah" outside the class definition. However, I CAN use ( and indeed MUST use ) self in assignment within the class definition like so: self.name = "blah".

If the use of self in Ruby refers to the Class object and not the instance, why does this work in ActiveRecord and how does Rails make the distinction?

Community
  • 1
  • 1
onetwopunch
  • 3,279
  • 2
  • 29
  • 44

1 Answers1

4

Everything is a method

Anything you do with an object is a method. When you have a class like this:

class A
  attr_accessor :foo
end

obj = A.new
obj.foo = 3
obj.foo #=> 3

It might feel weird, but you didn't make any assignment here. obj.foo = 3 is only a synthatic sugar for obj.send(:foo=, 3) so in fact, it executes foo= method. obj.foo is a method as well, it simply returns a value. This is because all the instance variables are private, and there is no other way to interact with them than inside the method. attr_accessor :foo is just a shortcut for:

def foo=(value)
  @foo = value
end

def foo
  @foo
end

Now, when ruby see any identifier like foo, it needs to find out what it is. Firstly it assumes it is a local variable, and if not, it tries to execute it as method on current self (see below). This means that:

class A
  attr_accessor :foo

  def bar
    foo = 'bar'
  end
end

a = A.new
a.bar   #=> 'bar'
a.foo   #=> nil

What happened? Interpretor first uses foo as an instance variable, it is not defined. But there is an assignment next to it, so it defines a new instance variable and make an assignment. If we want to use our setter we need to tell interpreter that it is not an instance varible:

class A
  attr_accessor :foo

  def bar
    self.foo = 'bar'
  end
end

a = A.new
a.bar   #=> 'bar'
a.foo   #=> 'bar'

I lied. Everything is an object

about self

self in ruby is very similar to this in javascript - it means different things in different context, and it can be easily changed at any point (obviously, not recommended).

First thing you need to know is that in Ruby every class is an object, namely it is an instance of class Class. When you do

class A

it is (almost, method below uses block and has access to external scope) equivalent to:

A = Class.new

self within a context of a class is always a class itself.

class A
  self #=> A

  def self.bar     #=> this is class method
    self #=> A
  end

  def foo      #=> this is instance method
    # We are no longer in context of class, but in context of the instance, hence
    self #=> instance of A
  end
end

Now, for any mutable object you can defined a singleton class. This is sort of weird concept - it is an extra subclass of the original class which only has a single instance. Since all the methods come from the class, this allows you to define extra methods on a particular instance of the method without affecting other object. In most of the cases, instance class is not needed and it is created when you access it for the first time. There are couple of ways to open it:

object.singleton_class do
  self #=> instance class
end

class << object
  self #=> instance class
end

Any method you defined within instance class is accessible only on that particular instance, hence this:

class A
  self #=> A
  class << A
    # we are now in the instance class of A (which is an instance of Class class)
    def foo
      # So this is an instance method, however our instance is a class A
      self #=> A
    end
end
BroiSatse
  • 44,031
  • 8
  • 61
  • 86
  • Oh duh! Completely forgot about the value of self being dependent on context! Thanks for the explanation albeit dishonest ;) lol – onetwopunch Oct 18 '15 at 19:38
  • Nice, I was typing up an answer but you managed to put present it much better. The only thing i would add is that `.name` is a method which all ruby classes inherit. `String.name -> "String"`. But it's just a method - not a class attribute. – max Oct 18 '15 at 19:38
  • ActiveRecord reads the database tables and modifies the model classes so that if your `users` table has a name column your User model class gets a `name` instance setter/getter. – max Oct 18 '15 at 19:41