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