aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rwxr-xr-xactionpack/lib/action_controller.rb51
-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
-rw-r--r--actionpack/lib/action_view.rb49
-rw-r--r--actionpack/lib/action_view/base.rb264
-rw-r--r--actionpack/lib/action_view/helpers/active_record_helper.rb171
-rwxr-xr-xactionpack/lib/action_view/helpers/date_helper.rb230
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb17
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb182
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb212
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb59
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb111
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb78
-rw-r--r--actionpack/lib/action_view/partials.rb64
-rw-r--r--actionpack/lib/action_view/template_error.rb84
-rw-r--r--actionpack/lib/action_view/vendor/builder.rb13
-rw-r--r--actionpack/lib/action_view/vendor/builder/blankslate.rb51
-rw-r--r--actionpack/lib/action_view/vendor/builder/xmlbase.rb143
-rw-r--r--actionpack/lib/action_view/vendor/builder/xmlevents.rb63
-rw-r--r--actionpack/lib/action_view/vendor/builder/xmlmarkup.rb288
55 files changed, 5385 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
new file mode 100755
index 0000000000..6445940d78
--- /dev/null
+++ b/actionpack/lib/action_controller.rb
@@ -0,0 +1,51 @@
+#--
+# Copyright (c) 2004 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+$:.unshift(File.dirname(__FILE__))
+
+require 'action_controller/support/clean_logger'
+
+require 'action_controller/base'
+require 'action_controller/rescue'
+require 'action_controller/benchmarking'
+require 'action_controller/filters'
+require 'action_controller/layout'
+require 'action_controller/flash'
+require 'action_controller/scaffolding'
+require 'action_controller/helpers'
+require 'action_controller/dependencies'
+require 'action_controller/cgi_process'
+
+ActionController::Base.class_eval do
+ include ActionController::Filters
+ include ActionController::Layout
+ include ActionController::Flash
+ include ActionController::Benchmarking
+ include ActionController::Rescue
+ include ActionController::Scaffolding
+ include ActionController::Helpers
+ include ActionController::Dependencies
+end
+
+require 'action_view'
+ActionController::Base.template_class = ActionView::Base \ No newline at end of file
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
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
new file mode 100644
index 0000000000..c39765d436
--- /dev/null
+++ b/actionpack/lib/action_view.rb
@@ -0,0 +1,49 @@
+#--
+# Copyright (c) 2004 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+begin
+ require 'rubygems'
+ require 'builder'
+rescue LoadError
+ # RubyGems is not available, use included Builder
+ $:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
+ require 'action_view/vendor/builder'
+ensure
+ # Temporary patch until it's in Builder 1.2.2
+ class BlankSlate
+ class << self
+ def hide(name)
+ undef_method name if instance_methods.include?(name) and name !~ /^(__|instance_eval)/
+ end
+ end
+ end
+end
+
+require 'action_view/base'
+require 'action_view/partials'
+
+ActionView::Base.class_eval do
+ include ActionView::Partials
+end
+
+ActionView::Base.load_helpers(File.dirname(__FILE__) + "/action_view/helpers/") \ No newline at end of file
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
new file mode 100644
index 0000000000..84c8040760
--- /dev/null
+++ b/actionpack/lib/action_view/base.rb
@@ -0,0 +1,264 @@
+require 'erb'
+
+module ActionView #:nodoc:
+ class ActionViewError < StandardError #:nodoc:
+ end
+
+ # Action View templates can be written in two ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb
+ # (included in Ruby) and HTML. If the template file has a +.rxml+ extension then Jim Weirich's Builder::XmlMarkup library is used.
+ #
+ # = ERb
+ #
+ # You trigger ERb by using embeddings such as <% %> and <%= %>. The difference is whether you want output or not. Consider the
+ # following loop for names:
+ #
+ # <b>Names of all the people</b>
+ # <% for person in @people %>
+ # Name: <%= person.name %><br/>
+ # <% end %>
+ #
+ # The loop is setup in regular embedding tags (<% %>) and the name is written using the output embedding tag (<%= %>). Note that this
+ # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
+ #
+ # Hi, Mr. <% puts "Frodo" %>
+ #
+ # (If you absolutely must write from within a function, you can use the TextHelper#concat)
+ #
+ # == Using sub templates
+ #
+ # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
+ # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
+ #
+ # <%= render "shared/header" %>
+ # Something really specific and terrific
+ # <%= render "shared/footer" %>
+ #
+ # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
+ # result of the rendering. The output embedding writes it to the current template.
+ #
+ # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
+ # variables defined in using the regular embedding tags. Like this:
+ #
+ # <% @page_title = "A Wonderful Hello" %>
+ # <%= render "shared/header" %>
+ #
+ # Now the header can pick up on the @page_title variable and use it for outputting a title tag:
+ #
+ # <title><%= @page_title %></title>
+ #
+ # == Passing local variables to sub templates
+ #
+ # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
+ #
+ # <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
+ #
+ # These can now be accessed in shared/header with:
+ #
+ # Headline: <%= headline %>
+ # First name: <%= person.first_name %>
+ #
+ # == Template caching
+ #
+ # The parsing of ERb templates are cached by default, but the reading of them are not. This means that the application by default
+ # will reflect changes to the templates immediatly. If you'd like to sacrifice that immediacy for the speed gain given by also
+ # caching the loading of templates (reading from the file systen), you can turn that on with
+ # <tt>ActionView::Base.cache_template_loading = true</tt>.
+ #
+ # == Builder
+ #
+ # Builder templates are a more programatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object
+ # named +xml+ is automatically made available to templates with a +.rxml+ extension.
+ #
+ # Here are some basic examples:
+ #
+ # xml.em("emphasized") # => <em>emphasized</em>
+ # xml.em { xml.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
+ # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
+ # xm.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
+ # # NOTE: order of attributes is not specified.
+ #
+ # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
+ #
+ # xml.div {
+ # xml.h1(@person.name)
+ # xml.p(@person.bio)
+ # }
+ #
+ # would produce something like:
+ #
+ # <div>
+ # <h1>David Heinemeier Hansson</h1>
+ # <p>A product of Danish Design during the Winter of '79...</p>
+ # </div>
+ #
+ # A full-length RSS example actually used on Basecamp:
+ #
+ # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
+ # xml.channel do
+ # xml.title(@feed_title)
+ # xml.link(@url)
+ # xml.description "Basecamp: Recent items"
+ # xml.language "en-us"
+ # xml.ttl "40"
+ #
+ # for item in @recent_items
+ # xml.item do
+ # xml.title(item_title(item))
+ # xml.description(item_description(item)) if item_description(item)
+ # xml.pubDate(item_pubDate(item))
+ # xml.guid(@person.firm.account.url + @recent_items.url(item))
+ # xml.link(@person.firm.account.url + @recent_items.url(item))
+ #
+ # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
+ # end
+ # end
+ # end
+ # end
+ #
+ # More builder documentation can be found at http://builder.rubyforge.org.
+ class Base
+ include ERB::Util
+
+ attr_reader :first_render
+ attr_accessor :base_path, :assigns, :template_extension
+ attr_accessor :controller
+
+ # Turn on to cache the reading of templates from the file system. Doing so means that you have to restart the server
+ # when changing templates, but that rendering will be faster.
+ @@cache_template_loading = false
+ cattr_accessor :cache_template_loading
+
+ @@compiled_erb_templates = {}
+ @@loaded_templates = {}
+
+ def self.load_helpers(helper_dir)#:nodoc:
+ Dir.foreach(helper_dir) do |helper_file|
+ next unless helper_file =~ /_helper.rb$/
+ require helper_dir + helper_file
+ helper_module_name = helper_file.capitalize.gsub(/_([a-z])/) { |m| $1.capitalize }[0..-4]
+
+ class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined?(helper_module_name)
+ end
+ end
+
+ def self.controller_delegate(*methods)
+ methods.flatten.each do |method|
+ class_eval <<-end_eval
+ def #{method}(*args, &block)
+ controller.send(%(#{method}), *args, &block)
+ end
+ end_eval
+ end
+ end
+
+ def initialize(base_path = nil, assigns_for_first_render = {}, controller = nil)#:nodoc:
+ @base_path, @assigns = base_path, assigns_for_first_render
+ @controller = controller
+ end
+
+ # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
+ # it's relative to the template_root, otherwise it's absolute. The hash in <tt>local_assigns</tt>
+ # is made available as local variables.
+ def render_file(template_path, use_full_path = true, local_assigns = {})
+ @first_render = template_path if @first_render.nil?
+
+ if use_full_path
+ template_extension = pick_template_extension(template_path)
+ template_file_name = full_template_path(template_path, template_extension)
+ else
+ template_file_name = template_path
+ template_extension = template_path.split(".").last
+ end
+
+ template_source = read_template_file(template_file_name)
+
+ begin
+ render_template(template_extension, template_source, local_assigns)
+ rescue Exception => e
+ if TemplateError === e
+ e.sub_template_of(template_file_name)
+ raise e
+ else
+ raise TemplateError.new(@base_path, template_file_name, @assigns, template_source, e)
+ end
+ end
+ end
+
+ # Renders the template present at <tt>template_path</tt> (relative to the template_root).
+ # The hash in <tt>local_assigns</tt> is made available as local variables.
+ def render(template_path, local_assigns = {})
+ render_file(template_path, true, local_assigns)
+ end
+
+ # Renders the +template+ which is given as a string as either rhtml or rxml depending on <tt>template_extension</tt>.
+ # The hash in <tt>local_assigns</tt> is made available as local variables.
+ def render_template(template_extension, template, local_assigns = {})
+ b = binding
+ local_assigns.each { |key, value| eval "#{key} = local_assigns[\"#{key}\"]", b }
+ @assigns.each { |key, value| instance_variable_set "@#{key}", value }
+ xml = Builder::XmlMarkup.new(:indent => 2)
+
+ send(pick_rendering_method(template_extension), template, binding)
+ end
+
+ def pick_template_extension(template_path)#:nodoc:
+ if erb_template_exists?(template_path)
+ "rhtml"
+ elsif builder_template_exists?(template_path)
+ "rxml"
+ else
+ raise ActionViewError, "No rhtml or rxml template found for #{template_path}"
+ end
+ end
+
+ def pick_rendering_method(template_extension)#:nodoc:
+ (template_extension == "rxml" ? "rxml" : "rhtml") + "_render"
+ end
+
+ def erb_template_exists?(template_path)#:nodoc:
+ template_exists?(template_path, "rhtml")
+ end
+
+ def builder_template_exists?(template_path)#:nodoc:
+ template_exists?(template_path, "rxml")
+ end
+
+ def file_exists?(template_path)#:nodoc:
+ erb_template_exists?(template_path) || builder_template_exists?(template_path)
+ end
+
+ # Returns true is the file may be rendered implicitly.
+ def file_public?(template_path)#:nodoc:
+ template_path.split("/").last[0,1] != "_"
+ end
+
+ private
+ def full_template_path(template_path, extension)
+ "#{@base_path}/#{template_path}.#{extension}"
+ end
+
+ def template_exists?(template_path, extension)
+ FileTest.exists?(full_template_path(template_path, extension))
+ end
+
+ def read_template_file(template_path)
+ unless cache_template_loading && @@loaded_templates[template_path]
+ @@loaded_templates[template_path] = File.read(template_path)
+ end
+
+ @@loaded_templates[template_path]
+ end
+
+ def rhtml_render(template, binding)
+ @@compiled_erb_templates[template] ||= ERB.new(template)
+ @@compiled_erb_templates[template].result(binding)
+ end
+
+ def rxml_render(template, binding)
+ @controller.headers["Content-Type"] ||= 'text/xml'
+ eval(template, binding)
+ end
+ end
+end
+
+require 'action_view/template_error'
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
new file mode 100644
index 0000000000..b02b807fe1
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -0,0 +1,171 @@
+require 'cgi'
+require File.dirname(__FILE__) + '/form_helper'
+
+module ActionView
+ class Base
+ @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
+ cattr_accessor :field_error_proc
+ end
+
+ module Helpers
+ # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form
+ # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
+ # is a great of making the record quickly available for editing, but likely to prove lacklusters for a complicated real-world form.
+ # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
+ module ActiveRecordHelper
+ # Returns a default input tag for the type of object returned by the method. Example
+ # (title is a VARCHAR column and holds "Hello World"):
+ # input("post", "title") =>
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ def input(record_name, method)
+ InstanceTag.new(record_name, method, self).to_tag
+ end
+
+ # Returns an entire form with input tags and everything for a specified Active Record object. Example
+ # (post is a new record that has a title using VARCHAR and a body using TEXT):
+ # form("post") =>
+ # <form action='create' method='POST'>
+ # <p>
+ # <label for="post_title">Title</label><br />
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ # </p>
+ # <p>
+ # <label for="post_body">Body</label><br />
+ # <textarea cols="40" id="post_body" name="post[body]" rows="20" wrap="virtual">
+ # Back to the hill and over it again!
+ # </textarea>
+ # </p>
+ # <input type='submit' value='Create' />
+ # </form>
+ #
+ # It's possible to specialize the form builder by using a different action name and by supplying another
+ # block renderer. Example (entry is a new record that has a message attribute using VARCHAR):
+ #
+ # form("entry", :action => "sign", :input_block =>
+ # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
+ #
+ # <form action='sign' method='POST'>
+ # Message:
+ # <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br />
+ # <input type='submit' value='Sign' />
+ # </form>
+ def form(record_name, options = {})
+ record = instance_eval("@#{record_name}")
+ action = options[:action] || (record.new_record? ? "create" : "update")
+ id_field = record.new_record? ? "" : InstanceTag.new(record_name, "id", self).to_input_field_tag("hidden")
+
+ "<form action='#{action}' method='POST'>" +
+ id_field + all_input_tags(record, record_name, options) +
+ "<input type='submit' value='#{action.gsub(/[^A-Za-z]/, "").capitalize}' />" +
+ "</form>"
+ end
+
+ # Returns a string containing the error message attached to the +method+ on the +object+, if one exists.
+ # This error message is wrapped in a DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+
+ # to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message
+ # "can't be empty" on the title attribute):
+ #
+ # <%= error_message_on "post", "title" %> =>
+ # <div class="formError">can't be empty</div>
+ #
+ # <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
+ # <div class="inputError">Title simply can't be empty (or it won't work)</div>
+ def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
+ if errors = instance_eval("@#{object}").errors.on(method)
+ "<div class=\"#{css_class}\">#{prepend_text + (errors.is_a?(Array) ? errors.first : errors) + append_text}</div>"
+ end
+ end
+
+ def error_messages_for(object_name)
+ object = instance_eval("@#{object_name}")
+ unless object.errors.empty?
+ "<div id=\"errorExplanation\">" +
+ "<h2>#{object.errors.count} error#{"s" unless object.errors.count == 1} prohibited this #{object_name.gsub("_", " ")} from being saved</h2>" +
+ "<p>There were problems with the following fields (marked in red below):</p>" +
+ "<ul>#{object.errors.full_messages.collect { |msg| "<li>#{msg}</li>"}}</ul>" +
+ "</div>"
+ end
+ end
+
+ private
+ def all_input_tags(record, record_name, options)
+ input_block = options[:input_block] || default_input_block
+ record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
+ end
+
+ def default_input_block
+ Proc.new { |record, column| "<p><label for=\"#{record}_#{column.name}\">#{column.human_name}</label><br />#{input(record, column.name)}</p>" }
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ def to_tag(options = {})
+ case column_type
+ when :string
+ field_type = @method_name.include?("password") ? "password" : "text"
+ to_input_field_tag(field_type, options)
+ when :text
+ to_text_area_tag(options)
+ when :integer, :float
+ to_input_field_tag("text", options)
+ when :date
+ to_date_select_tag(options)
+ when :datetime
+ to_datetime_select_tag(options)
+ when :boolean
+ to_boolean_select_tag(options)
+ end
+ end
+
+ alias_method :tag_without_error_wrapping, :tag
+
+ def tag(name, options)
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
+ else
+ tag_without_error_wrapping(name, options)
+ end
+ end
+
+ alias_method :content_tag_without_error_wrapping, :content_tag
+
+ def content_tag(name, value, options)
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
+ else
+ content_tag_without_error_wrapping(name, value, options)
+ end
+ end
+
+ alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
+ def to_date_select_tag(options = {})
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
+ else
+ to_date_select_tag_without_error_wrapping(options)
+ end
+ end
+
+ alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
+ def to_datetime_select_tag(options = {})
+ if object.respond_to?("errors") && object.errors.respond_to?("on")
+ error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
+ else
+ to_datetime_select_tag_without_error_wrapping(options)
+ end
+ end
+
+ def error_wrapping(html_tag, has_error)
+ has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
+ end
+
+ def error_message
+ object.errors.on(@method_name)
+ end
+
+ def column_type
+ object.send("column_for_attribute", @method_name).type
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
new file mode 100755
index 0000000000..5526c3eef4
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -0,0 +1,230 @@
+require "date"
+
+module ActionView
+ module Helpers
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
+ # share a number of common options that are as follows:
+ #
+ # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
+ # birthday[month] instead of date[month] if passed to the select_month method.
+ # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
+ # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
+ # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
+ module DateHelper
+ DEFAULT_PREFIX = "date" unless const_defined?("DEFAULT_PREFIX")
+
+ # Reports the approximate distance in time between to Time objects. For example, if the distance is 47 minutes, it'll return
+ # "about 1 hour". See the source for the complete wording list.
+ def distance_of_time_in_words(from_time, to_time)
+ distance_in_minutes = ((to_time - from_time) / 60).round
+
+ case distance_in_minutes
+ when 0 then "less than a minute"
+ when 1 then "1 minute"
+ when 2..45 then "#{distance_in_minutes} minutes"
+ when 46..90 then "about 1 hour"
+ when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
+ when 1441..2880 then "1 day"
+ else "#{(distance_in_minutes / 1440).round} days"
+ end
+ end
+
+ # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
+ def distance_of_time_in_words_to_now(from_time)
+ distance_of_time_in_words(from_time, Time.now)
+ end
+
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
+ # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
+ # which both accepts all the keys that each of the individual select builders does (like :use_month_numbers for select_month) and a range
+ # of discard options. The discard options are <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll drop the respective
+ # select. Discarding the month select will also automatically discard the day select.
+ #
+ # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. Additionally, you can get the
+ # month select before the year by setting :month_before_year to true in the options. This is especially useful for credit card forms.
+ # Examples:
+ #
+ # date_select("post", "written_on")
+ # date_select("post", "written_on", :start_year => 1995)
+ # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
+ # :discard_day => true, :include_blank => true)
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ def date_select(object, method, options = {})
+ InstanceTag.new(object, method, self).to_date_select_tag(options)
+ end
+
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
+ #
+ # datetime_select("post", "written_on")
+ # datetime_select("post", "written_on", :start_year => 1995)
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ def datetime_select(object, method, options = {})
+ InstanceTag.new(object, method, self).to_datetime_select_tag(options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
+ def select_date(date = Date.today, options = {})
+ select_year(date, options) + select_month(date, options) + select_day(date, options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) preselected the +datetime+.
+ def select_datetime(datetime = Time.now, options = {})
+ select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
+ select_hour(datetime, options) + select_minute(datetime, options)
+ end
+
+ # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
+ # The <tt>minute</tt> can also be substituted for a minute number.
+ def select_minute(datetime, options = {})
+ minute_options = []
+
+ 0.upto(59) do |minute|
+ minute_options << ((datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute ?
+ "<option selected=\"selected\">#{leading_zero_on_single_digits(minute)}</option>\n" :
+ "<option>#{leading_zero_on_single_digits(minute)}</option>\n"
+ )
+ end
+
+ select_html("minute", minute_options, options[:prefix], options[:include_blank], options[:discard_type])
+ end
+
+ # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
+ # The <tt>hour</tt> can also be substituted for a hour number.
+ def select_hour(datetime, options = {})
+ hour_options = []
+
+ 0.upto(23) do |hour|
+ hour_options << ((datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour ?
+ "<option selected=\"selected\">#{leading_zero_on_single_digits(hour)}</option>\n" :
+ "<option>#{leading_zero_on_single_digits(hour)}</option>\n"
+ )
+ end
+
+ select_html("hour", hour_options, options[:prefix], options[:include_blank], options[:discard_type])
+ end
+
+ # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
+ # The <tt>date</tt> can also be substituted for a hour number.
+ def select_day(date, options = {})
+ day_options = []
+
+ 1.upto(31) do |day|
+ day_options << ((date.kind_of?(Fixnum) ? date : date.day) == day ?
+ "<option selected=\"selected\">#{day}</option>\n" :
+ "<option>#{day}</option>\n"
+ )
+ end
+
+ select_html("day", day_options, options[:prefix], options[:include_blank], options[:discard_type])
+ end
+
+ # Returns a select tag with options for each of the months January through December with the current month selected.
+ # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
+ # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
+ # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
+ # set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
+ #
+ # select_month(Date.today) # Will use keys like "January", "March"
+ # select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
+ # select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
+ def select_month(date, options = {})
+ month_options = []
+
+ 1.upto(12) do |month_number|
+ month_name = if options[:use_month_numbers]
+ month_number
+ elsif options[:add_month_numbers]
+ month_number.to_s + " - " + Date::MONTHNAMES[month_number]
+ else
+ Date::MONTHNAMES[month_number]
+ end
+
+ month_options << ((date.kind_of?(Fixnum) ? date : date.month) == month_number ?
+ "<option value='#{month_number}' selected=\"selected\">#{month_name}</option>\n" :
+ "<option value='#{month_number}'>#{month_name}</option>\n"
+ )
+ end
+
+ select_html("month", month_options, options[:prefix], options[:include_blank], options[:discard_type])
+ end
+
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
+ # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. The <tt>date</tt> can also be substituted
+ # for a year given as a number. Example:
+ #
+ # select_year(Date.today, :start_year => 1992, :end_year => 2007)
+ def select_year(date, options = {})
+ year_options = []
+ unless date.kind_of?(Fixnum) then default_start_year, default_end_year = date.year - 5, date.year + 5 end
+
+ (options[:start_year] || default_start_year).upto(options[:end_year] || default_end_year) do |year|
+ year_options << ((date.kind_of?(Fixnum) ? date : date.year) == year ?
+ "<option selected=\"selected\">#{year}</option>\n" :
+ "<option>#{year}</option>\n"
+ )
+ end
+
+ select_html("year", year_options, options[:prefix], options[:include_blank], options[:discard_type])
+ end
+
+ private
+ def select_html(type, options, prefix = nil, include_blank = false, discard_type = false)
+ select_html = "<select name='#{prefix || DEFAULT_PREFIX}"
+ select_html << "[#{type}]" unless discard_type
+ select_html << "'>\n"
+ select_html << "<option></option>\n" if include_blank
+ select_html << options.to_s
+ select_html << "</select>\n"
+
+ return select_html
+ end
+
+ def leading_zero_on_single_digits(number)
+ number > 9 ? number : "0#{number}"
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include DateHelper
+
+ def to_date_select_tag(options = {})
+ defaults = { :discard_type => true }
+ options = defaults.merge(options)
+ options_with_prefix = Proc.new { |position| options.update({ :prefix => "#{@object_name}[#{@method_name}(#{position}i)]" }) }
+ date = options[:include_blank] ? (value || 0) : (value || Date.today)
+
+ date_select = ""
+
+ if options[:month_before_year]
+ date_select << select_month(date, options_with_prefix.call(2)) unless options[:discard_month]
+ date_select << select_year(date, options_with_prefix.call(1))
+ else
+ date_select << select_year(date, options_with_prefix.call(1))
+ date_select << select_month(date, options_with_prefix.call(2)) unless options[:discard_month]
+ end
+
+ date_select << select_day(date, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
+
+ return date_select
+ end
+
+ def to_datetime_select_tag(options = {})
+ defaults = { :discard_type => true }
+ options = defaults.merge(options)
+ options_with_prefix = Proc.new { |position| options.update({ :prefix => "#{@object_name}[#{@method_name}(#{position}i)]" }) }
+ datetime = options[:include_blank] ? (value || 0) : (value || Time.now)
+
+ datetime_select = select_year(datetime, options_with_prefix.call(1))
+ datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
+ datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
+ datetime_select << " &mdash; " + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
+ datetime_select << " : " + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
+
+ return datetime_select
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
new file mode 100644
index 0000000000..8baea6f450
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -0,0 +1,17 @@
+module ActionView
+ module Helpers
+ # Provides a set of methods for making it easier to locate problems.
+ module DebugHelper
+ # Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
+ def debug(object)
+ begin
+ Marshal::dump(object)
+ "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>"
+ rescue Object => e
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
+ "<code class='debug_dump'>#{h(object.inspect)}</code>"
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
new file mode 100644
index 0000000000..389aa302a9
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -0,0 +1,182 @@
+require 'cgi'
+require File.dirname(__FILE__) + '/date_helper'
+require File.dirname(__FILE__) + '/tag_helper'
+
+module ActionView
+ module Helpers
+ # Provides a set of methods for working with forms and especially forms related to objects assigned to the template.
+ # The following is an example of a complete form for a person object that works for both creates and updates built
+ # with all the form helpers. The <tt>@person</tt> object was assigned by an action on the controller:
+ # <form action="save_person" method="post">
+ # Name:
+ # <%= text_field "person", "name", "size" => 20 %>
+ #
+ # Password:
+ # <%= password_field "person", "password", "maxsize" => 20 %>
+ #
+ # Single?:
+ # <%= check_box "person", "single" %>
+ #
+ # Description:
+ # <%= text_area "person", "description", "cols" => 20 %>
+ #
+ # <input type="submit" value="Save">
+ # </form>
+ #
+ # ...is compiled to:
+ #
+ # <form action="save_person" method="post">
+ # Name:
+ # <input type="text" id="person_name" name="person[name]"
+ # size="20" value="<%= @person.name %>" />
+ #
+ # Password:
+ # <input type="password" id="person_password" name="person[password]"
+ # size="20" maxsize="20" value="<%= @person.password %>" />
+ #
+ # Single?:
+ # <input type="checkbox" id="person_single" name="person[single] value="1" />
+ #
+ # Description:
+ # <textarea cols="20" rows="40" id="person_description" name="person[description]">
+ # <%= @person.description %>
+ # </textarea>
+ #
+ # <input type="submit" value="Save">
+ # </form>
+ #
+ # There's also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
+ # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
+ module FormHelper
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # Examples (call, result):
+ # text_field("post", "title", "size" => 20)
+ # <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
+ def text_field(object, method, options = {})
+ InstanceTag.new(object, method, self).to_input_field_tag("text", options)
+ end
+
+ # Works just like text_field, but returns a input tag of the "password" type instead.
+ def password_field(object, method, options = {})
+ InstanceTag.new(object, method, self).to_input_field_tag("password", options)
+ end
+
+ # Works just like text_field, but returns a input tag of the "hidden" type instead.
+ def hidden_field(object, method, options = {})
+ InstanceTag.new(object, method, self).to_input_field_tag("hidden", options)
+ end
+
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # Example (call, result):
+ # text_area("post", "body", "cols" => 20, "rows" => 40)
+ # <textarea cols="20" rows="40" id="post_body" name="post[body]">
+ # #{@post.body}
+ # </textarea>
+ def text_area(object, method, options = {})
+ InstanceTag.new(object, method, self).to_text_area_tag(options)
+ end
+
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
+ # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
+ # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
+ # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything.
+ # We work around this problem by adding a hidden value with the same name as the checkbox.
+ #
+ # Example (call, result). Imagine that @post.validated? returns 1:
+ # check_box("post", "validated")
+ # <input type="checkbox" id="post_validate" name="post[validated] value="1" checked="checked" /><input name="post[validated]" type="hidden" value="0" />
+ #
+ # Example (call, result). Imagine that @puppy.gooddog returns no:
+ # check_box("puppy", "gooddog", {}, "yes", "no")
+ # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog] value="yes" /><input name="puppy[gooddog]" type="hidden" value="no" />
+ def check_box(object, method, options = {}, checked_value = "1", unchecked_value = "0")
+ InstanceTag.new(object, method, self).to_check_box_tag(options, checked_value, unchecked_value)
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include Helpers::TagHelper
+
+ attr_reader :method_name, :object_name
+
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 } unless const_defined?("DEFAULT_FIELD_OPTIONS")
+ DEFAULT_TEXT_AREA_OPTIONS = { "wrap" => "virtual", "cols" => 40, "rows" => 20 } unless const_defined?("DEFAULT_TEXT_AREA_OPTIONS")
+
+ def initialize(object_name, method_name, template_object, local_binding = nil)
+ @object_name, @method_name = object_name, method_name
+ @template_object, @local_binding = template_object, local_binding
+ end
+
+ def to_input_field_tag(field_type, options = {})
+ html_options = DEFAULT_FIELD_OPTIONS.merge(options)
+ html_options.merge!({ "size" => options["maxlength"]}) if options["maxlength"] && !options["size"]
+ html_options.merge!({ "type" => field_type, "value" => value.to_s })
+ add_default_name_and_id(html_options)
+ tag("input", html_options)
+ end
+
+ def to_text_area_tag(options = {})
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options)
+ add_default_name_and_id(options)
+ content_tag("textarea", html_escape(value), options)
+ end
+
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
+ options.merge!({"checked" => "checked"}) if !value.nil? && ((value.is_a?(TrueClass) || value.is_a?(FalseClass)) ? value : value.to_i > 0)
+ options.merge!({ "type" => "checkbox", "value" => checked_value })
+ add_default_name_and_id(options)
+ tag("input", options) << tag("input", ({ "name" => options['name'], "type" => "hidden", "value" => unchecked_value }))
+ end
+
+ def to_date_tag()
+ defaults = { "discard_type" => true }
+ date = value || Date.today
+ options = Proc.new { |position| defaults.update({ :prefix => "#{@object_name}[#{@method_name}(#{position}i)]" }) }
+
+ html_day_select(date, options.call(3)) +
+ html_month_select(date, options.call(2)) +
+ html_year_select(date, options.call(1))
+ end
+
+ def to_boolean_select_tag(options = {})
+ add_default_name_and_id(options)
+ tag_text = "<select"
+ tag_text << tag_options(options)
+ tag_text << "><option value=\"false\""
+ tag_text << " selected" if value == false
+ tag_text << ">False</option><option value=\"true\""
+ tag_text << " selected" if value
+ tag_text << ">True</option></select>"
+ end
+
+ def object
+ @template_object.instance_variable_get "@#{@object_name}"
+ end
+
+ def value
+ object.send(@method_name) unless object.nil?
+ end
+
+ private
+ def add_default_name_and_id(options)
+ options['name'] = tag_name unless options.has_key? "name"
+ options['id'] = tag_id unless options.has_key? "id"
+ end
+
+ def tag_name
+ "#{@object_name}[#{@method_name}]"
+ end
+
+ def tag_id
+ "#{@object_name}_#{@method_name}"
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
new file mode 100644
index 0000000000..ca3798ede6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -0,0 +1,212 @@
+require 'cgi'
+require 'erb'
+require File.dirname(__FILE__) + '/form_helper'
+
+module ActionView
+ module Helpers
+ # Provides a number of methods for turning different kinds of containers into a set of option tags. Neither of the methods provide
+ # the actual select tag, so you'll need to construct that in HTML manually.
+ module FormOptionsHelper
+ include ERB::Util
+
+ def select(object, method, choices, options = {}, html_options = {})
+ InstanceTag.new(object, method, self).to_select_tag(choices, options, html_options)
+ end
+
+ def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
+ InstanceTag.new(object, method, self).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ end
+
+ def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self).to_country_select_tag(priority_countries, options, html_options)
+ end
+
+ # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
+ # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
+ # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
+ # may also be an array of values to be selected when using a multiple select.
+ #
+ # Examples (call, result):
+ # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
+ # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
+ #
+ # options_for_select([ "VISA", "Mastercard" ], "Mastercard")
+ # <option>VISA</option>\n<option selected="selected">Mastercard</option>
+ #
+ # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
+ # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
+ #
+ # options_for_select([ "VISA", "Mastercard", "Discover" ], ["VISA", "Discover"])
+ # <option selected="selected">VISA</option>\n<option>Mastercard</option>\n<option selected="selected">Discover</option>
+ def options_for_select(container, selected = nil)
+ container = container.to_a if Hash === container
+
+ options_for_select = container.inject([]) do |options, element|
+ if element.respond_to?(:first) && element.respond_to?(:last)
+ is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
+ if is_selected
+ options << "<option value=\"#{html_escape(element.last.to_s)}\" selected=\"selected\">#{html_escape(element.first.to_s)}</option>"
+ else
+ options << "<option value=\"#{html_escape(element.last.to_s)}\">#{html_escape(element.first.to_s)}</option>"
+ end
+ else
+ is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) )
+ options << ((is_selected) ? "<option selected=\"selected\">#{html_escape(element.to_s)}</option>" : "<option>#{html_escape(element.to_s)}</option>")
+ end
+ end
+
+ options_for_select.join("\n")
+ end
+
+ # Returns a string of option tags that has been compiled by iterating over the +collection+ and assigning the
+ # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
+ # If +selected_value+ is specified, the element returning a match on +value_method+ will get the selected option tag.
+ #
+ # Example (call, result). Imagine a loop iterating over each +person+ in <tt>@project.people</tt> to generate a input tag:
+ # options_from_collection_for_select(@project.people, "id", "name")
+ # <option value="#{person.id}">#{person.name}</option>
+ def options_from_collection_for_select(collection, value_method, text_method, selected_value = nil)
+ options_for_select(
+ collection.inject([]) { |options, object| options << [ object.send(text_method), object.send(value_method) ] },
+ selected_value
+ )
+ end
+
+ # Returns a string of option tags, like options_from_collection_for_select, but surrounds them by <optgroup> tags.
+ #
+ # An array of group objects are passed. Each group should return an array of options when calling group_method
+ # Each group should should return its name when calling group_label_method.
+ #
+ # html_option_groups_from_collection(@continents, "countries", "contient_name", "country_id", "country_name", @selected_country.id)
+ #
+ # Could become:
+ # <optgroup label="Africa">
+ # <select>Egypt</select>
+ # <select>Rwanda</select>
+ # ...
+ # </optgroup>
+ # <optgroup label="Asia">
+ # <select>China</select>
+ # <select>India</select>
+ # <select>Japan</select>
+ # ...
+ # </optgroup>
+ #
+ # with objects of the following classes:
+ # class Continent
+ # def initialize(p_name, p_countries) @continent_name = p_name; @countries = p_countries; end
+ # def continent_name() @continent_name; end
+ # def countries() @countries; end
+ # end
+ # class Country
+ # def initialize(id, name) @id = id; @name = name end
+ # def country_id() @id; end
+ # def country_name() @name; end
+ # end
+ def option_groups_from_collection_for_select(collection, group_method, group_label_method,
+ option_key_method, option_value_method, selected_key = nil)
+ collection.inject("") do |options_for_select, group|
+ group_label_string = eval("group.#{group_label_method}")
+ options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
+ options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
+ options_for_select += '</optgroup>'
+ end
+ end
+
+ # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
+ # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
+ # that they will be listed above the rest of the (long) list.
+ def country_options_for_select(selected = nil, priority_countries = nil)
+ country_options = ""
+
+ if priority_countries
+ country_options += options_for_select(priority_countries, selected)
+ country_options += "<option>-------------</option>\n"
+ end
+
+ if priority_countries && priority_countries.include?(selected)
+ country_options += options_for_select(COUNTRIES - priority_countries, selected)
+ else
+ country_options += options_for_select(COUNTRIES, selected)
+ end
+
+ return country_options
+ end
+
+
+ private
+ # All the countries included in the country_options output.
+ COUNTRIES = [ "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla",
+ "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia",
+ "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
+ "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina",
+ "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory",
+ "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic",
+ "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia",
+ "Comoros", "Congo", "Congo, the Democratic Republic of the", "Cook Islands",
+ "Costa Rica", "Cote d'Ivoire", "Croatia", "Cyprus", "Czech Republic", "Denmark",
+ "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt",
+ "El Salvador", "England", "Equatorial Guinea", "Eritrea", "Espana", "Estonia",
+ "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France",
+ "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia",
+ "Georgia", "Germany", "Ghana", "Gibraltar", "Great Britain", "Greece", "Greenland",
+ "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana",
+ "Haiti", "Heard and Mc Donald Islands", "Honduras", "Hong Kong", "Hungary", "Iceland",
+ "India", "Indonesia", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan",
+ "Kazakhstan", "Kenya", "Kiribati", "Korea, Republic of", "Korea (South)", "Kuwait",
+ "Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho",
+ "Liberia", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+ "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico",
+ "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia",
+ "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal",
+ "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua",
+ "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Ireland",
+ "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama",
+ "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland",
+ "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda",
+ "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines",
+ "Samoa (Independent)", "San Marino", "Sao Tome and Principe", "Saudi Arabia",
+ "Scotland", "Senegal", "Seychelles", "Sierra Leone", "Singapore", "Slovakia",
+ "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Sri Lanka",
+ "St. Helena", "St. Pierre and Miquelon", "Suriname", "Svalbard and Jan Mayen Islands",
+ "Swaziland", "Sweden", "Switzerland", "Taiwan", "Tajikistan", "Tanzania", "Thailand",
+ "Togo", "Tokelau", "Tonga", "Trinidad", "Trinidad and Tobago", "Tunisia", "Turkey",
+ "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine",
+ "United Arab Emirates", "United Kingdom", "United States",
+ "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu",
+ "Vatican City State (Holy See)", "Venezuela", "Viet Nam", "Virgin Islands (British)",
+ "Virgin Islands (U.S.)", "Wales", "Wallis and Futuna Islands", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe" ] unless const_defined?("COUNTRIES")
+ end
+
+ class InstanceTag #:nodoc:
+ include FormOptionsHelper
+
+ def to_select_tag(choices, options, html_options)
+ add_default_name_and_id(html_options)
+ content_tag("select", add_blank_option(options_for_select(choices, value), options[:include_blank]), html_options)
+ end
+
+ def to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ add_default_name_and_id(html_options)
+ content_tag(
+ "select", add_blank_option(options_from_collection_for_select(collection, value_method, text_method, value), options[:include_blank]), html_options
+ )
+ end
+
+ def to_country_select_tag(priority_countries, options, html_options)
+ add_default_name_and_id(html_options)
+ content_tag("select", add_blank_option(country_options_for_select(value, priority_countries), options[:include_blank]), html_options)
+ end
+
+ private
+ def add_blank_option(option_tags, add_blank)
+ add_blank ? "<option></option>\n" + option_tags : option_tags
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
new file mode 100644
index 0000000000..90084c7a8d
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -0,0 +1,59 @@
+require 'cgi'
+
+module ActionView
+ module Helpers
+ # This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
+ module TagHelper
+ include ERB::Util
+
+ # Examples:
+ # * tag("br") => <br />
+ # * tag("input", { "type" => "text"}) => <input type="text" />
+ def tag(name, options = {}, open = false)
+ "<#{name + tag_options(options)}" + (open ? ">" : " />")
+ end
+
+ # Examples:
+ # * content_tag("p", "Hello world!") => <p>Hello world!</p>
+ # * content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") =>
+ # <div class="strong"><p>Hello world!</p></div>
+ def content_tag(name, content, options = {})
+ "<#{name + tag_options(options)}>#{content}</#{name}>"
+ end
+
+ # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
+ # ActionController::Base#url_for.
+ def form_tag(url_for_options, options = {}, *parameters_for_url)
+ html_options = { "method" => "POST" }.merge(options)
+
+ if html_options[:multipart]
+ html_options["enctype"] = "multipart/form-data"
+ html_options.delete(:multipart)
+ end
+
+ html_options["action"] = url_for(url_for_options, *parameters_for_url)
+
+ tag("form", html_options, true)
+ end
+
+ alias_method :start_form_tag, :form_tag
+
+ # Outputs "</form>"
+ def end_form_tag
+ "</form>"
+ end
+
+
+ private
+ def tag_options(options)
+ if options.empty?
+ ""
+ else
+ " " + options.collect { |pair|
+ "#{pair.first}=\"#{html_escape(pair.last)}\""
+ }.sort.join(" ")
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
new file mode 100644
index 0000000000..7e05e468b8
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -0,0 +1,111 @@
+module ActionView
+ module Helpers #:nodoc:
+ # Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
+ # templates. In the example below we iterate over a collection of posts provided to the template and prints each title
+ # after making sure it doesn't run longer than 20 characters:
+ # <% for post in @posts %>
+ # Title: <%= truncate(post.title, 20) %>
+ # <% end %>
+ module TextHelper
+ # The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
+ # If you absolutely must use a method-based output, you can use concat. It's use like this <% concat "hello", binding %>. Notice that
+ # it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
+ def concat(string, binding)
+ eval("_erbout", binding).concat(string)
+ end
+
+ # Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+
+ # if the +text+ is longer than +length+.
+ def truncate(text, length = 30, truncate_string = "...")
+ if text.nil? then return end
+ if text.length > length then text[0..(length - 3)] + truncate_string else text end
+ end
+
+ # Highlights the +phrase+ where it is found in the +text+ by surrounding it like
+ # <strong class="highlight">I'm a highlight phrase</strong>. The highlighter can be specialized by
+ # passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
+ # N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
+ def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
+ if text.nil? || phrase.nil? then return end
+ text.gsub(/(#{escape_regexp(phrase)})/i, highlighter) unless text.nil?
+ end
+
+ # Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
+ # by +radius+. If the phrase isn't found, nil is returned. Ex:
+ # excerpt("hello my world", "my", 3) => "...lo my wo..."
+ def excerpt(text, phrase, radius = 100, excerpt_string = "...")
+ if text.nil? || phrase.nil? then return end
+ phrase = escape_regexp(phrase)
+
+ if found_pos = text =~ /(#{phrase})/i
+ start_pos = [ found_pos - radius, 0 ].max
+ end_pos = [ found_pos + phrase.length + radius, text.length ].min
+
+ prefix = start_pos > 0 ? excerpt_string : ""
+ postfix = end_pos < text.length ? excerpt_string : ""
+
+ prefix + text[start_pos..end_pos].strip + postfix
+ else
+ nil
+ end
+ end
+
+ # Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules.
+ def pluralize(count, singular, plural = nil)
+ "#{count} " + if count == 1
+ singular
+ elsif plural
+ plural
+ elsif Object.const_defined?("Inflector")
+ Inflector.pluralize(singular)
+ else
+ singular + "s"
+ end
+ end
+
+ begin
+ require "redcloth"
+
+ # Returns the text with all the Textile codes turned into HTML-tags.
+ # <i>This method is only available if RedCloth can be required</i>.
+ def textilize(text)
+ RedCloth.new(text).to_html
+ end
+
+ # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
+ # <i>This method is only available if RedCloth can be required</i>.
+ def textilize_without_paragraph(text)
+ textiled = textilize(text)
+ if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
+ if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
+ return textiled
+ end
+ rescue LoadError
+ # We can't really help what's not there
+ end
+
+ begin
+ require "bluecloth"
+
+ # Returns the text with all the Markdown codes turned into HTML-tags.
+ # <i>This method is only available if BlueCloth can be required</i>.
+ def markdown(text)
+ BlueCloth.new(text).to_html
+ end
+ rescue LoadError
+ # We can't really help what's not there
+ end
+
+ # Turns all links into words, like "<a href="something">else</a>" to "else".
+ def strip_links(text)
+ text.gsub(/<a.*>(.*)<\/a>/m, '\1')
+ end
+
+ private
+ # Returns a version of the text that's safe to use in a regular expression without triggering engine features.
+ def escape_regexp(text)
+ text.gsub(/([\\|?+*\/\)\(])/) { |m| "\\#{$1}" }
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
new file mode 100644
index 0000000000..feda33d7c1
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -0,0 +1,78 @@
+module ActionView
+ module Helpers
+ # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
+ # you can use the same format for links in the views that you do in the controller. The different methods are even named
+ # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
+ # redirection in redirect_to.
+ module UrlHelper
+ # Returns the URL for the set of +options+ provided. See the valid options in link:classes/ActionController/Base.html#M000021
+ def url_for(options = {}, *parameters_for_method_reference)
+ if Hash === options then options = { :only_path => true }.merge(options) end
+ @controller.send(:url_for, options, *parameters_for_method_reference)
+ end
+
+ # Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
+ # link:classes/ActionController/Base.html#M000021. It's also possible to pass a string instead of an options hash to
+ # get a link tag that just points without consideration. The html_options have a special feature for creating javascript
+ # confirm alerts where if you pass :confirm => 'Are you sure?', the link will be guarded with a JS popup asking that question.
+ # If the user accepts, the link is processed, otherwise not.
+ def link_to(name, options = {}, html_options = {}, *parameters_for_method_reference)
+ convert_confirm_option_to_javascript!(html_options) unless html_options.nil?
+ if options.is_a?(String)
+ content_tag "a", name, (html_options || {}).merge({ "href" => options })
+ else
+ content_tag("a", name, (html_options || {}).merge({ "href" => url_for(options, *parameters_for_method_reference) }))
+ end
+ end
+
+ # Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
+ # controller, action, and id are the same as the link's, in which case only the name is returned (or the
+ # given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
+ # to the page currently being viewed.
+ def link_to_unless_current(name, options = {}, html_options = {}, *parameters_for_method_reference)
+ assume_current_url_options!(options)
+
+ if destination_equal_to_current(options)
+ block_given? ?
+ yield(name, options, html_options, *parameters_for_method_reference) :
+ html_escape(name)
+ else
+ link_to name, options, html_options, *parameters_for_method_reference
+ end
+ end
+
+ # Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
+ # link unless +name+ is specified. Additional HTML options, such as class or id, can be passed in the <tt>html_options</tt> hash.
+ def mail_to(email_address, name = nil, html_options = {})
+ content_tag "a", name || email_address, html_options.merge({ "href" => "mailto:#{email_address}" })
+ end
+
+ private
+ def destination_equal_to_current(options)
+ params_without_location = @params.reject { |key, value| %w( controller action id ).include?(key) }
+
+ options[:action] == @params['action'] &&
+ options[:id] == @params['id'] &&
+ options[:controller] == @params['controller'] &&
+ (options.has_key?(:params) ? params_without_location == options[:params] : true)
+ end
+
+ def assume_current_url_options!(options)
+ if options[:controller].nil?
+ options[:controller] = @params['controller']
+ if options[:action].nil?
+ options[:action] = @params['action']
+ if options[:id].nil? then options[:id] ||= @params['id'] end
+ end
+ end
+ end
+
+ def convert_confirm_option_to_javascript!(html_options)
+ if html_options.include?(:confirm)
+ html_options["onclick"] = "return confirm('#{html_options[:confirm]}');"
+ html_options.delete(:confirm)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
new file mode 100644
index 0000000000..96bde4c6d3
--- /dev/null
+++ b/actionpack/lib/action_view/partials.rb
@@ -0,0 +1,64 @@
+module ActionView
+ # There's also a convenience method for rendering sub templates within the current controller that depends on a single object
+ # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being
+ # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own. In the template for
+ # Advertiser#buy, we could have:
+ #
+ # <% for ad in @advertisements %>
+ # <%= render_partial "ad", ad %>
+ # <% end %>
+ #
+ # This would render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display.
+ #
+ # == Rendering a collection of partials
+ #
+ # The example of partial use describes a familar pattern where a template needs to iterate over an array and render a sub
+ # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders
+ # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten
+ # with a single line:
+ #
+ # <%= render_collection_of_partials "ad", @advertisements %>
+ #
+ # This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
+ # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
+ # example above, the template would be fed +ad_counter+.
+ #
+ # == Rendering shared partials
+ #
+ # Two controllers can share a set of partials and render them like this:
+ #
+ # <%= render_partial "advertisement/ad", ad %>
+ #
+ # This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
+ module Partials
+ def render_partial(partial_path, object = nil, local_assigns = {})
+ path, partial_name = partial_pieces(partial_path)
+ object ||= controller.instance_variable_get("@#{partial_name}")
+ render("#{path}/_#{partial_name}", { partial_name => object }.merge(local_assigns))
+ end
+
+ def render_collection_of_partials(partial_name, collection, partial_spacer_template = nil)
+ collection_of_partials = Array.new
+ collection.each_with_index do |element, counter|
+ collection_of_partials.push(render_partial(partial_name, element, "#{partial_name.split("/").last}_counter" => counter))
+ end
+
+ return nil if collection_of_partials.empty?
+ if partial_spacer_template
+ spacer_path, spacer_name = partial_pieces(partial_spacer_template)
+ collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}"))
+ else
+ collection_of_partials
+ end
+ end
+
+ private
+ def partial_pieces(partial_path)
+ if partial_path.include?('/')
+ return File.dirname(partial_path), File.basename(partial_path)
+ else
+ return controller.send(:controller_name), partial_path
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb
new file mode 100644
index 0000000000..ab05b3303f
--- /dev/null
+++ b/actionpack/lib/action_view/template_error.rb
@@ -0,0 +1,84 @@
+module ActionView
+ # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a
+ # bunch of intimate details and uses it to report a very precise exception message.
+ class TemplateError < ActionViewError #:nodoc:
+ SOURCE_CODE_RADIUS = 3
+
+ attr_reader :original_exception
+
+ def initialize(base_path, file_name, assigns, source, original_exception)
+ @base_path, @file_name, @assigns, @source, @original_exception =
+ base_path, file_name, assigns, source, original_exception
+ end
+
+ def message
+ if original_exception.message.include?("(eval):")
+ original_exception.message.scan(/\(eval\):(?:[0-9]*):in `.*'(.*)/).first.first
+ else
+ original_exception.message
+ end
+ end
+
+ def sub_template_message
+ if @sub_templates
+ "Trace of template inclusion: " +
+ @sub_templates.collect { |template| strip_base_path(template) }.join(", ")
+ else
+ ""
+ end
+ end
+
+ def source_extract
+ source_code = IO.readlines(@file_name)
+
+ start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
+ end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min
+
+ line_counter = start_on_line
+ extract = source_code[start_on_line..end_on_line].collect do |line|
+ line_counter += 1
+ "#{line_counter}: " + line
+ end
+
+ extract.join
+ end
+
+ def sub_template_of(file_name)
+ @sub_templates ||= []
+ @sub_templates << file_name
+ end
+
+ def line_number
+ begin
+ @original_exception.backtrace.join.scan(/\((?:erb)\):([0-9]*)/).first.first.to_i
+ rescue
+ begin
+ original_exception.message.scan(/\((?:eval)\):([0-9]*)/).first.first.to_i
+ rescue
+ 1
+ end
+ end
+ end
+
+ def file_name
+ strip_base_path(@file_name)
+ end
+
+ def to_s
+ "\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" +
+ source_extract + "\n " +
+ clean_backtrace(original_exception).join("\n ") +
+ "\n\n"
+ end
+
+ private
+ def strip_base_path(file_name)
+ file_name.gsub(@base_path, "")
+ 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 \ No newline at end of file
diff --git a/actionpack/lib/action_view/vendor/builder.rb b/actionpack/lib/action_view/vendor/builder.rb
new file mode 100644
index 0000000000..9719277669
--- /dev/null
+++ b/actionpack/lib/action_view/vendor/builder.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+
+#--
+# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
+# All rights reserved.
+
+# Permission is granted for use, copying, modification, distribution,
+# and distribution of modified versions of this work as long as the
+# above copyright notice is included.
+#++
+
+require 'builder/xmlmarkup'
+require 'builder/xmlevents'
diff --git a/actionpack/lib/action_view/vendor/builder/blankslate.rb b/actionpack/lib/action_view/vendor/builder/blankslate.rb
new file mode 100644
index 0000000000..25307b0e56
--- /dev/null
+++ b/actionpack/lib/action_view/vendor/builder/blankslate.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/env ruby
+#--
+# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
+# All rights reserved.
+
+# Permission is granted for use, copying, modification, distribution,
+# and distribution of modified versions of this work as long as the
+# above copyright notice is included.
+#++
+
+module Builder #:nodoc:
+
+ # BlankSlate provides an abstract base class with no predefined
+ # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
+ # BlankSlate is useful as a base class when writing classes that
+ # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
+ class BlankSlate #:nodoc:
+ class << self
+ def hide(name)
+ undef_method name unless name =~ /^(__|instance_eval)/
+ end
+ end
+
+ instance_methods.each { |m| hide(m) }
+ end
+end
+
+# Since Ruby is very dynamic, methods added to the ancestors of
+# BlankSlate <em>after BlankSlate is defined</em> will show up in the
+# list of available BlankSlate methods. We handle this by defining a hook in the Object and Kernel classes that will hide any defined
+module Kernel #:nodoc:
+ class << self
+ alias_method :blank_slate_method_added, :method_added
+ def method_added(name)
+ blank_slate_method_added(name)
+ return if self != Kernel
+ Builder::BlankSlate.hide(name)
+ end
+ end
+end
+
+class Object #:nodoc:
+ class << self
+ alias_method :blank_slate_method_added, :method_added
+ def method_added(name)
+ blank_slate_method_added(name)
+ return if self != Object
+ Builder::BlankSlate.hide(name)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/vendor/builder/xmlbase.rb b/actionpack/lib/action_view/vendor/builder/xmlbase.rb
new file mode 100644
index 0000000000..d065d6fae1
--- /dev/null
+++ b/actionpack/lib/action_view/vendor/builder/xmlbase.rb
@@ -0,0 +1,143 @@
+#!/usr/bin/env ruby
+
+require 'builder/blankslate'
+
+module Builder #:nodoc:
+
+ # Generic error for builder
+ class IllegalBlockError < RuntimeError #:nodoc:
+ end
+
+ # XmlBase is a base class for building XML builders. See
+ # Builder::XmlMarkup and Builder::XmlEvents for examples.
+ class XmlBase < BlankSlate #:nodoc:
+
+ # Create an XML markup builder.
+ #
+ # out:: Object receiving the markup.1 +out+ must respond to
+ # <tt><<</tt>.
+ # indent:: Number of spaces used for indentation (0 implies no
+ # indentation and no line breaks).
+ # initial:: Level of initial indentation.
+ #
+ def initialize(indent=0, initial=0)
+ @indent = indent
+ @level = initial
+ end
+
+ # Create a tag named +sym+. Other than the first argument which
+ # is the tag name, the arguements are the same as the tags
+ # implemented via <tt>method_missing</tt>.
+ def tag!(sym, *args, &block)
+ self.__send__(sym, *args, &block)
+ end
+
+ # Create XML markup based on the name of the method. This method
+ # is never invoked directly, but is called for each markup method
+ # in the markup block.
+ def method_missing(sym, *args, &block)
+ text = nil
+ attrs = nil
+ sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol)
+ args.each do |arg|
+ case arg
+ when Hash
+ attrs ||= {}
+ attrs.merge!(arg)
+ else
+ text ||= ''
+ text << arg.to_s
+ end
+ end
+ if block
+ unless text.nil?
+ raise ArgumentError, "XmlMarkup cannot mix a text argument with a block"
+ end
+ _capture_outer_self(block) if @self.nil?
+ _indent
+ _start_tag(sym, attrs)
+ _newline
+ _nested_structures(block)
+ _indent
+ _end_tag(sym)
+ _newline
+ elsif text.nil?
+ _indent
+ _start_tag(sym, attrs, true)
+ _newline
+ else
+ _indent
+ _start_tag(sym, attrs)
+ text! text
+ _end_tag(sym)
+ _newline
+ end
+ @target
+ end
+
+ # Append text to the output target. Escape any markup. May be
+ # used within the markup brakets as:
+ #
+ # builder.p { br; text! "HI" } #=> <p><br/>HI</p>
+ def text!(text)
+ _text(_escape(text))
+ end
+
+ # Append text to the output target without escaping any markup.
+ # May be used within the markup brakets as:
+ #
+ # builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
+ #
+ # This is useful when using non-builder enabled software that
+ # generates strings. Just insert the string directly into the
+ # builder without changing the inserted markup.
+ #
+ # It is also useful for stacking builder objects. Builders only
+ # use <tt><<</tt> to append to the target, so by supporting this
+ # method/operation builders can use oother builders as their
+ # targets.
+ def <<(text)
+ _text(text)
+ end
+
+ # For some reason, nil? is sent to the XmlMarkup object. If nil?
+ # is not defined and method_missing is invoked, some strange kind
+ # of recursion happens. Since nil? won't ever be an XML tag, it
+ # is pretty safe to define it here. (Note: this is an example of
+ # cargo cult programming,
+ # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
+ def nil?
+ false
+ end
+
+ private
+
+ def _escape(text)
+ text.
+ gsub(%r{&}, '&amp;').
+ gsub(%r{<}, '&lt;').
+ gsub(%r{>}, '&gt;')
+ end
+
+ def _capture_outer_self(block)
+ @self = eval("self", block)
+ end
+
+ def _newline
+ return if @indent == 0
+ text! "\n"
+ end
+
+ def _indent
+ return if @indent == 0 || @level == 0
+ text!(" " * (@level * @indent))
+ end
+
+ def _nested_structures(block)
+ @level += 1
+ block.call(self)
+ ensure
+ @level -= 1
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/vendor/builder/xmlevents.rb b/actionpack/lib/action_view/vendor/builder/xmlevents.rb
new file mode 100644
index 0000000000..15dc7b6421
--- /dev/null
+++ b/actionpack/lib/action_view/vendor/builder/xmlevents.rb
@@ -0,0 +1,63 @@
+#!/usr/bin/env ruby
+
+#--
+# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
+# All rights reserved.
+
+# Permission is granted for use, copying, modification, distribution,
+# and distribution of modified versions of this work as long as the
+# above copyright notice is included.
+#++
+
+require 'builder/xmlmarkup'
+
+module Builder
+
+ # Create a series of SAX-like XML events (e.g. start_tag, end_tag)
+ # from the markup code. XmlEvent objects are used in a way similar
+ # to XmlMarkup objects, except that a series of events are generated
+ # and passed to a handler rather than generating character-based
+ # markup.
+ #
+ # Usage:
+ # xe = Builder::XmlEvents.new(hander)
+ # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler.
+ #
+ # Indentation may also be selected by providing value for the
+ # indentation size and initial indentation level.
+ #
+ # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
+ #
+ # == XML Event Handler
+ #
+ # The handler object must expect the following events.
+ #
+ # [<tt>start_tag(tag, attrs)</tt>]
+ # Announces that a new tag has been found. +tag+ is the name of
+ # the tag and +attrs+ is a hash of attributes for the tag.
+ #
+ # [<tt>end_tag(tag)</tt>]
+ # Announces that an end tag for +tag+ has been found.
+ #
+ # [<tt>text(text)</tt>]
+ # Announces that a string of characters (+text+) has been found.
+ # A series of characters may be broken up into more than one
+ # +text+ call, so the client cannot assume that a single
+ # callback contains all the text data.
+ #
+ class XmlEvents < XmlMarkup #:nodoc:
+ def text!(text)
+ @target.text(text)
+ end
+
+ def _start_tag(sym, attrs, end_too=false)
+ @target.start_tag(sym, attrs)
+ _end_tag(sym) if end_too
+ end
+
+ def _end_tag(sym)
+ @target.end_tag(sym)
+ end
+ end
+
+end
diff --git a/actionpack/lib/action_view/vendor/builder/xmlmarkup.rb b/actionpack/lib/action_view/vendor/builder/xmlmarkup.rb
new file mode 100644
index 0000000000..716ff52535
--- /dev/null
+++ b/actionpack/lib/action_view/vendor/builder/xmlmarkup.rb
@@ -0,0 +1,288 @@
+#!/usr/bin/env ruby
+#--
+# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
+# All rights reserved.
+
+# Permission is granted for use, copying, modification, distribution,
+# and distribution of modified versions of this work as long as the
+# above copyright notice is included.
+#++
+
+# Provide a flexible and easy to use Builder for creating XML markup.
+# See XmlBuilder for usage details.
+
+require 'builder/xmlbase'
+
+module Builder
+
+ # Create XML markup easily. All (well, almost all) methods sent to
+ # an XmlMarkup object will be translated to the equivalent XML
+ # markup. Any method with a block will be treated as an XML markup
+ # tag with nested markup in the block.
+ #
+ # Examples will demonstrate this easier than words. In the
+ # following, +xm+ is an +XmlMarkup+ object.
+ #
+ # xm.em("emphasized") # => <em>emphasized</em>
+ # xm.em { xmm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
+ # xm.a("A Link", "href"=>"http://onestepback.org")
+ # # => <a href="http://onestepback.org">A Link</a>
+ # xm.div { br } # => <div><br/></div>
+ # xm.target("name"=>"compile", "option"=>"fast")
+ # # => <target option="fast" name="compile"\>
+ # # NOTE: order of attributes is not specified.
+ #
+ # xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
+ # xm.html { # <html>
+ # xm.head { # <head>
+ # xm.title("History") # <title>History</title>
+ # } # </head>
+ # xm.body { # <body>
+ # xm.comment! "HI" # <!-- HI -->
+ # xm.h1("Header") # <h1>Header</h1>
+ # xm.p("paragraph") # <p>paragraph</p>
+ # } # </body>
+ # } # </html>
+ #
+ # == Notes:
+ #
+ # * The order that attributes are inserted in markup tags is
+ # undefined.
+ #
+ # * Sometimes you wish to insert text without enclosing tags. Use
+ # the <tt>text!</tt> method to accomplish this.
+ #
+ # Example:
+ #
+ # xm.div { # <div>
+ # xm.text! "line"; xm.br # line<br/>
+ # xm.text! "another line"; xmbr # another line<br/>
+ # } # </div>
+ #
+ # * The special XML characters <, >, and & are converted to &lt;,
+ # &gt; and &amp; automatically. Use the <tt><<</tt> operation to
+ # insert text without modification.
+ #
+ # * Sometimes tags use special characters not allowed in ruby
+ # identifiers. Use the <tt>tag!</tt> method to handle these
+ # cases.
+ #
+ # Example:
+ #
+ # xml.tag!("SOAP:Envelope") { ... }
+ #
+ # will produce ...
+ #
+ # <SOAP:Envelope> ... </SOAP:Envelope>"
+ #
+ # <tt>tag!</tt> will also take text and attribute arguments (after
+ # the tag name) like normal markup methods. (But see the next
+ # bullet item for a better way to handle XML namespaces).
+ #
+ # * Direct support for XML namespaces is now available. If the
+ # first argument to a tag call is a symbol, it will be joined to
+ # the tag to produce a namespace:tag combination. It is easier to
+ # show this than describe it.
+ #
+ # xml.SOAP :Envelope do ... end
+ #
+ # Just put a space before the colon in a namespace to produce the
+ # right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
+ # "<tt>xml.SOAP :Envelope</tt>")
+ #
+ # * XmlMarkup builds the markup in any object (called a _target_)
+ # that accepts the <tt><<</tt> method. If no target is given,
+ # then XmlMarkup defaults to a string target.
+ #
+ # Examples:
+ #
+ # xm = Builder::XmlMarkup.new
+ # result = xm.title("yada")
+ # # result is a string containing the markup.
+ #
+ # buffer = ""
+ # xm = Builder::XmlMarkup.new(buffer)
+ # # The markup is appended to buffer (using <<)
+ #
+ # xm = Builder::XmlMarkup.new(STDOUT)
+ # # The markup is written to STDOUT (using <<)
+ #
+ # xm = Builder::XmlMarkup.new
+ # x2 = Builder::XmlMarkup.new(:target=>xm)
+ # # Markup written to +x2+ will be send to +xm+.
+ #
+ # * Indentation is enabled by providing the number of spaces to
+ # indent for each level as a second argument to XmlBuilder.new.
+ # Initial indentation may be specified using a third parameter.
+ #
+ # Example:
+ #
+ # xm = Builder.new(:ident=>2)
+ # # xm will produce nicely formatted and indented XML.
+ #
+ # xm = Builder.new(:indent=>2, :margin=>4)
+ # # xm will produce nicely formatted and indented XML with 2
+ # # spaces per indent and an over all indentation level of 4.
+ #
+ # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
+ # builder.name { |b| b.first("Jim"); b.last("Weirich) }
+ # # prints:
+ # # <name>
+ # # <first>Jim</first>
+ # # <last>Weirich</last>
+ # # </name>
+ #
+ # * The instance_eval implementation which forces self to refer to
+ # the message receiver as self is now obsolete. We now use normal
+ # block calls to execute the markup block. This means that all
+ # markup methods must now be explicitly send to the xml builder.
+ # For instance, instead of
+ #
+ # xml.div { strong("text") }
+ #
+ # you need to write:
+ #
+ # xml.div { xml.strong("text") }
+ #
+ # Although more verbose, the subtle change in semantics within the
+ # block was found to be prone to error. To make this change a
+ # little less cumbersome, the markup block now gets the markup
+ # object sent as an argument, allowing you to use a shorter alias
+ # within the block.
+ #
+ # For example:
+ #
+ # xml_builder = Builder::XmlMarkup.new
+ # xml_builder.div { |xml|
+ # xml.stong("text")
+ # }
+ #
+ class XmlMarkup < XmlBase
+
+ # Create an XML markup builder. Parameters are specified by an
+ # option hash.
+ #
+ # :target=><em>target_object</em>::
+ # Object receiving the markup. +out+ must respond to the
+ # <tt><<</tt> operator. The default is a plain string target.
+ # :indent=><em>indentation</em>::
+ # Number of spaces used for indentation. The default is no
+ # indentation and no line breaks.
+ # :margin=><em>initial_indentation_level</em>::
+ # Amount of initial indentation (specified in levels, not
+ # spaces).
+ #
+ def initialize(options={})
+ indent = options[:indent] || 0
+ margin = options[:margin] || 0
+ super(indent, margin)
+ @target = options[:target] || ""
+ end
+
+ # Return the target of the builder.
+ def target!
+ @target
+ end
+
+ def comment!(comment_text)
+ _ensure_no_block block_given?
+ _special("<!-- ", " -->", comment_text, nil)
+ end
+
+ # Insert an XML declaration into the XML markup.
+ #
+ # For example:
+ #
+ # xml.declare! :ELEMENT, :blah, "yada"
+ # # => <!ELEMENT blah "yada">
+ def declare!(inst, *args, &block)
+ _indent
+ @target << "<!#{inst}"
+ args.each do |arg|
+ case arg
+ when String
+ @target << %{ "#{arg}"}
+ when Symbol
+ @target << " #{arg}"
+ end
+ end
+ if block_given?
+ @target << " ["
+ _newline
+ _nested_structures(block)
+ @target << "]"
+ end
+ @target << ">"
+ _newline
+ end
+
+ # Insert a processing instruction into the XML markup. E.g.
+ #
+ # For example:
+ #
+ # xml.instruct!
+ # #=> <?xml encoding="UTF-8" version="1.0"?>
+ # xml.instruct! :aaa, :bbb=>"ccc"
+ # #=> <?aaa bbb="ccc"?>
+ #
+ def instruct!(directive_tag=:xml, attrs={})
+ _ensure_no_block block_given?
+ if directive_tag == :xml
+ a = { :version=>"1.0", :encoding=>"UTF-8" }
+ attrs = a.merge attrs
+ end
+ _special("<?#{directive_tag}", "?>", nil, attrs)
+ end
+
+ private
+
+ # NOTE: All private methods of a builder object are prefixed when
+ # a "_" character to avoid possible conflict with XML tag names.
+
+ # Insert text directly in to the builder's target.
+ def _text(text)
+ @target << text
+ end
+
+ # Insert special instruction.
+ def _special(open, close, data=nil, attrs=nil)
+ _indent
+ @target << open
+ @target << data if data
+ _insert_attributes(attrs) if attrs
+ @target << close
+ _newline
+ end
+
+ # Start an XML tag. If <tt>end_too</tt> is true, then the start
+ # tag is also the end tag (e.g. <br/>
+ def _start_tag(sym, attrs, end_too=false)
+ @target << "<#{sym}"
+ _insert_attributes(attrs)
+ @target << "/" if end_too
+ @target << ">"
+ end
+
+ # Insert an ending tag.
+ def _end_tag(sym)
+ @target << "</#{sym}>"
+ end
+
+ # Insert the attributes (given in the hash).
+ def _insert_attributes(attrs)
+ return if attrs.nil?
+ attrs.each do |k, v|
+ @target << %{ #{k}="#{v}"}
+ end
+ end
+
+ def _ensure_no_block(got_block)
+ if got_block
+ fail IllegalBlockError,
+ "Blocks are not allowed on XML instructions"
+ end
+ end
+
+ end
+
+end