Read more

Comparing Rails' flash hashes will not respect their internal lists of used entries

Arne Hartherz
March 18, 2013Software engineer at makandra GmbH

Rails flashes (FlashHash) track a list of used keys, which is not respected when comparing flash hashes.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

This does not concern you under most circumstances.

Basics

When ActionController picks up a flash object, it will call the #sweep method once; that method checks the list of used flash entries and deletes those. All other entries are flagged as used. This means they will be deleted on the next request, but are still be available for rendering during the current request.

Fun facts: When redirecting, this does not happen. Also, using #keep on a flash will reset that list of used records.

The issue

You will rarely run into this, but: assume you want to compare two FlashHash objects:

>> flash1
=> {:hello=>"Universe"}
>> flash2
=> {:hello=>"Universe"}
>> flash1 == flash2
=> true

So far so good. But: When flash1 was sweeped while flash2 was not this happens:

>> flash1.instance_variable_get('@used')
=> {:hello=>true}
>> flash2.instance_variable_get('@used')
=> {:hello=>false}
>> flash1 == flash2
=> true

While the keys are still the same, both flashes actually are not the same, since one will discard its records when processing the next request, while the other will not.

Monkey-patch away

While you don't want to change "regular" comparison methods (like == or eql?), you can use this initializer to get a FlashHash#same? method which checks for that:

ActionController::Flash::FlashHash.class_eval do
  # This is from a Rails 2 project. For Rails 3, say:
  # ActionDispatch::Flash::FlashHash.class_eval do

  def same?(other_flash)
    self == other_flash && used == other_flash.used
  end

  protected

  def used
    @used
  end

end

There you go:

>> flash1.same? flash2
=> true
>> flash1.sweep
>> flash1.same? flash2
=> false
Posted by Arne Hartherz to makandra dev (2013-03-18 17:30)