Prior to upgrading to Rails 4.1 we had a helper to display flash messages and to add css class to the message based on flash type. Here is the code.
1 2module FlashHelper 3 ALERT_TYPES = [:success, :info, :warning, :danger] 4 5 def bootstrap_flash 6 flash_messages = [] 7 flash.each do |type, message| 8 next if message.blank? 9 10 type = :success if type == :notice 11 type = :danger if type == :alert 12 type = :danger if type == :error 13 14 next unless ALERT_TYPES.include?(type) 15 16 .... 17 Array(message).each do |msg| 18 text = content_tag(:div, msg.html_safe, :class => "alert fade in alert-#{type} ") 19 flash_messages << text if msg 20 end 21 end 22 flash_messages.join("\n").html_safe 23 end 24end 25
After upgrading to Rails 4.1, we started using the new Cookies serializer. Following code was added to an initializer.
1 2 Rails.application.config.action_dispatch.cookies_serializer = :json 3
Soon after this our flash helper started misbehaving and all flash messages disappeared from the application.
JSON Cookies Serializer
Before we move ahead, a word on the new JSON Cookies Serializer. Applications created before Rails 4.1 uses Marshal to serialize cookie values into the signed and encrypted cookie jars.
Commits like this and this made it possible to have Cookies serializer and defaulted from Marshal Serializer to a secure Serializer using JSON.
The JSON Serializer works on JSON objects. Thus objects like Date and Time will be stored as strings. Hash keys will be stored as strings.
JSON serializer makes the application much safer since it is safer to pass around strings compare to passing around arbitrary values which is what was happens when values are marshalled and passed around.
Coming back to our problem, change Stringify the incoming hash in FlashHash coupled with above serialization changes meant that even if we put a symbol as a key in the flash we have to retrieve it as "string" since the keys are internally being converted into strings.
The difference is clearly illustrated below.
1 2flash["string"] = "a string" 3flash[:symbol] = "a symbol" 4 5# Rails < 4.1 6flash.keys # => ["string", :symbol] 7 8# Rails >= 4.1 9flash.keys # => ["string", "symbol"] 10
Solution
Now that we know the root cause of the problem the fix was simple. Instead of relying on symbols use "string" to access value from flash.
1 2module BootstrapFlashHelper 3 ALERT_TYPES = ['success', 'info', 'warning', 'danger'] 4 5 def bootstrap_flash 6 flash_messages = [] 7 flash.each do |type, message| 8 compare_type = type.to_s # Notice the stringifying of keys here, to make this work even with symbols. 9 next if message.blank? 10 11 compare_type = 'success' if compare_type == 'notice' 12 compare_type = 'danger' if compare_type == 'alert' 13 compare_type = 'danger' if compare_type == 'error' 14 15 next unless ALERT_TYPES.include?(compare_type) 16 17 Array(message).each do |msg| 18 text = content_tag(:div, msg.html_safe, :class => "alert fade in alert-#{compare_type} ") 19 flash_messages << text if msg 20 end 21 end 22 flash_messages.join("\n").html_safe 23 end 24end 25