session.rb 5.7 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 94 95 96 97 98 99
        key = key.to_s

        if key == "session_id"
          id&.public_id
        else
          @delegate[key]
        end
100 101
      end

102
      # Returns true if the session has the given key or false.
103 104
      def has_key?(key)
        load_for_read!
105
        @delegate.key?(key.to_s)
106 107 108 109
      end
      alias :key? :has_key?
      alias :include? :has_key?

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

116
      # Returns values of the session as Array.
117
      def values
118
        load_for_read!
119 120 121
        @delegate.values
      end

A
Akira Matsuda 已提交
122
      # Writes given value to given key of the session.
123 124
      def []=(key, value)
        load_for_write!
125
        @delegate[key.to_s] = value
126 127
      end

128
      # Clears the session.
129 130 131 132 133
      def clear
        load_for_write!
        @delegate.clear
      end

134
      # Returns the session as Hash.
135 136
      def to_hash
        load_for_read!
137
        @delegate.dup.delete_if { |_, v| v.nil? }
138
      end
139
      alias :to_h :to_hash
140

141 142 143 144 145 146 147 148 149 150
      # 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"}
151 152
      def update(hash)
        load_for_write!
153
        @delegate.update stringify_keys(hash)
154 155
      end

156
      # Deletes given key from the session.
157 158
      def delete(key)
        load_for_write!
159
        @delegate.delete key.to_s
160 161
      end

R
Rebecca Skinner 已提交
162 163
      # 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.
164 165 166 167 168 169 170 171 172 173 174 175
      # 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
176
      def fetch(key, default = Unspecified, &block)
177 178
        load_for_read!
        if default == Unspecified
179
          @delegate.fetch(key.to_s, &block)
D
Damien Mathieu 已提交
180
        else
181
          @delegate.fetch(key.to_s, default, &block)
D
Damien Mathieu 已提交
182 183 184
        end
      end

185 186 187 188 189 190 191 192 193 194
      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?
195
        @exists = @by.send(:session_exists?, @req)
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
      end

      def loaded?
        @loaded
      end

      def empty?
        load_for_read!
        @delegate.empty?
      end

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

212 213 214 215
      def each(&block)
        to_hash.each(&block)
      end

216 217
      private

218 219 220
        def load_for_read!
          load! if !loaded? && exists?
        end
221

222 223 224
        def load_for_write!
          load! unless loaded?
        end
225

226 227 228 229 230 231
        def load!
          id, session = @by.load_session @req
          options[:id] = id
          @delegate.replace(stringify_keys(session))
          @loaded = true
        end
232

233 234 235 236 237
        def stringify_keys(other)
          other.each_with_object({}) { |(key, value), hash|
            hash[key.to_s] = value
          }
        end
238 239 240
    end
  end
end