运行这个基本测试套件时,我一直看到这些警告:
spec/example_spec.rb:6: warning: already initialized constant DB_ID
spec/example_spec.rb:6: warning: previous definition of DB_ID was here
spec/example_spec.rb:8: warning: already initialized constant FIELDS
spec/example_spec.rb:8: warning: previous definition of FIELDS was here
以下是一个示例测试:
require_relative 'spec_helper'
RSpec.describe MyModule::BaseClass do
let(:example_class) do
Class.new(described_class) do
DB_ID = 'example'
FIELDS = {
a: 2,
b: 4,
c: 6
}
def foo
'foo'
end
def bar
'bar'
end
end
end
subject { example_class.new }
it 'does foo' do
expect(subject.foo).to eq('foo')
end
it 'does bar' do
expect(subject.bar).to eq('bar')
end
end
每次使用subject
时都会触发警告。
我的理解是let
定义了一个memoized辅助方法,以便example_class
仅在第一次引用时评估一次。
如果这是真的,为什么我会收到这些警告?
答案 0 :(得分:1)
Class.new
时,您正在创建一个匿名(未命名)类。 与模块或类不同,匿名类中的命名空间为空。
class A
p Module.nesting # => [A]
B = 1
end
B #=> NameError: uninitialized constant B
A::B #=> 1
Class.new(A) do
p Module.nesting # => []
C = 1
end
C #=> 1
由于没有明确的命名空间将这些常量放在哪里,它们最终会被定义为"全局"在Object
班。
您可以使用以下示例自行尝试(使用--order defined
运行):
require_relative 'spec_helper'
class Base
end
RSpec.describe Base do
context 'creating an anonymous class with a constant' do
let(:example_class) do
Class.new(described_class) do
DB_ID = 'example'
def foo
'foo'
end
end
end
it 'does foo' do
expect(example_class.new.foo).to eq('foo')
end
end
context 'the constant is on Object' do
it 'does bar' do
expect(DB_ID).to eq('example')
end
end
end
归结为红宝石常量查找的工作原理。这是一篇关于它的好文章https://cirw.in/blog/constant-lookup.html。