aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG738
-rw-r--r--actionpack/MIT-LICENSE21
-rwxr-xr-xactionpack/README418
-rw-r--r--actionpack/RUNNING_UNIT_TESTS25
-rwxr-xr-xactionpack/Rakefile105
-rw-r--r--actionpack/examples/.htaccess24
-rw-r--r--actionpack/examples/address_book/index.rhtml33
-rw-r--r--actionpack/examples/address_book/layout.rhtml8
-rwxr-xr-xactionpack/examples/address_book_controller.cgi9
-rwxr-xr-xactionpack/examples/address_book_controller.fcgi6
-rw-r--r--actionpack/examples/address_book_controller.rb52
-rw-r--r--actionpack/examples/address_book_controller.rbx4
-rw-r--r--actionpack/examples/benchmark.rb52
-rwxr-xr-xactionpack/examples/benchmark_with_ar.fcgi89
-rwxr-xr-xactionpack/examples/blog_controller.cgi53
-rw-r--r--actionpack/examples/debate/index.rhtml14
-rw-r--r--actionpack/examples/debate/new_topic.rhtml22
-rw-r--r--actionpack/examples/debate/topic.rhtml32
-rwxr-xr-xactionpack/examples/debate_controller.cgi57
-rw-r--r--actionpack/install.rb97
-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
-rw-r--r--actionpack/test/abstract_unit.rb9
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb323
-rw-r--r--actionpack/test/controller/active_record_assertions_test.rb119
-rwxr-xr-xactionpack/test/controller/cgi_test.rb142
-rw-r--r--actionpack/test/controller/cookie_test.rb38
-rw-r--r--actionpack/test/controller/filters_test.rb159
-rw-r--r--actionpack/test/controller/flash_test.rb69
-rw-r--r--actionpack/test/controller/helper_test.rb110
-rw-r--r--actionpack/test/controller/layout_test.rb49
-rwxr-xr-xactionpack/test/controller/redirect_test.rb44
-rw-r--r--actionpack/test/controller/render_test.rb178
-rw-r--r--actionpack/test/controller/send_file_test.rb68
-rw-r--r--actionpack/test/controller/url_test.rb368
-rw-r--r--actionpack/test/fixtures/helpers/abc_helper.rb5
-rw-r--r--actionpack/test/fixtures/layouts/builder.rxml3
-rw-r--r--actionpack/test/fixtures/layouts/standard.rhtml1
-rw-r--r--actionpack/test/fixtures/scope/test/modgreet.rhtml1
-rw-r--r--actionpack/test/fixtures/test/_customer.rhtml1
-rw-r--r--actionpack/test/fixtures/test/greeting.rhtml1
-rw-r--r--actionpack/test/fixtures/test/hello.rxml4
-rw-r--r--actionpack/test/fixtures/test/hello_world.rhtml1
-rw-r--r--actionpack/test/fixtures/test/hello_xml_world.rxml11
-rw-r--r--actionpack/test/fixtures/test/list.rhtml1
-rw-r--r--actionpack/test/template/active_record_helper_test.rb76
-rwxr-xr-xactionpack/test/template/date_helper_test.rb104
-rw-r--r--actionpack/test/template/form_helper_test.rb124
-rw-r--r--actionpack/test/template/form_options_helper_test.rb165
-rw-r--r--actionpack/test/template/tag_helper_test.rb18
-rw-r--r--actionpack/test/template/text_helper_test.rb62
-rw-r--r--actionpack/test/template/url_helper_test.rb49
105 files changed, 9547 insertions, 0 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
new file mode 100644
index 0000000000..a422af3f0e
--- /dev/null
+++ b/actionpack/CHANGELOG
@@ -0,0 +1,738 @@
+*CVS*
+
+* Upgraded to Builder 1.2.1
+
+* Added :module as an alias for :controller_prefix to url_for and friends, so you can do redirect_to(:module => "shop", :controller => "purchases")
+ and go to /shop/purchases/
+
+* Added support for controllers in modules through @params["module"].
+
+* Added reloading for dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
+ This is turned on by default, but can be turned off with ActionController::Base.reload_dependencies = false in production environments.
+
+ NOTE: This will only have an effect if you use the new model, service, and observer class methods to mark dependencies. All libraries loaded through
+ require will be "forever" cached. You can, however, use ActionController::Base.load_or_require("library") to get this behavior outside of the new
+ dependency style.
+
+* Added that controllers will automatically require their own helper if possible. So instead of doing:
+
+ class MsgController < AbstractApplicationController
+ helper :msg
+ end
+
+ ...you can just do:
+
+ class MsgController < AbstractApplicationController
+ end
+
+* Added dependencies_on(layer) to query the dependencies of a controller. Examples:
+
+ MsgController.dependencies_on(:model) # => [ :post, :comment, :attachment ]
+ MsgController.dependencies_on(:service) # => [ :notification_service ]
+ MsgController.dependencies_on(:observer) # => [ :comment_observer ]
+
+* Added a new dependency model with the class methods model, service, and observer. Example:
+
+ class MsgController < AbstractApplicationController
+ model :post, :comment, :attachment
+ service :notification_service
+ observer :comment_observer
+ end
+
+ These new "keywords" remove the need for explicitly calling 'require' in most cases. The observer method even instantiates the
+ observer as well as requiring it.
+
+* Fixed that link_to would escape & in the url again after url_for already had done so
+
+*0.9.5* (28)
+
+* Added helper_method to designate that a given private or protected method you should available as a helper in the view. [bitsweat]
+
+* Fixed assert_rendered_file so it actually verifies if that was the rendered file [htonl]
+
+* Added the option for sharing partial spacer templates just like partials themselves [radsaq]
+
+* Fixed that Russia was named twice in country_select [alexey]
+
+* Fixed request_origin to use remote_ip instead of remote_addr [bitsweat]
+
+* Fixed link_to breakage when nil was passed for html_options [alexey]
+
+* Fixed redirect_to on a virtual server setup with apache with a port other than the default where it would forget the port number [seanohalpin]
+
+* Fixed that auto-loading webrick on Windows would cause file uploads to fail [bitsweat]
+
+* Fixed issues with sending files on WEBrick by setting the proper binmode [bitsweat]
+
+* Added send_data as an alternative to send_file when the stream is not read off the filesystem but from a database or generated live [bitsweat]
+
+* Added a new way to include helpers that doesn't require the include hack and can go without the explicit require. [bitsweat]
+
+ Before:
+
+ module WeblogHelper
+ def self.append_features(controller) #:nodoc:
+ controller.ancestors.include?(ActionController::Base) ? controller.add_template_helper(self) : super
+ end
+ end
+
+ require 'weblog_helper'
+ class WeblogController < ActionController::Base
+ include WeblogHelper
+ end
+
+ After:
+
+ module WeblogHelper
+ end
+
+ class WeblogController < ActionController::Base
+ helper :weblog
+ end
+
+* Added a default content-type of "text/xml" to .rxml renders [Ryan Platte]
+
+* Fixed that when /controller/index was requested by the browser, url_for would generates wrong URLs [Ryan Platte]
+
+* Fixed a bug that would share cookies between users when using FastCGI and mod_ruby [The Robot Co-op]
+
+* Added an optional third hash parameter to the process method in functional tests that takes the session data to be used [alexey]
+
+* Added UrlHelper#mail_to to make it easier to create mailto: style ahrefs
+
+* Added better error messages for layouts declared with the .rhtml extension (which they shouldn't) [geech]
+
+* Added another case to DateHelper#distance_in_minutes to return "less than a minute" instead of "0 minutes" and "1 minute" instead of "1 minutes"
+
+* Added a hidden field to checkboxes generated with FormHelper#check_box that will make sure that the unchecked value (usually 0)
+ is sent even if the checkbox is not checked. This relieves the controller from doing custom checking if the the checkbox wasn't
+ checked. BEWARE: This might conflict with your run-on-the-mill work-around code. [Tobias Luetke]
+
+* Fixed error_message_on to just use the first if more than one error had been added [marcel]
+
+* Fixed that URL rewriting with /controller/ was working but /controller was not and that you couldn't use :id on index [geech]
+
+* Fixed a bug with link_to where the :confirm option wouldn't be picked up if the link was a straight url instead of an option hash
+
+* Changed scaffolding of forms to use <label> tags instead of <b> to please W3C [evl]
+
+* Added DateHelper#distance_of_time_in_words_to_now(from_time) that works like distance_of_time_in_words,
+ but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
+
+* Added assert_flash_equal(expected, key, message), assert_session_equal(expected, key, message),
+ assert_assigned_equal(expected, key, message) to test the contents of flash, session, and template assigns.
+
+* Improved the failure report on assert_success when the action triggered a redirection [alexey].
+
+* Added "markdown" to accompany "textilize" as a TextHelper method for converting text to HTML using the Markdown syntax.
+ BlueCloth must be installed in order for this method to become available.
+
+* Made sure that an active session exists before we attempt to delete it [Samuel]
+
+* Changed link_to with Javascript confirmation to use onclick instead of onClick for XHTML validity [Scott Barron]
+
+
+*0.9.0 (43)*
+
+* Added support for Builder-based templates for files with the .rxml extension. These new templates are an alternative to ERb that
+ are especially useful for generating XML content, such as this RSS example from 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
+
+ ...which will generate something like:
+
+ <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <channel>
+ <title>Web Site Redesign</title>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/</link>
+ <description>Basecamp: Recent items</description>
+ <language>en-us</language>
+ <ttl>40</ttl>
+ <item>
+ <title>Post: don't you know</title>
+ <description>&amp;lt;p&amp;gt;deeper and down&amp;lt;/p&amp;gt;</description>
+ <pubDate>Fri, 20 Aug 2004 21:13:50 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</link>
+ <dc:creator>David H. Heinemeier</dc:creator>
+ </item>
+ <item>
+ <title>Milestone completed: Design Comp 2</title>
+ <pubDate>Mon, 9 Aug 2004 14:42:06 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</link>
+ </item>
+ </channel>
+ </rss>
+
+ The "xml" local variable is automatically available in .rxml templates. You construct the template by calling a method with the name
+ of the tag you want. Options for the tag can be specified as a hash parameter to that method.
+
+ Builder-based templates can be mixed and matched with the regular ERb ones. The only thing that differentiates them is the extension.
+ No new methods have been added to the public interface to handle them.
+
+ Action Pack ships with a version of Builder, but it will use the RubyGems version if you have one installed.
+
+ Read more about Builder on: http://onestepback.org/index.cgi/Tech/Ruby/StayingSimple.rdoc
+
+ [Builder is created by Jim Weirich]
+
+* Added much improved support for functional testing [what-a-day].
+
+ # Old style
+ def test_failing_authenticate
+ @request.request_uri = "/login/authenticate"
+ @request.action = "authenticate"
+ @request.request_parameters["user_name"] = "nop"
+ @request.request_parameters["password"] = ""
+
+ response = LoginController.process_test(@request)
+
+ assert_equal "The username and/or password you entered is invalid.", response.session["flash"]["alert"]
+ assert_equal "http://37signals.basecamp.com/login/", response.headers["location"]
+ end
+
+ # New style
+ def test_failing_authenticate
+ process :authenticate, "user_name" => "nop", "password" => ""
+ assert_flash_has 'alert'
+ assert_redirected_to :action => "index"
+ end
+
+ See a full example on http://codepaste.org/view/paste/334
+
+* Increased performance by up to 100% with a revised 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
+
+* Added caching for compiled ERb templates. On Basecamp, it gave between 8.5% and 71% increase in performance [Andreas Schwarz].
+
+* Added implicit counter variable to render_collection_of_partials [Marcel]. From the docs:
+
+ <%= 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+.
+
+* Fixed problems with two sessions being maintained on reset_session that would particularly screw up ActiveRecordStore.
+
+* Fixed reset_session to start an entirely new session instead of merely deleting the old. So you can now safely access @session
+ after calling reset_ression and expect it to work.
+
+* Added @request.get?, @request.post?, @request.put?, @request.delete? as convenience query methods for @request.method [geech]
+
+* Added @request.method that'll return a symbol representing the HTTP method, such as :get, :post, :put, :delete [geech]
+
+* Changed @request.remote_ip and @request.host to work properly even when a proxy is in front of the application [geech]
+
+* Added JavaScript confirm feature to link_to. Documentation:
+
+ 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.
+
+* Added link_to_unless_current as a UrlHelper method [Sam Stephenson]. Documentation:
+
+ 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.
+
+* Fixed that UrlRewriter (the driver for url_for, link_to, etc) would blow up when the anchor was an integer [alexey]
+
+* Added that layouts defined with no directory defaults to layouts. So layout "weblog/standard" will use
+ weblog/standard (as always), but layout "standard" will use layouts/standard.
+
+* Fixed that partials (or any template starting with an underscore) was publically viewable [Marten]
+
+* Added HTML escaping to text_area helper.
+
+* Added :overwrite_params to url_for and friends to keep the parameters as they were passed to the current action and only overwrite a subset.
+ The regular :params will clear the slate so you need to manually add in existing parameters if you want to reuse them. [raphinou]
+
+* Fixed scaffolding problem with composite named objects [Moo Jester]
+
+* Added the possibility for shared partials. Example:
+
+ <%= render_partial "advertisement/ad", ad %>
+
+ This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
+
+ [Jacob Fugal]
+
+* Fixed crash when encountering forms that have empty-named fields [James Prudente]
+
+* Added check_box form helper method now accepts true/false as well as 1/0 [what-a-day]
+
+* Fixed the lacking creation of all directories with install.rb [Dave Steinberg]
+
+* Fixed that date_select returns valid XHTML selected options [Andreas Schwarz]
+
+* Fixed referencing an action with the same name as a controller in url_for [what-a-day]
+
+* Fixed the destructive nature of Base#attributes= on the argument [Kevin Watt]
+
+* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
+
+* Added SessionRestoreError that is raised when a session being restored holds objects where there is no class available.
+
+* Added block as option for inline filters. So what used to be written as:
+
+ before_filter Proc { |controller| return false if controller.params["stop_action"] }
+
+ ...can now be as:
+
+ before_filter { |controller| return false if controller.params["stop_action"] }
+
+ [Jeremy Kemper]
+
+* Made the following methods public (was protected): url_for, controller_class_name, controller_name, action_name
+ This makes it easier to write filters without cheating around the encapsulation with send.
+
+* ActionController::Base#reset_session now sticks even if you access @session afterwards [Kent Sibilev]
+
+* Improved the exception logging so the log file gets almost as much as in-browser debugging.
+
+* Changed base class setup from AbstractTemplate/ERbTemplate to ActionView::Base. This change should be harmless unless you were
+ accessing Action View directly in which case you now need to reference the Base class.\
+
+* Added that render_collection_of_partials returns nil if the collection is empty. This makes showing a “no items” message easier.
+ For example: <%= render_collection_of_partials("message", @messages) || "No messages found." %> [Sam Stephenson]
+
+* Added :month_before_year as an option to date_select to get the month select before the year. Especially useful for credit card forms.
+
+* Added :add_month_numbers to select_month to get options like "3 - March".
+
+* Removed Base.has_active_layout? as it couldn't answer the question without the instance. Use Base#active_layout instead.
+
+* Removed redundant call to update on ActionController::Base#close_session [Andreas Schwarz]
+
+* Fixed that DRb Store accidently started its own server (instead of just client) [Andreas]
+
+* Fixed strip_links so it now works across multiple lines [Chad Fowler]
+
+* Fixed the TemplateError exception to show the proper trace on to_s (useful for unit test debugging)
+
+* Implemented class inheritable attributes without eval [Caio Chassot]
+
+* Made TextHelper#concat accept binding as it would otherwise not work
+
+* The FormOptionsHelper will now call to_s on the keys and values used to generate options
+
+
+*0.8.5*
+
+* Introduced passing of locally scoped variables between templates:
+
+ You can pass local variables to sub templates by using a hash of 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 %>
+
+* Introduced the concept of partials as a certain type of sub templates:
+
+ 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+
+ for the template to display.
+
+ == Rendering a collection of partials
+
+ The example of partial use describes a familar pattern where a template needs
+ to iterate over a 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 of 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 %>
+
+ So this will render "advertiser/_ad.rhtml" and pass the local variable +ad+ for
+ the template to display.
+
+* Improved send_file by allowing a wide range of options to be applied [Jeremy Kemper]:
+
+ 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> - specifies the filename the browser will see.
+ 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>: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.
+
+* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
+
+* Added proper escaping for the rescues [Andreas Schwartz]
+
+* Added proper escaping for the option and collection tags [Andreas Schwartz]
+
+* Fixed NaN errors on benchmarking [Jim Weirich]
+
+* Fixed query string parsing for URLs that use the escaped versions of & or ; as part of a key or value
+
+* Fixed bug with custom Content-Type headers being in addition to rather than instead of the default header.
+ (This bug didn't matter with neither CGI or mod_ruby, but FCGI exploded on it) [With help from Ara T. Howard]
+
+
+*0.8.0*
+
+* Added select, collection_select, and country_select to make it easier for Active Records to set attributes through
+ drop-down lists of options. Example:
+
+ <%= select "person", "gender", %w( Male Female ) %>
+
+ ...would give the following:
+
+ <select name="person[gender]" id="person_gender"><option>Male</option><option>Female</option></select>
+
+* Added an option for getting multiple values on a single form name into an array instead of having the last one overwrite.
+ This is especially useful for groups of checkboxes, which can now be written as:
+
+ <input type="checkbox" name="rights[]" value="CREATE" />
+ <input type="checkbox" name="rights[]" value="UPDATE" />
+ <input type="checkbox" name="rights[]" value="DELETE" />
+
+ ...and retrieved in the controller action with:
+
+ @params["rights"] # => [ "CREATE", "UPDATE", "DELETE" ]
+
+ The old behavior (where the last one wins, "DELETE" in the example) is still available. Just don't add "[]" to the
+ end of the name. [Scott Baron]
+
+* Added send_file which uses the new render_text block acceptance to make it feasible to send large files.
+ The files is sent with a bunch of voodoo HTTP headers required to get arbitrary files to download as
+ expected in as many browsers as possible (eg, IE hacks). Example:
+
+ def play_movie
+ send_file "/movies/that_movie.avi"
+ end
+
+ [Jeremy Kemper]
+
+* render_text now accepts a block for deferred rendering. Useful for streaming large files, displaying
+ a “please wait” message during a complex search, etc. Streaming example:
+
+ render_text do |response|
+ File.open(path, 'rb') do |file|
+ while buf = file.read(1024)
+ print buf
+ end
+ end
+ end
+
+ [Jeremy Kemper]
+
+* Added a new Tag Helper that can generate generic tags programmatically insted of through HTML. Example:
+
+ tag("br", "clear" => "all") => <br clear="all" />
+
+ ...that's usually not terribly interesting (unless you have a lot of options already in a hash), but it
+ gives way for more specific tags, like the new form tag:
+
+ form_tag({ :controller => "weblog", :action => "update" }, { :multipart => "true", "style" => "width: 200px"}) =>
+ <form action="/weblog/update" enctype="multipart/formdata" style="width: 200px">
+
+ There's even a "pretty" version for people who don't like to open tags in code and close them in HTML:
+
+ <%= start_form_tag :action => "update" %>
+ # all the input fields
+ <%= end_form_tag %>
+
+ (end_form_tag just returns "</form>")
+
+* The selected parameter in options_for_select may now also an array of values to be selected when
+ using a multiple select. Example:
+
+ options_for_select([ "VISA", "Mastercard", "Discover" ], ["VISA", "Discover"]) =>
+ <option selected>VISA</option>\n<option>Mastercard</option>\n<option selected>Discover</option>
+
+ [Scott Baron]
+
+* Changed the URL rewriter so controller_prefix and action_prefix can be used in isolation. You can now do:
+
+ url_for(:controller_prefix => "clients")
+
+ ...or:
+
+ url_for(:action_prefix => "category/messages")
+
+ Neither would have worked in isolation before (:controller_prefix required a :controller and :action_prefix required an :action)
+
+* Started process of a cleaner separation between Action Controller and ERb-based Action Views by introducing an
+ abstract base class for views. And Amita adapter could be fitted in more easily now.
+
+* The date helper methods date_select and datetime_select now also use the field error wrapping
+ (div with class fieldWithErrors by default).
+
+* The date helper methods date_select and datetime_select can now discard selects
+
+* Added option on AbstractTemplate to specify a different field error wrapping. Example:
+
+ ActionView::AbstractTemplate.field_error_proc = Proc.new do |html, instance|
+ "<p>#{instance.method_name + instance.error_message}</p><div style='background-color: red'>#{html}</div>"
+ end
+
+ ...would give the following on a Post#title (text field) error:
+
+ <p>Title can't be empty</p>
+ <div style='background-color: red'>
+ <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ </div>
+
+* The UrlHelper methods url_for and link_to will now by default only return paths, not complete URIs.
+ That should make it easier to fit a Rails application behind a proxy or load-balancer.
+ You can overwrite this by passing :only_path => false as part of the options. [Suggested by U235]
+
+* Fixed bug with having your own layout for use with scaffolding [Kevin Radloff]
+
+* Fixed bug where redirect_to_path didn't append the port on non-standard ports [dhawkins]
+
+* Scaffolding plays nicely with single-table inheritance (LoadErrors are caught) [Jeremy Kemper]
+
+* Scaffolding plays nice with plural models like Category/categories [Jeremy Kemper]
+
+* Fixed missing suffix appending in scaffolding [Kevin Radloff]
+
+
+*0.7.9*
+
+* The "form" method now present boolean fields from PostgreSQL as drop-down menu. [Scott]
+
+* Scaffolding now automatically attempts to require the class that's being scaffolded.
+
+* Scaffolding will use the current active layout, instead of its own, if one has been specified. Example:
+
+ class WeblogController < ActionController::Base
+ layout "layouts/weblog"
+ scaffold :post
+ end
+
+ [Suggested by Scott]
+
+* Changed url_for (and all the that drives, like redirect_to, link_to, link_for) so you can pass it a symbol instead of a hash.
+ This symbol is a method reference which is then called to calculate the url. Example:
+
+ class WeblogController < ActionController::Base
+ def update
+ # do some update
+ redirect_to :dashboard_url
+ end
+
+ protected
+ def dashboard_url
+ if @project.active?
+ url_for :controller => "project", :action => "dashboard"
+ else
+ url_for :controller => "account", :action => "dashboard"
+ end
+ end
+ end
+
+* Added default_url_options to specialize behavior for all url_for (and friends) calls:
+
+ Overwrite to implement a number of default options that all url_for-based methods will use.
+ The default options should come in 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 dissions 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.
+
+
+* Changed url_for so that an "id" passed in the :params is not treated special. You need to use the dedicated :id to get
+ the special auto path-params treatment. Considering the url http://localhost:81/friends/list
+
+ url_for(:action => "show", :params => { "id" => 5 })
+ ...used to give http://localhost:81/friends/show/5
+ ......now gives http://localhost:81/friends/show?id=5
+
+ If you want the automated id behavior, do:
+
+ url_for(:action => "show", :id => 5 )
+ ....which gives http://localhost:81/friends/show/5
+
+
+* Fixed problem with anchor being inserted before path parameters with url_for (and friends)
+
+
+*0.7.8*
+
+* Fixed session bug where you couldn't store any objects that didn't exist in the standard library
+ (such as Active Record objects).
+
+* Added reset_session method for Action Controller objects to clear out all objects in the session.
+
+* Fixed that exceptions raised during filters are now also caught by the default rescues
+
+* Added new around_filter for doing before and after filtering with a single object [Florian Weber]:
+
+ 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
+
+* Added the options for specifying a different name and id for the form helper methods than what is guessed [Florian Weber]:
+
+ text_field "post", "title"
+ ...just gives: <input id="post_title" name="post[title]" size="30" type="text" value="" />
+
+ text_field "post", "title", "id" => "title_for_post", "name" => "first_post_title"
+ ...can now give: <input id="title_for_post" name="first_post_title" size="30" type="text" value="" />
+
+* Added DebugHelper with a single "debug" method for doing pretty dumps of objects in the view
+ (now used in the default rescues to better present the contents of session and template variables)
+
+* Added note to log about the templates rendered within layouts (before just the layout was shown)
+
+* Fixed redirects on https setups [Andreas]
+
+* Fixed scaffolding problem on the edit action when using :suffix => true [Scott]
+
+* Fixed scaffolding problem where implementing list.rhtml wouldn't work for the index action
+
+* URLs generated now uses &amp; instead of just & so pages using it can validate with W3C [Spotted by Andreas]
+
+
+*0.7.7*
+
+* Fixed bug in CGI extension that prevented multipart forms from working
+
+
+*0.7.6*
+
+* Included ERB::Util so all templates can easily escape HTML content with <%=h @person.content %>
+
+* All requests are now considered local by default, so everyone will be exposed to detailed debugging screens on errors.
+ When the application is ready to go public, set ActionController::Base.consider_all_requests_local to false,
+ and implement the protected method local_request? in the controller to determine when debugging screens should be shown.
+
+* Fixed three bugs with the url_for/redirect_to/link_to handling. Considering the url http://localhost:81/friends/show/1
+
+ url_for(:action => "list")
+ ...used to give http://localhost:81/friends/list/1
+ ......now gives http://localhost:81/friends/list
+
+ url_for(:controller => "friends", :action => "destroy", :id => 5)
+ ...used to give http://localhost:81/friends/destroy
+ ......now gives http://localhost:81/friends/destroy/5
+
+ Considering the url http://localhost:81/teachers/show/t
+
+ url_for(:action => "list", :id => 5)
+ ...used to give http://localhost:81/5eachers/list/t
+ ......now gives http://localhost:81/teachers/list/5
+
+ [Reported by David Morton & Radsaq]
+
+* Logs exception to logfile in addition to showing them for local requests
+
+* Protects the eruby load behind a begin/rescue block. eRuby is not required to run ActionController.
+
+* Fixed install.rb to also install clean_logger and the templates
+
+* Added ActiveRecordStore as a session option. Read more in lib/action_controller/session/active_record_store.rb [Tim Bates]
+
+* Change license to MIT License (and included license file in package)
+
+* Application error page now returns status code 500 instead of 200
+
+* Fixed using Procs as layout handlers [Florian Weber]
+
+* Fixed bug with using redirects ports other than 80
+
+* Added index method that calls list on scaffolding
+
+
+*0.7.5*
+
+* First public release \ No newline at end of file
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
new file mode 100644
index 0000000000..26f55e7799
--- /dev/null
+++ b/actionpack/MIT-LICENSE
@@ -0,0 +1,21 @@
+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.
+
diff --git a/actionpack/README b/actionpack/README
new file mode 100755
index 0000000000..d22ca0a701
--- /dev/null
+++ b/actionpack/README
@@ -0,0 +1,418 @@
+= Action Pack -- On rails from request to response
+
+Action Pack splits the response to a web request into a controller part
+(performing the logic) and a view part (rendering a template). This two-step
+approach is known as an action, which will normally create, read, update, or
+delete (CRUD for short) some sort of model part (often backed by a database)
+before choosing either to render a template or redirecting to another action.
+
+Action Pack implements these actions as public methods on Action Controllers
+and uses Action Views to implement the template rendering. Action Controllers
+are then responsible for handling all the actions relating to a certain part
+of an application. This grouping usually consists of actions for lists and for
+CRUDs revolving around a single (or a few) model objects. So ContactController
+would be responsible for listing contacts, creating, deleting, and updating
+contacts. A WeblogController could be responsible for both posts and comments.
+
+Action View templates are written using embedded Ruby in tags mingled in with
+the HTML. To avoid cluttering the templates with code, a bunch of helper
+classes provide common behavior for forms, dates, and strings. And it's easy
+to add specific helpers to keep the separation as the application evolves.
+
+Note: Some of the features, such as scaffolding and form building, are tied to
+ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
+mapping package), but that doesn't mean that Action Pack depends on Active
+Record. Action Pack is an independent package that can be used with any sort
+of backend (Instiki[http://www.instiki.org], which is based on an older version
+of Action Pack, uses Madeleine for example). Read more about the role Action
+Pack can play when used together with Active Record on
+http://www.rubyonrails.org.
+
+A short rundown of the major features:
+
+* Actions grouped in controller as methods instead of separate command objects
+ and can therefore helper share methods.
+
+ BlogController < ActionController::Base
+ def display
+ @customer = find_customer
+ end
+
+ def update
+ @customer = find_customer
+ @customer.attributes = @params["customer"]
+ @customer.save ?
+ redirect_to(:action => "display") :
+ render("customer/edit")
+ end
+
+ private
+ def find_customer() Customer.find(@params["id"]) end
+ end
+
+ Learn more in link:classes/ActionController/Base.html
+
+
+* Embedded Ruby for templates (no new "easy" template language)
+
+ <% for post in @posts %>
+ Title: <%= post.title %>
+ <% end %>
+
+ All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
+
+ <% unless @person.is_client? %>
+ Not for clients to see...
+ <% end %>
+
+ Learn more in link:classes/ActionView.html
+
+
+* Builder-based templates (great for XML content, like RSS)
+
+ xml.rss("version" => "2.0") 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))
+ xml.pubDate(item_pubDate(item))
+ xml.guid(@recent_items.url(item))
+ xml.link(@recent_items.url(item))
+ end
+ end
+ end
+ end
+
+
+* Filters for pre and post processing of the response (as methods, procs, and classes)
+
+ class WeblogController < ActionController::Base
+ before_filter :authenticate, :cache, :audit
+ after_filter { |c| c.response.body = GZip::compress(c.response.body) }
+ after_filter LocalizeFilter
+
+ def list
+ # Before this action is run, the user will be authenticated, the cache
+ # will be examined to see if a valid copy of the results already
+ # exist, and the action will be logged for auditing.
+
+ # After this action has run, the output will first be localized then
+ # compressed to minimize bandwith usage
+ end
+
+ private
+ def authenticate
+ # Implement the filter will full access to both request and response
+ end
+ end
+
+ Learn more in link:classes/ActionController/Filters/ClassMethods.html
+
+
+* Helpers for forms, dates, action links, and text
+
+ <%= text_field "post", "title", "size" => 30 %>
+ <%= html_date_select(Date.today) %>
+ <%= link_to "New post", :controller => "post", :action => "new" %>
+ <%= truncate(post.title, 25) %>
+
+ Learn more in link:classes/ActionView/Helpers.html
+
+
+* Layout sharing for template reuse (think simple version of Struts
+ Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
+
+ class WeblogController < ActionController::Base
+ layout "weblog_layout"
+
+ def hello_world
+ end
+ end
+
+ Layout file (called weblog_layout):
+ <html><body><%= @content_for_layout %></body></html>
+
+ Template for hello_world action:
+ <h1>Hello world</h1>
+
+ Result of running hello_world action:
+ <html><body><h1>Hello world</h1></body></html>
+
+ Learn more in link:classes/ActionController/Layout.html
+
+
+* Advanced redirection that makes pretty urls easy
+
+ RewriteRule ^/library/books/([A-Z]+)([0-9]+)/([-_a-zA-Z0-9]+)$ \
+ /books_controller.cgi?action=$3&type=$1&code=$2 [QSA] [L]
+
+ Accessing /library/books/ISBN/0743536703/show calls BooksController#show
+
+ From that URL, you can rewrite the redirect in a number of ways:
+
+ redirect_to(:action => "edit") =>
+ /library/books/ISBN/0743536703/edit
+
+ redirect_to(:path_params => { "type" => "XTC", "code" => "12354345" }) =>
+ /library/books/XTC/12354345/show
+
+ redirect_to(:controller_prefix => "admin", :controller => "accounts") =>
+ /admin/accounts/
+
+ Learn more in link:classes/ActionController/Base.html
+
+
+* Easy testing of both controller and template result through TestRequest/Response
+
+ class LoginControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = LoginController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_failing_authenticate
+ process :authenticate, "user_name" => "nop", "password" => ""
+ assert_flash_has 'alert'
+ assert_redirected_to :action => "index"
+ end
+ end
+
+ Learn more in link:classes/ActionController/TestRequest.html
+
+
+* Automated benchmarking and integrated logging
+
+ Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
+ Parameters: {"action"=>"index", "controller"=>"weblog"}
+ Rendering weblog/index (200 OK)
+ Completed in 0.029281 (34 reqs/sec)
+
+ If Active Record is used as the model, you'll have the database debugging
+ as well:
+
+ Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
+ Params: {"controller"=>"weblog", "action"=>"create",
+ "post"=>{"title"=>"this is good"} }
+ SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
+ Redirected to http://test/weblog/display/5
+ Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
+
+ You specify a logger through a class method, such as:
+
+ ActionController::Base.logger = Logger.new("Application Log")
+ ActionController::Base.logger = Log4r::Logger.new("Application Log")
+
+
+* Powerful debugging mechanism for local requests
+
+ All exceptions raised on actions performed on the request of a local user
+ will be presented with a tailored debugging screen that includes exception
+ message, stack trace, request parameters, session contents, and the
+ half-finished response.
+
+ Learn more in link:classes/ActionController/Rescue.html
+
+
+* Scaffolding for Action Record model objects
+
+ require 'account' # must be an Active Record class
+ class AccountController < ActionController::Base
+ scaffold :account
+ end
+
+ The AccountController now has the full CRUD range of actions and default
+ templates: list, show, destroy, new, create, edit, update
+
+ Learn more in link:classes/ActionController/Scaffolding/ClassMethods.html
+
+
+* Form building for Active Record model objects
+
+ The post object has a title (varchar), content (text), and
+ written_on (date)
+
+ <%= form "post" %>
+
+ ...will generate something like (the selects will have more options of
+ course):
+
+ <form action="create" method="POST">
+ <p>
+ <b>Title:</b><br/>
+ <input type="text" name="post[title]" value="<%= @post.title %>" />
+ </p>
+ <p>
+ <b>Content:</b><br/>
+ <textarea name="post[content]"><%= @post.title %></textarea>
+ </p>
+ <p>
+ <b>Written on:</b><br/>
+ <select name='post[written_on(3i)]'><option>18</option></select>
+ <select name='post[written_on(2i)]'><option value='7'>July</option></select>
+ <select name='post[written_on(1i)]'><option>2004</option></select>
+ </p>
+
+ <input type="submit" value="Create">
+ </form>
+
+ This form generates a @params["post"] array that can be used directly in a save action:
+
+ class WeblogController < ActionController::Base
+ def save
+ post = Post.create(@params["post"])
+ redirect_to :action => "display", :path_params => { "id" => post.id }
+ end
+ end
+
+ Learn more in link:classes/ActionView/Helpers/ActiveRecordHelper.html
+
+
+* Automated mapping of URLs to controller/action pairs through Apache's
+ mod_rewrite
+
+ Requesting /blog/display/5 will call BlogController#display and
+ make 5 available as an instance variable through @params["id"]
+
+
+* Runs on top of CGI, FCGI, and mod_ruby
+
+ See the address_book_controller example for all three forms
+
+
+== Simple example
+
+This example will implement a simple weblog system using inline templates and
+an Active Record model. The first thing we need to do is setup an .htaccess to
+interpret pretty URLs into something the controller can use. Let's use the
+simplest form for starters:
+
+ RewriteRule ^weblog/([-_a-zA-Z0-9]+)/([0-9]+)$ \
+ /weblog_controller.cgi?action=$2&id=$3 [QSA]
+ RewriteRule ^weblog/([-_a-zA-Z0-9]+)$ \
+ /weblog_controller.cgi?action=$2 [QSA]
+ RewriteRule ^weblog/$ \
+ /weblog_controller.cgi?action=index [QSA]
+
+Now we'll be able to access URLs like weblog/display/5 and have
+WeblogController#display called with { "id" => 5 } in the @params array
+available for the action. So let's build that WeblogController with just a few
+methods:
+
+ require 'action_controller'
+ require 'post'
+ class WeblogController < ActionController::Base
+ layout "weblog/layout"
+
+ def index
+ @posts = Post.find_all
+ end
+
+ def display
+ @post = Post.find(@params["id"])
+ end
+
+ def new
+ @post = Post.new
+ end
+
+ def create
+ @post = Post.create(@params["post"])
+ @post.save
+ redirect_to :action => "display", :id => @post.id
+ end
+ end
+
+ WeblogController::Base.template_root = File.dirname(__FILE__)
+ WeblogController.process_cgi if $0 == __FILE__
+
+The last two lines are responsible for telling ActionController where the
+template files are located and actually running the controller on a new
+request from the web-server (like to be Apache).
+
+And the templates look like this:
+
+ weblog/layout.rhtml:
+ <html><body>
+ <%= @content_for_layout %>
+ </body></html>
+
+ weblog/index.rhtml:
+ <% for post in @posts %>
+ <p><%= link_to(post.title, :action => "display", :id => post.id %></p>
+ <% end %>
+
+ weblog/display.rhtml:
+ <p>
+ <b><%= post.title %></b><br/>
+ <b><%= post.content %></b>
+ </p>
+
+ weblog/new.rhtml:
+ <%= form "post" %>
+
+This simple setup will list all the posts in the system on the index page,
+which is called by accessing /weblog/. It uses the form builder for the Active
+Record model to make the new screen, which in turns hand everything over to
+the create action (that's the default target for the form builder when given a
+new model). After creating the post, it'll redirect to the display page using
+an URL such as /weblog/display/5 (where 5 is the id of the post.
+
+
+== Examples
+
+Action Pack ships with three examples that all demonstrate an increasingly
+detailed view of the possibilities. First is blog_controller that is just a
+single file for the whole MVC (but still split into separate parts). Second is
+the debate_controller that uses separate template files and multiple screens.
+Third is the address_book_controller that uses the layout feature to separate
+template casing from content.
+
+Please note that you might need to change the "shebang" line to
+#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
+
+
+== Download
+
+The latest version of Action Pack can be found at
+
+* http://rubyforge.org/project/showfiles.php?group_id=249
+
+Documentation can be found at
+
+* http://actionpack.rubyonrails.org
+
+
+== Installation
+
+You can install Action Pack with the following command.
+
+ % [sudo] ruby install.rb
+
+from its distribution directory.
+
+
+== License
+
+Action Pack is released under the same license as Ruby.
+
+
+== Support
+
+The Action Pack homepage is http://actionpack.rubyonrails.org. You can find
+the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
+And as Jim from Rake says:
+
+ Feel free to submit commits or feature requests. If you send a patch,
+ remember to update the corresponding unit tests. If fact, I prefer
+ new feature to be submitted in the form of new unit tests.
+
+For other information, feel free to ask on the ruby-talk mailing list (which
+is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. \ No newline at end of file
diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS
new file mode 100644
index 0000000000..c27ee02d67
--- /dev/null
+++ b/actionpack/RUNNING_UNIT_TESTS
@@ -0,0 +1,25 @@
+== Running with Rake
+
+The easiest way to run the unit tests is through Rake. The default task runs
+the entire test suite for all classes. For more information, checkout the
+full array of rake tasks with "rake -T"
+
+Rake can be found at http://rake.rubyforge.org
+
+== Running by hand
+
+If you only want to run a single test suite, or don't want to bother with Rake,
+you can do so with something like:
+
+ ruby controller/base_test.rb
+
+== Dependency on ActiveRecord and database setup
+
+Test cases in test/controller/active_record_assertions.rb depend on having
+activerecord installed and configured in a particular way. See comment in the
+test file itself for details. If ActiveRecord is not in
+actionpack/../activerecord directory, these tests are skipped. If activerecord
+is installed, but not configured as expected, the tests will fail.
+
+Other tests are runnable from a fresh copy of actionpack without any configuration.
+
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
new file mode 100755
index 0000000000..fb9f3c9bfb
--- /dev/null
+++ b/actionpack/Rakefile
@@ -0,0 +1,105 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/contrib/rubyforgepublisher'
+
+PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
+PKG_NAME = 'actionpack'
+PKG_VERSION = '0.9.5' + PKG_BUILD
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+
+desc "Default Task"
+task :default => [ :test ]
+
+# Run the unit tests
+
+Rake::TestTask.new { |t|
+ t.libs << "test"
+ t.pattern = 'test/*/*_test.rb'
+ t.verbose = true
+}
+
+
+# Genereate the RDoc documentation
+
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "Action Pack -- On rails from request to response"
+ rdoc.options << '--line-numbers --inline-source --main README'
+ rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+}
+
+
+# Create compressed packages
+
+
+dist_dirs = [ "lib", "test", "examples" ]
+
+spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = PKG_NAME
+ s.version = PKG_VERSION
+ s.summary = "Web-flow and rendering framework putting the VC in MVC."
+ s.description = %q{Eases web-request routing, handling, and response as a half-way front, half-way page controller. Implemented with specific emphasis on enabling easy unit/integration testing that doesn't require a browser.}
+
+ s.author = "David Heinemeier Hansson"
+ s.email = "david@loudthinking.com"
+ s.rubyforge_project = "actionpack"
+ s.homepage = "http://actionpack.rubyonrails.org"
+
+ s.has_rdoc = true
+ s.requirements << 'none'
+ s.require_path = 'lib'
+ s.autorequire = 'action_controller'
+
+ s.files = [ "rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE", "examples/.htaccess" ]
+ dist_dirs.each do |dir|
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "CVS" ) }
+ end
+ s.files.delete "examples/benchmark.rb"
+ s.files.delete "examples/benchmark_with_ar.fcgi"
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = true
+ p.need_zip = true
+end
+
+
+# Publish beta gem
+desc "Publish the API documentation"
+task :pgem => [:package] do
+ Rake::SshFilePublisher.new("davidhh@one.textdrive.com", "domains/rubyonrails.org/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh davidhh@one.textdrive.com './gemupdate.sh'`
+end
+
+# Publish documentation
+desc "Publish the API documentation"
+task :pdoc => [:rdoc] do
+ Rake::SshDirPublisher.new("davidhh@one.textdrive.com", "domains/rubyonrails.org/ap", "doc").upload
+end
+
+
+desc "Count lines in the main rake file"
+task :lines do
+ lines = 0
+ codelines = 0
+ Dir.foreach("lib/action_controller") { |file_name|
+ next unless file_name =~ /.*rb/
+
+ f = File.open("lib/action_controller/" + file_name)
+
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
+ }
+ puts "Lines #{lines}, LOC #{codelines}"
+end \ No newline at end of file
diff --git a/actionpack/examples/.htaccess b/actionpack/examples/.htaccess
new file mode 100644
index 0000000000..fb59fa105e
--- /dev/null
+++ b/actionpack/examples/.htaccess
@@ -0,0 +1,24 @@
+<IfModule mod_ruby.c>
+ RubyRequire apache/ruby-run
+ RubySafeLevel 0
+
+ <Files *.rbx>
+ SetHandler ruby-object
+ RubyHandler Apache::RubyRun.instance
+ </Files>
+</IfModule>
+
+
+RewriteEngine On
+RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.fcgi?action=$2&id=$3 [QSA]
+RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.fcgi?action=$2 [QSA]
+RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/$ /$1_controller.fcgi?action=index [QSA]
+
+RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.rbx?action=$2&id=$3 [QSA]
+RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.rbx?action=$2 [QSA]
+RewriteRule ^modruby/([-_a-zA-Z0-9]+)/$ /$1_controller.rbx?action=index [QSA]
+
+RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.cgi?action=$2&id=$3 [QSA]
+RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.cgi?action=$2 [QSA]
+RewriteRule ^([-_a-zA-Z0-9]+)/$ /$1_controller.cgi?action=index [QSA]
+
diff --git a/actionpack/examples/address_book/index.rhtml b/actionpack/examples/address_book/index.rhtml
new file mode 100644
index 0000000000..217d39075c
--- /dev/null
+++ b/actionpack/examples/address_book/index.rhtml
@@ -0,0 +1,33 @@
+<h1>Address Book</h1>
+
+<% if @people.empty? %>
+ <p>No people in the address book yet</p>
+<% else %>
+ <table>
+ <tr><th>Name</th><th>Email Address</th><th>Phone Number</th></tr>
+ <% for person in @people %>
+ <tr><td><%= person.name %></td><td><%= person.email_address %></td><td><%= person.phone_number %></td></tr>
+ <% end %>
+ </table>
+<% end %>
+
+<form action="create_person">
+ <p>
+ Name:<br />
+ <input type="text" name="person[name]">
+ </p>
+
+ <p>
+ Email address:<br />
+ <input type="text" name="person[email_address]">
+ </p>
+
+ <p>
+ Phone number:<br />
+ <input type="text" name="person[phone_number]">
+ </p>
+
+ <p>
+ <input type="submit" value="Create Person">
+ </p>
+</form> \ No newline at end of file
diff --git a/actionpack/examples/address_book/layout.rhtml b/actionpack/examples/address_book/layout.rhtml
new file mode 100644
index 0000000000..931e141c01
--- /dev/null
+++ b/actionpack/examples/address_book/layout.rhtml
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title><%= @title || "Untitled" %></title>
+</head>
+<body>
+<%= @content_for_layout %>
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/examples/address_book_controller.cgi b/actionpack/examples/address_book_controller.cgi
new file mode 100755
index 0000000000..2e15467285
--- /dev/null
+++ b/actionpack/examples/address_book_controller.cgi
@@ -0,0 +1,9 @@
+#!/usr/local/bin/ruby
+
+require "address_book_controller"
+
+begin
+ AddressBookController.process_cgi(CGI.new)
+rescue => e
+ CGI.new.out { "#{e.class}: #{e.message}" }
+end \ No newline at end of file
diff --git a/actionpack/examples/address_book_controller.fcgi b/actionpack/examples/address_book_controller.fcgi
new file mode 100755
index 0000000000..39947b4444
--- /dev/null
+++ b/actionpack/examples/address_book_controller.fcgi
@@ -0,0 +1,6 @@
+#!/usr/local/bin/ruby
+
+require "address_book_controller"
+require "fcgi"
+
+FCGI.each_cgi { |cgi| AddressBookController.process_cgi(cgi) } \ No newline at end of file
diff --git a/actionpack/examples/address_book_controller.rb b/actionpack/examples/address_book_controller.rb
new file mode 100644
index 0000000000..01d498e1bc
--- /dev/null
+++ b/actionpack/examples/address_book_controller.rb
@@ -0,0 +1,52 @@
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+
+require "action_controller"
+require "action_controller/test_process"
+
+Person = Struct.new("Person", :id, :name, :email_address, :phone_number)
+
+class AddressBookService
+ attr_reader :people
+
+ def initialize() @people = [] end
+ def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
+ def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
+ def next_person_id() people.first.id + 1 end
+end
+
+class AddressBookController < ActionController::Base
+ layout "address_book/layout"
+
+ before_filter :initialize_session_storage
+
+ # Could also have used a proc
+ # before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) }
+
+ def index
+ @title = "Address Book"
+ @people = @address_book.people
+ end
+
+ def person
+ @person = @address_book.find_person(@params["id"])
+ end
+
+ def create_person
+ @address_book.create_person(@params["person"])
+ redirect_to :action => "index"
+ end
+
+ private
+ def initialize_session_storage
+ @address_book = @session["address_book"] ||= AddressBookService.new
+ end
+end
+
+ActionController::Base.template_root = File.dirname(__FILE__)
+# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
+
+begin
+ AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
+rescue => e
+ CGI.new.out { "#{e.class}: #{e.message}" }
+end \ No newline at end of file
diff --git a/actionpack/examples/address_book_controller.rbx b/actionpack/examples/address_book_controller.rbx
new file mode 100644
index 0000000000..8c04eeccc8
--- /dev/null
+++ b/actionpack/examples/address_book_controller.rbx
@@ -0,0 +1,4 @@
+#!/usr/local/bin/ruby
+
+require "address_book_controller"
+AddressBookController.process_cgi(CGI.new) \ No newline at end of file
diff --git a/actionpack/examples/benchmark.rb b/actionpack/examples/benchmark.rb
new file mode 100644
index 0000000000..1e10a0c962
--- /dev/null
+++ b/actionpack/examples/benchmark.rb
@@ -0,0 +1,52 @@
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+
+require "action_controller"
+require 'action_controller/test_process'
+
+Person = Struct.new("Person", :name, :address, :age)
+
+class BenchmarkController < ActionController::Base
+ def message
+ render_text "hello world"
+ end
+
+ def list
+ @people = [ Person.new("David"), Person.new("Mary") ]
+ render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
+ end
+
+ def form_helper
+ @person = Person.new "david", "hyacintvej", 24
+ render_template(
+ "<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
+ "change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
+ )
+ end
+end
+
+#ActionController::Base.template_root = File.dirname(__FILE__)
+
+require "benchmark"
+
+RUNS = ARGV[0] ? ARGV[0].to_i : 50
+
+require "profile" if ARGV[1]
+
+runtime = Benchmark.measure {
+ RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
+}
+
+puts "List: #{RUNS / runtime.real}"
+
+
+runtime = Benchmark.measure {
+ RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
+}
+
+puts "Message: #{RUNS / runtime.real}"
+
+runtime = Benchmark.measure {
+ RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
+}
+
+puts "Form helper: #{RUNS / runtime.real}"
diff --git a/actionpack/examples/benchmark_with_ar.fcgi b/actionpack/examples/benchmark_with_ar.fcgi
new file mode 100755
index 0000000000..b9de370e24
--- /dev/null
+++ b/actionpack/examples/benchmark_with_ar.fcgi
@@ -0,0 +1,89 @@
+#!/usr/local/bin/ruby
+
+begin
+
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+$:.unshift(File.dirname(__FILE__) + "/../../../edge/activerecord/lib")
+
+require 'fcgi'
+require 'action_controller'
+require 'action_controller/test_process'
+
+require 'active_record'
+
+class Post < ActiveRecord::Base; end
+
+ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "basecamp")
+
+SESSION_OPTIONS = { "database_manager" => CGI::Session::MemoryStore }
+
+class TestController < ActionController::Base
+ def index
+ render_template <<-EOT
+ <% for post in Post.find_all(nil,nil,100) %>
+ <%= post.title %>
+ <% end %>
+ EOT
+ end
+
+ def show_one
+ render_template <<-EOT
+ <%= Post.find_first.title %>
+ EOT
+ end
+
+ def text
+ render_text "hello world"
+ end
+
+ def erb_text
+ render_template "hello <%= 'world' %>"
+ end
+
+ def erb_loop
+ render_template <<-EOT
+ <% for post in 1..100 %>
+ <%= post %>
+ <% end %>
+ EOT
+ end
+
+ def rescue_action(e) puts e.message + e.backtrace.join("\n") end
+end
+
+if ARGV.empty? && ENV["REQUEST_URI"]
+ FCGI.each_cgi do |cgi|
+ TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi)).out
+ end
+else
+ if ARGV.empty?
+ cgi = CGI.new
+ end
+
+ require 'benchmark'
+ require 'profile' if ARGV[2] == "profile"
+
+ RUNS = ARGV[1] ? ARGV[1].to_i : 50
+
+ runtime = Benchmark::measure {
+ RUNS.times {
+ if ARGV.empty?
+ TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi))
+ else
+ response = TestController.process_test(
+ ActionController::TestRequest.new({"action" => ARGV[0]})
+ )
+ puts(response.body) if ARGV[2] == "show"
+ end
+ }
+ }
+
+ puts "Runs: #{RUNS}"
+ puts "Avg. runtime: #{runtime.real / RUNS}"
+ puts "Requests/second: #{RUNS / runtime.real}"
+end
+
+rescue Exception => e
+ # CGI.new.out { "<pre>" + e.message + e.backtrace.join("\n") + "</pre>" }
+ $stderr << e.message + e.backtrace.join("\n")
+end \ No newline at end of file
diff --git a/actionpack/examples/blog_controller.cgi b/actionpack/examples/blog_controller.cgi
new file mode 100755
index 0000000000..e64fe85f0c
--- /dev/null
+++ b/actionpack/examples/blog_controller.cgi
@@ -0,0 +1,53 @@
+#!/usr/local/bin/ruby
+
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+
+require "action_controller"
+
+Post = Struct.new("Post", :title, :body)
+
+class BlogController < ActionController::Base
+ before_filter :initialize_session_storage
+
+ def index
+ @posts = @session["posts"]
+
+ render_template <<-"EOF"
+ <html><body>
+ <%= @flash["alert"] %>
+ <h1>Posts</h1>
+ <% @posts.each do |post| %>
+ <p><b><%= post.title %></b><br /><%= post.body %></p>
+ <% end %>
+
+ <h1>Create post</h1>
+ <form action="create">
+ Title: <input type="text" name="post[title]"><br>
+ Body: <textarea name="post[body]"></textarea><br>
+ <input type="submit" value="save">
+ </form>
+
+ </body></html>
+ EOF
+ end
+
+ def create
+ @session["posts"].unshift(Post.new(@params["post"]["title"], @params["post"]["body"]))
+ flash["alert"] = "New post added!"
+ redirect_to :action => "index"
+ end
+
+ private
+ def initialize_session_storage
+ @session["posts"] = [] if @session["posts"].nil?
+ end
+end
+
+ActionController::Base.template_root = File.dirname(__FILE__)
+# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
+
+begin
+ BlogController.process_cgi(CGI.new) if $0 == __FILE__
+rescue => e
+ CGI.new.out { "#{e.class}: #{e.message}" }
+end \ No newline at end of file
diff --git a/actionpack/examples/debate/index.rhtml b/actionpack/examples/debate/index.rhtml
new file mode 100644
index 0000000000..ddaa87da57
--- /dev/null
+++ b/actionpack/examples/debate/index.rhtml
@@ -0,0 +1,14 @@
+<html>
+<body>
+<h1>Topics</h1>
+
+<%= link_to "New topic", :action => "new_topic" %>
+
+<ul>
+<% for topic in @topics %>
+ <li><%= link_to "#{topic.title} (#{topic.replies.length} replies)", :action => "topic", :path_params => { "id" => topic.id } %></li>
+<% end %>
+</ul>
+
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/examples/debate/new_topic.rhtml b/actionpack/examples/debate/new_topic.rhtml
new file mode 100644
index 0000000000..f52a69cc31
--- /dev/null
+++ b/actionpack/examples/debate/new_topic.rhtml
@@ -0,0 +1,22 @@
+<html>
+<body>
+<h1>New topic</h1>
+
+<form action="<%= url_for(:action => "create_topic") %>" method="post">
+ <p>
+ Title:<br>
+ <input type="text" name="topic[title]">
+ </p>
+
+ <p>
+ Body:<br>
+ <textarea name="topic[body]" style="width: 200px; height: 200px"></textarea>
+ </p>
+
+ <p>
+ <input type="submit" value="Create topic">
+ </p>
+</form>
+
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/examples/debate/topic.rhtml b/actionpack/examples/debate/topic.rhtml
new file mode 100644
index 0000000000..e247c00f0d
--- /dev/null
+++ b/actionpack/examples/debate/topic.rhtml
@@ -0,0 +1,32 @@
+<html>
+<body>
+<h1><%= @topic.title %></h1>
+
+<p><%= @topic.body %></p>
+
+<%= link_to "Back to topics", :action => "index" %>
+
+<% unless @topic.replies.empty? %>
+ <h2>Replies</h2>
+ <ol>
+ <% for reply in @topic.replies %>
+ <li><%= reply.body %></li>
+ <% end %>
+ </ol>
+<% end %>
+
+<h2>Reply to this topic</h2>
+
+<form action="<%= url_for(:action => "create_reply") %>" method="post">
+ <input type="hidden" name="reply[topic_id]" value="<%= @topic.id %>">
+ <p>
+ <textarea name="reply[body]" style="width: 200px; height: 200px"></textarea>
+ </p>
+
+ <p>
+ <input type="submit" value="Create reply">
+ </p>
+</form>
+
+</body>
+</html> \ No newline at end of file
diff --git a/actionpack/examples/debate_controller.cgi b/actionpack/examples/debate_controller.cgi
new file mode 100755
index 0000000000..b82ac6259d
--- /dev/null
+++ b/actionpack/examples/debate_controller.cgi
@@ -0,0 +1,57 @@
+#!/usr/local/bin/ruby
+
+$:.unshift(File.dirname(__FILE__) + "/../lib")
+
+require "action_controller"
+
+Topic = Struct.new("Topic", :id, :title, :body, :replies)
+Reply = Struct.new("Reply", :body)
+
+class DebateService
+ attr_reader :topics
+
+ def initialize() @topics = [] end
+ def create_topic(data) topics.unshift(Topic.new(next_topic_id, data["title"], data["body"], [])) end
+ def create_reply(data) find_topic(data["topic_id"]).replies << Reply.new(data["body"]) end
+ def find_topic(topic_id) topics.select { |topic| topic.id == topic_id.to_i }.first end
+ def next_topic_id() topics.first.id + 1 end
+end
+
+class DebateController < ActionController::Base
+ before_filter :initialize_session_storage
+
+ def index
+ @topics = @debate.topics
+ end
+
+ def topic
+ @topic = @debate.find_topic(@params["id"])
+ end
+
+ # def new_topic() end <-- This is not needed as the template doesn't require any assigns
+
+ def create_topic
+ @debate.create_topic(@params["topic"])
+ redirect_to :action => "index"
+ end
+
+ def create_reply
+ @debate.create_reply(@params["reply"])
+ redirect_to :action => "topic", :path_params => { "id" => @params["reply"]["topic_id"] }
+ end
+
+ private
+ def initialize_session_storage
+ @session["debate"] = DebateService.new if @session["debate"].nil?
+ @debate = @session["debate"]
+ end
+end
+
+ActionController::Base.template_root = File.dirname(__FILE__)
+# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
+
+begin
+ DebateController.process_cgi(CGI.new) if $0 == __FILE__
+rescue => e
+ CGI.new.out { "#{e.class}: #{e.message}" }
+end \ No newline at end of file
diff --git a/actionpack/install.rb b/actionpack/install.rb
new file mode 100644
index 0000000000..758c476a70
--- /dev/null
+++ b/actionpack/install.rb
@@ -0,0 +1,97 @@
+require 'rbconfig'
+require 'find'
+require 'ftools'
+
+include Config
+
+# this was adapted from rdoc's install.rb by ways of Log4r
+
+$sitedir = CONFIG["sitelibdir"]
+unless $sitedir
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
+ if !$sitedir
+ $sitedir = File.join($libdir, "site_ruby")
+ elsif $sitedir !~ Regexp.quote(version)
+ $sitedir = File.join($sitedir, version)
+ end
+end
+
+makedirs = %w{ action_controller/assertions action_controller/cgi_ext
+ action_controller/session action_controller/support
+ action_controller/templates action_controller/templates/rescues
+ action_controller/templates/scaffolds
+ action_view/helpers action_view/vendor action_view/vendor/builder
+}
+
+
+makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
+
+# deprecated files that should be removed
+# deprecated = %w{ }
+
+# files to install in library path
+files = %w-
+ action_controller.rb
+ action_controller/assertions/action_pack_assertions.rb
+ action_controller/assertions/active_record_assertions.rb
+ action_controller/base.rb
+ action_controller/benchmarking.rb
+ action_controller/cgi_ext/cgi_ext.rb
+ action_controller/cgi_ext/cgi_methods.rb
+ action_controller/cgi_process.rb
+ action_controller/filters.rb
+ action_controller/flash.rb
+ action_controller/helpers.rb
+ action_controller/layout.rb
+ action_controller/request.rb
+ action_controller/rescue.rb
+ action_controller/response.rb
+ action_controller/scaffolding.rb
+ action_controller/session/active_record_store.rb
+ action_controller/session/drb_server.rb
+ action_controller/session/drb_store.rb
+ action_controller/support/class_inheritable_attributes.rb
+ action_controller/support/class_attribute_accessors.rb
+ action_controller/support/clean_logger.rb
+ action_controller/support/cookie_performance_fix.rb
+ action_controller/support/inflector.rb
+ action_controller/templates/rescues/_request_and_response.rhtml
+ action_controller/templates/rescues/diagnostics.rhtml
+ action_controller/templates/rescues/layout.rhtml
+ action_controller/templates/rescues/missing_template.rhtml
+ action_controller/templates/rescues/template_error.rhtml
+ action_controller/templates/rescues/unknown_action.rhtml
+ action_controller/templates/scaffolds/edit.rhtml
+ action_controller/templates/scaffolds/layout.rhtml
+ action_controller/templates/scaffolds/list.rhtml
+ action_controller/templates/scaffolds/new.rhtml
+ action_controller/templates/scaffolds/show.rhtml
+ action_controller/test_process.rb
+ action_controller/url_rewriter.rb
+ action_view.rb
+ action_view/base.rb
+ action_view/helpers/active_record_helper.rb
+ action_view/helpers/date_helper.rb
+ action_view/helpers/debug_helper.rb
+ action_view/helpers/form_helper.rb
+ action_view/helpers/form_options_helper.rb
+ action_view/helpers/text_helper.rb
+ action_view/helpers/tag_helper.rb
+ action_view/helpers/url_helper.rb
+ action_view/partials.rb
+ action_view/template_error.rb
+ action_view/vendor/builder.rb
+ action_view/vendor/builder/blankslate.rb
+ action_view/vendor/builder/xmlbase.rb
+ action_view/vendor/builder/xmlevents.rb
+ action_view/vendor/builder/xmlmarkup.rb
+-
+
+# the acual gruntwork
+Dir.chdir("lib")
+# File::safe_unlink *deprecated.collect{|f| File.join($sitedir, f.split(/\//))}
+files.each {|f|
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
+}
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
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
new file mode 100644
index 0000000000..0fcb5e852d
--- /dev/null
+++ b/actionpack/test/abstract_unit.rb
@@ -0,0 +1,9 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+
+require 'test/unit'
+require 'action_controller'
+
+require 'action_controller/test_process'
+
+ActionController::Base.logger = nil
+ActionController::Base.ignore_missing_templates = true \ No newline at end of file
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
new file mode 100644
index 0000000000..6d727be5a2
--- /dev/null
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -0,0 +1,323 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+# a controller class to facilitate the tests
+class ActionPackAssertionsController < ActionController::Base
+
+ # this does absolutely nothing
+ def nothing() render_text ""; end
+
+ # a standard template
+ def hello_world() render "test/hello_world"; end
+
+ # a standard template
+ def hello_xml_world() render "test/hello_xml_world"; end
+
+ # a redirect to an internal location
+ def redirect_internal() redirect_to "nothing"; end
+
+ # a redirect to an external location
+ def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end
+
+ # a 404
+ def response404() render_text "", "404 AWOL"; end
+
+ # a 500
+ def response500() render_text "", "500 Sorry"; end
+
+ # a fictional 599
+ def response599() render_text "", "599 Whoah!"; end
+
+ # putting stuff in the flash
+ def flash_me
+ flash['hello'] = 'my name is inigo montoya...'
+ render_text "Inconceivable!"
+ end
+
+ # we have a flash, but nothing is in it
+ def flash_me_naked
+ flash.clear
+ render_text "wow!"
+ end
+
+ # assign some template instance variables
+ def assign_this
+ @howdy = "ho"
+ render_text "Mr. Henke"
+ end
+
+ def render_based_on_parameters
+ render_text "Mr. #{@params["name"]}"
+ end
+
+ # puts something in the session
+ def session_stuffing
+ session['xmas'] = 'turkey'
+ render_text "ho ho ho"
+ end
+
+ # 911
+ def rescue_action(e) raise; end
+
+end
+
+# ---------------------------------------------------------------------------
+
+
+# tell the controller where to find its templates but start from parent
+# directory of test_request_response to simulate the behaviour of a
+# production environment
+ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fixtures/"
+
+
+# a test case to exercise the new capabilities TestRequest & TestResponse
+class ActionPackAssertionsControllerTest < Test::Unit::TestCase
+ # let's get this party started
+ def setup
+ @controller = ActionPackAssertionsController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ # -- assertion-based testing ------------------------------------------------
+
+ # test the session assertion to make sure something is there.
+ def test_assert_session_has
+ process :session_stuffing
+ assert_session_has 'xmas'
+ assert_session_has_no 'halloween'
+ end
+
+ # test the assertion of goodies in the template
+ def test_assert_template_has
+ process :assign_this
+ assert_template_has 'howdy'
+ end
+
+ # test the assertion for goodies that shouldn't exist in the template
+ def test_assert_template_has_no
+ process :nothing
+ assert_template_has_no 'maple syrup'
+ assert_template_has_no 'howdy'
+ end
+
+ # test the redirection assertions
+ def test_assert_redirect
+ process :redirect_internal
+ assert_redirect
+ end
+
+ # test the redirect url string
+ def test_assert_redirect_url
+ process :redirect_external
+ assert_redirect_url 'http://www.rubyonrails.org'
+ end
+
+ # test the redirection pattern matching on a string
+ def test_assert_redirect_url_match_string
+ process :redirect_external
+ assert_redirect_url_match 'rails.org'
+ end
+
+ # test the redirection pattern matching on a pattern
+ def test_assert_redirect_url_match_pattern
+ process :redirect_external
+ assert_redirect_url_match /ruby/
+ end
+
+ # test the flash-based assertions with something is in the flash
+ def test_flash_assertions_full
+ process :flash_me
+ assert @response.has_flash_with_contents?
+ assert_flash_exists
+ assert ActionController::TestResponse.assertion_target.has_flash_with_contents?
+ assert_flash_not_empty
+ assert_flash_has 'hello'
+ assert_flash_has_no 'stds'
+ end
+
+ # test the flash-based assertions with no flash at all
+ def test_flash_assertions_negative
+ process :nothing
+ assert_flash_not_exists
+ assert_flash_empty
+ assert_flash_has_no 'hello'
+ assert_flash_has_no 'qwerty'
+ end
+
+ # test the assert_rendered_file
+ def test_assert_rendered_file
+ process :hello_world
+ assert_rendered_file 'test/hello_world'
+ assert_rendered_file 'hello_world'
+ assert_rendered_file
+ end
+
+ # test the assert_success assertion
+ def test_assert_success
+ process :nothing
+ assert_success
+ end
+
+ # -- standard request/reponse object testing --------------------------------
+
+ # ensure our session is working properly
+ def test_session_objects
+ process :session_stuffing
+ assert @response.has_session_object?('xmas')
+ assert_session_equal 'turkey', 'xmas'
+ assert !@response.has_session_object?('easter')
+ end
+
+ # make sure that the template objects exist
+ def test_template_objects_alive
+ process :assign_this
+ assert !@response.has_template_object?('hi')
+ assert @response.has_template_object?('howdy')
+ end
+
+ # make sure we don't have template objects when we shouldn't
+ def test_template_object_missing
+ process :nothing
+ assert_nil @response.template_objects['howdy']
+ end
+
+ def test_assigned_equal
+ process :assign_this
+ assert_assigned_equal "ho", :howdy
+ end
+
+ # check the empty flashing
+ def test_flash_me_naked
+ process :flash_me_naked
+ assert @response.has_flash?
+ assert !@response.has_flash_with_contents?
+ end
+
+ # check if we have flash objects
+ def test_flash_haves
+ process :flash_me
+ assert @response.has_flash?
+ assert @response.has_flash_with_contents?
+ assert @response.has_flash_object?('hello')
+ end
+
+ # ensure we don't have flash objects
+ def test_flash_have_nots
+ process :nothing
+ assert !@response.has_flash?
+ assert !@response.has_flash_with_contents?
+ assert_nil @response.flash['hello']
+ end
+
+ # examine that the flash objects are what we expect
+ def test_flash_equals
+ process :flash_me
+ assert_flash_equal 'my name is inigo montoya...', 'hello'
+ end
+
+
+ # check if we were rendered by a file-based template?
+ def test_rendered_action
+ process :nothing
+ assert !@response.rendered_with_file?
+
+ process :hello_world
+ assert @response.rendered_with_file?
+ assert 'hello_world', @response.rendered_file
+ end
+
+ # check the redirection location
+ def test_redirection_location
+ process :redirect_internal
+ assert_equal 'nothing', @response.redirect_url
+
+ process :redirect_external
+ assert_equal 'http://www.rubyonrails.org', @response.redirect_url
+
+ process :nothing
+ assert_nil @response.redirect_url
+ end
+
+
+ # check server errors
+ def test_server_error_response_code
+ process :response500
+ assert @response.server_error?
+
+ process :response599
+ assert @response.server_error?
+
+ process :response404
+ assert !@response.server_error?
+ end
+
+ # check a 404 response code
+ def test_missing_response_code
+ process :response404
+ assert @response.missing?
+ end
+
+ # check to see if our redirection matches a pattern
+ def test_redirect_url_match
+ process :redirect_external
+ assert @response.redirect?
+ assert @response.redirect_url_match?("rubyonrails")
+ assert @response.redirect_url_match?(/rubyonrails/)
+ assert !@response.redirect_url_match?("phpoffrails")
+ assert !@response.redirect_url_match?(/perloffrails/)
+ end
+
+ # check for a redirection
+ def test_redirection
+ process :redirect_internal
+ assert @response.redirect?
+
+ process :redirect_external
+ assert @response.redirect?
+
+ process :nothing
+ assert !@response.redirect?
+ end
+
+ # check a successful response code
+ def test_successful_response_code
+ process :nothing
+ assert @response.success?
+ end
+
+ # a basic check to make sure we have a TestResponse object
+ def test_has_response
+ process :nothing
+ assert_kind_of ActionController::TestResponse, @response
+ end
+
+ def test_render_based_on_parameters
+ process :render_based_on_parameters, "name" => "David"
+ assert_equal "Mr. David", @response.body
+ end
+
+ def test_simple_one_element_xpath_match
+ process :hello_xml_world
+ assert_template_xpath_match('//title', "Hello World")
+ end
+
+ def test_array_of_elements_in_xpath_match
+ process :hello_xml_world
+ assert_template_xpath_match('//p', %w( abes monks wiseguys ))
+ end
+end
+
+class ActionPackHeaderTest < Test::Unit::TestCase
+ def setup
+ @controller = ActionPackAssertionsController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+ def test_rendering_xml_sets_content_type
+ process :hello_xml_world
+ assert_equal('text/xml', @controller.headers['Content-Type'])
+ end
+ def test_rendering_xml_respects_content_type
+ @response.headers['Content-Type'] = 'application/pdf'
+ process :hello_xml_world
+ assert_equal('application/pdf', @controller.headers['Content-Type'])
+ end
+end
diff --git a/actionpack/test/controller/active_record_assertions_test.rb b/actionpack/test/controller/active_record_assertions_test.rb
new file mode 100644
index 0000000000..53106aaee7
--- /dev/null
+++ b/actionpack/test/controller/active_record_assertions_test.rb
@@ -0,0 +1,119 @@
+path_to_ar = File.dirname(__FILE__) + '/../../../activerecord'
+
+if Object.const_defined?("ActiveRecord") || File.exist?(path_to_ar)
+# This test is very different than the others. It requires ActiveRecord to
+# run. There's a bunch of stuff we are assuming here:
+#
+# 1. activerecord exists as a sibling directory to actionpack
+# (i.e., actionpack/../activerecord)
+# 2. you've created the appropriate database to run the active_record unit tests
+# 3. you set the appropriate database connection below
+
+driver_to_use = 'native_sqlite'
+
+$: << path_to_ar + '/lib/'
+$: << path_to_ar + '/test/'
+require 'active_record' unless Object.const_defined?("ActiveRecord")
+require "connections/#{driver_to_use}/connection"
+require 'fixtures/company'
+
+# -----------------------------------------------------------------------------
+
+# add some validation rules to trip up the assertions
+class Company
+ def validate
+ errors.add_on_empty('name')
+ errors.add('rating', 'rating should not be 2') if rating == 2
+ errors.add_to_base('oh oh') if rating == 3
+ end
+end
+
+# -----------------------------------------------------------------------------
+
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+# a controller class to handle the AR assertions
+class ActiveRecordAssertionsController < ActionController::Base
+ # fail with 1 bad column
+ def nasty_columns_1
+ @company = Company.new
+ @company.name = "B"
+ @company.rating = 2
+ render_text "snicker...."
+ end
+
+ # fail with 2 bad column
+ def nasty_columns_2
+ @company = Company.new
+ @company.name = ""
+ @company.rating = 2
+ render_text "double snicker...."
+ end
+
+ # this will pass validation
+ def good_company
+ @company = Company.new
+ @company.name = "A"
+ @company.rating = 69
+ render_text "Goodness Gracious!"
+ end
+
+ # this will fail validation
+ def bad_company
+ @company = Company.new
+ render_text "Who's Bad?"
+ end
+
+ # the safety dance......
+ def rescue_action(e) raise; end
+end
+
+# -----------------------------------------------------------------------------
+
+ActiveRecordAssertionsController.template_root = File.dirname(__FILE__) + "/../fixtures/"
+
+# The test case to try the AR assertions
+class ActiveRecordAssertionsControllerTest < Test::Unit::TestCase
+ # set it up
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = ActiveRecordAssertionsController.new
+ end
+
+ # test for 1 bad apple column
+ def test_some_invalid_columns
+ process :nasty_columns_1
+ assert_success
+ assert_invalid_record 'company'
+ assert_invalid_column_on_record 'company', 'rating'
+ assert_valid_column_on_record 'company', 'name'
+ assert_valid_column_on_record 'company', ['name','id']
+ end
+
+ # test for 2 bad apples columns
+ def test_all_invalid_columns
+ process :nasty_columns_2
+ assert_success
+ assert_invalid_record 'company'
+ assert_invalid_column_on_record 'company', 'rating'
+ assert_invalid_column_on_record 'company', 'name'
+ assert_invalid_column_on_record 'company', ['name','rating']
+ end
+
+ # ensure we have no problems with an ActiveRecord
+ def test_valid_record
+ process :good_company
+ assert_success
+ assert_valid_record 'company'
+ end
+
+ # ensure we have problems with an ActiveRecord
+ def test_invalid_record
+ process :bad_company
+ assert_success
+ assert_invalid_record 'company'
+ end
+end
+
+end \ No newline at end of file
diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb
new file mode 100755
index 0000000000..46e24ab403
--- /dev/null
+++ b/actionpack/test/controller/cgi_test.rb
@@ -0,0 +1,142 @@
+$:.unshift(File.dirname(__FILE__) + '/../../lib')
+
+require 'test/unit'
+require 'action_controller/cgi_ext/cgi_methods'
+require 'stringio'
+
+class MockUploadedFile < StringIO
+ def content_type
+ "img/jpeg"
+ end
+
+ def original_filename
+ "my_file.doc"
+ end
+end
+
+class CGITest < Test::Unit::TestCase
+ def setup
+ @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
+ @query_string_with_nil = "action=create_customer&full_name="
+ @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
+ @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
+ @query_string_with_multiple_of_same_name =
+ "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
+ end
+
+ def test_query_string
+ assert_equal(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
+ CGIMethods.parse_query_parameters(@query_string)
+ )
+ end
+
+ def test_query_string_with_nil
+ assert_equal(
+ { "action" => "create_customer", "full_name" => nil},
+ CGIMethods.parse_query_parameters(@query_string_with_nil)
+ )
+ end
+
+ def test_query_string_with_array
+ assert_equal(
+ { "action" => "create_customer", "selected" => ["1", "2", "3"]},
+ CGIMethods.parse_query_parameters(@query_string_with_array)
+ )
+ end
+
+ def test_query_string_with_amps
+ assert_equal(
+ { "action" => "create_customer", "name" => "Don't & Does"},
+ CGIMethods.parse_query_parameters(@query_string_with_amps)
+ )
+ end
+
+ def test_parse_params
+ input = {
+ "customers[boston][first][name]" => [ "David" ],
+ "customers[boston][first][url]" => [ "http://David" ],
+ "customers[boston][second][name]" => [ "Allan" ],
+ "customers[boston][second][url]" => [ "http://Allan" ],
+ "something_else" => [ "blah" ],
+ "something_nil" => [ nil ],
+ "something_empty" => [ "" ],
+ "products[first]" => [ "Apple Computer" ],
+ "products[second]" => [ "Pc" ]
+ }
+
+ expected_output = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David",
+ "url" => "http://David"
+ },
+ "second" => {
+ "name" => "Allan",
+ "url" => "http://Allan"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "something_empty" => "",
+ "something_nil" => "",
+ "products" => {
+ "first" => "Apple Computer",
+ "second" => "Pc"
+ }
+ }
+
+ assert_equal expected_output, CGIMethods.parse_request_parameters(input)
+ end
+
+ def test_parse_params_from_multipart_upload
+ mock_file = MockUploadedFile.new
+
+ input = {
+ "something" => [ StringIO.new("") ],
+ "products[string]" => [ StringIO.new("Apple Computer") ],
+ "products[file]" => [ mock_file ]
+ }
+
+ expected_output = {
+ "something" => "",
+ "products" => {
+ "string" => "Apple Computer",
+ "file" => mock_file
+ }
+ }
+
+ assert_equal expected_output, CGIMethods.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_file
+ input = {
+ "customers[boston][first][name]" => [ "David" ],
+ "something_else" => [ "blah" ],
+ "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
+ }
+
+ expected_output = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
+ }
+
+ assert_equal expected_output, CGIMethods.parse_request_parameters(input)
+ end
+
+ def test_parse_params_with_array
+ input = { "selected[]" => [ "1", "2", "3" ] }
+
+ expected_output = { "selected" => [ "1", "2", "3" ] }
+
+ assert_equal expected_output, CGIMethods.parse_request_parameters(input)
+ end
+end
diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb
new file mode 100644
index 0000000000..d3099bcd99
--- /dev/null
+++ b/actionpack/test/controller/cookie_test.rb
@@ -0,0 +1,38 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class CookieTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def authenticate
+ cookie "name" => "user_name", "value" => "david"
+ render_text "hello world"
+ end
+
+ def access_frozen_cookies
+ @cookies["wont"] = "work"
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_setting_cookie
+ @request.action = "authenticate"
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
+ end
+
+ def test_setting_cookie
+ @request.action = "access_frozen_cookies"
+ assert_raises(TypeError) { process_request }
+ end
+
+ private
+ def process_request
+ TestController.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
new file mode 100644
index 0000000000..f4d7a689b5
--- /dev/null
+++ b/actionpack/test/controller/filters_test.rb
@@ -0,0 +1,159 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class FilterTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ before_filter :ensure_login
+
+ def show
+ render_text "ran action"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+ end
+
+ class PrependingController < TestController
+ prepend_before_filter :wonderful_life
+
+ private
+ def wonderful_life
+ @ran_filter ||= []
+ @ran_filter << "wonderful_life"
+ end
+ end
+
+ class ProcController < PrependingController
+ before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
+ end
+
+ class ImplicitProcController < PrependingController
+ before_filter { |c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class AuditFilter
+ def self.filter(controller)
+ controller.assigns["was_audited"] = true
+ end
+ end
+
+ class AroundFilter
+ def before(controller)
+ @execution_log = "before"
+ controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
+ controller.assigns["before_ran"] = true
+ end
+
+ def after(controller)
+ controller.assigns["execution_log"] = @execution_log + " and after"
+ controller.assigns["after_ran"] = true
+ controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
+ end
+ end
+
+ class AppendedAroundFilter
+ def before(controller)
+ controller.class.execution_log << " before appended aroundfilter "
+ end
+
+ def after(controller)
+ controller.class.execution_log << " after appended aroundfilter "
+ end
+ end
+
+ class AuditController < ActionController::Base
+ before_filter(AuditFilter)
+
+ def show
+ render_text "hello"
+ end
+ end
+
+ class BadFilterController < ActionController::Base
+ before_filter 2
+
+ def show() "show" end
+
+ protected
+ def rescue_action(e) raise(e) end
+ end
+
+ class AroundFilterController < PrependingController
+ around_filter AroundFilter.new
+ end
+
+ class MixedFilterController < PrependingController
+ cattr_accessor :execution_log
+ def initialize
+ @@execution_log = ""
+ end
+
+ before_filter { |c| c.class.execution_log << " before procfilter " }
+ prepend_around_filter AroundFilter.new
+
+ after_filter { |c| c.class.execution_log << " after procfilter " }
+ append_around_filter AppendedAroundFilter.new
+ end
+
+
+ def test_added_filter_to_inheritance_graph
+ assert_equal [ :fire_flash, :ensure_login ], TestController.before_filters
+ end
+
+ def test_base_class_in_isolation
+ assert_equal [ :fire_flash ], ActionController::Base.before_filters
+ end
+
+ def test_prepending_filter
+ assert_equal [ :wonderful_life, :fire_flash, :ensure_login ], PrependingController.before_filters
+ end
+
+ def test_running_filters
+ assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
+ end
+
+ def test_running_filters_with_proc
+ assert test_process(ProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_implicit_proc
+ assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_class
+ assert test_process(AuditController).template.assigns["was_audited"]
+ end
+
+ def test_bad_filter
+ assert_raises(ActionController::ActionControllerError) {
+ test_process(BadFilterController)
+ }
+ end
+
+ def test_around_filter
+ controller = test_process(AroundFilterController)
+ assert controller.template.assigns["before_ran"]
+ assert controller.template.assigns["after_ran"]
+ end
+
+ def test_having_properties_in_around_filter
+ controller = test_process(AroundFilterController)
+ assert_equal "before and after", controller.template.assigns["execution_log"]
+ end
+
+ def test_prepending_and_appending_around_filter
+ controller = test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after aroundfilter after procfilter ",
+ MixedFilterController.execution_log
+ end
+
+ private
+ def test_process(controller)
+ request = ActionController::TestRequest.new
+ request.action = "show"
+ controller.process(request, ActionController::TestResponse.new)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
new file mode 100644
index 0000000000..033477fe39
--- /dev/null
+++ b/actionpack/test/controller/flash_test.rb
@@ -0,0 +1,69 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class FlashTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def set_flash
+ flash["that"] = "hello"
+ render_text "hello"
+ end
+
+ def use_flash
+ @flashy = flash["that"]
+ render_text "hello"
+ end
+
+ def use_flash_and_keep_it
+ @flashy = flash["that"]
+ keep_flash
+ render_text "hello"
+ end
+
+ def rescue_action(e)
+ raise unless ActionController::MissingTemplate === e
+ end
+ end
+
+ def setup
+ initialize_request_and_response
+ end
+
+ def test_flash
+ @request.action = "set_flash"
+ response = process_request
+
+ @request.action = "use_flash"
+ first_response = process_request
+ assert_equal "hello", first_response.template.assigns["flash"]["that"]
+ assert_equal "hello", first_response.template.assigns["flashy"]
+
+ second_response = process_request
+ assert_nil second_response.template.assigns["flash"]["that"], "On second flash"
+ end
+
+ def test_keep_flash
+ @request.action = "set_flash"
+ response = process_request
+
+ @request.action = "use_flash_and_keep_it"
+ first_response = process_request
+ assert_equal "hello", first_response.template.assigns["flash"]["that"]
+ assert_equal "hello", first_response.template.assigns["flashy"]
+
+ @request.action = "use_flash"
+ second_response = process_request
+ assert_equal "hello", second_response.template.assigns["flash"]["that"], "On second flash"
+
+ third_response = process_request
+ assert_nil third_response.template.assigns["flash"]["that"], "On third flash"
+ end
+
+ private
+ def initialize_request_and_response
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def process_request
+ TestController.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
new file mode 100644
index 0000000000..9d1da53241
--- /dev/null
+++ b/actionpack/test/controller/helper_test.rb
@@ -0,0 +1,110 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class HelperTest < Test::Unit::TestCase
+ HELPER_PATHS = %w(/../fixtures/helpers)
+
+ class TestController < ActionController::Base
+ attr_accessor :delegate_attr
+ def delegate_method() end
+ def rescue_action(e) raise end
+ end
+
+ module LocalAbcHelper
+ def a() end
+ def b() end
+ def c() end
+ end
+
+
+ def setup
+ # Increment symbol counter.
+ @symbol = (@@counter ||= 'A0').succ!.dup
+
+ # Generate new controller class.
+ controller_class_name = "Helper#{@symbol}Controller"
+ eval("class #{controller_class_name} < TestController; end")
+ @controller_class = self.class.const_get(controller_class_name)
+
+ # Generate new template class and assign to controller.
+ template_class_name = "Test#{@symbol}View"
+ eval("class #{template_class_name} < ActionView::Base; end")
+ @template_class = self.class.const_get(template_class_name)
+ @controller_class.template_class = @template_class
+
+ # Add helper paths to LOAD_PATH.
+ HELPER_PATHS.each { |path|
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + path)
+ }
+
+ # Set default test helper.
+ self.test_helper = LocalAbcHelper
+ end
+
+ def teardown
+ # Reset template class.
+ #ActionController::Base.template_class = ActionView::Base
+
+ # Remove helper paths from LOAD_PATH.
+ HELPER_PATHS.each { |path|
+ $LOAD_PATH.delete(File.dirname(__FILE__) + path)
+ }
+ end
+
+
+ def test_deprecated_helper
+ assert_equal helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper TestHelper }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_helper
+ require 'abc_helper'
+ self.test_helper = AbcHelper
+ assert_equal helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper :abc }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_missing_helper
+ assert_equal helper_methods, missing_methods
+ assert_raise(LoadError) { @controller_class.helper :missing }
+ end
+
+ def test_helper_block
+ assert_nothing_raised {
+ @controller_class.helper { def block_helper_method; end }
+ }
+ assert template_methods.include?('block_helper_method')
+ end
+
+ def test_helper_block_include
+ assert_equal helper_methods, missing_methods
+ assert_nothing_raised {
+ @controller_class.helper { include TestHelper }
+ }
+ assert [], missing_methods
+ end
+
+ def test_helper_method
+ assert_nothing_raised { @controller_class.helper_method :delegate_method }
+ assert template_methods.include?('delegate_method')
+ end
+
+ def test_helper_attr
+ assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
+ assert template_methods.include?('delegate_attr')
+ assert template_methods.include?('delegate_attr=')
+ end
+
+
+ private
+ def helper_methods; TestHelper.instance_methods end
+ def template_methods; @template_class.instance_methods end
+ def missing_methods; helper_methods - template_methods end
+
+ def test_helper=(helper_module)
+ old_verbose, $VERBOSE = $VERBOSE, nil
+ self.class.const_set('TestHelper', helper_module)
+ $VERBOSE = old_verbose
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
new file mode 100644
index 0000000000..f652453ebd
--- /dev/null
+++ b/actionpack/test/controller/layout_test.rb
@@ -0,0 +1,49 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class TestLayoutController < ActionController::Base
+ layout "layouts/standard"
+
+ def hello_world
+ end
+
+ def hello_world_outside_layout
+ end
+
+ def rescue_action(e) raise end
+end
+
+class ChildWithoutTestLayoutController < TestLayoutController
+ layout nil
+
+ def hello_world
+ end
+end
+
+class ChildWithOtherTestLayoutController < TestLayoutController
+ layout nil
+
+ def hello_world
+ end
+end
+
+class RenderTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_layout_rendering
+ @request.action = "hello_world"
+ response = process_request
+ assert_equal "200 OK", response.headers["Status"]
+ assert_equal "layouts/standard", response.template.template_name
+ end
+
+
+ private
+ def process_request
+ TestLayoutController.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
new file mode 100755
index 0000000000..6302016a53
--- /dev/null
+++ b/actionpack/test/controller/redirect_test.rb
@@ -0,0 +1,44 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class RedirectTest < Test::Unit::TestCase
+ class RedirectController < ActionController::Base
+ def simple_redirect
+ redirect_to :action => "hello_world"
+ end
+
+ def method_redirect
+ redirect_to :dashbord_url, 1, "hello"
+ end
+
+ def rescue_errors(e) raise e end
+
+ protected
+ def dashbord_url(id, message)
+ url_for :action => "dashboard", :params => { "id" => id, "message" => message }
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_simple_redirect
+ @request.path = "/redirect/simple_redirect"
+ @request.action = "simple_redirect"
+ response = process_request
+ assert_equal "http://test.host/redirect/hello_world", response.headers["location"]
+ end
+
+ def test_redirect_with_method_reference_and_parameters
+ @request.path = "/redirect/method_redirect"
+ @request.action = "method_redirect"
+ response = process_request
+ assert_equal "http://test.host/redirect/dashboard?message=hello&id=1", response.headers["location"]
+ end
+
+ private
+ def process_request
+ RedirectController.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
new file mode 100644
index 0000000000..ce778e1d7d
--- /dev/null
+++ b/actionpack/test/controller/render_test.rb
@@ -0,0 +1,178 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+Customer = Struct.new("Customer", :name)
+
+class RenderTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ layout :determine_layout
+
+ def hello_world
+ end
+
+ def render_hello_world
+ render "test/hello_world"
+ end
+
+ def render_hello_world_from_variable
+ @person = "david"
+ render_text "hello #{@person}"
+ end
+
+ def render_action_hello_world
+ render_action "hello_world"
+ end
+
+ def render_text_hello_world
+ render_text "hello world"
+ end
+
+ def render_custom_code
+ render_text "hello world", "404 Moved"
+ end
+
+ def render_xml_hello
+ @name = "David"
+ render "test/hello"
+ end
+
+ def greeting
+ # let's just rely on the template
+ end
+
+ def layout_test
+ render_action "hello_world"
+ end
+
+ def builder_layout_test
+ render_action "hello"
+ end
+
+ def partials_list
+ @customers = [ Customer.new("david"), Customer.new("mary") ]
+ render_action "list"
+ end
+
+ def modgreet
+ end
+
+ def rescue_action(e) raise end
+
+ private
+ def determine_layout
+ case action_name
+ when "layout_test": "layouts/standard"
+ when "builder_layout_test": "layouts/builder"
+ end
+ end
+ end
+
+ TestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
+
+ class TestLayoutController < ActionController::Base
+ layout "layouts/standard"
+
+ def hello_world
+ end
+
+ def hello_world_outside_layout
+ end
+
+ def rescue_action(e)
+ raise unless ActionController::MissingTemplate === e
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_simple_show
+ @request.action = "hello_world"
+ response = process_request
+ assert_equal "200 OK", response.headers["Status"]
+ assert_equal "test/hello_world", response.template.first_render
+ end
+
+ def test_do_with_render
+ @request.action = "render_hello_world"
+ assert_equal "test/hello_world", process_request.template.first_render
+ end
+
+ def test_do_with_render_from_variable
+ @request.action = "render_hello_world_from_variable"
+ assert_equal "hello david", process_request.body
+ end
+
+ def test_do_with_render_action
+ @request.action = "render_action_hello_world"
+ assert_equal "test/hello_world", process_request.template.first_render
+ end
+
+ def test_do_with_render_text
+ @request.action = "render_text_hello_world"
+ assert_equal "hello world", process_request.body
+ end
+
+ def test_do_with_render_custom_code
+ @request.action = "render_custom_code"
+ assert_equal "404 Moved", process_request.headers["Status"]
+ end
+
+ def test_attempt_to_access_object_method
+ @request.action = "clone"
+ assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { process_request }
+ end
+
+ def test_access_to_request_in_view
+ ActionController::Base.view_controller_internals = false
+
+ @request.action = "hello_world"
+ response = process_request
+ assert_nil response.template.assigns["request"]
+
+ ActionController::Base.view_controller_internals = true
+
+ @request.action = "hello_world"
+ response = process_request
+ assert_kind_of ActionController::AbstractRequest, response.template.assigns["request"]
+ end
+
+ def test_render_xml
+ @request.action = "render_xml_hello"
+ assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", process_request.body
+ end
+
+ def test_render_xml_with_default
+ @request.action = "greeting"
+ assert_equal "<p>This is grand!</p>\n", process_request.body
+ end
+
+ def test_layout_rendering
+ @request.action = "layout_test"
+ assert_equal "<html>Hello world!</html>", process_request.body
+ end
+
+ def test_render_xml_with_layouts
+ @request.action = "builder_layout_test"
+ assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", process_request.body
+ end
+
+ def test_partials_list
+ @request.action = "partials_list"
+ assert_equal "Hello: davidHello: mary", process_request.body
+ end
+
+ def test_module_rendering
+ @request.action = "modgreet"
+ @request.parameters["module"] = "scope"
+ assert_equal "<p>Beautiful modules!</p>", process_request.body
+ end
+
+ private
+ def process_request
+ TestController.process(@request, @response)
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
new file mode 100644
index 0000000000..57205a01c5
--- /dev/null
+++ b/actionpack/test/controller/send_file_test.rb
@@ -0,0 +1,68 @@
+require File.join(File.dirname(__FILE__), '..', 'abstract_unit')
+
+
+module TestFileUtils
+ def file_name() File.basename(__FILE__) end
+ def file_path() File.expand_path(__FILE__) end
+ def file_data() File.open(file_path, 'rb') { |f| f.read } end
+end
+
+
+class SendFileController < ActionController::Base
+ include TestFileUtils
+
+ attr_writer :options
+ def options() @options ||= {} end
+
+ def file() send_file(file_path, options) end
+ def data() send_data(file_data, options) end
+
+ def rescue_action(e) raise end
+end
+
+
+class SendFileTest < Test::Unit::TestCase
+ include TestFileUtils
+
+ def setup
+ @controller = SendFileController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_file_nostream
+ @controller.options = { :stream => false }
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+
+ def test_file_stream
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_kind_of Proc, response.body
+
+ old_stdout = $stdout
+ begin
+ require 'stringio'
+ $stdout = StringIO.new
+ $stdout.binmode
+ assert_nothing_raised { response.body.call }
+ assert_equal file_data, $stdout.string
+ ensure
+ $stdout = old_stdout
+ end
+ end
+
+ def test_data
+ response = nil
+ assert_nothing_raised { response = process('data') }
+ assert_not_nil response
+
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+end
diff --git a/actionpack/test/controller/url_test.rb b/actionpack/test/controller/url_test.rb
new file mode 100644
index 0000000000..bf6a7aab75
--- /dev/null
+++ b/actionpack/test/controller/url_test.rb
@@ -0,0 +1,368 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+require 'action_controller/url_rewriter'
+
+MockRequest = Struct.new("MockRequest", :protocol, :host, :port, :path, :parameters)
+class MockRequest
+ def host_with_port
+ if (protocol == "http://" && port == 80) || (protocol == "https://" && port == 443)
+ host
+ else
+ host + ":#{port}"
+ end
+ end
+end
+
+class UrlTest < Test::Unit::TestCase
+ def setup
+ @library_url = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://",
+ "www.singlefile.com",
+ 80,
+ "/library/books/ISBN/0743536703/show",
+ { "type" => "ISBN", "code" => "0743536703" }
+ ), "books", "show")
+
+ @library_url_on_index = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://",
+ "www.singlefile.com",
+ 80,
+ "/library/books/ISBN/0743536703/",
+ { "type" => "ISBN", "code" => "0743536703" }
+ ), "books", "index")
+
+ @clean_urls = [
+ ActionController::UrlRewriter.new(MockRequest.new(
+ "http://", "www.singlefile.com", 80, "/identity/", {}
+ ), "identity", "index"),
+ ActionController::UrlRewriter.new(MockRequest.new(
+ "http://", "www.singlefile.com", 80, "/identity", {}
+ ), "identity", "index")
+ ]
+
+ @clean_url_with_id = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://", "www.singlefile.com", 80, "/identity/show/5", { "id" => "5" }
+ ), "identity", "show")
+
+ @clean_url_with_id_as_char = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://", "www.singlefile.com", 80, "/teachers/show/t", { "id" => "t" }
+ ), "teachers", "show")
+ end
+
+ def test_clean_action
+ assert_equal "http://www.singlefile.com/library/books/ISBN/0743536703/edit", @library_url.rewrite(:action => "edit")
+ end
+
+ def test_clean_action_with_only_path
+ assert_equal "/library/books/ISBN/0743536703/edit", @library_url.rewrite(:action => "edit", :only_path => true)
+ end
+
+ def test_action_from_index
+ assert_equal "http://www.singlefile.com/library/books/ISBN/0743536703/edit", @library_url_on_index.rewrite(:action => "edit")
+ end
+
+ def test_action_from_index_on_clean
+ @clean_urls.each do |url|
+ assert_equal "http://www.singlefile.com/identity/edit", url.rewrite(:action => "edit")
+ end
+ end
+
+ def test_action_without_prefix
+ assert_equal "http://www.singlefile.com/library/books/", @library_url.rewrite(:action => "index", :action_prefix => "")
+ end
+
+ def test_action_with_prefix
+ assert_equal(
+ "http://www.singlefile.com/library/books/XTC/123/show",
+ @library_url.rewrite(:action => "show", :action_prefix => "XTC/123")
+ )
+ end
+
+ def test_action_prefix_alone
+ assert_equal(
+ "http://www.singlefile.com/library/books/XTC/123/",
+ @library_url.rewrite(:action_prefix => "XTC/123")
+ )
+ end
+
+ def test_action_with_suffix
+ assert_equal(
+ "http://www.singlefile.com/library/books/show/XTC/123",
+ @library_url.rewrite(:action => "show", :action_prefix => "", :action_suffix => "XTC/123")
+ )
+ end
+
+ def test_clean_controller
+ assert_equal "http://www.singlefile.com/library/settings/", @library_url.rewrite(:controller => "settings")
+ end
+
+ def test_clean_controller_prefix
+ assert_equal "http://www.singlefile.com/shop/", @library_url.rewrite(:controller_prefix => "shop")
+ end
+
+ def test_clean_controller_with_module
+ assert_equal "http://www.singlefile.com/shop/purchases/", @library_url.rewrite(:module => "shop", :controller => "purchases")
+ end
+
+ def test_controller_and_action
+ assert_equal "http://www.singlefile.com/library/settings/show", @library_url.rewrite(:controller => "settings", :action => "show")
+ end
+
+ def test_controller_and_action_and_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :anchor => "5")
+ )
+ end
+
+ def test_controller_and_action_and_empty_overwrite_params_and_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show?code=0743536703&type=ISBN#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :overwrite_params => {}, :anchor => "5")
+ )
+ end
+
+ def test_controller_and_action_and_overwrite_params_and_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show?code=0000001&type=ISBN#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :overwrite_params => {"code"=>"0000001"}, :anchor => "5")
+ )
+ end
+
+ def test_controller_and_action_and_overwrite_params_with_nil_value_and_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show?type=ISBN#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :overwrite_params => {"code" => nil}, :anchor => "5")
+ )
+ end
+
+ def test_controller_and_action_params_and_overwrite_params_and_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show?code=0000001&version=5.0#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :params=>{"version" => "5.0"}, :overwrite_params => {"code"=>"0000001"}, :anchor => "5")
+ )
+ end
+
+ def test_controller_and_action_and_params_anchor
+ assert_equal(
+ "http://www.singlefile.com/library/settings/show?update=1#5",
+ @library_url.rewrite(:controller => "settings", :action => "show", :params => { "update" => "1"}, :anchor => "5")
+ )
+ end
+
+ def test_controller_and_index_action
+ assert_equal "http://www.singlefile.com/library/settings/", @library_url.rewrite(:controller => "settings", :action => "index")
+ end
+
+ def test_controller_and_action_with_same_name_as_controller
+ @clean_urls.each do |url|
+ assert_equal "http://www.singlefile.com/anything/identity", url.rewrite(:controller => "anything", :action => "identity")
+ end
+ end
+
+ def test_controller_and_index_action_without_controller_prefix
+ assert_equal(
+ "http://www.singlefile.com/settings/",
+ @library_url.rewrite(:controller => "settings", :action => "index", :controller_prefix => "")
+ )
+ end
+
+ def test_controller_and_index_action_with_controller_prefix
+ assert_equal(
+ "http://www.singlefile.com/fantastic/settings/show",
+ @library_url.rewrite(:controller => "settings", :action => "show", :controller_prefix => "fantastic")
+ )
+ end
+
+ def test_path_parameters
+ assert_equal "http://www.singlefile.com/library/books/EXBC/0743536703/show", @library_url.rewrite(:path_params => {"type" => "EXBC"})
+ end
+
+ def test_parameters
+ assert_equal(
+ "http://www.singlefile.com/library/books/ISBN/0743536703/show?delete=1&name=David",
+ @library_url.rewrite(:params => {"delete" => "1", "name" => "David"})
+ )
+ end
+
+ def test_parameters_with_id
+ @clean_urls.each do |url|
+ assert_equal(
+ "http://www.singlefile.com/identity/show?name=David&id=5",
+ url.rewrite(
+ :action => "show",
+ :params => { "id" => "5", "name" => "David" }
+ )
+ )
+ end
+ end
+
+ def test_action_with_id
+ assert_equal(
+ "http://www.singlefile.com/identity/show/7",
+ @clean_url_with_id.rewrite(
+ :action => "show",
+ :id => 7
+ )
+ )
+ @clean_urls.each do |url|
+ assert_equal(
+ "http://www.singlefile.com/identity/index/7",
+ url.rewrite(:id => 7)
+ )
+ end
+ end
+
+ def test_parameters_with_id_and_away
+ assert_equal(
+ "http://www.singlefile.com/identity/show/25?name=David",
+ @clean_url_with_id.rewrite(
+ :path_params => { "id" => "25" },
+ :params => { "name" => "David" }
+ )
+ )
+ end
+
+ def test_parameters_with_index_and_id
+ @clean_urls.each do |url|
+ assert_equal(
+ "http://www.singlefile.com/identity/index/25?name=David",
+ url.rewrite(
+ :path_params => { "id" => "25" },
+ :params => { "name" => "David" }
+ )
+ )
+ end
+ end
+
+ def test_action_going_away_from_id
+ assert_equal(
+ "http://www.singlefile.com/identity/list",
+ @clean_url_with_id.rewrite(
+ :action => "list"
+ )
+ )
+ end
+
+ def test_parameters_with_direct_id_and_away
+ assert_equal(
+ "http://www.singlefile.com/identity/show/25?name=David",
+ @clean_url_with_id.rewrite(
+ :id => "25",
+ :params => { "name" => "David" }
+ )
+ )
+ end
+
+ def test_parameters_with_direct_id_and_away
+ assert_equal(
+ "http://www.singlefile.com/store/open/25?name=David",
+ @clean_url_with_id.rewrite(
+ :controller => "store",
+ :action => "open",
+ :id => "25",
+ :params => { "name" => "David" }
+ )
+ )
+ end
+
+ def test_parameters_to_id
+ @clean_urls.each do |url|
+ %w(show index).each do |action|
+ assert_equal(
+ "http://www.singlefile.com/identity/#{action}/25?name=David",
+ url.rewrite(
+ :action => action,
+ :path_params => { "id" => "25" },
+ :params => { "name" => "David" }
+ )
+ )
+ end
+ end
+ end
+
+ def test_parameters_from_id
+ assert_equal(
+ "http://www.singlefile.com/identity/",
+ @clean_url_with_id.rewrite(
+ :action => "index"
+ )
+ )
+ end
+
+ def test_id_as_char_and_part_of_controller
+ assert_equal(
+ "http://www.singlefile.com/teachers/skill/5",
+ @clean_url_with_id_as_char.rewrite(
+ :action => "skill",
+ :id => 5
+ )
+ )
+ end
+
+ def test_from_clean_to_library
+ @clean_urls.each do |url|
+ assert_equal(
+ "http://www.singlefile.com/library/books/ISBN/0743536703/show?delete=1&name=David",
+ url.rewrite(
+ :controller_prefix => "library",
+ :controller => "books",
+ :action_prefix => "ISBN/0743536703",
+ :action => "show",
+ :params => { "delete" => "1", "name" => "David" }
+ )
+ )
+ end
+ end
+
+ def test_from_library_to_clean
+ assert_equal(
+ "http://www.singlefile.com/identity/",
+ @library_url.rewrite(
+ :controller => "identity", :controller_prefix => ""
+ )
+ )
+ end
+
+ def test_from_another_port
+ @library_url = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://",
+ "www.singlefile.com",
+ 8080,
+ "/library/books/ISBN/0743536703/show",
+ { "type" => "ISBN", "code" => "0743536703" }
+ ), "books", "show")
+
+ assert_equal(
+ "http://www.singlefile.com:8080/identity/",
+ @library_url.rewrite(
+ :controller => "identity", :controller_prefix => ""
+ )
+ )
+ end
+
+ def test_basecamp
+ basecamp_url = ActionController::UrlRewriter.new(MockRequest.new(
+ "http://",
+ "projects.basecamp",
+ 80,
+ "/clients/disarray/1/msg/transcripts/",
+ {"category_name"=>"transcripts", "client_name"=>"disarray", "action"=>"index", "controller"=>"msg", "project_name"=>"1"}
+ ), "msg", "index")
+
+ assert_equal(
+ "http://projects.basecamp/clients/disarray/1/msg/transcripts/1/comments",
+ basecamp_url.rewrite(:action_prefix => "transcripts/1", :action => "comments")
+ )
+ end
+
+ def test_on_explicit_index_page # My index page is very modest, thank you...
+ url = ActionController::UrlRewriter.new(
+ MockRequest.new(
+ "http://", "example.com", 80, "/controller/index",
+ {"controller"=>"controller", "action"=>"index"}
+ ), "controller", "index"
+ )
+ assert_equal("http://example.com/controller/foo", url.rewrite(:action => 'foo'))
+ end
+
+end
diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb
new file mode 100644
index 0000000000..7104ff3730
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/abc_helper.rb
@@ -0,0 +1,5 @@
+module AbcHelper
+ def bare_a() end
+ def bare_b() end
+ def bare_c() end
+end
diff --git a/actionpack/test/fixtures/layouts/builder.rxml b/actionpack/test/fixtures/layouts/builder.rxml
new file mode 100644
index 0000000000..729af4b8bc
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/builder.rxml
@@ -0,0 +1,3 @@
+xml.wrapper do
+ xml << @content_for_layout
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/standard.rhtml b/actionpack/test/fixtures/layouts/standard.rhtml
new file mode 100644
index 0000000000..fcb28ec755
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/standard.rhtml
@@ -0,0 +1 @@
+<html><%= @content_for_layout %></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/scope/test/modgreet.rhtml b/actionpack/test/fixtures/scope/test/modgreet.rhtml
new file mode 100644
index 0000000000..8947726e89
--- /dev/null
+++ b/actionpack/test/fixtures/scope/test/modgreet.rhtml
@@ -0,0 +1 @@
+<p>Beautiful modules!</p> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer.rhtml b/actionpack/test/fixtures/test/_customer.rhtml
new file mode 100644
index 0000000000..872d8c44e6
--- /dev/null
+++ b/actionpack/test/fixtures/test/_customer.rhtml
@@ -0,0 +1 @@
+Hello: <%= customer.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/greeting.rhtml b/actionpack/test/fixtures/test/greeting.rhtml
new file mode 100644
index 0000000000..62fb0293f0
--- /dev/null
+++ b/actionpack/test/fixtures/test/greeting.rhtml
@@ -0,0 +1 @@
+<p>This is grand!</p>
diff --git a/actionpack/test/fixtures/test/hello.rxml b/actionpack/test/fixtures/test/hello.rxml
new file mode 100644
index 0000000000..82a4a310d3
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello.rxml
@@ -0,0 +1,4 @@
+xml.html do
+ xml.p "Hello #{@name}"
+ xml << render_file("test/greeting")
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world.rhtml b/actionpack/test/fixtures/test/hello_world.rhtml
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world.rhtml
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_xml_world.rxml b/actionpack/test/fixtures/test/hello_xml_world.rxml
new file mode 100644
index 0000000000..02b14fe87c
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_xml_world.rxml
@@ -0,0 +1,11 @@
+xml.html do
+ xml.head do
+ xml.title "Hello World"
+ end
+
+ xml.body do
+ xml.p "abes"
+ xml.p "monks"
+ xml.p "wiseguys"
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/list.rhtml b/actionpack/test/fixtures/test/list.rhtml
new file mode 100644
index 0000000000..39e2cad966
--- /dev/null
+++ b/actionpack/test/fixtures/test/list.rhtml
@@ -0,0 +1 @@
+<%= render_collection_of_partials "customer", @customers %> \ No newline at end of file
diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb
new file mode 100644
index 0000000000..4b32f4dd48
--- /dev/null
+++ b/actionpack/test/template/active_record_helper_test.rb
@@ -0,0 +1,76 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_helper'
+# require File.dirname(__FILE__) + '/../../lib/action_view/helpers/active_record_helper'
+
+class ActiveRecordHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormHelper
+ include ActionView::Helpers::ActiveRecordHelper
+
+ Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
+ Column = Struct.new("Column", :type, :name, :human_name)
+
+ def setup
+ @post = Post.new
+ def @post.errors() Class.new{ def on(field) field == "author_name" || field == "body" end }.new end
+ def @post.new_record?() true end
+
+ def @post.column_for_attribute(attr_name)
+ Post.content_columns.select { |column| column.name == attr_name }.first
+ end
+
+ def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+ end
+
+ def test_generic_input_tag
+ assert_equal(
+ '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', input("post", "title")
+ )
+ end
+
+ def test_text_area_with_errors
+ assert_equal(
+ "<div class=\"fieldWithErrors\"><textarea cols=\"40\" id=\"post_body\" name=\"post[body]\" rows=\"20\" wrap=\"virtual\">Back to the hill and over it again!</textarea></div>",
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_field_with_errors
+ assert_equal(
+ '<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>',
+ text_field("post", "author_name")
+ )
+ end
+
+ def test_form_with_string
+ assert_equal(
+ "<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>\n<p><label for=\"post_body\">Body</label><br /><div class=\"fieldWithErrors\"><textarea cols=\"40\" id=\"post_body\" name=\"post[body]\" rows=\"20\" wrap=\"virtual\">Back to the hill and over it again!</textarea></div></p><input type='submit' value='Create' /></form>",
+ form("post")
+ )
+ end
+
+ def test_form_with_date
+ def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
+
+ assert_equal(
+ "<form action='create' method='POST'><p><label for=\"post_written_on\">Written on</label><br /><select name='post[written_on(1i)]'>\n<option>1999</option>\n<option>2000</option>\n<option>2001</option>\n<option>2002</option>\n<option>2003</option>\n<option selected=\"selected\">2004</option>\n<option>2005</option>\n<option>2006</option>\n<option>2007</option>\n<option>2008</option>\n<option>2009</option>\n</select>\n<select name='post[written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option value='6' selected=\"selected\">June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n<select name='post[written_on(3i)]'>\n<option>1</option>\n<option>2</option>\n<option>3</option>\n<option>4</option>\n<option>5</option>\n<option>6</option>\n<option>7</option>\n<option>8</option>\n<option>9</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option selected=\"selected\">15</option>\n<option>16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option>30</option>\n<option>31</option>\n</select>\n</p><input type='submit' value='Create' /></form>",
+ form("post")
+ )
+ end
+
+ def test_form_with_datetime
+ def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
+ @post.written_on = Time.gm(2004, 6, 15, 16, 30)
+
+ assert_equal(
+ "<form action='create' method='POST'><p><label for=\"post_written_on\">Written on</label><br /><select name='post[written_on(1i)]'>\n<option>1999</option>\n<option>2000</option>\n<option>2001</option>\n<option>2002</option>\n<option>2003</option>\n<option selected=\"selected\">2004</option>\n<option>2005</option>\n<option>2006</option>\n<option>2007</option>\n<option>2008</option>\n<option>2009</option>\n</select>\n<select name='post[written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option value='6' selected=\"selected\">June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n<select name='post[written_on(3i)]'>\n<option>1</option>\n<option>2</option>\n<option>3</option>\n<option>4</option>\n<option>5</option>\n<option>6</option>\n<option>7</option>\n<option>8</option>\n<option>9</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option selected=\"selected\">15</option>\n<option>16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option>30</option>\n<option>31</option>\n</select>\n &mdash; <select name='post[written_on(4i)]'>\n<option>00</option>\n<option>01</option>\n<option>02</option>\n<option>03</option>\n<option>04</option>\n<option>05</option>\n<option>06</option>\n<option>07</option>\n<option>08</option>\n<option>09</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option>15</option>\n<option selected=\"selected\">16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n</select>\n : <select name='post[written_on(5i)]'>\n<option>00</option>\n<option>01</option>\n<option>02</option>\n<option>03</option>\n<option>04</option>\n<option>05</option>\n<option>06</option>\n<option>07</option>\n<option>08</option>\n<option>09</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option>15</option>\n<option>16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option selected=\"selected\">30</option>\n<option>31</option>\n<option>32</option>\n<option>33</option>\n<option>34</option>\n<option>35</option>\n<option>36</option>\n<option>37</option>\n<option>38</option>\n<option>39</option>\n<option>40</option>\n<option>41</option>\n<option>42</option>\n<option>43</option>\n<option>44</option>\n<option>45</option>\n<option>46</option>\n<option>47</option>\n<option>48</option>\n<option>49</option>\n<option>50</option>\n<option>51</option>\n<option>52</option>\n<option>53</option>\n<option>54</option>\n<option>55</option>\n<option>56</option>\n<option>57</option>\n<option>58</option>\n<option>59</option>\n</select>\n</p><input type='submit' value='Create' /></form>",
+ form("post")
+ )
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
new file mode 100755
index 0000000000..a8ad37918d
--- /dev/null
+++ b/actionpack/test/template/date_helper_test.rb
@@ -0,0 +1,104 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
+
+class DateHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::DateHelper
+
+ def test_distance_in_words
+ from = Time.mktime(2004, 3, 6, 21, 41, 18)
+
+ assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 25))
+ assert_equal "5 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 46, 25))
+ assert_equal "about 1 hour", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 22, 47, 25))
+ assert_equal "about 3 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 0, 41))
+ assert_equal "about 4 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 1, 20))
+ assert_equal "2 days", distance_of_time_in_words(from, Time.mktime(2004, 3, 9, 15, 40))
+ end
+
+ def test_select_day
+ expected = "<select name='date[day]'>\n"
+ expected <<
+"<option>1</option>\n<option>2</option>\n<option>3</option>\n<option>4</option>\n<option>5</option>\n<option>6</option>\n<option>7</option>\n<option>8</option>\n<option>9</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option>15</option>\n<option selected=\"selected\">16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option>30</option>\n<option>31</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_day(Time.mktime(2003, 8, 16))
+ assert_equal expected, select_day(16)
+ end
+
+ def test_select_day_with_blank
+ expected = "<select name='date[day]'>\n"
+ expected <<
+"<option></option>\n<option>1</option>\n<option>2</option>\n<option>3</option>\n<option>4</option>\n<option>5</option>\n<option>6</option>\n<option>7</option>\n<option>8</option>\n<option>9</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option>15</option>\n<option selected=\"selected\">16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option>30</option>\n<option>31</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true)
+ assert_equal expected, select_day(16, :include_blank => true)
+ end
+
+ def test_select_month
+ expected = "<select name='date[month]'>\n"
+ expected << "<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option value='6'>June</option>\n<option value='7'>July</option>\n<option value='8' selected=\"selected\">August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16))
+ assert_equal expected, select_month(8)
+ end
+
+ def test_select_month_with_numbers
+ expected = "<select name='date[month]'>\n"
+ expected << "<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8' selected=\"selected\">8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true)
+ assert_equal expected, select_month(8, :use_month_numbers => true)
+ end
+
+ def test_select_month_with_numbers_and_names
+ expected = "<select name='date[month]'>\n"
+ expected << "<option value='1'>1 - January</option>\n<option value='2'>2 - February</option>\n<option value='3'>3 - March</option>\n<option value='4'>4 - April</option>\n<option value='5'>5 - May</option>\n<option value='6'>6 - June</option>\n<option value='7'>7 - July</option>\n<option value='8' selected=\"selected\">8 - August</option>\n<option value='9'>9 - September</option>\n<option value='10'>10 - October</option>\n<option value='11'>11 - November</option>\n<option value='12'>12 - December</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true)
+ assert_equal expected, select_month(8, :add_month_numbers => true)
+ end
+
+ def test_select_year
+ expected = "<select name='date[year]'>\n"
+ expected << "<option selected=\"selected\">2003</option>\n<option>2004</option>\n<option>2005</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005)
+ assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005)
+ end
+
+ def test_select_year_with_type_discarding
+ expected = "<select name='date_year'>\n"
+ expected << "<option selected=\"selected\">2003</option>\n<option>2004</option>\n<option>2005</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_year(
+ Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
+ assert_equal expected, select_year(
+ 2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
+ end
+
+
+ def test_select_date
+ expected = "<select name='date[first][year]'>\n"
+ expected << "<option selected=\"selected\">2003</option>\n<option>2004</option>\n<option>2005</option>\n"
+ expected << "</select>\n"
+
+ expected << "<select name='date[first][month]'>\n"
+ expected << "<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option value='6'>June</option>\n<option value='7'>July</option>\n<option value='8' selected=\"selected\">August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n"
+ expected << "</select>\n"
+
+ expected << "<select name='date[first][day]'>\n"
+ expected <<
+"<option>1</option>\n<option>2</option>\n<option>3</option>\n<option>4</option>\n<option>5</option>\n<option>6</option>\n<option>7</option>\n<option>8</option>\n<option>9</option>\n<option>10</option>\n<option>11</option>\n<option>12</option>\n<option>13</option>\n<option>14</option>\n<option>15</option>\n<option selected=\"selected\">16</option>\n<option>17</option>\n<option>18</option>\n<option>19</option>\n<option>20</option>\n<option>21</option>\n<option>22</option>\n<option>23</option>\n<option>24</option>\n<option>25</option>\n<option>26</option>\n<option>27</option>\n<option>28</option>\n<option>29</option>\n<option>30</option>\n<option>31</option>\n"
+ expected << "</select>\n"
+
+ assert_equal expected, select_date(
+ Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]"
+ )
+ end
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
new file mode 100644
index 0000000000..8f3d5ebb94
--- /dev/null
+++ b/actionpack/test/template/form_helper_test.rb
@@ -0,0 +1,124 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_helper'
+
+class FormHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormHelper
+
+ old_verbose, $VERBOSE = $VERBOSE, nil
+ Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
+ $VERBOSE = old_verbose
+
+ def setup
+ @post = Post.new
+ def @post.errors() Class.new{ def on(field) field == "author_name" end }.new end
+
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+ end
+
+ def test_text_field
+ assert_equal(
+ '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
+ )
+ assert_equal(
+ '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title")
+ )
+ assert_equal(
+ '<input id="person_name" name="person[name]" size="30" type="password" value="" />', password_field("person", "name")
+ )
+ end
+
+ def test_text_field_with_escapes
+ @post.title = "<b>Hello World</b>"
+ assert_equal(
+ '<input id="post_title" name="post[title]" size="30" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
+ )
+ end
+
+ def test_text_field_with_options
+ assert_equal(
+ '<input id="post_title" name="post[title]" size="35" type="text" value="Hello World" />',
+ text_field("post", "title", "size" => "35")
+ )
+ end
+
+ def test_text_field_assuming_size
+ assert_equal(
+ '<input id="post_title" maxlength="35" name="post[title]" size="35" type="text" value="Hello World" />',
+ text_field("post", "title", "maxlength" => 35)
+ )
+ end
+
+ def test_check_box
+ assert_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+
+ @post.secret = 0
+ assert_equal(
+ '<input id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+
+ @post.secret = true
+ assert_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret")
+ )
+ end
+
+ def test_text_area
+ assert_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20" wrap="virtual">Back to the hill and over it again!</textarea>',
+ text_area("post", "body")
+ )
+ end
+
+ def test_text_area_with_escapes
+ @post.body = "Back to <i>the</i> hill and over it again!"
+ assert_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20" wrap="virtual">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
+ text_area("post", "body")
+ )
+ end
+
+ def test_date_selects
+ assert_equal(
+ '<textarea cols="40" id="post_body" name="post[body]" rows="20" wrap="virtual">Back to the hill and over it again!</textarea>',
+ text_area("post", "body")
+ )
+ end
+
+
+ def test_explicit_name
+ assert_equal(
+ '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
+ )
+ assert_equal(
+ '<textarea cols="40" id="post_body" name="really!" rows="20" wrap="virtual">Back to the hill and over it again!</textarea>',
+ text_area("post", "body", "name" => "really!")
+ )
+ assert_equal(
+ '<input checked="checked" id="post_secret" name="i mean it" type="checkbox" value="1" /><input name="i mean it" type="hidden" value="0" />',
+ check_box("post", "secret", "name" => "i mean it")
+ )
+ end
+
+ def test_explicit_id
+ assert_equal(
+ '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
+ )
+ assert_equal(
+ '<textarea cols="40" id="really!" name="post[body]" rows="20" wrap="virtual">Back to the hill and over it again!</textarea>',
+ text_area("post", "body", "id" => "really!")
+ )
+ assert_equal(
+ '<input checked="checked" id="i mean it" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
+ check_box("post", "secret", "id" => "i mean it")
+ )
+ end
+end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
new file mode 100644
index 0000000000..fa0a37aa36
--- /dev/null
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -0,0 +1,165 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_options_helper'
+
+class FormOptionsHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::FormOptionsHelper
+
+ old_verbose, $VERBOSE = $VERBOSE, nil
+ Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
+ Continent = Struct.new('Continent', :continent_name, :countries)
+ Country = Struct.new('Country', :country_id, :country_name)
+ $VERBOSE = old_verbose
+
+ def test_collection_options
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title")
+ )
+ end
+
+
+ def test_collection_options_with_preselected_value
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", "Babe")
+ )
+ end
+
+ def test_collection_options_with_preselected_value_array
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
+ )
+ end
+
+ def test_array_options_for_select
+ assert_equal(
+ "<option>&lt;Denmark&gt;</option>\n<option>USA</option>\n<option>Sweden</option>",
+ options_for_select([ "<Denmark>", "USA", "Sweden" ])
+ )
+ end
+
+ def test_array_options_for_select_with_selection
+ assert_equal(
+ "<option>Denmark</option>\n<option selected=\"selected\">&lt;USA&gt;</option>\n<option>Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
+ )
+ end
+
+ def test_array_options_for_select_with_selection_array
+ assert_equal(
+ "<option>Denmark</option>\n<option selected=\"selected\">&lt;USA&gt;</option>\n<option selected=\"selected\">Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
+ )
+ end
+
+ def test_hash_options_for_select
+ assert_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" })
+ )
+ end
+
+ def test_hash_options_for_select_with_selection
+ assert_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar")
+ )
+ end
+
+ def test_hash_options_for_select_with_selection
+ assert_equal(
+ "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ])
+ )
+ end
+
+ def test_html_option_groups_from_collection
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
+
+ assert_equal(
+ "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
+ option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
+ )
+ end
+
+ def test_select
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option>abe</option>\n<option selected=\"selected\">&lt;mus&gt;</option>\n<option>hest</option></select>",
+ select("post", "category", %w( abe <mus> hest))
+ )
+ end
+
+ def test_select_with_blank
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option></option>\n<option>abe</option>\n<option selected=\"selected\">&lt;mus&gt;</option>\n<option>hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :include_blank => true)
+ )
+ end
+
+ def test_collection_select
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name")
+ )
+ end
+
+ def test_collection_select_with_blank_and_style
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ assert_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
+ )
+ end
+
+ def test_country_select
+ @post = Post.new
+ @post.origin = "Denmark"
+ assert_equal(
+ "<select id=\"post_origin\" name=\"post[origin]\"><option>Albania</option>\n<option>Algeria</option>\n<option>American Samoa</option>\n<option>Andorra</option>\n<option>Angola</option>\n<option>Anguilla</option>\n<option>Antarctica</option>\n<option>Antigua And Barbuda</option>\n<option>Argentina</option>\n<option>Armenia</option>\n<option>Aruba</option>\n<option>Australia</option>\n<option>Austria</option>\n<option>Azerbaijan</option>\n<option>Bahamas</option>\n<option>Bahrain</option>\n<option>Bangladesh</option>\n<option>Barbados</option>\n<option>Belarus</option>\n<option>Belgium</option>\n<option>Belize</option>\n<option>Benin</option>\n<option>Bermuda</option>\n<option>Bhutan</option>\n<option>Bolivia</option>\n<option>Bosnia and Herzegowina</option>\n<option>Botswana</option>\n<option>Bouvet Island</option>\n<option>Brazil</option>\n<option>British Indian Ocean Territory</option>\n<option>Brunei Darussalam</option>\n<option>Bulgaria</option>\n<option>Burkina Faso</option>\n<option>Burma</option>\n<option>Burundi</option>\n<option>Cambodia</option>\n<option>Cameroon</option>\n<option>Canada</option>\n<option>Cape Verde</option>\n<option>Cayman Islands</option>\n<option>Central African Republic</option>\n<option>Chad</option>\n<option>Chile</option>\n<option>China</option>\n<option>Christmas Island</option>\n<option>Cocos (Keeling) Islands</option>\n<option>Colombia</option>\n<option>Comoros</option>\n<option>Congo</option>\n<option>Congo, the Democratic Republic of the</option>\n<option>Cook Islands</option>\n<option>Costa Rica</option>\n<option>Cote d'Ivoire</option>\n<option>Croatia</option>\n<option>Cyprus</option>\n<option>Czech Republic</option>\n<option selected=\"selected\">Denmark</option>\n<option>Djibouti</option>\n<option>Dominica</option>\n<option>Dominican Republic</option>\n<option>East Timor</option>\n<option>Ecuador</option>\n<option>Egypt</option>\n<option>El Salvador</option>\n<option>England</option>\n<option>Equatorial Guinea</option>\n<option>Eritrea</option>\n<option>Espana</option>\n<option>Estonia</option>\n<option>Ethiopia</option>\n<option>Falkland Islands</option>\n<option>Faroe Islands</option>\n<option>Fiji</option>\n<option>Finland</option>\n<option>France</option>\n<option>French Guiana</option>\n<option>French Polynesia</option>\n<option>French Southern Territories</option>\n<option>Gabon</option>\n<option>Gambia</option>\n<option>Georgia</option>\n<option>Germany</option>\n<option>Ghana</option>\n<option>Gibraltar</option>\n<option>Great Britain</option>\n<option>Greece</option>\n<option>Greenland</option>\n<option>Grenada</option>\n<option>Guadeloupe</option>\n<option>Guam</option>\n<option>Guatemala</option>\n<option>Guinea</option>\n<option>Guinea-Bissau</option>\n<option>Guyana</option>\n<option>Haiti</option>\n<option>Heard and Mc Donald Islands</option>\n<option>Honduras</option>\n<option>Hong Kong</option>\n<option>Hungary</option>\n<option>Iceland</option>\n<option>India</option>\n<option>Indonesia</option>\n<option>Ireland</option>\n<option>Israel</option>\n<option>Italy</option>\n<option>Jamaica</option>\n<option>Japan</option>\n<option>Jordan</option>\n<option>Kazakhstan</option>\n<option>Kenya</option>\n<option>Kiribati</option>\n<option>Korea, Republic of</option>\n<option>Korea (South)</option>\n<option>Kuwait</option>\n<option>Kyrgyzstan</option>\n<option>Lao People's Democratic Republic</option>\n<option>Latvia</option>\n<option>Lebanon</option>\n<option>Lesotho</option>\n<option>Liberia</option>\n<option>Liechtenstein</option>\n<option>Lithuania</option>\n<option>Luxembourg</option>\n<option>Macau</option>\n<option>Macedonia</option>\n<option>Madagascar</option>\n<option>Malawi</option>\n<option>Malaysia</option>\n<option>Maldives</option>\n<option>Mali</option>\n<option>Malta</option>\n<option>Marshall Islands</option>\n<option>Martinique</option>\n<option>Mauritania</option>\n<option>Mauritius</option>\n<option>Mayotte</option>\n<option>Mexico</option>\n<option>Micronesia, Federated States of</option>\n<option>Moldova, Republic of</option>\n<option>Monaco</option>\n<option>Mongolia</option>\n<option>Montserrat</option>\n<option>Morocco</option>\n<option>Mozambique</option>\n<option>Myanmar</option>\n<option>Namibia</option>\n<option>Nauru</option>\n<option>Nepal</option>\n<option>Netherlands</option>\n<option>Netherlands Antilles</option>\n<option>New Caledonia</option>\n<option>New Zealand</option>\n<option>Nicaragua</option>\n<option>Niger</option>\n<option>Nigeria</option>\n<option>Niue</option>\n<option>Norfolk Island</option>\n<option>Northern Ireland</option>\n<option>Northern Mariana Islands</option>\n<option>Norway</option>\n<option>Oman</option>\n<option>Pakistan</option>\n<option>Palau</option>\n<option>Panama</option>\n<option>Papua New Guinea</option>\n<option>Paraguay</option>\n<option>Peru</option>\n<option>Philippines</option>\n<option>Pitcairn</option>\n<option>Poland</option>\n<option>Portugal</option>\n<option>Puerto Rico</option>\n<option>Qatar</option>\n<option>Reunion</option>\n<option>Romania</option>\n<option>Russia</option>\n<option>Rwanda</option>\n<option>Saint Kitts and Nevis</option>\n<option>Saint Lucia</option>\n<option>Saint Vincent and the Grenadines</option>\n<option>Samoa (Independent)</option>\n<option>San Marino</option>\n<option>Sao Tome and Principe</option>\n<option>Saudi Arabia</option>\n<option>Scotland</option>\n<option>Senegal</option>\n<option>Seychelles</option>\n<option>Sierra Leone</option>\n<option>Singapore</option>\n<option>Slovakia</option>\n<option>Slovenia</option>\n<option>Solomon Islands</option>\n<option>Somalia</option>\n<option>South Africa</option>\n<option>South Georgia and the South Sandwich Islands</option>\n<option>South Korea</option>\n<option>Spain</option>\n<option>Sri Lanka</option>\n<option>St. Helena</option>\n<option>St. Pierre and Miquelon</option>\n<option>Suriname</option>\n<option>Svalbard and Jan Mayen Islands</option>\n<option>Swaziland</option>\n<option>Sweden</option>\n<option>Switzerland</option>\n<option>Taiwan</option>\n<option>Tajikistan</option>\n<option>Tanzania</option>\n<option>Thailand</option>\n<option>Togo</option>\n<option>Tokelau</option>\n<option>Tonga</option>\n<option>Trinidad</option>\n<option>Trinidad and Tobago</option>\n<option>Tunisia</option>\n<option>Turkey</option>\n<option>Turkmenistan</option>\n<option>Turks and Caicos Islands</option>\n<option>Tuvalu</option>\n<option>Uganda</option>\n<option>Ukraine</option>\n<option>United Arab Emirates</option>\n<option>United Kingdom</option>\n<option>United States</option>\n<option>United States Minor Outlying Islands</option>\n<option>Uruguay</option>\n<option>Uzbekistan</option>\n<option>Vanuatu</option>\n<option>Vatican City State (Holy See)</option>\n<option>Venezuela</option>\n<option>Viet Nam</option>\n<option>Virgin Islands (British)</option>\n<option>Virgin Islands (U.S.)</option>\n<option>Wales</option>\n<option>Wallis and Futuna Islands</option>\n<option>Western Sahara</option>\n<option>Yemen</option>\n<option>Zambia</option>\n<option>Zimbabwe</option></select>",
+ country_select("post", "origin")
+ )
+ end
+end
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
new file mode 100644
index 0000000000..c3289af50c
--- /dev/null
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -0,0 +1,18 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
+
+class TagHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::UrlHelper
+
+ def test_tag
+ assert_equal "<p class=\"show\" />", tag("p", "class" => "show")
+ end
+
+ def test_content_tag
+ assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
+ end
+
+ # FIXME: Test form tag
+end \ No newline at end of file
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
new file mode 100644
index 0000000000..347420a72b
--- /dev/null
+++ b/actionpack/test/template/text_helper_test.rb
@@ -0,0 +1,62 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/text_helper'
+
+class TextHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TextHelper
+
+ def test_truncate
+ assert_equal "Hello World!", truncate("Hello World!", 12)
+ assert_equal "Hello Worl...", truncate("Hello World!!", 12)
+ end
+
+ def test_strip_links
+ assert_equal "on my mind", strip_links("<a href='almost'>on my mind</a>")
+ end
+
+ def test_highlighter
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ highlight("This is a beautiful morning", "beautiful")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
+ )
+
+ assert_equal(
+ "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
+ )
+ end
+
+ def test_highlighter_with_regexp
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful!</strong> morning",
+ highlight("This is a beautiful! morning", "beautiful!")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful! morning</strong>",
+ highlight("This is a beautiful! morning", "beautiful! morning")
+ )
+
+ assert_equal(
+ "This is a <strong class=\"highlight\">beautiful? morning</strong>",
+ highlight("This is a beautiful? morning", "beautiful? morning")
+ )
+ end
+
+ def test_excerpt
+ assert_equal("...is a beautiful morni...", excerpt("This is a beautiful morning", "beautiful", 5))
+ assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_nil excerpt("This is a beautiful morning", "day")
+ end
+
+ def test_pluralization
+ assert_equal("1 count", pluralize(1, "count"))
+ assert_equal("2 counts", pluralize(2, "count"))
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
new file mode 100644
index 0000000000..198b26b113
--- /dev/null
+++ b/actionpack/test/template/url_helper_test.rb
@@ -0,0 +1,49 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
+require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
+
+class UrlHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::UrlHelper
+ include ActionView::Helpers::TagHelper
+
+ def setup
+ @controller = Class.new do
+ def url_for(options, *parameters_for_method_reference)
+ "http://www.world.com"
+ end
+ end
+ @controller = @controller.new
+ end
+
+ # todo: missing test cases
+ def test_link_tag_with_straight_url
+ assert_equal "<a href=\"http://www.world.com\">Hello</a>", link_to("Hello", "http://www.world.com")
+ end
+
+ def test_link_tag_with_javascript_confirm
+ assert_equal(
+ "<a href=\"http://www.world.com\" onclick=\"return confirm('Are you sure?');\">Hello</a>",
+ link_to("Hello", "http://www.world.com", :confirm => "Are you sure?")
+ )
+ end
+
+ def test_link_unless_current
+ @params = { "controller" => "weblog", "action" => "show"}
+ assert_equal "Showing", link_to_unless_current("Showing", :action => "show", :controller => "weblog")
+ assert "<a href=\"http://www.world.com\">Listing</a>", link_to_unless_current("Listing", :action => "list", :controller => "weblog")
+ end
+
+ def test_mail_to
+ assert_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com")
+ assert_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson")
+ assert_equal(
+ "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>",
+ mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin")
+ )
+ end
+
+ def test_link_with_nil_html_options
+ assert "<a href=\"http://www.world.com\">Hello</a>",
+ link_to("Hello", {:action => 'myaction'}, nil)
+ end
+end \ No newline at end of file