这是我多年编程中见过的最奇怪的错误。请忽略我在下面使用的奇怪习语,因为我正在使用Rails:的编程框架trailblazer
下方的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app_id = app.id
app = App::Get.(id: app_id).model
end
expect(app.app_type).to eq('game')
end
如预期工作。
下方的代码
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
it 'gets app details from Steam' do
VCR.use_cassette('app/get') do
p app.id
app = App::Get.(id: app.id).model
end
expect(app.app_type).to eq('game')
end
在行中为nil:NilClass抛出未定义的方法"id"
app = App::Get.(id: app.id).model
但是线路
p app.id
就在违规行显示正确的id之前。
可能发生了什么?
此行:
let (:app) { App::Create.(app: {name: "Half-Life", steam_id: "70"}).model }
创建一个名为app
的方法,该方法在调用时返回一个App
。(它存储结果,因此在初始调用后总是返回相同的App
。)
此行:
app = App::Get.(id: app.id).model
创建一个名为app
的新局部变量,该变量与方法app
不明确。根据Ruby文档分配:
在Ruby中,局部变量名和方法名几乎相同。如果你还没有给ruby假定的这些不明确的名字赋值您希望调用一个方法。一旦你指定了ruby这个名字将假定您希望引用局部变量。
当解析器遇到分配,而不是当分配发生时:
因此,当您尝试分配给app
时,会创建一个新的局部变量。当赋值的右侧被求值时,app.id
尝试调用局部变量app
(到目前为止是nil
)上的id
。
以下是我能想到的最简单的代码,它再现了这个问题:
def a
"Hello"
end
p a.upcase # "HELLO"
a = a.upcase # undefined method `upcase' for nil:NilClass (NoMethodError)
有几种方法可以修复代码。我认为到目前为止最好的解决方案是:
app2 = App::Get.(id: app.id).model
名称app
已被一个方法使用。。。不要通过创建具有相同名称的局部变量来隐藏它。
你也可以这样做:
app = App::Get.(id: app().id).model # the parens disambiguate
或者你已经拥有的解决方案:
app_id = app.id # capture the ID before the local variable gets created
app = App::Get.(id: app_id).model
但我认为这两者都比一开始就不创建命名冲突更糟糕。