Нежелательный символ для преобразования строки хеш-ключа

Когда я назначаю в своем контроллере

@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