模型before_save中的变量范围

时间:2013-10-12 21:10:09

标签: ruby-on-rails ruby

这是Rails Casts#250身份验证中的代码:

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_secret的参数是password_salt而不是self.password_salt?为什么在这种情况下它会自动识别实例变量?

2 个答案:

答案 0 :(得分: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_nameget_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以及这些方法调用实际上做了什么。

小摘要:

  • 实例变量不是方法,它们可以被称为对象的私有属性
  • 我们定义访问器方法以访问实例变量,通常使用attr_accessor,attr_reader(只读属性)方法
  • 当访问变量的本地范围内的某些变量Ruby搜索时,如果没有找到,Ruby会认为它是实例方法并在self上调用它
  • 在写入访问者(self)时,您需要明确说出self.name =,否则Ruby会将其视为本地变量的创建。如果你在本地范围内有一个同名变量,你还需要在阅读时使用self,但这是一个非常糟糕的做法,我怀疑你会遇到这样的。

答案 1 :(得分:0)

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