require 'forwardable' require 'facet/kernel/assign_with' require 'glue/cache/memory' require 'nitro/cgi' require 'nitro/cgi/request' require 'nitro/cgi/response' require 'nitro/render' require 'nitro/global' require 'nitro/session' module Nitro # Encapsulates an HTTP processing cycle context. Integrates the # HTTP Request, the HTTP Response and the Render used to # generate the response body. # # The Context object can be accessed by the context, request or # response aliases. You can use the alias that makes sense # every time. This means inside an action request, response and # context all point to the same object. class Context include Request include Response include Render # The configuration parameters. attr_accessor :conf # The session contains variables that stay alive # for the full user session. Session variables # should be generally avoided. This variable # becomes populated ONLY if needed. attr_reader :session # The dispatcher. attr_accessor :dispatcher # The rendering level. An action may include sub-actions, # each time the action is called, the level is increased, # when the action returns the level decreases. The first # action, called top level action has a level of 1. attr_accessor :level def initialize(conf) @level = 0 @conf = conf @dispatcher = @conf.dispatcher @context = self # initialize response. @status = Http::STATUS_OK @response_headers = { 'Content-Type' => 'text/html' } # initialize the output buffer. @out ||= OutputBuffer.new # Store this instance in a thread local variable for easy # access. Thread.current[:CURRENT_CONTEXT] = self end # Don't sync session. This method may be needed in low level # hacks with the session code. For example if you updade an # entity (managed) object that is cached in the session, you # may dissable the syncing to avoid resetting the old value # after the sync. def no_sync! @no_sync = true end # Close the context, should be called at the # end of the HTTP request handling code. def close if @session # Ugly hack: need to use AOP instead if @session.has_key?(:FLASH) @session[:FLASH].clean end # INVESTIGATE: is this needed? @session.sync unless @no_sync end end alias_method :finish, :close # The accomulated output for the current context (ie the # current request). #-- # FIXME: still something more elegant/efficient. #++ def out return @out if @out == '' if @rendering_errors @out = '' render '/error' end @out end # Lazy lookup of the session to avoid costly cookie # lookup when not needed. def session @session || @session = Session.lookup(self) end # Access global variables. In a distributed server scenario, # these variables can reside outside of the process. def global return Global end alias_method :application, :global # Lookup the controller for this request. #-- # FIXME: improve this! BUGGY #++ def controller @dispatcher.controllers[@base || '/'] end # Automagically populate an object from request parameters. # This is a truly dangerous method. # # === Options # # * name # * force_boolean # # === Example # # request.fill(User.new) # # Prefer to use the following form: # # User.new.assign_with(request) def fill(obj, options = {}) Property.populate_object(obj, @params, options) end alias_method :populate, :fill alias_method :assign, :fill # Is the current action the top level action? The level # of the top action is 1. def is_top_level? @level == 1 end alias_method :is_top?, :is_top_level? alias_method :in_top_level?, :is_top_level? alias_method :top_level?, :is_top_level? class << self # Returns the context for the current thread. def current Thread.current[:CURRENT_CONTEXT] end end end end # * George Moschovitis