This morning I put together the first small gem I've pushed to Rubygems, TinyRackFlash, a flash implementation for Rack applications.

"Flash" is a short-lived session cookie, typically used to allow a short message to be passed across a re-direct. For example, you might want to re-direct a user who has just failed to authenticate, but include a message to let them know the failure reason.

Rails has flash built in, and there are a number of Sinatra-specific implementations, but there aren't a lot of options for a smaller framework like Cuba. There is Rack::Flash, but a) it has more features than I needed, b) it hasn't been updated for years (not necessarily a bad thing), and c) I didn't especially like the FlashHash implementation.

I've used Stephen Eley's FlashHash implementation as found here. The gem can be used like:

require 'tiny_rack_flash'
class App < Cuba # or Sinatra::Base, etc.
  # ...
  use TinyRackFlash do |helpers|
    include helpers # adds `flash` method to your app class
  end
end

Here is the full text of the class:

require 'delegate'

class TinyRackFlash
  
  FlashKey   = 'tiny.rack.flash'.freeze
  SessionKey = 'rack.session'.freeze
  
  # This FlashHash implmentation is taken from Stephen Eley's work
  # at https://github.com/SFEley/sinatra-flash/blob/master/lib/sinatra/flash/hash.rb
  # which in turn is heavily inspired by ActionDispatch::Flash::FlashHash
  # at https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/flash.rb

  class FlashHash < DelegateClass(Hash)
    attr_reader :now, :next
    def initialize(session)
      @now = session || Hash.new
      @next = Hash.new
      super(@now)
    end

    def []=(key, value)
      self.next[key] = value
    end

    def sweep
      @now.replace(@next)
      @next = Hash.new
      @now
    end

    def keep(key=nil)
      if key
        @next[key] = @now[key]
      else
        @next.merge!(@now)
      end
    end
    
    def discard(key=nil)
      if key
        @next.delete(key)
      else
        @next = Hash.new
      end
    end
  end

  module Helpers
    def flash
      env[FlashKey] ||= begin
        session = env[SessionKey]
        FlashHash.new((session ? session[FlashKey] : {}))
      end
    end
  end
  
  def initialize(app, opts={})
    @app, @opts = app, opts
    yield Helpers if block_given?
  end
  
  def call(env)
    res = @app.call(env)
    env[SessionKey][FlashKey] = env[FlashKey].next if env[FlashKey]
    res
  end

end