0

这是 Rails Casts #250 authentication 中的代码:

class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation

  attr_accessor :password
  before_save :encrypt_password

...

  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end
end

encrypt_password,为什么在生成的时候password_hash,传递给的参数hash_secretpassword_salt而不是self.password_salt?为什么在这种情况下它会自动识别实例变量?

4

2 回答 2

0

简而言之:如果您访问方法或局部变量,则无需编写self. 但是如果你想给一个方法赋值,你总是需要self. 如果不使用self,Ruby 会将值分配给局部变量。

于 2013-10-12T21:40:37.143 回答
0

Ruby 有一个回退机制。考虑这个例子:

class SomeClass
  def my_method
    "my method"
  end

  def other_method_with_local_variable
    my_method = "lala"
    puts my_method
  end

  def other_method
    my_method
  end
end

现在让我们在控制台中测试这些方法:

1.9.3p448 :016 >   SomeClass.new.other_method # outputs instance method value, because there were none local variables inside the method and it falled back to instance scope
=> "my method"
1.9.3p448 :017 > SomeClass.new.other_method_with_local_variable # outputs "lala" because it's the value of lcoal variable
lala

不理解这个概念的人经常过度使用self,这让经验丰富的 Ruby 开发人员眼睛流血 :D

UPD

您似乎将实例方法与实例变量混淆了。如果您来自其他 OOP 语言,则与实例 var 最接近的类比将是私有属性/属性。当然,在 Ruby 中,无论如何都可以通过一些变通方法来实现它。例子:

class Person
  def initialize(name)
    @name = name
  end
end

p = Person.new("John")
#  => #<Person:0x007fbe910cc680 @name="John">
p.name #produces error, because object doesn't have such method, only a private property
#NoMethodError: undefined method `name' for #<Person:0x007fbe910cc680 @name="John">
p.instance_variables # we can always get list of instance variables
# => [:@name]
p.instance_variable_get(:@name) # access them
# => "John"
p.instance_variable_set(:@name, "Ben") # and set them
#  => "Ben"
p
# <Person:0x007fbe910cc680 @name="Ben">

以这种方式弄乱对象内部被认为是一个非常糟糕的习惯,我们应该使用类的公共接口。在许多语言中,人们通常开始为此类属性定义访问器/设置器。set_name在类中看到诸如,之类的方法是很常见的get_name。Ruby 语法允许并且约定建议这样做:

class Person
  def initialize(name)
    @name = name
  end

  def name
    @name
  end

  def name=(new_name)
    @name = new_name
  end
end

1.9.3p448 :015 >   p = Person.new("John")
# => #<Person:0x007f8b8b20ad28 @name="John">
1.9.3p448 :016 > p.name
# => "John"
1.9.3p448 :017 > p.name = "Ben"
# => "Ben"
1.9.3p448 :018 > p
# => #<Person:0x007f8b8b20ad28 @name="Ben">

在 Your 对象中定义它们是很常见的,因此 Ruby 提供了快捷方式。我们可以像这样重构我们的类:

class Person
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

1.9.3p448 :009 >   p = Person.new("John")
# => #<Person:0x007f8091233a48 @name="John">
1.9.3p448 :010 > p.name
# => "John"
1.9.3p448 :011 > p.name = "Ben"
# => "Ben"
1.9.3p448 :012 > p
# => #<Person:0x007f8091233a48 @name="Ben">

我们实际上可以验证它attr_accessor是否完全相同:

1.9.3p448 :013 > Person.instance_methods.grep(/^name=?/)
# => [:name, :name=]

现在回到 ActiveRecord。它以相同的方式为您的列定义方法,因此对于name列,它将定义方法namename=. 不同之处在于 AR 对象要复杂得多,并且那些调用读/写属性所做的事情不仅仅是设置实例变量@name

如果您已经理解了变量/方法的范围概念,那么您应该清楚何时使用self以及这些方法调用的实际作用。

小总结:

  • 实例变量不是方法,它们可以称为对象的私有属性
  • 我们定义访问器方法来访问实例var,通常使用attr_accessor、attr_reader(只读属性)方法
  • 当访问一些变量时,Ruby 在本地范围内搜索变量,如果没有找到,Ruby 会认为它是实例方法并调用它self
  • self写入 accessor() 时需要明确说明self.name =,否则 Ruby 将其视为局部变量的创建。如果您在本地范围内有一个同名的变量,您还需要self在阅读时使用,但这是一种非常糟糕的做法,我怀疑您会遇到这种情况。
于 2013-10-12T21:21:50.620 回答