Ruby 新手的变量作用域问题

4

我正在学习《Ruby on Rails 3教程》这本书,遇到了需要为静态页面编写基本单元测试的部分。我发现代码只是一些文本更改后就被复制了,于是我将其更改为以下内容:

require 'spec_helper'

describe PagesController do
  render_views

  pages = ['home', 'contact', 'about', 'help']

  before(:each) do
    @base_title = "Ruby on Rails Tutorial Sample App | "
  end

  pages.each do |page|
    describe "GET '#{page}'" do
      it "should be successful" do
        get "#{page}"
        response.should be_success
      end

      it "should have the right title" do
        get "#{page}"
        response.should have_selector("title", :content => @base_title + page.capitalize)
      end
    end
  end

end

在上面的例子中,让我感到困惑的是,我可以用以下内容替换'pages'变量:
@pages = ['home', 'contact', 'about', 'help']

它仍然有效。为什么?'@pages'和'pages'有何区别?

另一个令人困惑的事情是,这两者都会导致测试失败:

pages = ['home', 'contact', 'about', 'help']
@base_title = "Ruby on Rails Tutorial Sample App | "

并且
before(:each) do
  pages = ['home', 'contact', 'about', 'help']
  @base_title = "Ruby on Rails Tutorial Sample App | "
end

为什么上面的两个例子会失败?为什么代码必须像我在第一个代码片段中发布的那样?我认为这与变量作用域有关,但我还是 Ruby 的新手,所以我正在寻找更深入的理解。
顺便说一下,我是一名经验丰富的 C# 开发人员,因此获取可比较的 Java 或 C# 代码将帮助我理解,或者是一个写得很好的描述。
感谢任何支持。
编辑: 添加了当我将 @base_title 移出 'before' 块时的错误消息。
Failure/Error: response.should have_selector("title", :content => @base_title + page.capitalize)
NoMethodError:
   You have a nil object when you didn't expect it!
   You might have expected an instance of Array.
   The error occurred while evaluating nil.+
 # ./spec/controllers/pages_controller_spec.rb:21:in `block (4 levels) in <top (required)>'

对于第一个代码片段,它所在的方法是什么?请提供上下文。另外,如果在第三个代码片段中使用 @pages(即实例变量,不是局部变量 pages),它是否有效? 在第一个代码片段中,需要提供更多上下文才能确定该方法的名称。对于第三个代码片段,如果使用实例变量 @pages 而不是局部变量 pages,则应该可以正常工作。 - Zabba
我添加了剩余的方法。 - Brandon
1
失败的测试详情是什么? - Zabba
我添加了我得到的错误消息。 - Brandon
2个回答

4
我将按顺序回答你的问题... 1. 当你从

pages = ['home', 'contact', 'about', 'help']

to...

@pages = ['home', 'contact', 'about', 'help']

您只是将一个局部变量更改为实例变量...这不应该起作用,并且应该导致测试失败...

2. 以下代码不应该起作用。

pages = ['home', 'contact', 'about', 'help']
@base_title = "Ruby on Rails Tutorial Sample App | "

这是因为@base_title在“it”块中不可用。变量pages将在作用域内...但你有@base_title错误条件。

3. 这也行不通。

before(:each) do
  pages = ['home', 'contact', 'about', 'help']
  @base_title = "Ruby on Rails Tutorial Sample App | "
end

这里定义的变量pages超出了你所拥有的each循环范围。使用@base_title即可,并且它将完美地传递到你的所有方法中。

- 总结 -

你最后发布的示例是正确的。你只需要为每个循环创建一个本地变量和一个实例变量(@base_title),以便在测试运行时整个实例化类都可以使用它。希望这对你有所帮助。我建议你看一些其他的在线Ruby教程,个人喜欢向人们介绍http://rubykoans.com/ =)

最后提醒一句,RSpec在作用域方面比较复杂,因为它使用许多块并且会将代码移动到相应位置以完成所需操作。基本上你有嵌套的块...事情可能会很快变得棘手。我建议从一些更简单的示例开始。


这可能是最接近的解决方案。#1之所以在两种情况下都有效,是因为我访问@pages变量的范围。 - Brandon
我也可以在 before 代码块之外执行 pages = ['home', 'contact', 'about', 'help']base_title = "Ruby on Rails Tutorial Sample App | ",并且这样做也能够正常工作。在这种情况下,base_title 变量的范围为 PagesController 级别。 - Brandon
1
这是一篇非常好的文章,解释了为什么我的案例失败:变量作用域 - Brandon
我有些困惑,我以为你的意思是@pages = ['home', 'contact', 'about', 'help'],然后pages.each do |page|是你所拥有的...这不应该起作用。在顶层和each中都没有替换变量@pages。 - quest
抱歉造成了沟通上的误解。我正在将所有的 pages 实例更改为 @pages。通过查看您的答案和我提供的链接,我能够审查我的代码以理解我的作用域问题。 - Brandon

2

在Ruby中,你可以创建访问器方法来允许外部代码访问实例变量:

class Foo
  def pages
    @pages
  end

  def pages=(value)
    @pages = value
  end
end

如果您从类内部访问@pages,那么它与从类内部访问pages是相同的,这将调用self.pages,返回@pages

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接