module ActionDispatch
  module Session
    class MongoidStore < AbstractStore

      class Session
        include Mongoid::Document
        include Mongoid::Timestamps

        field :data, :type => String, :default => [Marshal.dump({})].pack("m*")
        index :updated_at
      end

      # The class used for session storage.
      cattr_accessor :session_class
      self.session_class = Session

      SESSION_RECORD_KEY = 'rack.session.record'.freeze

      private
        def generate_sid
          BSON::ObjectId.new
        end

        def get_session(env, sid)
          sid ||= generate_sid
          session = find_session(sid)
          env[SESSION_RECORD_KEY] = session
          [sid, unpack(session.data)]
        end

        def set_session(env, sid, session_data)
          record = env[SESSION_RECORD_KEY] ||= find_session(sid)
          record.data = pack(session_data)
          # Rack spec dictates that set_session should return true or false
          # depending on whether or not the session was saved or not.
          # However, ActionPack seems to want a session id instead.
          record.save ? sid : false
        end

        def find_session(id)
          id = BSON::ObjectId.from_string(id.to_s)
          @@session_class.first(:conditions => { :_id => id }) ||
            @@session_class.new(:id => id)
        end

        def pack(data)
          [Marshal.dump(data)].pack("m*")
        end

        def unpack(packed)
          return nil unless packed
          Marshal.load(packed.unpack("m*").first)
        end

        def destroy(env)
          session = @@session_class.first(:conditions => { :_id => env[SESSION_RECORD_KEY].id })
          session.destroy

          env[SESSION_RECORD_KEY] = nil
        end

    end
  end
end