Matthew
Matthew

Reputation: 1617

Why can't I access contents of flash when it's a hash?

According to the Flash documentation, I should be able to pass strings, arrays or hashes through Flash. Strings and arrays work fine but hashes aren't working.

Here's a stripped down (but still failing) version of my code:

Flash messages partial

# views/layouts/flash_messages.html.haml

- flash.each do |type, content|
  - message = content if content.class == String
  - message, detail = content if content.class == Array
  - message, detail = content[:message], content[:detail] if content.class == Hash
  - if message || detail
    .alert
      %h3= message if message.present?
      %p= detail if detail.present?

Home controller

class HomeController < ApplicationController
  def index
  end

  def test_string
    redirect_to root_url, alert: 'Plain string works'
  end

  def test_array
    redirect_to root_url, alert: ['Array works', 'Tested with 0, 1, 2 or more items without problems']
  end

  def test_hash
    redirect_to root_url, alert: {message: 'Hash not working :(', detail: 'Even though I think it should'}
  end
end

The problem seems to be with the assignment line in the case of the hash, the keys are present but method and detail always end up nil for hashes. But when I try the same code in the console it works fine...

IRB

irb> content = { message: 'This hash works...', detail: '...as expected' }
=> {:message=>"This hash works...", :detail=>"...as expected"}
irb> message, detail = content[:message], content[:detail] if content.class == Hash
=> [ 'This hash works...', '...as expected' ]
irb> message
=> 'This hash works...'
irb> detail
=> '...as expected'

Upvotes: 0

Views: 247

Answers (1)

Matthew
Matthew

Reputation: 1617

Closer inspection revealed that, while the keys were indeed set, they'd been converted from symbols to strings.

To fix this I had to change line 4 of the controller from symbols to strings:

- message, detail = content[:message], content[:detail] if content.class == Hash
- message, detail = content['message'], content['detail'] if content.class == Hash

If I understand correctly, this is a result of flashes being stored in the session and the session object being stored in cookies as JSON objects. JSON doesn't support symbolised keys.

As an experiment I tried setting matching string and symbol keys. If you try doing both in one assignment, Ruby takes the first key and the second value (with a warning):

irb> content = { message: 'Symbol key first', 'message': 'String key second' }
=> warning: key :message is duplicated and overwritten on line X
=> {:message=>"String key second"}

But if you deliberately duplicate the keys in a hash passed to flash, whichever one is defined last "wins" (in my limited testing, but it makes sense given hashes are most likely iterated in insertion order):

symbol_first = {}
symbol_first[:message] = 'Symbol wins'
symbol_first['message'] = 'String wins'
flash[:alert] = symbol_first # 'String wins' is displayed

string_first = {}
string_first['message'] = 'String wins'
string_first[:message] = 'Symbol wins'
flash[:alert] = string_first # 'Symbol wins' is displayed

Upvotes: 2

Related Questions