session.rb 5.6 KB
Newer Older
1 2
# frozen_string_literal: true

3
require "rack/session/abstract/id"
4 5

module ActionDispatch
6
  class Request
S
Steve Klabnik 已提交
7
    # Session is responsible for lazily loading the session from store.
8
    class Session # :nodoc:
A
Aaron Patterson 已提交
9 10
      ENV_SESSION_KEY         = Rack::RACK_SESSION # :nodoc:
      ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
11

12
      # Singleton object used to determine if an optional param wasn't specified.
13
      Unspecified = Object.new
14

15
      # Creates a session hash, merging the properties of the previous session if any.
16 17 18
      def self.create(store, req, default_options)
        session_was = find req
        session     = Request::Session.new(store, req)
19 20
        session.merge! session_was if session_was

21 22
        set(req, session)
        Options.set(req, Request::Session::Options.new(store, default_options))
23 24 25
        session
      end

26 27
      def self.find(req)
        req.get_header ENV_SESSION_KEY
28 29
      end

30 31
      def self.set(req, session)
        req.set_header ENV_SESSION_KEY, session
32 33 34
      end

      class Options #:nodoc:
35 36
        def self.set(req, options)
          req.set_header ENV_SESSION_OPTIONS_KEY, options
37 38
        end

39 40
        def self.find(req)
          req.get_header ENV_SESSION_OPTIONS_KEY
41 42
        end

43
        def initialize(by, default_options)
44
          @by       = by
45
          @delegate = default_options.dup
46 47 48
        end

        def [](key)
49 50 51
          @delegate[key]
        end

52
        def id(req)
53
          @delegate.fetch(:id) {
54
            @by.send(:extract_session_id, req)
55
          }
56 57
        end

58
        def []=(k, v);        @delegate[k] = v; end
59 60 61 62
        def to_hash;          @delegate.dup; end
        def values_at(*args); @delegate.values_at(*args); end
      end

63
      def initialize(by, req)
64
        @by       = by
65
        @req      = req
66
        @delegate = {}
67
        @loaded   = false
68
        @exists   = nil # We haven't checked yet.
69 70
      end

71
      def id
72
        options.id(@req)
73 74
      end

75
      def options
76
        Options.find @req
77 78 79 80 81
      end

      def destroy
        clear
        options = self.options || {}
82
        @by.send(:delete_session, @req, options.id(@req), options)
83

84
        # Load the new sid to be written with the response.
85
        @loaded = false
86
        load_for_write!
87 88
      end

89
      # Returns value of the key stored in the session or
90
      # +nil+ if the given key is not found in the session.
91 92
      def [](key)
        load_for_read!
93
        @delegate[key.to_s]
94 95
      end

96
      # Returns true if the session has the given key or false.
97 98
      def has_key?(key)
        load_for_read!
99
        @delegate.key?(key.to_s)
100 101 102 103
      end
      alias :key? :has_key?
      alias :include? :has_key?

104
      # Returns keys of the session as Array.
105
      def keys
106
        load_for_read!
107 108 109
        @delegate.keys
      end

110
      # Returns values of the session as Array.
111
      def values
112
        load_for_read!
113 114 115
        @delegate.values
      end

A
Akira Matsuda 已提交
116
      # Writes given value to given key of the session.
117 118
      def []=(key, value)
        load_for_write!
119
        @delegate[key.to_s] = value
120 121
      end

122
      # Clears the session.
123 124 125 126 127
      def clear
        load_for_write!
        @delegate.clear
      end

128
      # Returns the session as Hash.
129 130
      def to_hash
        load_for_read!
131
        @delegate.dup.delete_if { |_, v| v.nil? }
132
      end
133
      alias :to_h :to_hash
134

135 136 137 138 139 140 141 142 143 144
      # Updates the session with given Hash.
      #
      #   session.to_hash
      #   # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
      #
      #   session.update({ "foo" => "bar" })
      #   # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
      #
      #   session.to_hash
      #   # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
145 146
      def update(hash)
        load_for_write!
147
        @delegate.update stringify_keys(hash)
148 149
      end

150
      # Deletes given key from the session.
151 152
      def delete(key)
        load_for_write!
153
        @delegate.delete key.to_s
154 155
      end

R
Rebecca Skinner 已提交
156 157
      # Returns value of the given key from the session, or raises +KeyError+
      # if can't find the given key and no default value is set.
158 159 160 161 162 163 164 165 166 167 168 169
      # Returns default value if specified.
      #
      #   session.fetch(:foo)
      #   # => KeyError: key not found: "foo"
      #
      #   session.fetch(:foo, :bar)
      #   # => :bar
      #
      #   session.fetch(:foo) do
      #     :bar
      #   end
      #   # => :bar
170
      def fetch(key, default = Unspecified, &block)
171 172
        load_for_read!
        if default == Unspecified
173
          @delegate.fetch(key.to_s, &block)
D
Damien Mathieu 已提交
174
        else
175
          @delegate.fetch(key.to_s, default, &block)
D
Damien Mathieu 已提交
176 177 178
        end
      end

179 180 181 182 183 184 185 186 187 188
      def inspect
        if loaded?
          super
        else
          "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
        end
      end

      def exists?
        return @exists unless @exists.nil?
189
        @exists = @by.send(:session_exists?, @req)
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
      end

      def loaded?
        @loaded
      end

      def empty?
        load_for_read!
        @delegate.empty?
      end

      def merge!(other)
        load_for_write!
        @delegate.merge!(other)
      end

206 207 208 209
      def each(&block)
        to_hash.each(&block)
      end

210 211
      private

212 213 214
        def load_for_read!
          load! if !loaded? && exists?
        end
215

216 217 218
        def load_for_write!
          load! unless loaded?
        end
219

220 221 222 223 224 225
        def load!
          id, session = @by.load_session @req
          options[:id] = id
          @delegate.replace(stringify_keys(session))
          @loaded = true
        end
226

227 228 229 230 231
        def stringify_keys(other)
          other.each_with_object({}) { |(key, value), hash|
            hash[key.to_s] = value
          }
        end
232 233 234
    end
  end
end