public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
commit  f8df510045587cd49a78851c1c6fc17a0e333a7d
tree    5e503adc9db174d7fd3853da3379e0ad502ab247
parent  1a01f84b098a8936a123e2fcf91a5cd219a2e35c
merb-core / lib / merb-core / dispatch / session / memcached.rb
100644 180 lines (156 sloc) 5.023 kb
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
172
173
174
175
176
177
178
179
180
module Merb
 
  module SessionMixin #:nodoc:
 
    # Adds a before and after dispatch hook for setting up the memcached
    # session store.
    #
    # ==== Parameters
    # base<Class>:: The class to which the SessionMixin is mixed into.
    def setup_session
      before = cookies[_session_id_key]
      request.session, cookies[_session_id_key] = Merb::MemCacheSession.persist(cookies[_session_id_key])
      @_fingerprint = Marshal.dump(request.session.data).hash
      @_new_cookie = cookies[_session_id_key] != before
    end
 
    # Finalizes the session by storing the session ID in a cookie, if the
    # session has changed.
    def finalize_session
      if @_fingerprint != Marshal.dump(request.session.data).hash
        begin
          CACHE.set("session:#{request.session.session_id}", request.session.data)
        rescue => err
          Merb.logger.debug("MemCache Error: #{err.message}")
          Merb::SessionMixin::finalize_session_exception_callbacks.each {|x| x.call(err) }
        end
      end
      set_cookie(_session_id_key, request.session.session_id, Time.now + _session_expiry) if (@_new_cookie || request.session.needs_new_cookie)
    end
 
    # ==== Returns
    # String:: The session store type, i.e. "memcache".
    def session_store_type
      "memcache"
    end
  end
 
  ##
  # Sessions stored in memcached.
  #
  # Requires setup in your +init.rb+.
  #
  # require 'memcache'
  # CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
  #
  # And a setting in +init.rb+:
  #
  # c[:session_store] = 'memcache'
  #
  # If you are using the memcached gem instead of memcache-client, you must setup like this:
  #
  # require 'memcached'
  # CACHE = Memcached.new('127.0.0.1:11211', { :namespace => 'my_app' })
  #
  class MemCacheSession
 
    attr_accessor :session_id
    attr_accessor :data
    attr_accessor :needs_new_cookie
 
    # ==== Parameters
    # session_id<String>:: A unique identifier for this session.
    def initialize(session_id)
      @session_id = session_id
      @data = {}
    end
 
    class << self
 
      # Generates a new session ID and creates a new session.
      #
      # ==== Returns
      # MemCacheSession:: The new session.
      def generate
        sid = Merb::SessionMixin::rand_uuid
        new(sid)
      end
 
      # ==== Parameters
      # session_id<String:: The ID of the session to retrieve.
      #
      # ==== Returns
      # Array::
      # A pair consisting of a MemCacheSession and the session's ID. If no
      # sessions matched session_id, a new MemCacheSession will be generated.
      #
      # ==== Notes
      # If there are persiste exceptions callbacks to execute, they all get executed
      # when Memcache library raises an exception.
      def persist(session_id)
        unless session_id.blank?
          begin
            session = CACHE.get("session:#{session_id}")
          rescue => err
            Merb.logger.debug("MemCache Error: #{err.message}")
            Merb::SessionMixin::persist_exception_callbacks.each {|x| x.call(err) }
          end
          if session.nil?
            # Not in memcached, but assume that cookie exists
            session = new(session_id)
          end
        else
          # No cookie...make a new session_id
          session = generate
        end
        if session.is_a?(MemCacheSession)
          [session, session.session_id]
        else
          # recreate using the rails session as the data
          session_object = MemCacheSession.new(session_id)
          session_object.data = session
          [session_object, session_object.session_id]
        end
      end
 
      # Don't try to reload in dev mode.
      def reloadable? #:nodoc:
        false
      end
 
    end
 
    # Regenerate the session ID.
    def regenerate
      @session_id = Merb::SessionMixin::rand_uuid
      self.needs_new_cookie=true
    end
 
    # Recreates the cookie with the default expiration time. Useful during log
    # in for pushing back the expiration date.
    def refresh_expiration
      self.needs_new_cookie=true
    end
 
    # Deletes the session by emptying stored data.
    def delete
      @data = {}
    end
 
    # ==== Returns
    # Boolean:: True if session has been loaded already.
    def loaded?
      !! @data
    end
 
    # ==== Parameters
    # k<~to_s>:: The key of the session parameter to set.
    # v<~to_s>:: The value of the session parameter to set.
    def []=(k, v)
      @data[k] = v
    end
 
    # ==== Parameters
    # k<~to_s>:: The key of the session parameter to retrieve.
    #
    # ==== Returns
    # String:: The value of the session parameter.
    def [](k)
      @data[k]
    end
 
    # Yields the session data to an each block.
    #
    # ==== Parameter
    # &b:: The block to pass to each.
    def each(&b)
      @data.each(&b)
    end
 
    private
 
    # Attempts to redirect any messages to the data object.
    def method_missing(name, *args, &block)
      @data.send(name, *args, &block)
    end
 
  end
 
end