Нежелательный символ для преобразования строки хеш-ключа
Когда я назначаю в своем контроллере
@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 ==)
Почему этот автоматический символ преобразуется в строковое преобразование? Почему это влияет на ключ хеша?
Ответы
Ответ 1
Он может оказаться как HashWithIndifferentAccess
, если Rails каким-то образом получает его, и он использует внутренние ключи внутри. Возможно, вам захочется проверить, что класс тот же:
assert_equal Hash, assigns(:my_hash).class
Параметры всегда обрабатываются как безразличный вид доступа хеширования, поэтому вы можете получить его с помощью любой строки или символа. Если вы назначаете это вашему хэшу params в вызове get
или post
, или вы можете быть преобразованы.
Еще одна вещь, которую вы можете сделать, это заморозить ее и посмотреть, пытается ли кто-нибудь ее изменить, потому что это должно вызвать исключение:
@my_hash = { :my_key => :my_value }.freeze
Ответ 2
Вы можете попробовать вызвать "stringify_keys":
assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
Ответ 3
АГА! Это происходит не из-за 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 }
(конечно, .should
больше не поддерживается в новых версиях RSpec, но просто для сравнения я сохранил то же самое)
См. Эту статью для дальнейшего объяснения: http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html
Ответ 4
Вы также можете передать свой объект Hash
в инициализатор HashWithIndifferentAccess
.
Ответ 5
Вы можете использовать HashWithIndifferentAccess.new
как Hash init:
Thor::CoreExt::HashWithIndifferentAccess.new( to: '[email protected]', from: '[email protected]')
Ответ 6
Я знаю, что это старая версия, но если вы переходите с 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
https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L10
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
https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L11