aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/session_management.rb
blob: 207db90c83c8249c73277a3a75d900b0a19d4588 (plain) (blame)
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
require 'action_controller/session/cookie_store'
require 'action_controller/session/drb_store'
require 'action_controller/session/mem_cache_store'
if Object.const_defined?(:ActiveRecord)
  require 'action_controller/session/active_record_store'
end

module ActionController #:nodoc:
  module SessionManagement #:nodoc:
    def self.included(base)
      base.class_eval do
        extend ClassMethods
        alias_method_chain :process, :session_management_support
        alias_method_chain :process_cleanup, :session_management_support
      end
    end

    module ClassMethods
      # Set the session store to be used for keeping the session data between requests. The default is using the
      # file system, but you can also specify one of the other included stores (:active_record_store, :drb_store, 
      # :mem_cache_store, or :memory_store) or use your own class.
      def session_store=(store)
        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
          store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
      end

      # Returns the session store class currently used.
      def session_store
        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
      end

      # Returns the hash used to configure the session. Example use:
      #
      #   ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
      def session_options
        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
      end
      
      # Specify how sessions ought to be managed for a subset of the actions on
      # the controller. Like filters, you can specify <tt>:only</tt> and
      # <tt>:except</tt> clauses to restrict the subset, otherwise options
      # apply to all actions on this controller.
      #
      # The session options are inheritable, as well, so if you specify them in
      # a parent controller, they apply to controllers that extend the parent.
      #
      # Usage:
      #
      #   # turn off session management for all actions.
      #   session :off
      #
      #   # turn off session management for all actions _except_ foo and bar.
      #   session :off, :except => %w(foo bar)
      #
      #   # turn off session management for only the foo and bar actions.
      #   session :off, :only => %w(foo bar)
      #
      #   # the session will only work over HTTPS, but only for the foo action
      #   session :only => :foo, :session_secure => true
      #
      #   # the session will only be disabled for 'foo', and only if it is
      #   # requested as a web service
      #   session :off, :only => :foo,
      #           :if => Proc.new { |req| req.parameters[:ws] }
      #
      #   # the session will be disabled for non html/ajax requests
      #   session :off, 
      #     :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
      #
      # All session options described for ActionController::Base.process_cgi
      # are valid arguments.
      def session(*args)
        options = args.extract_options!

        options[:disabled] = true if !args.empty?
        options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
        options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
        if options[:only] && options[:except]
          raise ArgumentError, "only one of either :only or :except are allowed"
        end

        write_inheritable_array("session_options", [options])
      end

      # So we can declare session options in the Rails initializer.
      alias_method :session=, :session

      def cached_session_options #:nodoc:
        @session_options ||= read_inheritable_attribute("session_options") || []
      end

      def session_options_for(request, action) #:nodoc:
        if (session_options = cached_session_options).empty?
          {}
        else
          options = {}

          action = action.to_s
          session_options.each do |opts|
            next if opts[:if] && !opts[:if].call(request)
            if opts[:only] && opts[:only].include?(action)
              options.merge!(opts)
            elsif opts[:except] && !opts[:except].include?(action)
              options.merge!(opts)
            elsif !opts[:only] && !opts[:except]
              options.merge!(opts)
            end
          end
          
          if options.empty? then options
          else
            options.delete :only
            options.delete :except
            options.delete :if
            options[:disabled] ? false : options
          end
        end
      end
    end

    def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
      set_session_options(request)
      process_without_session_management_support(request, response, method, *arguments)
    end

    private
      def set_session_options(request)
        request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
      end
      
      def process_cleanup_with_session_management_support
        clear_persistent_model_associations
        process_cleanup_without_session_management_support
      end

      # Clear cached associations in session data so they don't overflow
      # the database field.  Only applies to ActiveRecordStore since there
      # is not a standard way to iterate over session data.
      def clear_persistent_model_associations #:doc:
        if defined?(@_session) && @_session.respond_to?(:data)
          session_data = @_session.data

          if session_data && session_data.respond_to?(:each_value)
            session_data.each_value do |obj|
              obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
            end
          end
        end
      end
  end
end