How to use the “self” keyword in Rails

2020-06-23 05:09发布

问题:

Regarding the keyword self in Rails, I know that the keyword refers to an instance of the class itself. For example, in self.encrypted_password.

I have less of an idea why the attribute password, passed as an parameter on the right hand side, isn't prefixed with the self keyword too?

class User < ActiveRecord::Base

  attr_accessor :password
  attr_accessible  :name, :email, :password, :password_confirmation

  validates :password, :presence     => true,
                      :confirmation => true,
                      :length       => { :within => 6..40 }

  before_save :encrypt_password

  private

  def encrypt_password
    self.encrypted_password = encrypt(password)
  end

  def encrypt(string)
    string # Only a temporary implementation!
  end
end

Can someone explain when to use, or not use, the self keyword?

回答1:

Answer

The answer is simple: scope visibility.

def encrypt_password
  self.encrypted_password = encrypt(password)
end

There is (or, rather, there should be at runtime) something called password. In your case, it's an attribute from the database. But it also can be a local variable. If such name isn't found, error will be raised.

But you have to prefix encrypted_password with self to explicitly state that you're going to update instance attribute. Otherwise, new local variable encrypted_password will be created. Obviously, not the effect you wanted.

More explanation

Here's a little snippet of code

class Foo
  attr_accessor :var

  def bar1
    var = 4
    puts var
    puts self.var
  end
end

f = Foo.new
f.var = 3
f.bar1

Output

4
3

So, as we can see, var is assigned without self keyword and, because of this, now there are two names var in the scope: local variable and instance attribute. Instance attribute is hidden by local variable, so if you really want to access it, use self.



回答2:

The question has nothing to do with Rails, but with Ruby. When you look at your code:

def encrypt_password
  self.encrypted_password = encrypt(password)
end

it will be translated by Ruby into:

  • self.encrypted_password = is a method call to encrypted_password= method.
  • This method is generated by Rails on the fly, because there exists a database column with the same name.
  • encrypt(password) contains 2 methods calls.

    1. password calls the method password which is a real attribute of the model by the declaration attr_accessor :password. This declaration creates two methods:
      • password: getter of the attribute
      • password=: setter of the attribute

    See the explanation of @Sergio_Tulentsev how the getter could be hidden by a local variable (which is not the case in your implementation, there is no local variable).

    1. Calls the method encrypt with the return value of method call password.

So the use of self. makes it explicit that you have (all the time) a method call, you don't access the attribute directly. If you want to do that, you have to use @password = <some value> inside an instance method, but I like the style with self.password = <some value> much more.

I hope it is clear now how your code is interpreted.



回答3:

setter method => use self

Use self when you need to write something.
In other words, when you need to assign a value.

getter method => don't use self

And without self when you need to read something.
When you need to read a value.

for instance:

def write_name(name)
  self.name = name
end

def read_name
  name
end

In your case, the virtual attribute password isn't prefixed with the self keyword because it is only read(getter).

encrypted_password is what will be stored in the database, it is written in the db (setter). Thus the use of self.

Edit:

Michael HARTL has the same explanation about the very same case:

(Of course, as we’ve noted, the self is not optional when assigning to an attribute, so we have to write self.encrypted_password in this case.)

http://ruby.railstutorial.org/chapters/modeling-and-viewing-users-two#sec:an_active_record_callback

(last lines of 7.1.3)