aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
commitdb045dbbf60b53dbe013ef25554fd013baf88134 (patch)
tree257830e3c76458c8ff3d1329de83f32b23926028 /actionpack/lib/action_controller
downloadrails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.gz
rails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.bz2
rails-db045dbbf60b53dbe013ef25554fd013baf88134.zip
Initial
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/assertions/action_pack_assertions.rb199
-rw-r--r--actionpack/lib/action_controller/assertions/active_record_assertions.rb65
-rwxr-xr-xactionpack/lib/action_controller/base.rb689
-rw-r--r--actionpack/lib/action_controller/benchmarking.rb49
-rwxr-xr-xactionpack/lib/action_controller/cgi_ext/cgi_ext.rb43
-rwxr-xr-xactionpack/lib/action_controller/cgi_ext/cgi_methods.rb91
-rw-r--r--actionpack/lib/action_controller/cgi_process.rb124
-rw-r--r--actionpack/lib/action_controller/dependencies.rb49
-rw-r--r--actionpack/lib/action_controller/filters.rb279
-rw-r--r--actionpack/lib/action_controller/flash.rb65
-rw-r--r--actionpack/lib/action_controller/helpers.rb100
-rw-r--r--actionpack/lib/action_controller/layout.rb149
-rwxr-xr-xactionpack/lib/action_controller/request.rb99
-rw-r--r--actionpack/lib/action_controller/rescue.rb94
-rwxr-xr-xactionpack/lib/action_controller/response.rb15
-rw-r--r--actionpack/lib/action_controller/scaffolding.rb183
-rw-r--r--actionpack/lib/action_controller/session/active_record_store.rb72
-rw-r--r--actionpack/lib/action_controller/session/drb_server.rb9
-rw-r--r--actionpack/lib/action_controller/session/drb_store.rb31
-rw-r--r--actionpack/lib/action_controller/support/class_attribute_accessors.rb57
-rw-r--r--actionpack/lib/action_controller/support/class_inheritable_attributes.rb37
-rw-r--r--actionpack/lib/action_controller/support/clean_logger.rb10
-rw-r--r--actionpack/lib/action_controller/support/cookie_performance_fix.rb121
-rw-r--r--actionpack/lib/action_controller/support/inflector.rb78
-rw-r--r--actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml28
-rw-r--r--actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml22
-rw-r--r--actionpack/lib/action_controller/templates/rescues/layout.rhtml29
-rw-r--r--actionpack/lib/action_controller/templates/rescues/missing_template.rhtml2
-rw-r--r--actionpack/lib/action_controller/templates/rescues/template_error.rhtml26
-rw-r--r--actionpack/lib/action_controller/templates/rescues/unknown_action.rhtml2
-rw-r--r--actionpack/lib/action_controller/templates/scaffolds/edit.rhtml6
-rw-r--r--actionpack/lib/action_controller/templates/scaffolds/layout.rhtml29
-rw-r--r--actionpack/lib/action_controller/templates/scaffolds/list.rhtml24
-rw-r--r--actionpack/lib/action_controller/templates/scaffolds/new.rhtml5
-rw-r--r--actionpack/lib/action_controller/templates/scaffolds/show.rhtml9
-rw-r--r--actionpack/lib/action_controller/test_process.rb195
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb170
37 files changed, 3255 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/assertions/action_pack_assertions.rb b/actionpack/lib/action_controller/assertions/action_pack_assertions.rb
new file mode 100644
index 0000000000..2cfbcbc938
--- /dev/null
+++ b/actionpack/lib/action_controller/assertions/action_pack_assertions.rb
@@ -0,0 +1,199 @@
+require 'test/unit'
+require 'test/unit/assertions'
+require 'rexml/document'
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ # Adds a wealth of assertions to do functional testing of Action Controllers.
+ module Assertions
+ # -- basic assertions ---------------------------------------------------
+
+ # ensure that the web request has been serviced correctly
+ def assert_success(message=nil)
+ response = acquire_assertion_target
+ if response.success?
+ # to count the assertion
+ assert_block("") { true }
+ else
+ if response.redirect?
+ msg = build_message(message, "Response unexpectedly redirect to <?>", response.redirect_url)
+ else
+ msg = build_message(message, "unsuccessful request (response code = <?>)",
+ response.response_code)
+ end
+ assert_block(msg) { false }
+ end
+ end
+
+ # ensure the request was rendered with the appropriate template file
+ def assert_rendered_file(expected=nil, message=nil)
+ response = acquire_assertion_target
+ rendered = expected ? response.rendered_file(!expected.include?('/')) : response.rendered_file
+ msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
+ assert_block(msg) do
+ if expected.nil?
+ response.rendered_with_file?
+ else
+ expected == rendered
+ end
+ end
+ end
+
+ # -- session assertions -------------------------------------------------
+
+ # ensure that the session has an object with the specified name
+ def assert_session_has(key=nil, message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is not in the session <?>", key, response.session)
+ assert_block(msg) { response.has_session_object?(key) }
+ end
+
+ # ensure that the session has no object with the specified name
+ def assert_session_has_no(key=nil, message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is in the session <?>", key, response.session)
+ assert_block(msg) { !response.has_session_object?(key) }
+ end
+
+ def assert_session_equal(expected = nil, key = nil, message = nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, response.session[key])
+ assert_block(msg) { expected == response.session[key] }
+ end
+
+ # -- flash assertions ---------------------------------------------------
+
+ # ensure that the flash has an object with the specified name
+ def assert_flash_has(key=nil, message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is not in the flash <?>", key, response.flash)
+ assert_block(msg) { response.has_flash_object?(key) }
+ end
+
+ # ensure that the flash has no object with the specified name
+ def assert_flash_has_no(key=nil, message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is in the flash <?>", key, response.flash)
+ assert_block(msg) { !response.has_flash_object?(key) }
+ end
+
+ # ensure the flash exists
+ def assert_flash_exists(message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "the flash does not exist <?>", response.session['flash'] )
+ assert_block(msg) { response.has_flash? }
+ end
+
+ # ensure the flash does not exist
+ def assert_flash_not_exists(message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "the flash exists <?>", response.flash)
+ assert_block(msg) { !response.has_flash? }
+ end
+
+ # ensure the flash is empty but existant
+ def assert_flash_empty(message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "the flash is not empty <?>", response.flash)
+ assert_block(msg) { !response.has_flash_with_contents? }
+ end
+
+ # ensure the flash is not empty
+ def assert_flash_not_empty(message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "the flash is empty")
+ assert_block(msg) { response.has_flash_with_contents? }
+ end
+
+ def assert_flash_equal(expected = nil, key = nil, message = nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, response.flash[key])
+ assert_block(msg) { expected == response.flash[key] }
+ end
+
+ # -- redirection assertions ---------------------------------------------
+
+ # ensure we have be redirected
+ def assert_redirect(message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "response is not a redirection (response code is <?>)", response.response_code)
+ assert_block(msg) { response.redirect? }
+ end
+
+ def assert_redirected_to(options = {}, message=nil)
+ assert_redirect(message)
+ response = acquire_assertion_target
+
+ msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)", response.redirected_to)
+ assert_block(msg) do
+ if options.is_a?(Symbol)
+ response.redirected_to == options
+ else
+ options.keys.all? { |k| options[k] == response.redirected_to[k] }
+ end
+ end
+ end
+
+ # ensure our redirection url is an exact match
+ def assert_redirect_url(url=nil, message=nil)
+ assert_redirect(message)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is not the redirected location <?>", url, response.redirect_url)
+ assert_block(msg) { response.redirect_url == url }
+ end
+
+ # ensure our redirection url matches a pattern
+ def assert_redirect_url_match(pattern=nil, message=nil)
+ assert_redirect(message)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> was not found in the location: <?>", pattern, response.redirect_url)
+ assert_block(msg) { response.redirect_url_match?(pattern) }
+ end
+
+ # -- template assertions ------------------------------------------------
+
+ # ensure that a template object with the given name exists
+ def assert_template_has(key=nil, message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is not a template object", key )
+ assert_block(msg) { response.has_template_object?(key) }
+ end
+
+ # ensure that a template object with the given name does not exist
+ def assert_template_has_no(key=nil,message=nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> is a template object <?>", key, response.template_objects[key])
+ assert_block(msg) { !response.has_template_object?(key) }
+ end
+
+ # ensures that the object assigned to the template on +key+ is equal to +expected+ object.
+ def assert_assigned_equal(expected = nil, key = nil, message = nil)
+ response = acquire_assertion_target
+ msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, response.template.assigns[key.to_s])
+ assert_block(msg) { expected == response.template.assigns[key.to_s] }
+ end
+
+ # Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
+ # This will only work if the template rendered a valid XML document.
+ def assert_template_xpath_match(expression=nil, expected=nil, message=nil)
+ response = acquire_assertion_target
+ xml, matches = REXML::Document.new(response.body), []
+ xml.elements.each(expression) { |e| matches << e.text }
+ matches = matches.first if matches.length < 2
+
+ msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
+ assert_block(msg) { matches == expected }
+ end
+
+ # -- helper functions ---------------------------------------------------
+
+ # get the TestResponse object that these assertions depend upon
+ def acquire_assertion_target
+ target = ActionController::TestResponse.assertion_target
+ assert_block( "Unable to acquire the TestResponse.assertion_target. Please set this before calling this assertion." ) { !target.nil? }
+ target
+ end
+
+ end # Assertions
+ end # Unit
+end # Test
diff --git a/actionpack/lib/action_controller/assertions/active_record_assertions.rb b/actionpack/lib/action_controller/assertions/active_record_assertions.rb
new file mode 100644
index 0000000000..9167eae53e
--- /dev/null
+++ b/actionpack/lib/action_controller/assertions/active_record_assertions.rb
@@ -0,0 +1,65 @@
+require 'test/unit'
+require 'test/unit/assertions'
+# active_record is assumed to be loaded by this point
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ module Assertions
+ # Assert the template object with the given name is an Active Record descendant and is valid.
+ def assert_valid_record(key = nil, message = nil)
+ record = find_record_in_template(key)
+ msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
+ assert_block(msg) { record.valid? }
+ end
+
+ # Assert the template object with the given name is an Active Record descendant and is invalid.
+ def assert_invalid_record(key = nil, message = nil)
+ record = find_record_in_template(key)
+ msg = build_message(message, "Active Record is valid)")
+ assert_block(msg) { !record.valid? }
+ end
+
+ # Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
+ def assert_valid_column_on_record(key = nil, columns = "", message = nil)
+ record = find_record_in_template(key)
+ record.validate
+
+ cols = glue_columns(columns)
+ cols.delete_if { |col| !record.errors.invalid?(col) }
+ msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
+ assert_block(msg) { cols.empty? }
+ end
+
+ # Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
+ def assert_invalid_column_on_record(key = nil, columns = "", message = nil)
+ record = find_record_in_template(key)
+ record.validate
+
+ cols = glue_columns(columns)
+ cols.delete_if { |col| record.errors.invalid?(col) }
+ msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
+ assert_block(msg) { cols.empty? }
+ end
+
+ private
+ def glue_columns(columns)
+ cols = []
+ cols << columns if columns.class == String
+ cols += columns if columns.class == Array
+ cols
+ end
+
+ def find_record_in_template(key = nil)
+ response = acquire_assertion_target
+
+ assert_template_has(key)
+ record = response.template_objects[key]
+
+ assert_not_nil(record)
+ assert_kind_of ActiveRecord::Base, record
+
+ return record
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
new file mode 100755
index 0000000000..0cbcbbf688
--- /dev/null
+++ b/actionpack/lib/action_controller/base.rb
@@ -0,0 +1,689 @@
+require 'action_controller/request'
+require 'action_controller/response'
+require 'action_controller/url_rewriter'
+require 'action_controller/support/class_attribute_accessors'
+require 'action_controller/support/class_inheritable_attributes'
+require 'action_controller/support/inflector'
+
+module ActionController #:nodoc:
+ class ActionControllerError < StandardError #:nodoc:
+ end
+ class SessionRestoreError < ActionControllerError #:nodoc:
+ end
+ class MissingTemplate < ActionControllerError #:nodoc:
+ end
+ class UnknownAction < ActionControllerError #:nodoc:
+ end
+
+ # Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
+ # redirects to another action. An action is defined as a public method on the controller, which will automatically be
+ # made accessible to the web-server through a mod_rewrite mapping. A sample controller could look like this:
+ #
+ # class GuestBookController < ActionController::Base
+ # def index
+ # @entries = Entry.find_all
+ # end
+ #
+ # def sign
+ # Entry.create(@params["entry"])
+ # redirect_to :action => "index"
+ # end
+ # end
+ #
+ # GuestBookController.template_root = "templates/"
+ # GuestBookController.process_cgi
+ #
+ # All actions assume that you want to render a template matching the name of the action at the end of the performance
+ # unless you tell it otherwise. The index action complies with this assumption, so after populating the @entries instance
+ # variable, the GuestBookController will render "templates/guestbook/index.rhtml".
+ #
+ # Unlike index, the sign action isn't interested in rendering a template. So after performing its main purpose (creating a
+ # new entry in the guest book), it sheds the rendering assumption and initiates a redirect instead. This redirect works by
+ # returning an external "302 Moved" HTTP response that takes the user to the index action.
+ #
+ # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
+ # Most actions are variations of these themes.
+ #
+ # Also note that it's the final call to <tt>process_cgi</tt> that actually initiates the action performance. It will extract
+ # request and response objects from the CGI
+ #
+ # == Requests
+ #
+ # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
+ # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
+ # request parameters, the session (if one is available), and the full request with all the http headers are made available to
+ # the action through instance variables. Then the action is performed.
+ #
+ # The full request object is available in @request and is primarily used to query for http headers. These queries are made by
+ # accessing the environment hash, like this:
+ #
+ # def hello_ip
+ # location = @request.env["REMOTE_ADDRESS"]
+ # render_text "Hello stranger from #{location}"
+ # end
+ #
+ # == Parameters
+ #
+ # All request parameters whether they come from a GET or POST request, or from the URL, are available through the @params hash.
+ # So an action that was performed through /weblog/list?category=All&limit=5 will include { "category" => "All", "limit" => 5 }
+ # in @params.
+ #
+ # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
+ #
+ # <input type="text" name="post[name]" value="david">
+ # <input type="text" name="post[address]" value="hyacintvej">
+ #
+ # A request stemming from a form holding these inputs will include { "post" # => { "name" => "david", "address" => "hyacintvej" } }.
+ # If the address input had been named "post[address][street]", the @params would have included
+ # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting.
+ #
+ # == Sessions
+ #
+ # Sessions allows you to store objects in memory between requests. This is useful for objects that are not yet ready to be persisted,
+ # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
+ # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
+ # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
+ #
+ # You can place objects in the session by using the <tt>@session</tt> hash:
+ #
+ # @session["person"] = Person.authenticate(user_name, password)
+ #
+ # And retrieved again through the same hash:
+ #
+ # Hello #{@session["person"]}
+ #
+ # Any object can be placed in the session (as long as it can be Marshalled). But remember that 1000 active sessions each storing a
+ # 50kb object could lead to a 50MB memory overhead. In other words, think carefully about size and caching before resorting to the use
+ # of the session.
+ #
+ # == Responses
+ #
+ # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
+ # object is generated automatically through the use of renders and redirects, so it's normally nothing you'll need to be concerned about.
+ #
+ # == Renders
+ #
+ # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
+ # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
+ # The controller passes objects to the view by assigning instance variables:
+ #
+ # def show
+ # @post = Post.find(@params["id"])
+ # end
+ #
+ # Which are then automatically available to the view:
+ #
+ # Title: <%= @post.title %>
+ #
+ # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
+ # the manual rendering methods:
+ #
+ # def search
+ # @results = Search.find(@params["query"])
+ # case @results
+ # when 0 then render "weblog/no_results"
+ # when 1 then render_action "show"
+ # when 2..10 then render_action "show_many"
+ # end
+ # end
+ #
+ # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
+ #
+ # == Redirects
+ #
+ # Redirecting is what actions that update the model do when they're done. The <tt>save_post</tt> method shouldn't be responsible for also
+ # showing the post once it's saved -- that's the job for <tt>show_post</tt>. So once <tt>save_post</tt> has completed its business, it'll
+ # redirect to <tt>show_post</tt>. All redirects are external, which means that when the user refreshes his browser, it's not going to save
+ # the post again, but rather just show it one more time.
+ #
+ # This sounds fairly simple, but the redirection is complicated by the quest for a phenomenon known as "pretty urls". Instead of accepting
+ # the dreadful beings that is "weblog_controller?action=show&post_id=5", Action Controller goes out of its way to represent the former as
+ # "/weblog/show/5". And this is even the simple case. As an example of a more advanced pretty url consider
+ # "/library/books/ISBN/0743536703/show", which can be mapped to books_controller?action=show&type=ISBN&id=0743536703.
+ #
+ # Redirects work by rewriting the URL of the current action. So if the show action was called by "/library/books/ISBN/0743536703/show",
+ # we can redirect to an edit action simply by doing <tt>redirect_to(:action => "edit")</tt>, which could throw the user to
+ # "/library/books/ISBN/0743536703/edit". Naturally, you'll need to setup the .htaccess (or other means of URL rewriting for the web server)
+ # to point to the proper controller and action in the first place, but once you have, it can be rewritten with ease.
+ #
+ # Let's consider a bunch of examples on how to go from "/library/books/ISBN/0743536703/edit" to somewhere else:
+ #
+ # redirect_to(:action => "show", :action_prefix => "XTC/123") =>
+ # "http://www.singlefile.com/library/books/XTC/123/show"
+ #
+ # redirect_to(:path_params => {"type" => "EXBC"}) =>
+ # "http://www.singlefile.com/library/books/EXBC/0743536703/show"
+ #
+ # redirect_to(:controller => "settings") =>
+ # "http://www.singlefile.com/library/settings/"
+ #
+ # For more examples of redirecting options, have a look at the unit test in test/controller/url_test.rb. It's very readable and will give
+ # you an excellent understanding of the different options and what they do.
+ #
+ # == Environments
+ #
+ # Action Controller works out of the box with CGI, FastCGI, and mod_ruby. CGI and mod_ruby controllers are triggered just the same using:
+ #
+ # WeblogController.process_cgi
+ #
+ # FastCGI controllers are triggered using:
+ #
+ # FCGI.each_cgi{ |cgi| WeblogController.process_cgi(cgi) }
+ class Base
+ include ClassInheritableAttributes
+
+ DEFAULT_RENDER_STATUS_CODE = "200 OK"
+
+ DEFAULT_SEND_FILE_OPTIONS = {
+ :type => 'application/octet_stream',
+ :disposition => 'attachment',
+ :stream => true,
+ :buffer_size => 4096
+ }
+
+
+ # Determines whether the view has access to controller internals @request, @response, @session, and @template.
+ # By default, it does.
+ @@view_controller_internals = true
+ cattr_accessor :view_controller_internals
+
+ # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
+ # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
+ # should instead be implemented in the controller to determine when debugging screens should be shown.
+ @@consider_all_requests_local = true
+ cattr_accessor :consider_all_requests_local
+
+ # When turned on (which is default), all dependencies are included using "load". This mean that any change is instant in cached
+ # environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
+ # be effective.
+ @@reload_dependencies = true
+ cattr_accessor :reload_dependencies
+
+ # Template root determines the base from which template references will be made. So a call to render("test/template")
+ # will be converted to "#{template_root}/test/template.rhtml".
+ cattr_accessor :template_root
+
+ # The logger is used for generating information on the action run-time (including benchmarking) if available.
+ # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
+ cattr_accessor :logger
+
+ # Determines which template class should be used by ActionController.
+ cattr_accessor :template_class
+
+ # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
+ cattr_accessor :ignore_missing_templates
+
+ # Holds the request object that's primarily used to get environment variables through access like
+ # <tt>@request.env["REQUEST_URI"]</tt>.
+ attr_accessor :request
+
+ # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>@params["post_id"]</tt>
+ # to get the post_id. No type casts are made, so all values are returned as strings.
+ attr_accessor :params
+
+ # Holds the response object that's primarily used to set additional HTTP headers through access like
+ # <tt>@response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
+ # has been rendered through @response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
+ # such as a OutputCompressionFilter.
+ attr_accessor :response
+
+ # Holds a hash of objects in the session. Accessed like <tt>@session["person"]</tt> to get the object tied to the "person"
+ # key. The session will hold any type of object as values, but the key should be a string.
+ attr_accessor :session
+
+ # Holds a hash of header names and values. Accessed like <tt>@headers["Cache-Control"]</tt> to get the value of the Cache-Control
+ # directive. Values should always be specified as strings.
+ attr_accessor :headers
+
+ # Holds a hash of cookie names and values. Accessed like <tt>@cookies["user_name"]</tt> to get the value of the user_name cookie.
+ # This hash is read-only. You set new cookies using the cookie method.
+ attr_accessor :cookies
+
+ # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
+ # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
+ attr_accessor :assigns
+
+ class << self
+ # Factory for the standard create, process loop where the controller is discarded after processing.
+ def process(request, response) #:nodoc:
+ new.process(request, response)
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
+ def controller_class_name
+ Inflector.demodulize(name)
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
+ def controller_name
+ Inflector.underscore(controller_class_name.sub(/Controller/, ""))
+ end
+
+ # Loads the <tt>file_name</tt> if reload_dependencies is true or requires if it's false.
+ def require_or_load(file_name)
+ reload_dependencies ? load("#{file_name}.rb") : require(file_name)
+ end
+ end
+
+ public
+ # Extracts the action_name from the request parameters and performs that action.
+ def process(request, response) #:nodoc:
+ initialize_template_class(response)
+ assign_shortcuts(request, response)
+ initialize_current_url
+
+ log_processing unless logger.nil?
+ perform_action
+ close_session
+
+ return @response
+ end
+
+ # Returns an URL that has been rewritten according to the hash of +options+ (for doing a complete redirect, use redirect_to). The
+ # valid keys in options are specified below with an example going from "/library/books/ISBN/0743536703/show" (mapped to
+ # books_controller?action=show&type=ISBN&id=0743536703):
+ #
+ # .---> controller .--> action
+ # /library/books/ISBN/0743536703/show
+ # '------> '--------------> action_prefix
+ # controller_prefix
+ #
+ # * <tt>:controller_prefix</tt> - specifies the string before the controller name, which would be "/library" for the example.
+ # Called with "/shop" gives "/shop/books/ISBN/0743536703/show".
+ # * <tt>:controller</tt> - specifies a new controller and clears out everything after the controller name (including the action,
+ # the pre- and suffix, and all params), so called with "settings" gives "/library/settings/".
+ # * <tt>:action_prefix</tt> - specifies the string between the controller name and the action name, which would
+ # be "/ISBN/0743536703" for the example. Called with "/XTC/123/" gives "/library/books/XTC/123/show".
+ # * <tt>:action</tt> - specifies a new action, so called with "edit" gives "/library/books/ISBN/0743536703/edit"
+ # * <tt>:action_suffix</tt> - specifies the string after the action name, which would be empty for the example.
+ # Called with "/detailed" gives "/library/books/ISBN/0743536703/detailed".
+ # * <tt>:path_params</tt> - specifies a hash that contains keys mapping to the request parameter names. In the example,
+ # { "type" => "ISBN", "id" => "0743536703" } would be the path_params. It serves as another way of replacing part of
+ # the action_prefix or action_suffix. So passing { "type" => "XTC" } would give "/library/books/XTC/0743536703/show".
+ # * <tt>:id</tt> - shortcut where ":id => 5" can be used instead of specifying :path_params => { "id" => 5 }.
+ # Called with "123" gives "/library/books/ISBN/123/show".
+ # * <tt>:params</tt> - specifies a hash that represents the regular request parameters, such as { "cat" => 1,
+ # "origin" => "there"} that would give "?cat=1&origin=there". Called with { "temporary" => 1 } in the example would give
+ # "/library/books/ISBN/0743536703/show?temporary=1"
+ # * <tt>:anchor</tt> - specifies the anchor name to be appended to the path. Called with "x14" would give
+ # "/library/books/ISBN/0743536703/show#x14"
+ # * <tt>:only_path</tt> - if true, returns the absolute URL (omitting the protocol, host name, and port).
+ #
+ # Naturally, you can combine multiple options in a single redirect. Examples:
+ #
+ # redirect_to(:controller_prefix => "/shop", :controller => "settings")
+ # redirect_to(:action => "edit", :id => 3425)
+ # redirect_to(:action => "edit", :path_params => { "type" => "XTC" }, :params => { "temp" => 1})
+ # redirect_to(:action => "publish", :action_prefix => "/published", :anchor => "x14")
+ #
+ # Instead of passing an options hash, you can also pass a method reference in the form of a symbol. Consider this example:
+ #
+ # class WeblogController < ActionController::Base
+ # def update
+ # # do some update
+ # redirect_to :dashboard_url
+ # end
+ #
+ # protected
+ # def dashboard_url
+ # url_for :controller => (@project.active? ? "project" : "account"), :action => "dashboard"
+ # end
+ # end
+ def url_for(options = {}, *parameters_for_method_reference) #:doc:
+ case options
+ when String then options
+ when Symbol then send(options, *parameters_for_method_reference)
+ when Hash then @url.rewrite(rewrite_options(options))
+ end
+ end
+
+ def module_name
+ @params["module"]
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
+ def controller_class_name
+ self.class.controller_class_name
+ end
+
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
+ def controller_name
+ self.class.controller_name
+ end
+
+ # Returns the name of the action this controller is processing.
+ def action_name
+ @params["action"] || "index"
+ end
+
+ protected
+ # Renders the template specified by <tt>template_name</tt>, which defaults to the name of the current controller and action.
+ # So calling +render+ in WeblogController#show will attempt to render "#{template_root}/weblog/show.rhtml" or
+ # "#{template_root}/weblog/show.rxml" (in that order). The template_root is set on the ActionController::Base class and is
+ # shared by all controllers. It's also possible to pass a status code using the second parameter. This defaults to "200 OK",
+ # but can be changed, such as by calling <tt>render("weblog/error", "500 Error")</tt>.
+ def render(template_name = nil, status = nil) #:doc:
+ render_file(template_name || default_template_name, status, true)
+ end
+
+ # Works like render, but instead of requiring a full template name, you can get by with specifying the action name. So calling
+ # <tt>render_action "show_many"</tt> in WeblogController#display will render "#{template_root}/weblog/show_many.rhtml" or
+ # "#{template_root}/weblog/show_many.rxml".
+ def render_action(action_name, status = nil) #:doc:
+ render default_template_name(action_name), status
+ end
+
+ # Works like render, but disregards the template_root and requires a full path to the template that needs to be rendered. Can be
+ # used like <tt>render_file "/Users/david/Code/Ruby/template"</tt> to render "/Users/david/Code/Ruby/template.rhtml" or
+ # "/Users/david/Code/Ruby/template.rxml".
+ def render_file(template_path, status = nil, use_full_path = false) #:doc:
+ assert_existance_of_template_file(template_path) if use_full_path
+ logger.info("Rendering #{template_path} (#{status || DEFAULT_RENDER_STATUS_CODE})") unless logger.nil?
+
+ add_variables_to_assigns
+ render_text(@template.render_file(template_path, use_full_path), status)
+ end
+
+ # Renders the +template+ string, which is useful for rendering short templates you don't want to bother having a file for. So
+ # you'd call <tt>render_template "Hello, <%= @user.name %>"</tt> to greet the current user. Or if you want to render as Builder
+ # template, you could do <tt>render_template "xml.h1 @user.name", nil, "rxml"</tt>.
+ def render_template(template, status = nil, type = "rhtml") #:doc:
+ add_variables_to_assigns
+ render_text(@template.render_template(type, template), status)
+ end
+
+ # Renders the +text+ string without parsing it through any template engine. Useful for rendering static information as it's
+ # considerably faster than rendering through the template engine.
+ # Use block for response body if provided (useful for deferred rendering or streaming output).
+ def render_text(text = nil, status = nil, &block) #:doc:
+ add_variables_to_assigns
+ @response.headers["Status"] = status || DEFAULT_RENDER_STATUS_CODE
+ @response.body = block_given? ? block : text
+ @performed_render = true
+ end
+
+ # Sends the file by streaming it 4096 bytes at a time. This way the
+ # whole file doesn't need to be read into memory at once. This makes
+ # it feasible to send even large files.
+ #
+ # Be careful to sanitize the path parameter if it coming from a web
+ # page. send_file(@params['path']) allows a malicious user to
+ # download any file on your server.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # Defaults to File.basename(path).
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)
+ # or to read the entire file before sending (false). Defaults to true.
+ # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
+ # Defaults to 4096.
+ #
+ # The default Content-Type and Content-Disposition headers are
+ # set to download arbitrary binary files in as many browsers as
+ # possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ # a variety of quirks (especially when downloading over SSL).
+ #
+ # Simple download:
+ # send_file '/path/to.zip'
+ #
+ # Show a JPEG in browser:
+ # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ #
+ # Read about the other Content-* HTTP headers if you'd like to
+ # provide the user with more information (such as Content-Description).
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+ #
+ # Also be aware that the document may be cached by proxies and browsers.
+ # The Pragma and Cache-Control headers declare how the file may be cached
+ # by intermediaries. They default to require clients to validate with
+ # the server before releasing cached responses. See
+ # http://www.mnot.net/cache_docs/ for an overview of web caching and
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ # for the Cache-Control header spec.
+ def send_file(path, options = {})
+ raise MissingFile unless File.file?(path) and File.readable?(path)
+
+ options[:length] ||= File.size(path)
+ options[:filename] ||= File.basename(path)
+ send_file_headers! options
+
+ if options[:stream]
+ render_text do
+ logger.info "Streaming file #{path}" unless logger.nil?
+ len = options[:buffer_size] || 4096
+ File.open(path, 'rb') do |file|
+ begin
+ while true
+ $stdout.syswrite file.sysread(len)
+ end
+ rescue EOFError
+ end
+ end
+ end
+ else
+ logger.info "Sending file #{path}" unless logger.nil?
+ File.open(path, 'rb') { |file| render_text file.read }
+ end
+ end
+
+ # Send binary data to the user as a file download. May set content type, apparent file name,
+ # and specify whether to show data inline or download as an attachment.
+ #
+ # Options:
+ # * <tt>:filename</tt> - Suggests a filename for the browser to use.
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ #
+ # Generic data download:
+ # send_data buffer
+ #
+ # Download a dynamically-generated tarball:
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ #
+ # Display an image Active Record in the browser:
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ #
+ # See +send_file+ for more information on HTTP Content-* headers and caching.
+ def send_data(data, options = {})
+ logger.info "Sending data #{options[:filename]}" unless logger.nil?
+ send_file_headers! options.merge(:length => data.size)
+ render_text data
+ end
+
+ def rewrite_options(options)
+ if defaults = default_url_options(options)
+ defaults.merge(options)
+ else
+ options
+ end
+ end
+
+ # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
+ # the form of a hash, just like the one you would use for url_for directly. Example:
+ #
+ # def default_url_options(options)
+ # { :controller_prefix => @project.active? ? "projects/" : "accounts/" }
+ # end
+ #
+ # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
+ # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
+ # by this method.
+ def default_url_options(options) #:doc:
+ end
+
+ # Redirects the browser to an URL that has been rewritten according to the hash of +options+ using a "302 Moved" HTTP header.
+ # See url_for for a description of the valid options.
+ def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
+ if parameters_for_method_reference.empty?
+ @response.redirected_to = options
+ redirect_to_url(url_for(options))
+ else
+ @response.redirected_to, @response.redirected_to_method_params = options, parameters_for_method_reference
+ redirect_to_url(url_for(options, *parameters_for_method_reference))
+ end
+ end
+
+ # Redirects the browser to the specified <tt>path</tt> within the current host (specified with a leading /). Used to sidestep
+ # the URL rewriting and go directly to a known path. Example: <tt>redirect_to_path "/images/screenshot.jpg"</tt>.
+ def redirect_to_path(path) #:doc:
+ redirect_to_url(@request.protocol + @request.host_with_port + path)
+ end
+
+ # Redirects the browser to the specified <tt>url</tt>. Used to redirect outside of the current application. Example:
+ # <tt>redirect_to_url "http://www.rubyonrails.org"</tt>.
+ def redirect_to_url(url) #:doc:
+ logger.info("Redirected to #{url}") unless logger.nil?
+ @response.redirect(url)
+ @performed_redirect = true
+ end
+
+ # Creates a new cookie that is sent along-side the next render or redirect command. API is the same as for CGI::Cookie.
+ # Examples:
+ #
+ # cookie("name", "value1", "value2", ...)
+ # cookie("name" => "name", "value" => "value")
+ # cookie('name' => 'name',
+ # 'value' => ['value1', 'value2', ...],
+ # 'path' => 'path', # optional
+ # 'domain' => 'domain', # optional
+ # 'expires' => Time.now, # optional
+ # 'secure' => true # optional
+ # )
+ def cookie(*options) #:doc:
+ @response.headers["cookie"] << CGI::Cookie.new(*options)
+ end
+
+ # Resets the session by clearsing out all the objects stored within and initializing a new session object.
+ def reset_session #:doc:
+ @request.reset_session
+ @session = @request.session
+ @response.session = @session
+ end
+
+ private
+ def initialize_template_class(response)
+ begin
+ response.template = template_class.new(template_root, {}, self)
+ rescue
+ raise "You must assign a template class through ActionController.template_class= before processing a request"
+ end
+
+ @performed_render = @performed_redirect = false
+ end
+
+ def assign_shortcuts(request, response)
+ @request, @params, @cookies = request, request.parameters, request.cookies
+
+ @response = response
+ @response.session = request.session
+
+ @session = @response.session
+ @template = @response.template
+ @assigns = @response.template.assigns
+ @headers = @response.headers
+ end
+
+ def initialize_current_url
+ @url = UrlRewriter.new(@request, controller_name, action_name)
+ end
+
+ def log_processing
+ logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin})"
+ logger.info " Parameters: #{@params.inspect}"
+ end
+
+ def perform_action
+ if action_methods.include?(action_name)
+ send(action_name)
+ render unless @performed_render || @performed_redirect
+ elsif template_exists? && template_public?
+ render
+ else
+ raise UnknownAction, "No action responded to #{action_name}", caller
+ end
+ end
+
+ def action_methods
+ action_controller_classes = self.class.ancestors.reject{ |a| [Object, Kernel].include?(a) }
+ action_controller_classes.inject([]) { |action_methods, klass| action_methods + klass.instance_methods(false) }
+ end
+
+ def add_variables_to_assigns
+ add_instance_variables_to_assigns
+ add_class_variables_to_assigns if view_controller_internals
+ end
+
+ def add_instance_variables_to_assigns
+ protected_variables_cache = protected_instance_variables
+ instance_variables.each do |var|
+ next if protected_variables_cache.include?(var)
+ @assigns[var[1..-1]] = instance_variable_get(var)
+ end
+ end
+
+ def add_class_variables_to_assigns
+ %w( template_root logger template_class ignore_missing_templates ).each do |cvar|
+ @assigns[cvar] = self.send(cvar)
+ end
+ end
+
+ def protected_instance_variables
+ if view_controller_internals
+ [ "@assigns", "@performed_redirect", "@performed_render" ]
+ else
+ [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template" ]
+ end
+ end
+
+ def request_origin
+ "#{@request.remote_ip} at #{Time.now.to_s}"
+ end
+
+ def close_session
+ @session.close unless @session.nil? || Hash === @session
+ end
+
+ def template_exists?(template_name = default_template_name)
+ @template.file_exists?(template_name)
+ end
+
+ def template_public?(template_name = default_template_name)
+ @template.file_public?(template_name)
+ end
+
+ def assert_existance_of_template_file(template_name)
+ unless template_exists?(template_name) || ignore_missing_templates
+ full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
+ template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
+ raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
+ end
+ end
+
+ def send_file_headers!(options)
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
+ [:length, :type, :disposition].each do |arg|
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ end
+
+ disposition = options[:disposition] || 'attachment'
+ disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
+
+ @headers.update(
+ 'Content-Length' => options[:length],
+ 'Content-Type' => options[:type],
+ 'Content-Disposition' => disposition,
+ 'Content-Transfer-Encoding' => 'binary'
+ );
+ end
+
+ def default_template_name(default_action_name = action_name)
+ module_name ? "#{module_name}/#{controller_name}/#{default_action_name}" : "#{controller_name}/#{default_action_name}"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/benchmarking.rb b/actionpack/lib/action_controller/benchmarking.rb
new file mode 100644
index 0000000000..e6ff65e150
--- /dev/null
+++ b/actionpack/lib/action_controller/benchmarking.rb
@@ -0,0 +1,49 @@
+require 'benchmark'
+
+module ActionController #:nodoc:
+ # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
+ # package has been included, a separate timing section for database calls will be added as well.
+ module Benchmarking #:nodoc:
+ def self.append_features(base)
+ super
+ base.class_eval {
+ alias_method :perform_action_without_benchmark, :perform_action
+ alias_method :perform_action, :perform_action_with_benchmark
+
+ alias_method :render_without_benchmark, :render
+ alias_method :render, :render_with_benchmark
+ }
+ end
+
+ def render_with_benchmark(template_name = default_template_name, status = "200 OK")
+ if logger.nil?
+ render_without_benchmark(template_name, status)
+ else
+ @rendering_runtime = Benchmark::measure{ render_without_benchmark(template_name, status) }.real
+ end
+ end
+
+ def perform_action_with_benchmark
+ if logger.nil?
+ perform_action_without_benchmark
+ else
+ runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
+ log_message = "Completed in #{sprintf("%4f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
+ log_message << rendering_runtime(runtime) if @rendering_runtime
+ log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
+ logger.info(log_message)
+ end
+ end
+
+ private
+ def rendering_runtime(runtime)
+ " | Rendering: #{sprintf("%f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime / runtime) * 100)}%)"
+ end
+
+ def active_record_runtime(runtime)
+ db_runtime = ActiveRecord::Base.connection.reset_runtime
+ db_percentage = (db_runtime / runtime) * 100
+ " | DB: #{sprintf("%f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/cgi_ext/cgi_ext.rb b/actionpack/lib/action_controller/cgi_ext/cgi_ext.rb
new file mode 100755
index 0000000000..371ead695b
--- /dev/null
+++ b/actionpack/lib/action_controller/cgi_ext/cgi_ext.rb
@@ -0,0 +1,43 @@
+require 'cgi'
+require 'cgi/session'
+require 'cgi/session/pstore'
+require 'action_controller/cgi_ext/cgi_methods'
+
+# Wrapper around the CGIMethods that have been secluded to allow testing without
+# an instatiated CGI object
+class CGI #:nodoc:
+ class << self
+ alias :escapeHTML_fail_on_nil :escapeHTML
+
+ def escapeHTML(string)
+ escapeHTML_fail_on_nil(string) unless string.nil?
+ end
+ end
+
+ # Returns a parameter hash including values from both the request (POST/GET)
+ # and the query string with the latter taking precedence.
+ def parameters
+ request_parameters.update(query_parameters)
+ end
+
+ def query_parameters
+ CGIMethods.parse_query_parameters(query_string)
+ end
+
+ def request_parameters
+ CGIMethods.parse_request_parameters(params)
+ end
+
+ def redirect(where)
+ header({
+ "Status" => "302 Moved",
+ "location" => "#{where}"
+ })
+ end
+
+ def session(parameters = nil)
+ parameters = {} if parameters.nil?
+ parameters['database_manager'] = CGI::Session::PStore
+ CGI::Session.new(self, parameters)
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb b/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
new file mode 100755
index 0000000000..261490580c
--- /dev/null
+++ b/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
@@ -0,0 +1,91 @@
+require 'cgi'
+
+# Static methods for parsing the query and request parameters that can be used in
+# a CGI extension class or testing in isolation.
+class CGIMethods #:nodoc:
+ public
+ # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
+ # parse_request_params is not done here.
+ def CGIMethods.parse_query_parameters(query_string)
+ parsed_params = {}
+
+ query_string.split(/[&;]/).each { |p|
+ k, v = p.split('=')
+
+ k = CGI.unescape(k) unless k.nil?
+ v = CGI.unescape(v) unless v.nil?
+
+ if k =~ /(.*)\[\]$/
+ if parsed_params.has_key? $1
+ parsed_params[$1] << v
+ else
+ parsed_params[$1] = [v]
+ end
+ else
+ parsed_params[k] = v.nil? ? nil : v
+ end
+ }
+
+ return parsed_params
+ end
+
+ # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
+ # "Somewhere cool!" are translated into a full hash hierarchy, like
+ # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
+ def CGIMethods.parse_request_parameters(params)
+ parsed_params = {}
+
+ for key, value in params
+ value = [value] if key =~ /.*\[\]$/
+ CGIMethods.build_deep_hash(
+ CGIMethods.get_typed_value(value[0]),
+ parsed_params,
+ CGIMethods.get_levels(key)
+ )
+ end
+
+ return parsed_params
+ end
+
+ private
+ def CGIMethods.get_typed_value(value)
+ if value.respond_to?(:content_type) && !value.content_type.empty?
+ # Uploaded file
+ value
+ elsif value.respond_to?(:read)
+ # Value as part of a multipart request
+ value.read
+ elsif value.class == Array
+ value
+ else
+ # Standard value (not a multipart request)
+ value.to_s
+ end
+ end
+
+ def CGIMethods.get_levels(key_string)
+ return [] if key_string.nil? or key_string.empty?
+
+ levels = []
+ main, existance = /(\w+)(\[)?.?/.match(key_string).captures
+ levels << main
+
+ unless existance.nil?
+ hash_part = key_string.sub(/\w+\[/, "")
+ hash_part.slice!(-1, 1)
+ levels += hash_part.split(/\]\[/)
+ end
+
+ levels
+ end
+
+ def CGIMethods.build_deep_hash(value, hash, levels)
+ if levels.length == 0
+ value;
+ elsif hash.nil?
+ { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
+ else
+ hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb
new file mode 100644
index 0000000000..e69a0b2f8d
--- /dev/null
+++ b/actionpack/lib/action_controller/cgi_process.rb
@@ -0,0 +1,124 @@
+require 'action_controller/cgi_ext/cgi_ext'
+require 'action_controller/support/cookie_performance_fix'
+require 'action_controller/session/drb_store'
+require 'action_controller/session/active_record_store'
+
+module ActionController #:nodoc:
+ class Base
+ # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
+ # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
+ #
+ # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
+ # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
+ # lib/action_controller/session.
+ # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
+ # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
+ # of the request, or automatically generated for a new session.
+ # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
+ # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
+ # an ArgumentError is raised.
+ # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
+ # indefinitely.
+ # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
+ # server.
+ # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
+ # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
+ def self.process_cgi(cgi = CGI.new, session_options = {})
+ new.process_cgi(cgi, session_options)
+ end
+
+ def process_cgi(cgi, session_options = {}) #:nodoc:
+ process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
+ end
+ end
+
+ class CgiRequest < AbstractRequest #:nodoc:
+ attr_accessor :cgi
+
+ DEFAULT_SESSION_OPTIONS =
+ { "database_manager" => CGI::Session::PStore, "prefix" => "ruby_sess.", "session_path" => "/" }
+
+ def initialize(cgi, session_options = {})
+ @cgi = cgi
+ @session_options = session_options
+ super()
+ end
+
+ def query_parameters
+ @cgi.query_string ? CGIMethods.parse_query_parameters(@cgi.query_string) : {}
+ end
+
+ def request_parameters
+ CGIMethods.parse_request_parameters(@cgi.params)
+ end
+
+ def env
+ @cgi.send(:env_table)
+ end
+
+ def cookies
+ @cgi.cookies.freeze
+ end
+
+ def host
+ env["HTTP_X_FORWARDED_HOST"] || @cgi.host.split(":").first
+ end
+
+ def session
+ return @session unless @session.nil?
+ begin
+ @session = (@session_options == false ? {} : CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options)))
+ @session["__valid_session"]
+ return @session
+ rescue ArgumentError => e
+ @session.delete if @session
+ raise(
+ ActionController::SessionRestoreError,
+ "Session contained objects where the class definition wasn't available. " +
+ "Remember to require classes for all objects kept in the session. " +
+ "The session has been deleted."
+ )
+ end
+ end
+
+ def reset_session
+ @session.delete
+ @session = (@session_options == false ? {} : new_session)
+ end
+
+ def method_missing(method_id, *arguments)
+ @cgi.send(method_id, *arguments) rescue super
+ end
+
+ private
+ def new_session
+ CGI::Session.new(@cgi, DEFAULT_SESSION_OPTIONS.merge(@session_options).merge("new_session" => true))
+ end
+ end
+
+ class CgiResponse < AbstractResponse #:nodoc:
+ def initialize(cgi)
+ @cgi = cgi
+ super()
+ end
+
+ def out
+ convert_content_type!(@headers)
+ $stdout.binmode if $stdout.respond_to?(:binmode)
+ print @cgi.header(@headers)
+ if @body.respond_to?(:call)
+ @body.call(self)
+ else
+ print @body
+ end
+ end
+
+ private
+ def convert_content_type!(headers)
+ if headers["Content-Type"]
+ headers["type"] = headers["Content-Type"]
+ headers.delete "Content-Type"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/dependencies.rb b/actionpack/lib/action_controller/dependencies.rb
new file mode 100644
index 0000000000..6f092500d1
--- /dev/null
+++ b/actionpack/lib/action_controller/dependencies.rb
@@ -0,0 +1,49 @@
+module ActionController #:nodoc:
+ module Dependencies #:nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def model(*models)
+ require_dependencies(:model, models)
+ depend_on(:model, models)
+ end
+
+ def service(*services)
+ require_dependencies(:service, services)
+ depend_on(:service, services)
+ end
+
+ def observer(*observers)
+ require_dependencies(:observer, observers)
+ depend_on(:observer, observers)
+ instantiate_observers(observers)
+ end
+
+ def dependencies_on(layer) # :nodoc:
+ read_inheritable_attribute("#{layer}_dependencies")
+ end
+
+ def depend_on(layer, dependencies)
+ write_inheritable_array("#{layer}_dependencies", dependencies)
+ end
+
+ private
+ def instantiate_observers(observers)
+ observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
+ end
+
+ def require_dependencies(layer, dependencies)
+ dependencies.flatten.each do |dependency|
+ begin
+ require_or_load(dependency.to_s)
+ rescue LoadError
+ raise LoadError, "Missing #{layer} #{dependency}.rb"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
new file mode 100644
index 0000000000..bd5c545dfb
--- /dev/null
+++ b/actionpack/lib/action_controller/filters.rb
@@ -0,0 +1,279 @@
+module ActionController #:nodoc:
+ module Filters #:nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ base.class_eval { include ActionController::Filters::InstanceMethods }
+ end
+
+ # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
+ # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
+ # compression after the action has been performed.
+ #
+ # Filters have access to the request, response, and all the instance variables set by other filters in the chain
+ # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
+ # to halt the processing before the intended action is processed by returning false. This is especially useful for
+ # filters like authentication where you're not interested in allowing the action to be performed if the proper
+ # credentials are not in order.
+ #
+ # == Filter inheritance
+ #
+ # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
+ # affecting the superclass. For example:
+ #
+ # class BankController < ActionController::Base
+ # before_filter :audit
+ #
+ # private
+ # def audit
+ # # record the action and parameters in an audit log
+ # end
+ # end
+ #
+ # class VaultController < BankController
+ # before_filter :verify_credentials
+ #
+ # private
+ # def verify_credentials
+ # # make sure the user is allowed into the vault
+ # end
+ # end
+ #
+ # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
+ # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
+ # verify_credentials and the intended action is never called.
+ #
+ # == Filter types
+ #
+ # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
+ # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
+ # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
+ #
+ # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
+ # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
+ #
+ # class OutputCompressionFilter
+ # def self.filter(controller)
+ # controller.response.body = compress(controller.response.body)
+ # end
+ # end
+ #
+ # class NewspaperController < ActionController::Base
+ # after_filter OutputCompressionFilter
+ # end
+ #
+ # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
+ # manipulate them as it sees fit.
+ #
+ # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
+ # Or just as a quick test. It works like this:
+ #
+ # class WeblogController < ActionController::Base
+ # before_filter { |controller| return false if controller.params["stop_action"] }
+ # end
+ #
+ # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
+ # This means that the block has access to both the request and response objects complete with convenience methods for params,
+ # session, template, and assigns. Note: The inline method doesn't strictly has to be a block. Any object that responds to call
+ # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
+ #
+ # == Filter chain ordering
+ #
+ # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
+ # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
+ # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
+ # beginning of their respective chain and executed before the rest. For example:
+ #
+ # class ShoppingController
+ # before_filter :verify_open_shop
+ #
+ # class CheckoutController
+ # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
+ #
+ # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
+ # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
+ # is open or not.
+ #
+ # You may pass multiple filter arguments of each type as well as a filter block.
+ # If a block is given, it is treated as the last argument.
+ #
+ # == Around filters
+ #
+ # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
+ # both the before and after call. That's especially usefuly when you need to keep state active between the before and after,
+ # such as the example of a benchmark filter below:
+ #
+ # class WeblogController < ActionController::Base
+ # around_filter BenchmarkingFilter.new
+ #
+ # # Before this action is performed, BenchmarkingFilter#before(controller) is executed
+ # def index
+ # end
+ # # After this action has been performed, BenchmarkingFilter#after(controller) is executed
+ # end
+ #
+ # class BenchmarkingFilter
+ # def initialize
+ # @runtime
+ # end
+ #
+ # def before
+ # start_timer
+ # end
+ #
+ # def after
+ # stop_timer
+ # report_result
+ # end
+ # end
+ module ClassMethods
+ # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
+ # on this controller are performed.
+ def append_before_filter(*filters, &block)
+ filters << block if block_given?
+ append_filter_to_chain("before", filters)
+ end
+
+ # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
+ # on this controller are performed.
+ def prepend_before_filter(*filters, &block)
+ filters << block if block_given?
+ prepend_filter_to_chain("before", filters)
+ end
+
+ # Short-hand for append_before_filter since that's the most common of the two.
+ alias :before_filter :append_before_filter
+
+ # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
+ # on this controller are performed.
+ def append_after_filter(*filters, &block)
+ filters << block if block_given?
+ append_filter_to_chain("after", filters)
+ end
+
+ # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
+ # on this controller are performed.
+ def prepend_after_filter(*filters, &block)
+ filters << block if block_given?
+ prepend_filter_to_chain("after", filters)
+ end
+
+ # Short-hand for append_after_filter since that's the most common of the two.
+ alias :after_filter :append_after_filter
+
+ # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
+ # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
+ # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
+ #
+ # B#before
+ # A#before
+ # A#after
+ # B#after
+ def append_around_filter(filters)
+ for filter in [filters].flatten
+ ensure_filter_responds_to_before_and_after(filter)
+ append_before_filter { |c| filter.before(c) }
+ prepend_after_filter { |c| filter.after(c) }
+ end
+ end
+
+ # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
+ # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
+ # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
+ #
+ # A#before
+ # B#before
+ # B#after
+ # A#after
+ def prepend_around_filter(filters)
+ for filter in [filters].flatten
+ ensure_filter_responds_to_before_and_after(filter)
+ prepend_before_filter { |c| filter.before(c) }
+ append_after_filter { |c| filter.after(c) }
+ end
+ end
+
+ # Short-hand for append_around_filter since that's the most common of the two.
+ alias :around_filter :append_around_filter
+
+ # Returns all the before filters for this class and all its ancestors.
+ def before_filters #:nodoc:
+ read_inheritable_attribute("before_filters")
+ end
+
+ # Returns all the after filters for this class and all its ancestors.
+ def after_filters #:nodoc:
+ read_inheritable_attribute("after_filters")
+ end
+
+ private
+ def append_filter_to_chain(condition, filters)
+ write_inheritable_array("#{condition}_filters", filters)
+ end
+
+ def prepend_filter_to_chain(condition, filters)
+ write_inheritable_attribute("#{condition}_filters", filters + read_inheritable_attribute("#{condition}_filters"))
+ end
+
+ def ensure_filter_responds_to_before_and_after(filter)
+ unless filter.respond_to?(:before) && filter.respond_to?(:after)
+ raise ActionControllerError, "Filter object must respond to both before and after"
+ end
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ def self.append_features(base)
+ super
+ base.class_eval {
+ alias_method :perform_action_without_filters, :perform_action
+ alias_method :perform_action, :perform_action_with_filters
+ }
+ end
+
+ def perform_action_with_filters
+ return if before_action == false
+ perform_action_without_filters
+ after_action
+ end
+
+ # Calls all the defined before-filter filters, which are added by using "before_filter :method".
+ # If any of the filters return false, no more filters will be executed and the action is aborted.
+ def before_action #:doc:
+ call_filters(self.class.before_filters)
+ end
+
+ # Calls all the defined after-filter filters, which are added by using "after_filter :method".
+ # If any of the filters return false, no more filters will be executed.
+ def after_action #:doc:
+ call_filters(self.class.after_filters)
+ end
+
+ private
+ def call_filters(filters)
+ filters.each do |filter|
+ if Symbol === filter
+ if self.send(filter) == false then return false end
+ elsif filter_block?(filter)
+ if filter.call(self) == false then return false end
+ elsif filter_class?(filter)
+ if filter.filter(self) == false then return false end
+ else
+ raise(
+ ActionControllerError,
+ "Filters need to be either a symbol, proc/method, or class implementing a static filter method"
+ )
+ end
+ end
+ end
+
+ def filter_block?(filter)
+ filter.respond_to?("call") && (filter.arity == 1 || filter.arity == -1)
+ end
+
+ def filter_class?(filter)
+ filter.respond_to?("filter")
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/flash.rb b/actionpack/lib/action_controller/flash.rb
new file mode 100644
index 0000000000..220ed8c77a
--- /dev/null
+++ b/actionpack/lib/action_controller/flash.rb
@@ -0,0 +1,65 @@
+module ActionController #:nodoc:
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
+ # that sets <tt>flash["notice"] = "Succesfully created"</tt> before redirecting to a display action that can then expose
+ # the flash to its template. Actually, that exposure is automatically done. Example:
+ #
+ # class WeblogController < ActionController::Base
+ # def create
+ # # save post
+ # flash["notice"] = "Succesfully created post"
+ # redirect_to :action => "display", :params => { "id" => post.id }
+ # end
+ #
+ # def display
+ # # doesn't need to assign the flash notice to the template, that's done automatically
+ # end
+ # end
+ #
+ # display.rhtml
+ # <% if @flash["notice"] %><div class="notice"><%= @flash["notice"] %></div><% end %>
+ #
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
+ # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
+ module Flash
+ def self.append_features(base) #:nodoc:
+ super
+ base.before_filter(:fire_flash)
+ base.after_filter(:clear_flash)
+ end
+
+ protected
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
+ # <tt>flash["notice"] = "hello"</tt> to put a new one.
+ def flash #:doc:
+ if @session["flash"].nil?
+ @session["flash"] = {}
+ @session["flashes"] ||= 0
+ end
+ @session["flash"]
+ end
+
+ # Can be called by any action that would like to keep the current content of the flash around for one more action.
+ def keep_flash #:doc:
+ @session["flashes"] = 0
+ end
+
+ private
+ # Records that the contents of @session["flash"] was flashed to the action
+ def fire_flash
+ if @session["flash"]
+ @session["flashes"] += 1 unless @session["flash"].empty?
+ @assigns["flash"] = @session["flash"]
+ else
+ @assigns["flash"] = {}
+ end
+ end
+
+ def clear_flash
+ if @session["flash"] && (@session["flashes"].nil? || @session["flashes"] >= 1)
+ @session["flash"] = {}
+ @session["flashes"] = 0
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb
new file mode 100644
index 0000000000..9c88582288
--- /dev/null
+++ b/actionpack/lib/action_controller/helpers.rb
@@ -0,0 +1,100 @@
+module ActionController #:nodoc:
+ module Helpers #:nodoc:
+ def self.append_features(base)
+ super
+ base.class_eval { class << self; alias_method :inherited_without_helper, :inherited; end }
+ base.extend(ClassMethods)
+ end
+
+ # The template helpers serves to relieve the templates from including the same inline code again and again. It's a
+ # set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and
+ # Active Records (ActiveRecordHelper) that's available to all templates by default.
+ #
+ # It's also really easy to make your own helpers and it's much encouraged to keep the template files free
+ # from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers
+ # (often the common helpers) as they're used by the specific application.
+ #
+ # module MyHelper
+ # def hello_world() "hello world" end
+ # end
+ #
+ # MyHelper can now be included in a controller, like this:
+ #
+ # class MyController < ActionController::Base
+ # helper :my_helper
+ # end
+ #
+ # ...and, same as above, used in any template rendered from MyController, like this:
+ #
+ # Let's hear what the helper has to say: <tt><%= hello_world %></tt>
+ module ClassMethods
+ # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
+ # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
+ # available to the templates.
+ def add_template_helper(helper_module)
+ template_class.class_eval "include #{helper_module}"
+ end
+
+ # Declare a helper. If you use this method in your controller, you don't
+ # have to do the +self.append_features+ incantation in your helper class.
+ # helper :foo
+ # requires 'foo_helper' and includes FooHelper in the template class.
+ # helper FooHelper
+ # includes FooHelper in the template class.
+ # helper { def foo() "#{bar} is the very best" end }
+ # evaluates the block in the template class, adding method #foo.
+ # helper(:three, BlindHelper) { def mice() 'mice' end }
+ # does all three.
+ def helper(*args, &block)
+ args.flatten.each do |arg|
+ case arg
+ when Module
+ add_template_helper(arg)
+ when String, Symbol
+ file_name = Inflector.underscore(arg.to_s.downcase) + '_helper'
+ class_name = Inflector.camelize(file_name)
+ begin
+ require_or_load(file_name)
+ rescue LoadError
+ raise LoadError, "Missing helper file helpers/#{file_name}.rb"
+ end
+ raise ArgumentError, "Missing #{class_name} module in helpers/#{file_name}.rb" unless Object.const_defined?(class_name)
+ add_template_helper(Object.const_get(class_name))
+ else
+ raise ArgumentError, 'helper expects String, Symbol, or Module argument'
+ end
+ end
+
+ # Evaluate block in template class if given.
+ template_class.module_eval(&block) if block_given?
+ end
+
+ # Declare a controller method as a helper. For example,
+ # helper_method :link_to
+ # def link_to(name, options) ... end
+ # makes the link_to controller method available in the view.
+ def helper_method(*methods)
+ template_class.controller_delegate(*methods)
+ end
+
+ # Declare a controller attribute as a helper. For example,
+ # helper_attr :name
+ # attr_accessor :name
+ # makes the name and name= controller methods available in the view.
+ # The is a convenience wrapper for helper_method.
+ def helper_attr(*attrs)
+ attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
+ end
+
+ private
+ def inherited(child)
+ inherited_without_helper(child)
+ begin
+ child.helper(child.controller_name)
+ rescue LoadError
+ # No default helper available for this controller
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
new file mode 100644
index 0000000000..7ae25ddabd
--- /dev/null
+++ b/actionpack/lib/action_controller/layout.rb
@@ -0,0 +1,149 @@
+module ActionController #:nodoc:
+ module Layout #:nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ base.class_eval do
+ alias_method :render_without_layout, :render
+ alias_method :render, :render_with_layout
+ end
+ end
+
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer is only mentioned in one place, like this:
+ #
+ # <!-- The header part of this layout -->
+ # <%= @content_for_layout %>
+ # <!-- The footer part of this layout -->
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
+ # like this:
+ #
+ # <!-- The header part of this layout -->
+ # hello world
+ # <!-- The footer part of this layout -->
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= @content_for_layout %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Inheritance for layouts
+ #
+ # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
+ #
+ # class BankController < ActionController::Base
+ # layout "layouts/bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "layouts/bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template path:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "layouts/weblog_standard"
+ #
+ # == Avoiding the use of a layout
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option to rendering
+ # a given action without a layout. Just use the method <tt>render_without_layout</tt>, which works just like Base.render --
+ # it just doesn't apply any layouts.
+ module ClassMethods
+ # If a layout is specified, all actions rendered through render and render_action will have their result assigned
+ # to <tt>@content_for_layout</tt>, which can then be used by the layout to insert their contents with
+ # <tt><%= @content_for_layout %></tt>. This layout can itself depend on instance variables assigned during action
+ # performance and have access to them as any normal template would.
+ def layout(template_name)
+ write_inheritable_attribute "layout", template_name
+ end
+ end
+
+ # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
+ # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
+ # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
+ # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
+ def active_layout(passed_layout = nil)
+ layout = passed_layout || self.class.read_inheritable_attribute("layout")
+ active_layout = case layout
+ when Symbol then send(layout)
+ when Proc then layout.call(self)
+ when String then layout
+ end
+ active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
+ end
+
+ def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
+ if layout ||= active_layout
+ add_variables_to_assigns
+ logger.info("Rendering #{template_name} within #{layout}") unless logger.nil?
+ @content_for_layout = @template.render_file(template_name, true)
+ render_without_layout(layout, status)
+ else
+ render_without_layout(template_name, status)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
new file mode 100755
index 0000000000..1085066ea0
--- /dev/null
+++ b/actionpack/lib/action_controller/request.rb
@@ -0,0 +1,99 @@
+module ActionController
+ # These methods are available in both the production and test Request objects.
+ class AbstractRequest
+ # Returns both GET and POST parameters in a single hash.
+ def parameters
+ @parameters ||= request_parameters.update(query_parameters)
+ end
+
+ def method
+ env['REQUEST_METHOD'].downcase.intern
+ end
+
+ def get?
+ method == :get
+ end
+
+ def post?
+ method == :post
+ end
+
+ def put?
+ method == :put
+ end
+
+ def delete?
+ method == :delete
+ end
+
+ # Determine originating IP address. REMOTE_ADDR is the standard
+ # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
+ # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
+ # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
+ # delimited list in the case of multiple chained proxies; the first is
+ # the originating IP.
+ def remote_ip
+ if env['HTTP_CLIENT_IP']
+ env['HTTP_CLIENT_IP']
+ elsif env['HTTP_X_FORWARDED_FOR']
+ remote_ip = env['HTTP_X_FORWARDED_FOR'].split(',').reject { |ip|
+ ip =~ /^unknown$|^(10|172\.16|192\.168)\./i
+ }.first
+
+ remote_ip ? remote_ip.strip : env['REMOTE_ADDR']
+ else
+ env['REMOTE_ADDR']
+ end
+ end
+
+ def request_uri
+ env["REQUEST_URI"]
+ end
+
+ def protocol
+ port == 443 ? "https://" : "http://"
+ end
+
+ def path
+ request_uri ? request_uri.split("?").first : ""
+ end
+
+ def port
+ env["SERVER_PORT"].to_i
+ end
+
+ def host_with_port
+ if env['HTTP_HOST']
+ env['HTTP_HOST']
+ elsif (protocol == "http://" && port == 80) || (protocol == "https://" && port == 443)
+ host
+ else
+ host + ":#{port}"
+ end
+ end
+
+ #--
+ # Must be implemented in the concrete request
+ #++
+ def query_parameters
+ end
+
+ def request_parameters
+ end
+
+ def env
+ end
+
+ def host
+ end
+
+ def cookies
+ end
+
+ def session
+ end
+
+ def reset_session
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
new file mode 100644
index 0000000000..c0933b2666
--- /dev/null
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -0,0 +1,94 @@
+module ActionController #:nodoc:
+ # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
+ # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
+ # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
+ # could the decision on whether something is a public or a developer request.
+ #
+ # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
+ module Rescue
+ def self.append_features(base) #:nodoc:
+ super
+ base.class_eval do
+ alias_method :perform_action_without_rescue, :perform_action
+ alias_method :perform_action, :perform_action_with_rescue
+ end
+ end
+
+ protected
+ # Exception handler called when the performance of an action raises an exception.
+ def rescue_action(exception)
+ log_error(exception) unless logger.nil?
+
+ if consider_all_requests_local || local_request?
+ rescue_action_locally(exception)
+ else
+ rescue_action_in_public(exception)
+ end
+ end
+
+ # Overwrite to implement custom logging of errors. By default logs as fatal.
+ def log_error(exception) #:doc:
+ if ActionView::TemplateError === exception
+ logger.fatal(exception.to_s)
+ else
+ logger.fatal(
+ "\n\n#{exception.class} (#{exception.message}):\n " +
+ clean_backtrace(exception).join("\n ") +
+ "\n\n"
+ )
+ end
+ end
+
+ # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
+ def rescue_action_in_public(exception) #:doc:
+ render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
+ end
+
+ # Overwrite to expand the meaning of a local request in order to show local rescues on other occurances than
+ # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
+ # remotely.
+ def local_request? #:doc:
+ @request.remote_addr == "127.0.0.1"
+ end
+
+ # Renders a detailed diagnostics screen on action exceptions.
+ def rescue_action_locally(exception)
+ @exception = exception
+ @rescues_path = File.dirname(__FILE__) + "/templates/rescues/"
+ add_variables_to_assigns
+ @contents = @template.render_file(template_path_for_local_rescue(exception), false)
+
+ @headers["Content-Type"] = "text/html"
+ render_file(rescues_path("layout"), "500 Internal Error")
+ end
+
+ private
+ def perform_action_with_rescue #:nodoc:
+ begin
+ perform_action_without_rescue
+ rescue Exception => exception
+ rescue_action(exception)
+ end
+ end
+
+ def rescues_path(template_name)
+ File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
+ end
+
+ def template_path_for_local_rescue(exception)
+ rescues_path(
+ case exception
+ when MissingTemplate then "missing_template"
+ when UnknownAction then "unknown_action"
+ when ActionView::TemplateError then "template_error"
+ else "diagnostics"
+ end
+ )
+ end
+
+ def clean_backtrace(exception)
+ base_dir = File.expand_path(File.dirname(__FILE__) + "/../../../../")
+ exception.backtrace.collect { |line| line.gsub(base_dir, "").gsub("/public/../config/environments/../../", "").gsub("/public/../", "") }
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
new file mode 100755
index 0000000000..836dd5ffef
--- /dev/null
+++ b/actionpack/lib/action_controller/response.rb
@@ -0,0 +1,15 @@
+module ActionController
+ class AbstractResponse #:nodoc:
+ DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
+ attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
+
+ def initialize
+ @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
+ end
+
+ def redirect(to_url)
+ @headers["Status"] = "302 Moved"
+ @headers["location"] = to_url
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/scaffolding.rb b/actionpack/lib/action_controller/scaffolding.rb
new file mode 100644
index 0000000000..49b35b37df
--- /dev/null
+++ b/actionpack/lib/action_controller/scaffolding.rb
@@ -0,0 +1,183 @@
+module ActionController
+ module Scaffolding # :nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ end
+
+ # Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions
+ # for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come
+ # with both controller logic and default templates that through introspection already know which fields to display
+ # and which input types to use. Example:
+ #
+ # class WeblogController < ActionController::Base
+ # scaffold :entry
+ # end
+ #
+ # This tiny piece of code will add all of the following methods to the controller:
+ #
+ # class WeblogController < ActionController::Base
+ # def index
+ # list
+ # end
+ #
+ # def list
+ # @entries = Entry.find_all
+ # render_scaffold "list"
+ # end
+ #
+ # def show
+ # @entry = Entry.find(@params["id"])
+ # render_scaffold
+ # end
+ #
+ # def destroy
+ # Entry.find(@params["id"]).destroy
+ # redirect_to :action => "list"
+ # end
+ #
+ # def new
+ # @entry = Entry.new
+ # render_scaffold
+ # end
+ #
+ # def create
+ # @entry = Entry.new(@params["entry"])
+ # if @entry.save
+ # flash["notice"] = "Entry was succesfully created"
+ # redirect_to :action => "list"
+ # else
+ # render "entry/new"
+ # end
+ # end
+ #
+ # def edit
+ # @entry = Entry.find(@params["id"])
+ # render_scaffold
+ # end
+ #
+ # def update
+ # @entry = Entry.find(@params["entry"]["id"])
+ # @entry.attributes = @params["entry"]
+ #
+ # if @entry.save
+ # flash["notice"] = "Entry was succesfully updated"
+ # redirect_to :action => "show/" + @entry.id.to_s
+ # else
+ # render "entry/edit"
+ # end
+ # end
+ # end
+ #
+ # The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
+ # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
+ # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
+ # and one action at a time while relying on the rest of the scaffolded templates and actions.
+ module ClassMethods
+ # Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
+ # one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
+ # and @post/@posts for the instance variables.
+ #
+ # It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
+ # make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
+ # instead of just list, show, and post. If suffix is used, then no index method is added.
+ def scaffold(model_id, options = {})
+ validate_options([ :class_name, :suffix ], options.keys)
+
+ require "#{model_id.id2name}" rescue logger.warn "Couldn't auto-require #{model_id.id2name}.rb" unless logger.nil?
+
+ singular_name = model_id.id2name
+ class_name = options[:class_name] || Inflector.camelize(singular_name)
+ plural_name = Inflector.pluralize(singular_name)
+ suffix = options[:suffix] ? "_#{singular_name}" : ""
+
+ unless options[:suffix]
+ module_eval <<-"end_eval", __FILE__, __LINE__
+ def index
+ list
+ end
+ end_eval
+ end
+
+ module_eval <<-"end_eval", __FILE__, __LINE__
+ def list#{suffix}
+ @#{plural_name} = #{class_name}.find_all
+ render#{suffix}_scaffold "list#{suffix}"
+ end
+
+ def show#{suffix}
+ @#{singular_name} = #{class_name}.find(@params["id"])
+ render#{suffix}_scaffold
+ end
+
+ def destroy#{suffix}
+ #{class_name}.find(@params["id"]).destroy
+ redirect_to :action => "list#{suffix}"
+ end
+
+ def new#{suffix}
+ @#{singular_name} = #{class_name}.new
+ render#{suffix}_scaffold
+ end
+
+ def create#{suffix}
+ @#{singular_name} = #{class_name}.new(@params["#{singular_name}"])
+ if @#{singular_name}.save
+ flash["notice"] = "#{class_name} was succesfully created"
+ redirect_to :action => "list#{suffix}"
+ else
+ render "#{singular_name}/new#{suffix}"
+ end
+ end
+
+ def edit#{suffix}
+ @#{singular_name} = #{class_name}.find(@params["id"])
+ render#{suffix}_scaffold
+ end
+
+ def update#{suffix}
+ @#{singular_name} = #{class_name}.find(@params["#{singular_name}"]["id"])
+ @#{singular_name}.attributes = @params["#{singular_name}"]
+
+ if @#{singular_name}.save
+ flash["notice"] = "#{class_name} was succesfully updated"
+ redirect_to :action => "show#{suffix}/" + @#{singular_name}.id.to_s
+ else
+ render "#{singular_name}/edit#{suffix}"
+ end
+ end
+
+ private
+ def render#{suffix}_scaffold(action = caller_method_name(caller))
+ if template_exists?("\#{controller_name}/\#{action}")
+ render_action(action)
+ else
+ @scaffold_class = #{class_name}
+ @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
+ @scaffold_suffix = "#{suffix}"
+ add_instance_variables_to_assigns
+
+ @content_for_layout = @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false)
+ self.active_layout ? render_file(self.active_layout, "200 OK", true) : render_file(scaffold_path("layout"))
+ end
+ end
+
+ def scaffold_path(template_name)
+ File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
+ end
+
+ def caller_method_name(caller)
+ caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
+ end
+ end_eval
+ end
+
+ private
+ # Raises an exception if an invalid option has been specified to prevent misspellings from slipping through
+ def validate_options(valid_option_keys, supplied_option_keys)
+ unknown_option_keys = supplied_option_keys - valid_option_keys
+ raise(ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty?
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/session/active_record_store.rb b/actionpack/lib/action_controller/session/active_record_store.rb
new file mode 100644
index 0000000000..c144f62e35
--- /dev/null
+++ b/actionpack/lib/action_controller/session/active_record_store.rb
@@ -0,0 +1,72 @@
+begin
+
+require 'active_record'
+require 'cgi'
+require 'cgi/session'
+
+# Contributed by Tim Bates
+class CGI
+ class Session
+ # ActiveRecord database based session storage class.
+ #
+ # Implements session storage in a database using the ActiveRecord ORM library. Assumes that the database
+ # has a table called +sessions+ with columns +id+ (numeric, primary key), +sessid+ and +data+ (text).
+ # The session data is stored in the +data+ column in YAML format; the user is responsible for ensuring that
+ # only data that can be YAMLized is stored in the session.
+ class ActiveRecordStore
+ # The ActiveRecord class which corresponds to the database table.
+ class Session < ActiveRecord::Base
+ serialize :data
+ # Isn't this class definition beautiful?
+ end
+
+ # Create a new ActiveRecordStore instance. This constructor is used internally by CGI::Session.
+ # The user does not generally need to call it directly.
+ #
+ # +session+ is the session for which this instance is being created.
+ #
+ # +option+ is currently ignored as no options are recognized.
+ #
+ # This session's ActiveRecord database row will be created if it does not exist, or opened if it does.
+ def initialize(session, option=nil)
+ @session = Session.find_first(["sessid = '%s'", session.session_id])
+ if @session
+ @data = @session.data
+ else
+ @session = Session.new("sessid" => session.session_id, "data" => {})
+ end
+ end
+
+ # Update and close the session's ActiveRecord object.
+ def close
+ return unless @session
+ update
+ @session = nil
+ end
+
+ # Close and destroy the session's ActiveRecord object.
+ def delete
+ return unless @session
+ @session.destroy
+ @session = nil
+ end
+
+ # Restore session state from the session's ActiveRecord object.
+ def restore
+ return unless @session
+ @data = @session.data
+ end
+
+ # Save session state in the session's ActiveRecord object.
+ def update
+ return unless @session
+ @session.data = @data
+ @session.save
+ end
+ end #ActiveRecordStore
+ end #Session
+end #CGI
+
+rescue LoadError
+ # Couldn't load Active Record, so don't make this store available
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/session/drb_server.rb b/actionpack/lib/action_controller/session/drb_server.rb
new file mode 100644
index 0000000000..6005b8b2b3
--- /dev/null
+++ b/actionpack/lib/action_controller/session/drb_server.rb
@@ -0,0 +1,9 @@
+#!/usr/local/bin/ruby -w
+
+# This is a really simple session storage daemon, basically just a hash,
+# which is enabled for DRb access.
+
+require 'drb'
+
+DRb.start_service('druby://127.0.0.1:9192', Hash.new)
+DRb.thread.join \ No newline at end of file
diff --git a/actionpack/lib/action_controller/session/drb_store.rb b/actionpack/lib/action_controller/session/drb_store.rb
new file mode 100644
index 0000000000..8ea23e8fff
--- /dev/null
+++ b/actionpack/lib/action_controller/session/drb_store.rb
@@ -0,0 +1,31 @@
+require 'cgi'
+require 'cgi/session'
+require 'drb'
+
+class CGI #:nodoc:all
+ class Session
+ class DRbStore
+ @@session_data = DRbObject.new(nil, 'druby://localhost:9192')
+
+ def initialize(session, option=nil)
+ @session_id = session.session_id
+ end
+
+ def restore
+ @h = @@session_data[@session_id] || {}
+ end
+
+ def update
+ @@session_data[@session_id] = @h
+ end
+
+ def close
+ update
+ end
+
+ def delete
+ @@session_data.delete(@session_id)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/support/class_attribute_accessors.rb b/actionpack/lib/action_controller/support/class_attribute_accessors.rb
new file mode 100644
index 0000000000..786dcf98cb
--- /dev/null
+++ b/actionpack/lib/action_controller/support/class_attribute_accessors.rb
@@ -0,0 +1,57 @@
+# Extends the class object with class and instance accessors for class attributes,
+# just like the native attr* accessors for instance attributes.
+class Class # :nodoc:
+ def cattr_reader(*syms)
+ syms.each do |sym|
+ class_eval <<-EOS
+ if ! defined? @@#{sym.id2name}
+ @@#{sym.id2name} = nil
+ end
+
+ def self.#{sym.id2name}
+ @@#{sym}
+ end
+
+ def #{sym.id2name}
+ @@#{sym}
+ end
+
+ def call_#{sym.id2name}
+ case @@#{sym.id2name}
+ when Symbol then send(@@#{sym})
+ when Proc then @@#{sym}.call(self)
+ when String then @@#{sym}
+ else nil
+ end
+ end
+ EOS
+ end
+ end
+
+ def cattr_writer(*syms)
+ syms.each do |sym|
+ class_eval <<-EOS
+ if ! defined? @@#{sym.id2name}
+ @@#{sym.id2name} = nil
+ end
+
+ def self.#{sym.id2name}=(obj)
+ @@#{sym.id2name} = obj
+ end
+
+ def self.set_#{sym.id2name}(obj)
+ @@#{sym.id2name} = obj
+ end
+
+ def #{sym.id2name}=(obj)
+ @@#{sym} = obj
+ end
+ EOS
+ end
+ end
+
+ def cattr_accessor(*syms)
+ cattr_reader(*syms)
+ cattr_writer(*syms)
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/support/class_inheritable_attributes.rb b/actionpack/lib/action_controller/support/class_inheritable_attributes.rb
new file mode 100644
index 0000000000..7f061fdf1b
--- /dev/null
+++ b/actionpack/lib/action_controller/support/class_inheritable_attributes.rb
@@ -0,0 +1,37 @@
+# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
+# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
+# to, for example, an array without those additions being shared with either their parent, siblings, or
+# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
+module ClassInheritableAttributes # :nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods # :nodoc:
+ @@classes ||= {}
+
+ def inheritable_attributes
+ @@classes[self] ||= {}
+ end
+
+ def write_inheritable_attribute(key, value)
+ inheritable_attributes[key] = value
+ end
+
+ def write_inheritable_array(key, elements)
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
+ end
+
+ def read_inheritable_attribute(key)
+ inheritable_attributes[key]
+ end
+
+ private
+ def inherited(child)
+ @@classes[child] = inheritable_attributes.dup
+ end
+
+ end
+end
diff --git a/actionpack/lib/action_controller/support/clean_logger.rb b/actionpack/lib/action_controller/support/clean_logger.rb
new file mode 100644
index 0000000000..1a36562892
--- /dev/null
+++ b/actionpack/lib/action_controller/support/clean_logger.rb
@@ -0,0 +1,10 @@
+require 'logger'
+
+class Logger #:nodoc:
+ private
+ remove_const "Format"
+ Format = "%s\n"
+ def format_message(severity, timestamp, msg, progname)
+ Format % [msg]
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/support/cookie_performance_fix.rb b/actionpack/lib/action_controller/support/cookie_performance_fix.rb
new file mode 100644
index 0000000000..225cea1905
--- /dev/null
+++ b/actionpack/lib/action_controller/support/cookie_performance_fix.rb
@@ -0,0 +1,121 @@
+CGI.module_eval { remove_const "Cookie" }
+
+class CGI #:nodoc:
+ # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.
+ # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on
+ # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
+ class Cookie < DelegateClass(Array)
+ # Create a new CGI::Cookie object.
+ #
+ # The contents of the cookie can be specified as a +name+ and one
+ # or more +value+ arguments. Alternatively, the contents can
+ # be specified as a single hash argument. The possible keywords of
+ # this hash are as follows:
+ #
+ # name:: the name of the cookie. Required.
+ # value:: the cookie's value or list of values.
+ # path:: the path for which this cookie applies. Defaults to the
+ # base directory of the CGI script.
+ # domain:: the domain for which this cookie applies.
+ # expires:: the time at which this cookie expires, as a +Time+ object.
+ # secure:: whether this cookie is a secure cookie or not (default to
+ # false). Secure cookies are only transmitted to HTTPS
+ # servers.
+ #
+ # These keywords correspond to attributes of the cookie object.
+ def initialize(name = "", *value)
+ options = if name.kind_of?(String)
+ { "name" => name, "value" => value }
+ else
+ name
+ end
+ unless options.has_key?("name")
+ raise ArgumentError, "`name' required"
+ end
+
+ @name = options["name"]
+ @value = Array(options["value"])
+ # simple support for IE
+ if options["path"]
+ @path = options["path"]
+ else
+ %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
+ @path = ($1 or "")
+ end
+ @domain = options["domain"]
+ @expires = options["expires"]
+ @secure = options["secure"] == true ? true : false
+
+ super(@value)
+ end
+
+ def __setobj__(obj)
+ @_dc_obj = obj
+ end
+
+ attr_accessor("name", "value", "path", "domain", "expires")
+ attr_reader("secure")
+
+ # Set whether the Cookie is a secure cookie or not.
+ #
+ # +val+ must be a boolean.
+ def secure=(val)
+ @secure = val if val == true or val == false
+ @secure
+ end
+
+ # Convert the Cookie to its string representation.
+ def to_s
+ buf = ""
+ buf += @name + '='
+
+ if @value.kind_of?(String)
+ buf += CGI::escape(@value)
+ else
+ buf += @value.collect{|v| CGI::escape(v) }.join("&")
+ end
+
+ if @domain
+ buf += '; domain=' + @domain
+ end
+
+ if @path
+ buf += '; path=' + @path
+ end
+
+ if @expires
+ buf += '; expires=' + CGI::rfc1123_date(@expires)
+ end
+
+ if @secure == true
+ buf += '; secure'
+ end
+
+ buf
+ end
+
+ # Parse a raw cookie string into a hash of cookie-name=>Cookie
+ # pairs.
+ #
+ # cookies = CGI::Cookie::parse("raw_cookie_string")
+ # # { "name1" => cookie1, "name2" => cookie2, ... }
+ #
+ def self.parse(raw_cookie)
+ cookies = Hash.new([])
+ return cookies unless raw_cookie
+
+ raw_cookie.split(/; /).each do |pairs|
+ name, values = pairs.split('=',2)
+ next unless name and values
+ name = CGI::unescape(name)
+ values ||= ""
+ values = values.split('&').collect{|v| CGI::unescape(v) }
+ unless cookies.has_key?(name)
+ cookies[name] = new({ "name" => name, "value" => values })
+ end
+ end
+
+ cookies
+ end
+ end # class Cookie
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/support/inflector.rb b/actionpack/lib/action_controller/support/inflector.rb
new file mode 100644
index 0000000000..05ff4fede9
--- /dev/null
+++ b/actionpack/lib/action_controller/support/inflector.rb
@@ -0,0 +1,78 @@
+# The Inflector transforms words from singular to plural, class names to table names, modulized class names to ones without,
+# and class names to foreign keys.
+module Inflector
+ extend self
+
+ def pluralize(word)
+ result = word.dup
+ plural_rules.each do |(rule, replacement)|
+ break if result.gsub!(rule, replacement)
+ end
+ return result
+ end
+
+ def singularize(word)
+ result = word.dup
+ singular_rules.each do |(rule, replacement)|
+ break if result.gsub!(rule, replacement)
+ end
+ return result
+ end
+
+ def camelize(lower_case_and_underscored_word)
+ lower_case_and_underscored_word.gsub(/(^|_)(.)/){$2.upcase}
+ end
+
+ def underscore(camel_cased_word)
+ camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
+ end
+
+ def demodulize(class_name_in_module)
+ class_name_in_module.gsub(/^.*::/, '')
+ end
+
+ def tableize(class_name)
+ pluralize(underscore(class_name))
+ end
+
+ def classify(table_name)
+ camelize(singularize(table_name))
+ end
+
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
+ Inflector.underscore(Inflector.demodulize(class_name)) +
+ (separate_class_name_and_id_with_underscore ? "_id" : "id")
+ end
+
+ private
+ def plural_rules #:doc:
+ [
+ [/(x|ch|ss)$/, '\1es'], # search, switch, fix, box, process, address
+ [/([^aeiouy]|qu)y$/, '\1ies'], # query, ability, agency
+ [/(?:([^f])fe|([lr])f)$/, '\1\2ves'], # half, safe, wife
+ [/sis$/, 'ses'], # basis, diagnosis
+ [/([ti])um$/, '\1a'], # datum, medium
+ [/person$/, 'people'], # person, salesperson
+ [/man$/, 'men'], # man, woman, spokesman
+ [/child$/, 'children'], # child
+ [/s$/, 's'], # no change (compatibility)
+ [/$/, 's']
+ ]
+ end
+
+ def singular_rules #:doc:
+ [
+ [/(x|ch|ss)es$/, '\1'],
+ [/([^aeiouy]|qu)ies$/, '\1y'],
+ [/([lr])ves$/, '\1f'],
+ [/([^f])ves$/, '\1fe'],
+ [/(analy|ba|diagno|parenthe|progno|synop|the)ses$/, '\1sis'],
+ [/([ti])a$/, '\1um'],
+ [/people$/, 'person'],
+ [/men$/, 'man'],
+ [/status$/, 'status'],
+ [/children$/, 'child'],
+ [/s$/, '']
+ ]
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml b/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml
new file mode 100644
index 0000000000..f1b4a2a1dd
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/_request_and_response.rhtml
@@ -0,0 +1,28 @@
+<%
+ base_dir = File.expand_path(File.dirname(__FILE__))
+
+ request_parameters_without_action = @request.parameters.clone
+ request_parameters_without_action.delete("action")
+ request_parameters_without_action.delete("controller")
+
+ request_dump = request_parameters_without_action.inspect.gsub(/,/, ",\n")
+ session_dump = @request.session.instance_variable_get("@data").inspect.gsub(/,/, ",\n")
+ response_dump = @response.inspect.gsub(/,/, ",\n")
+
+ template_assigns = @response.template.instance_variable_get("@assigns")
+ %w( response exception template session request template_root template_class url ignore_missing_templates logger cookies headers params ).each { |t| template_assigns.delete(t) }
+ template_dump = template_assigns.inspect.gsub(/,/, ",\n")
+%>
+
+<h2 style="margin-top: 30px">Request</h2>
+<p><b>Parameters</b>: <%=h request_dump == "{}" ? "None" : request_dump %></p>
+
+<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
+<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div>
+
+
+<h2 style="margin-top: 30px">Response</h2>
+<b>Headers</b>: <%=h @response.headers.inspect.gsub(/,/, ",\n") %><br/>
+
+<p><a href="#" onclick="document.getElementById('template_dump').style.display='block'; return false;">Show template parameters</a></p>
+<div id="template_dump" style="display:none"><%= debug(template_assigns) %></div>
diff --git a/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml b/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml
new file mode 100644
index 0000000000..4eb1ed0439
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/diagnostics.rhtml
@@ -0,0 +1,22 @@
+<%
+ base_dir = File.expand_path(File.dirname(__FILE__))
+
+ clean_backtrace = @exception.backtrace.collect { |line| line.gsub(base_dir, "").gsub("/../config/environments/../../", "") }
+ app_trace = clean_backtrace.reject { |line| line[0..6] == "vendor/" || line.include?("dispatch.cgi") }
+ framework_trace = clean_backtrace - app_trace
+%>
+
+<h1>
+ <%=h @exception.class.to_s %> in
+ <%=h @request.parameters["controller"].capitalize %>#<%=h @request.parameters["action"] %>
+</h1>
+<p><%=h @exception.message %></p>
+
+<% unless app_trace.empty? %><pre><code><%=h app_trace.collect { |line| line.gsub("/../", "") }.join("\n") %></code></pre><% end %>
+
+<% unless framework_trace.empty? %>
+ <a href="#" onclick="document.getElementById('framework_trace').style.display='block'; return false;">Show framework trace</a>
+ <pre id="framework_trace" style="display:none"><code><%=h framework_trace.join("\n") %></code></pre>
+<% end %>
+
+<%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
diff --git a/actionpack/lib/action_controller/templates/rescues/layout.rhtml b/actionpack/lib/action_controller/templates/rescues/layout.rhtml
new file mode 100644
index 0000000000..d38f3e67f9
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/layout.rhtml
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <title>Action Controller: Exception caught</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+ </style>
+</head>
+<body>
+
+<%= @contents %>
+
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/lib/action_controller/templates/rescues/missing_template.rhtml b/actionpack/lib/action_controller/templates/rescues/missing_template.rhtml
new file mode 100644
index 0000000000..dbfdf76947
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/missing_template.rhtml
@@ -0,0 +1,2 @@
+<h1>Template is missing</h1>
+<p><%=h @exception.message %></p>
diff --git a/actionpack/lib/action_controller/templates/rescues/template_error.rhtml b/actionpack/lib/action_controller/templates/rescues/template_error.rhtml
new file mode 100644
index 0000000000..326fd0b057
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/template_error.rhtml
@@ -0,0 +1,26 @@
+<%
+ base_dir = File.expand_path(File.dirname(__FILE__))
+
+ framework_trace = @exception.original_exception.backtrace.collect do |line|
+ line.gsub(base_dir, "").gsub("/../config/environments/../../", "")
+ end
+%>
+
+<h1>
+ <%=h @exception.original_exception.class.to_s %> in
+ <%=h @request.parameters["controller"].capitalize %>#<%=h @request.parameters["action"] %>
+</h1>
+
+<p>
+ Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised
+ <u><%=h @exception.message %></u>
+</p>
+
+<pre><code><%=h @exception.source_extract %></code></pre>
+
+<p><%=h @exception.sub_template_message %></p>
+
+<a href="#" onclick="document.getElementById('framework_trace').style.display='block'">Show template trace</a>
+<pre id="framework_trace" style="display:none"><code><%=h framework_trace.join("\n") %></code></pre>
+
+<%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
diff --git a/actionpack/lib/action_controller/templates/rescues/unknown_action.rhtml b/actionpack/lib/action_controller/templates/rescues/unknown_action.rhtml
new file mode 100644
index 0000000000..683379da10
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/rescues/unknown_action.rhtml
@@ -0,0 +1,2 @@
+<h1>Unknown action</h1>
+<p><%=h @exception.message %></p>
diff --git a/actionpack/lib/action_controller/templates/scaffolds/edit.rhtml b/actionpack/lib/action_controller/templates/scaffolds/edit.rhtml
new file mode 100644
index 0000000000..1c7f4d9770
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/scaffolds/edit.rhtml
@@ -0,0 +1,6 @@
+<h1>Editing <%= @scaffold_singular_name %></h1>
+
+<%= form(@scaffold_singular_name, :action => "../update" + @scaffold_suffix) %>
+
+<%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => instance_variable_get("@#{@scaffold_singular_name}").id %> |
+<%= link_to "Back", :action => "list#{@scaffold_suffix}" %>
diff --git a/actionpack/lib/action_controller/templates/scaffolds/layout.rhtml b/actionpack/lib/action_controller/templates/scaffolds/layout.rhtml
new file mode 100644
index 0000000000..511054abe8
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/scaffolds/layout.rhtml
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <title>Scaffolding</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+ </style>
+</head>
+<body>
+
+<%= @content_for_layout %>
+
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/lib/action_controller/templates/scaffolds/list.rhtml b/actionpack/lib/action_controller/templates/scaffolds/list.rhtml
new file mode 100644
index 0000000000..33af7079b2
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/scaffolds/list.rhtml
@@ -0,0 +1,24 @@
+<h1>Listing <%= @scaffold_plural_name %></h1>
+
+<table>
+ <tr>
+ <% for column in @scaffold_class.content_columns %>
+ <th><%= column.human_name %></th>
+ <% end %>
+ </tr>
+
+<% for entry in instance_variable_get("@#{@scaffold_plural_name}") %>
+ <tr>
+ <% for column in @scaffold_class.content_columns %>
+ <td><%= entry.send(column.name) %></td>
+ <% end %>
+ <td><%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => entry.id %></td>
+ <td><%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => entry.id %></td>
+ <td><%= link_to "Destroy", :action => "destroy#{@scaffold_suffix}", :id => entry.id %></td>
+ </tr>
+<% end %>
+</table>
+
+<br />
+
+<%= link_to "New #{@scaffold_singular_name}", :action => "new#{@scaffold_suffix}" %>
diff --git a/actionpack/lib/action_controller/templates/scaffolds/new.rhtml b/actionpack/lib/action_controller/templates/scaffolds/new.rhtml
new file mode 100644
index 0000000000..02f52e72f5
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/scaffolds/new.rhtml
@@ -0,0 +1,5 @@
+<h1>New <%= @scaffold_singular_name %></h1>
+
+<%= form(@scaffold_singular_name, :action => "create" + @scaffold_suffix) %>
+
+<%= link_to "Back", :action => "list#{@scaffold_suffix}" %> \ No newline at end of file
diff --git a/actionpack/lib/action_controller/templates/scaffolds/show.rhtml b/actionpack/lib/action_controller/templates/scaffolds/show.rhtml
new file mode 100644
index 0000000000..10c46342fd
--- /dev/null
+++ b/actionpack/lib/action_controller/templates/scaffolds/show.rhtml
@@ -0,0 +1,9 @@
+<% for column in @scaffold_class.content_columns %>
+ <p>
+ <b><%= column.human_name %>:</b>
+ <%= instance_variable_get("@#{@scaffold_singular_name}").send(column.name) %>
+ </p>
+<% end %>
+
+<%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => instance_variable_get("@#{@scaffold_singular_name}").id %> |
+<%= link_to "Back", :action => "list#{@scaffold_suffix}" %>
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
new file mode 100644
index 0000000000..969f2573c5
--- /dev/null
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -0,0 +1,195 @@
+require File.dirname(__FILE__) + '/assertions/action_pack_assertions'
+require File.dirname(__FILE__) + '/assertions/active_record_assertions'
+
+module ActionController #:nodoc:
+ class Base
+ # Process a test request called with a +TestRequest+ object.
+ def self.process_test(request)
+ new.process_test(request)
+ end
+
+ def process_test(request) #:nodoc:
+ process(request, TestResponse.new)
+ end
+ end
+
+ class TestRequest < AbstractRequest #:nodoc:
+ attr_writer :cookies
+ attr_accessor :query_parameters, :request_parameters, :session, :env
+ attr_accessor :host, :path, :request_uri, :remote_addr
+
+ def initialize(query_parameters = nil, request_parameters = nil, session = nil)
+ @query_parameters = query_parameters || {}
+ @request_parameters = request_parameters || {}
+ @session = session || TestSession.new
+
+ initialize_containers
+ initialize_default_values
+
+ super()
+ end
+
+ def reset_session
+ @session = {}
+ end
+
+ def cookies
+ @cookies.freeze
+ end
+
+ def action=(action_name)
+ @query_parameters.update({ "action" => action_name })
+ @parameters = nil
+ end
+
+ def request_uri=(uri)
+ @request_uri = uri
+ @path = uri.split("?").first
+ end
+
+ private
+ def initialize_containers
+ @env, @cookies = {}, {}
+ end
+
+ def initialize_default_values
+ @host = "test.host"
+ @request_uri = "/"
+ @remote_addr = "127.0.0.1"
+ @env["SERVER_PORT"] = 80
+ end
+ end
+
+ class TestResponse < AbstractResponse #:nodoc:
+ # the class attribute ties a TestResponse to the assertions
+ class << self
+ attr_accessor :assertion_target
+ end
+
+ # initializer
+ def initialize
+ TestResponse.assertion_target=self# if TestResponse.assertion_target.nil?
+ super()
+ end
+
+ # the response code of the request
+ def response_code
+ headers['Status'][0,3].to_i rescue 0
+ end
+
+ # was the response successful?
+ def success?
+ response_code == 200
+ end
+
+ # was the URL not found?
+ def missing?
+ response_code == 404
+ end
+
+ # were we redirected?
+ def redirect?
+ (300..399).include?(response_code)
+ end
+
+ # was there a server-side error?
+ def server_error?
+ (500..599).include?(response_code)
+ end
+
+ # returns the redirection location or nil
+ def redirect_url
+ redirect? ? headers['location'] : nil
+ end
+
+ # does the redirect location match this regexp pattern?
+ def redirect_url_match?( pattern )
+ return false if redirect_url.nil?
+ p = Regexp.new(pattern) if pattern.class == String
+ p = pattern if pattern.class == Regexp
+ return false if p.nil?
+ p.match(redirect_url) != nil
+ end
+
+ # returns the template path of the file which was used to
+ # render this response (or nil)
+ def rendered_file(with_controller=false)
+ unless template.first_render.nil?
+ unless with_controller
+ template.first_render
+ else
+ template.first_render.split('/').last || template.first_render
+ end
+ end
+ end
+
+ # was this template rendered by a file?
+ def rendered_with_file?
+ !rendered_file.nil?
+ end
+
+ # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!)
+ def flash
+ session['flash'] || {}
+ end
+
+ # do we have a flash?
+ def has_flash?
+ !session['flash'].nil?
+ end
+
+ # do we have a flash that has contents?
+ def has_flash_with_contents?
+ !flash.empty?
+ end
+
+ # does the specified flash object exist?
+ def has_flash_object?(name=nil)
+ !flash[name].nil?
+ end
+
+ # does the specified object exist in the session?
+ def has_session_object?(name=nil)
+ !session[name].nil?
+ end
+
+ # a shortcut to the template.assigns
+ def template_objects
+ template.assigns || {}
+ end
+
+ # does the specified template object exist?
+ def has_template_object?(name=nil)
+ !template_objects[name].nil?
+ end
+end
+
+ class TestSession #:nodoc:
+ def initialize(attributes = {})
+ @attributes = attributes
+ end
+
+ def [](key)
+ @attributes[key]
+ end
+
+ def []=(key, value)
+ @attributes[key] = value
+ end
+
+ def update() end
+ def close() end
+ def delete() @attributes = {} end
+ end
+end
+
+class Test::Unit::TestCase #:nodoc:
+ private
+ # execute the request and set/volley the response
+ def process(action, parameters = nil, session = nil)
+ @request.action = action.to_s
+ @request.parameters.update(parameters) unless parameters.nil?
+ @request.session = ActionController::TestSession.new(session) unless session.nil?
+ @controller.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
new file mode 100644
index 0000000000..78638da39e
--- /dev/null
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -0,0 +1,170 @@
+module ActionController
+ # Rewrites urls for Base.redirect_to and Base.url_for in the controller.
+ class UrlRewriter #:nodoc:
+ VALID_OPTIONS = [:action, :action_prefix, :action_suffix, :module, :controller, :controller_prefix, :anchor, :params, :path_params, :id, :only_path, :overwrite_params ]
+
+ def initialize(request, controller, action)
+ @request, @controller, @action = request, controller, action
+ @rewritten_path = @request.path ? @request.path.dup : ""
+ end
+
+ def rewrite(options = {})
+ validate_options(VALID_OPTIONS, options.keys)
+
+ rewrite_url(
+ rewrite_path(@rewritten_path, options),
+ options
+ )
+ end
+
+ def to_s
+ to_str
+ end
+
+ def to_str
+ "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@controller}, #{@action}, #{@request.parameters.inspect}"
+ end
+
+ private
+ def validate_options(valid_option_keys, supplied_option_keys)
+ unknown_option_keys = supplied_option_keys - valid_option_keys
+ raise(ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty?
+ end
+
+ def rewrite_url(path, options)
+ rewritten_url = ""
+ rewritten_url << @request.protocol unless options[:only_path]
+ rewritten_url << @request.host_with_port unless options[:only_path]
+
+ rewritten_url << path
+ rewritten_url << build_query_string(new_parameters(options)) if options[:params] || options[:overwrite_params]
+ rewritten_url << "##{options[:anchor]}" if options[:anchor]
+ return rewritten_url
+ end
+
+ def rewrite_path(path, options)
+ include_id_in_path_params(options)
+
+ path = rewrite_action(path, options) if options[:action] || options[:action_prefix]
+ path = rewrite_path_params(path, options) if options[:path_params]
+ path = rewrite_controller(path, options) if options[:controller] || options[:controller_prefix]
+ return path
+ end
+
+ def rewrite_path_params(path, options)
+ index_action = options[:action] == 'index' || options[:action].nil? && @action == 'index'
+ id_only = options[:path_params].size == 1 && options[:path_params]['id']
+
+ if index_action && id_only
+ path += '/' unless path[-1..-1] == '/'
+ path += "index/#{options[:path_params]['id']}"
+ path
+ else
+ options[:path_params].inject(path) do |path, pair|
+ if options[:action].nil? && @request.parameters[pair.first]
+ path.sub(/\b#{@request.parameters[pair.first]}\b/, pair.last.to_s)
+ else
+ path += "/#{pair.last}"
+ end
+ end
+ end
+ end
+
+ def rewrite_action(path, options)
+ # This regex assumes that "index" actions won't be included in the URL
+ all, controller_prefix, action_prefix, action_suffix =
+ /^\/(.*)#{@controller}\/(.*)#{@action == "index" ? "" : @action}(.*)/.match(path).to_a
+
+ if @action == "index"
+ if action_prefix == "index"
+ # we broke the parsing assumption that this would be excluded, so
+ # don't tell action_name about our little boo-boo
+ path = path.sub(action_prefix, action_name(options, nil))
+ elsif action_prefix && !action_prefix.empty?
+ path = path.sub(action_prefix, action_name(options, action_prefix))
+ else
+ path = path.sub(%r(#{@controller}/?), @controller + "/" + action_name(options)) # " ruby-mode
+ end
+ else
+ path = path.sub((action_prefix || "") + @action + (action_suffix || ""), action_name(options, action_prefix))
+ end
+
+ if options[:controller_prefix] && !options[:controller]
+ ensure_slash_suffix(options, :controller_prefix)
+ if controller_prefix
+ path = path.sub(controller_prefix, options[:controller_prefix])
+ else
+ path = options[:controller_prefix] + path
+ end
+ end
+
+ return path
+ end
+
+ def rewrite_controller(path, options)
+ all, controller_prefix = /^\/(.*?)#{@controller}/.match(path).to_a
+ path = "/"
+ path << controller_name(options, controller_prefix)
+ path << action_name(options) if options[:action]
+ path << path_params_in_list(options) if options[:path_params]
+ return path
+ end
+
+ def action_name(options, action_prefix = nil, action_suffix = nil)
+ ensure_slash_suffix(options, :action_prefix)
+ ensure_slash_prefix(options, :action_suffix)
+
+ prefix = options[:action_prefix] || action_prefix || ""
+ suffix = options[:action] == "index" ? "" : (options[:action_suffix] || action_suffix || "")
+ name = (options[:action] == "index" ? "" : options[:action]) || ""
+
+ return prefix + name + suffix
+ end
+
+ def controller_name(options, controller_prefix)
+ options[:controller_prefix] = "#{options[:module]}/#{options[:controller_prefix]}" if options[:module]
+ ensure_slash_suffix(options, :controller_prefix)
+ controller_name = options[:controller_prefix] || controller_prefix || ""
+ controller_name << (options[:controller] + "/") if options[:controller]
+ return controller_name
+ end
+
+ def path_params_in_list(options)
+ options[:path_params].inject("") { |path, pair| path += "/#{pair.last}" }
+ end
+
+ def ensure_slash_suffix(options, key)
+ options[key] = options[key] + "/" if options[key] && !options[key].empty? && options[key][-1..-1] != "/"
+ end
+
+ def ensure_slash_prefix(options, key)
+ options[key] = "/" + options[key] if options[key] && !options[key].empty? && options[key][0..1] != "/"
+ end
+
+ def include_id_in_path_params(options)
+ options[:path_params] = (options[:path_params] || {}).merge({"id" => options[:id]}) if options[:id]
+ end
+
+ def new_parameters(options)
+ parameters = options[:params] || existing_parameters
+ parameters.update(options[:overwrite_params]) if options[:overwrite_params]
+ parameters.reject { |key,value| value.nil? }
+ end
+
+ def existing_parameters
+ @request.parameters.reject { |key, value| %w( id action controller).include?(key) }
+ end
+
+ # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
+ # be added as a path element instead of a regular parameter pair.
+ def build_query_string(hash)
+ elements = []
+ query_string = ""
+
+ hash.each { |key, value| elements << "#{CGI.escape(key)}=#{CGI.escape(value.to_s)}" }
+ unless elements.empty? then query_string << ("?" + elements.join("&")) end
+
+ return query_string
+ end
+ end
+end