当我在控制器中分配时
@my_hash = { :my_key => :my_value }
并通过执行
来测试该控制器get 'index'
assigns(:my_hash).should == { :my_key => :my_value }
然后我收到以下错误消息:
expected: {:my_key=>:my_value},
got: {"my_key"=>:my_value} (using ==)
为什么这种自动符号转换为字符串?为什么它会影响哈希的键?
答案 0 :(得分:8)
您可以尝试调用“stringify_keys”:
assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
答案 1 :(得分:6)
如果Rails以某种方式得到它,并且内部使用字符串键,它可能最终为HashWithIndifferentAccess
。您可能希望验证该类是否相同:
assert_equal Hash, assigns(:my_hash).class
参数始终作为无差别访问类型的哈希处理,因此您可以使用字符串或符号进行检索。如果您在get
或post
来电时将此分配给您的参数哈希,或者您可能正在转换。
你可以做的另一件事是冻结它,看看是否有人试图修改它,因为这应该抛出异常:
@my_hash = { :my_key => :my_value }.freeze
答案 2 :(得分:1)
AHA!发生这种情况不是因为Rails本身,而是因为Rspec。
我在控制器规范中测试Hashie::Mash
的值时遇到了同样的问题(但它适用于任何像Hash
那样嘎嘎作响的事情)
具体来说,在控制器规范中,当您调用assigns
来访问控制器操作中设置的实例变量时,它不会完全返回您设置的实例变量,而是返回Rspec存储的变量的副本作为HashWithIndifferentAccess
的成员(包含所有已分配的实例变量)。不幸的是,当你将Hash
(或任何继承自Hash
的内容)粘贴到HashWithIndifferentAccess
时,它会自动转换为相同的实例,非常方便,但不是 - 相当准确的课程:)
最简单的解决方法是通过直接访问变量来避免转换,在“为方便起见”转换之前,使用:controller.view_assigns['variable_name']
(注意:此处的键必须是字符串,而不是符号)
因此,如果将原始帖子中的测试更改为:
,则应该通过该测试get 'index'
controller.view_assigns['my_hash'].should == { :my_key => :my_value }
(当然,在新版本的RSpec中不再支持.should
,但仅仅为了比较我保持相同)
有关进一步说明,请参阅此文章: http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html
答案 3 :(得分:1)
我知道这很旧,但是如果您要从Rails-3升级到4,则您的控制器测试可能仍会使用带有符号键的Hash
,但与字符串化版本相比,只是为了防止错误的期望。
Rails-4已解决以下问题:https://github.com/rails/rails/pull/5082。 我建议您更新测试以对实际的密钥有期望。
在Rails-3中,assigns
方法将您的@my_hash
转换为HashWithIndifferentAccess
,以对所有键进行字符串化-
def assigns(key = nil)
assigns = @controller.view_assigns.with_indifferent_access
key.nil? ? assigns : assigns[key]
end
Rails-4更新了它以返回原始密钥-
def assigns(key = nil)
assigns = {}.with_indifferent_access
@controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
答案 4 :(得分:0)
您还可以将Hash
对象传递给HashWithIndifferentAccess
的初始值设定项。
答案 5 :(得分:0)
您可以使用HashWithIndifferentAccess.new
作为Hash init:
Thor::CoreExt::HashWithIndifferentAccess.new( to: 'mail@somehost.com', from: 'from@host.com')