aboutsummaryrefslogtreecommitdiff
path: root/lib/rack-flash.rb
blob: bc95b21234e89a52f6a942f52af7b654bb82ccdc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# 
# borrowed from http://github.com/nakajima/rack-flash - thanks!
#

require 'rack/builder'

module Rack
  class Builder
    attr :ins
    def use(middleware, *args, &block)
      middleware.instance_variable_set "@rack_builder", self
      def middleware.rack_builder
        @rack_builder
      end
      @ins << lambda { |app|
        middleware.new(app, *args, &block)
      }
    end

    def run(app)
      klass = app.class
      klass.instance_variable_set "@rack_builder", self
      def klass.rack_builder
        @rack_builder
      end
      @ins << app #lambda { |nothing| app }
    end

    def leaf_app
      ins.last
    end
  end
end

module Rack
  class Flash
    # Raised when the session passed to FlashHash initialize is nil. This
    # is usually an indicator that session middleware is not in use.
    class SessionUnavailable < StandardError; end

    # Implements bracket accessors for storing and retrieving flash entries.
    class FlashHash
      attr_reader :flagged

      def initialize(store, opts={})
        raise Rack::Flash::SessionUnavailable \
          .new('Rack::Flash depends on session middleware.') unless store

        @opts = opts
        @store = store

        if accessors = @opts[:accessorize]
          accessors.each { |opt| def_accessor(opt) }
        end
      end

      # Remove an entry from the session and return its value. Cache result in
      # the instance cache.
      def [](key)
        key = key.to_sym
        cache[key] ||= values.delete(key)
      end

      # Store the entry in the session, updating the instance cache as well.
      def []=(key,val)
        key = key.to_sym
        cache[key] = values[key] = val
      end

      # Store a flash entry for only the current request, swept regardless of
      # whether or not it was actually accessed. Useful for AJAX requests, where
      # you want a flash message, even though you're response isn't redirecting.
      def now
        cache
      end

      # Checks for the presence of a flash entry without retrieving or removing
      # it from the cache or store.
      def has?(key)
        [cache, values].any? { |store| store.keys.include?(key.to_sym) }
      end
      alias_method :include?, :has?

      # Mark existing entries to allow for sweeping.
      def flag!
        @flagged = values.keys
      end

      # Remove flagged entries from flash session, clear flagged list.
      def sweep!
        Array(flagged).each { |key| values.delete(key) }
        flagged.clear
      end

      # Hide the underlying :__FLASH__ session key and only expose values stored
      # in the flash.
      def inspect
        '#<FlashHash @values=%s @cache=%s>' % [values.inspect, cache.inspect]
      end

      # Human readable for logging.
      def to_s
        values.inspect
      end

      private

      # Maintain an instance-level cache of retrieved flash entries. These
      # entries will have been removed from the session, but are still available
      # through the cache.
      def cache
        @cache ||= {}
      end

      # Helper to access flash entries from :__FLASH__ session value. This key
      # is used to prevent collisions with other user-defined session values.
      def values
        @store[:__FLASH__] ||= {}
      end

      # Generate accessor methods for the given entry key if :accessorize is true.
      def def_accessor(key)
        raise ArgumentError.new('Invalid entry type: %s' % key) if respond_to?(key)

        class << self; self end.class_eval do
          define_method(key) { |*args| val = args.first; val ? (self[key]=val) : self[key] }
          define_method("#{key}=") { |val| self[key] = val }
          define_method("#{key}!") { |val| cache[key] = val }
        end
      end
    end

    # -------------------------------------------------------------------------
    # - Rack Middleware implementation

    def initialize(app, opts={})
      if klass = app_class(app, opts)
        klass.class_eval do
          def flash; env['x-rack.flash'] end
        end
      end

      @app, @opts = app, opts
    end

    def call(env)
      env['x-rack.flash'] ||= Rack::Flash::FlashHash.new(env['rack.session'], @opts)

      if @opts[:sweep]
        env['x-rack.flash'].flag!
      end

      res = @app.call(env)

      if @opts[:sweep]
        env['x-rack.flash'].sweep!
      end

      res
    end

    private

    def app_class(app, opts)
      return nil if opts.has_key?(:helper) and not opts[:helper]
      opts[:flash_app_class] ||
        defined?(Sinatra::Base) && Sinatra::Base ||
        self.class.rack_builder.leaf_app.class
    end
  end
end