aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG181
-rw-r--r--actionpack/README.rdoc8
-rw-r--r--actionpack/RUNNING_UNIT_TESTS2
-rwxr-xr-xactionpack/Rakefile9
-rw-r--r--actionpack/actionpack.gemspec24
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb3
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb120
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb18
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb9
-rw-r--r--actionpack/lib/action_controller.rb28
-rw-r--r--actionpack/lib/action_controller/base.rb28
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb8
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb5
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb13
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb3
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb9
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb28
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb13
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb11
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb12
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb36
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb9
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb24
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb31
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb44
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb5
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb2
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb8
-rw-r--r--actionpack/lib/action_controller/test_case.rb78
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/node.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb3
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb27
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/head.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb5
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb140
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb67
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb111
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb21
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb17
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb19
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb13
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb27
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view.rb6
-rw-r--r--actionpack/lib/action_view/asset_paths.rb136
-rw-r--r--actionpack/lib/action_view/base.rb6
-rw-r--r--actionpack/lib/action_view/buffers.rb4
-rw-r--r--actionpack/lib/action_view/helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb76
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb23
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb17
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb14
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb19
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb124
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb82
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb99
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb48
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb16
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb56
-rw-r--r--actionpack/lib/action_view/helpers/rendering_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb69
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb78
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_view/lookup_context.rb231
-rw-r--r--actionpack/lib/action_view/path_set.rb73
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb27
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb94
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb26
-rw-r--r--actionpack/lib/action_view/template.rb1
-rw-r--r--actionpack/lib/action_view/template/error.rb1
-rw-r--r--actionpack/lib/action_view/template/handler.rb49
-rw-r--r--actionpack/lib/action_view/template/handlers.rb6
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb3
-rw-r--r--actionpack/lib/action_view/template/resolver.rb65
-rw-r--r--actionpack/lib/action_view/test_case.rb19
-rw-r--r--actionpack/lib/action_view/testing/resolvers.rb2
-rw-r--r--actionpack/lib/sprockets/assets.rake95
-rw-r--r--actionpack/lib/sprockets/bootstrap.rb37
-rw-r--r--actionpack/lib/sprockets/compressors.rb83
-rw-r--r--actionpack/lib/sprockets/helpers.rb6
-rw-r--r--actionpack/lib/sprockets/helpers/isolated_helper.rb13
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb166
-rw-r--r--actionpack/lib/sprockets/railtie.rb123
-rw-r--r--actionpack/lib/sprockets/static_compiler.rb61
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb18
-rw-r--r--actionpack/test/abstract/callbacks_test.rb2
-rw-r--r--actionpack/test/abstract/layouts_test.rb12
-rw-r--r--actionpack/test/abstract_unit.rb1
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb31
-rw-r--r--actionpack/test/activerecord/controller_runtime_test.rb26
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb8
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb3
-rw-r--r--actionpack/test/controller/assert_select_test.rb19
-rw-r--r--actionpack/test/controller/caching_test.rb71
-rw-r--r--actionpack/test/controller/content_type_test.rb1
-rw-r--r--actionpack/test/controller/default_url_options_with_filter_test.rb29
-rw-r--r--actionpack/test/controller/deprecation/deprecated_base_methods_test.rb26
-rw-r--r--actionpack/test/controller/filters_test.rb5
-rw-r--r--actionpack/test/controller/force_ssl_test.rb22
-rw-r--r--actionpack/test/controller/helper_test.rb3
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb8
-rw-r--r--actionpack/test/controller/integration_test.rb13
-rw-r--r--actionpack/test/controller/layout_test.rb4
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb19
-rw-r--r--actionpack/test/controller/mime_responds_test.rb84
-rw-r--r--actionpack/test/controller/new_base/base_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb10
-rw-r--r--actionpack/test/controller/new_base/render_partial_test.rb10
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb6
-rw-r--r--actionpack/test/controller/redirect_test.rb5
-rw-r--r--actionpack/test/controller/render_test.rb67
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb22
-rw-r--r--actionpack/test/controller/resources_test.rb20
-rw-r--r--actionpack/test/controller/routing_test.rb109
-rw-r--r--actionpack/test/controller/send_file_test.rb19
-rw-r--r--actionpack/test/controller/test_test.rb81
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb183
-rw-r--r--actionpack/test/controller/url_for_test.rb18
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb4
-rw-r--r--actionpack/test/controller/view_paths_test.rb8
-rw-r--r--actionpack/test/controller/webservice_test.rb4
-rw-r--r--actionpack/test/dispatch/cookies_test.rb104
-rw-r--r--actionpack/test/dispatch/mapper_test.rb15
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb10
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb12
-rw-r--r--actionpack/test/dispatch/request_id_test.rb65
-rw-r--r--actionpack/test/dispatch/request_test.rb7
-rw-r--r--actionpack/test/dispatch/response_body_is_proc_test.rb37
-rw-r--r--actionpack/test/dispatch/routing_test.rb91
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb181
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb1
-rw-r--r--actionpack/test/dispatch/test_request_test.rb29
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb7
-rw-r--r--actionpack/test/fixtures/comments/empty.de.html.erb1
-rw-r--r--actionpack/test/fixtures/comments/empty.html.builder1
-rw-r--r--actionpack/test/fixtures/comments/empty.html.erb1
-rw-r--r--actionpack/test/fixtures/comments/empty.xml.erb1
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.erb1
-rw-r--r--actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css1
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/application.js1
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/extra.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/application.css1
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/extra.css0
-rw-r--r--actionpack/test/fixtures/test/_200.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb2
-rw-r--r--actionpack/test/fixtures/test/deprecated_nested_layout.erb3
-rw-r--r--actionpack/test/fixtures/test/hello,world.erb1
-rw-r--r--actionpack/test/lib/controller/fake_models.rb22
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb53
-rw-r--r--actionpack/test/template/capture_helper_test.rb36
-rw-r--r--actionpack/test/template/compiled_templates_test.rb18
-rw-r--r--actionpack/test/template/compressors_test.rb28
-rw-r--r--actionpack/test/template/date_helper_test.rb9
-rw-r--r--actionpack/test/template/erb_util_test.rb10
-rw-r--r--actionpack/test/template/form_helper_test.rb59
-rw-r--r--actionpack/test/template/form_options_helper_test.rb81
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb34
-rw-r--r--actionpack/test/template/html-scanner/document_test.rb4
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb7
-rw-r--r--actionpack/test/template/html-scanner/tag_node_test.rb7
-rw-r--r--actionpack/test/template/javascript_helper_test.rb15
-rw-r--r--actionpack/test/template/log_subscriber_test.rb2
-rw-r--r--actionpack/test/template/lookup_context_test.rb10
-rw-r--r--actionpack/test/template/number_helper_test.rb17
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb43
-rw-r--r--actionpack/test/template/render_test.rb120
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb306
-rw-r--r--actionpack/test/template/streaming_render_test.rb20
-rw-r--r--actionpack/test/template/template_test.rb4
-rw-r--r--actionpack/test/template/test_case_test.rb20
-rw-r--r--actionpack/test/template/test_test.rb18
-rw-r--r--actionpack/test/template/text_helper_test.rb15
-rw-r--r--actionpack/test/template/translation_helper_test.rb8
-rw-r--r--actionpack/test/template/url_helper_test.rb39
216 files changed, 4323 insertions, 1927 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 15abfb8369..a7a47bf930 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,4 +1,175 @@
-*Rails 3.1.0 (unreleased)*
+*Rails 3.2.0 (unreleased)*
+
+* Responders now return 204 No Content for API requests without a response body (as in the new scaffold) [José Valim]
+
+* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog [DHH]
+
+* Limit the number of options for select_year to 1000.
+
+ Pass the :max_years_allowed option to set your own limit.
+
+ [Libo Cannici]
+
+* Passing formats or handlers to render :template and friends is deprecated. For example: [Nick Sutterer & José Valim]
+
+ render :template => "foo.html.erb"
+
+ Instead, you can provide :handlers and :formats directly as option:
+
+ render :template => "foo", :formats => [:html, :js], :handlers => :erb
+
+* Changed log level of warning for missing CSRF token from :debug to :warn. [Mike Dillon]
+
+* content_tag_for and div_for can now take the collection of records. It will also yield the record as the first argument if you set a receiving argument in your block [Prem Sichanugrist]
+
+ So instead of having to do this:
+
+ @items.each do |item|
+ content_tag_for(:li, item) do
+ Title: <%= item.title %>
+ end
+ end
+
+ You can now do this:
+
+ content_tag_for(:li, @items) do |item|
+ Title: <%= item.title %>
+ end
+
+* send_file now guess the mime type [Esad Hajdarevic]
+
+* Mime type entries for PDF, ZIP and other formats were added [Esad Hajdarevic]
+
+* Generate hidden input before select with :multiple option set to true.
+ This is useful when you rely on the fact that when no options is set,
+ the state of select will be sent to rails application. Without hidden field
+ nothing is sent according to HTML spec [Bogdan Gusiev]
+
+* Refactor ActionController::TestCase cookies [Andrew White]
+
+ Assigning cookies for test cases should now use cookies[], e.g:
+
+ cookies[:email] = 'user@example.com'
+ get :index
+ assert_equal 'user@example.com', cookies[:email]
+
+ To clear the cookies, use clear, e.g:
+
+ cookies.clear
+ get :index
+ assert_nil cookies[:email]
+
+ We now no longer write out HTTP_COOKIE and the cookie jar is
+ persistent between requests so if you need to manipulate the environment
+ for your test you need to do it before the cookie jar is created.
+
+
+*Rails 3.1.1 (unreleased)*
+
+* javascript_path and stylesheet_path now refer to /assets if asset pipelining
+is on. [Santiago Pastorino]
+
+* button_to support form option. Now you're able to pass for example
+'data-type' => 'json'. [ihower]
+
+* image_path and image_tag should use /assets if asset pipelining is turned
+on. Closes #3126 [Santiago Pastorino and christos]
+
+* Avoid use of existing precompiled assets during rake assets:precompile run.
+Closes #3119 [Guillermo Iguaran]
+
+* Copy assets to nondigested filenames too [Santiago Pastorino]
+
+* Give precedence to `config.digest = false` over the existence of
+manifest.yml asset digests [christos]
+
+* escape options for the stylesheet_link_tag method [Alexey Vakhov]
+
+* Re-launch assets:precompile task using (Rake.)ruby instead of Kernel.exec so
+it works on Windows [cablegram]
+
+* env var passed to process shouldn't be modified in process method. [Santiago
+Pastorino]
+
+* `rake assets:precompile` loads the application but does not initialize
+it.
+
+ To the app developer, this means configuration add in
+ config/initializers/* will not be executed.
+
+ Plugins developers need to special case their initializers that are
+ meant to be run in the assets group by adding :group => :assets. [José Valim]
+
+* Sprockets uses config.assets.prefix for asset_path [asee]
+
+* FileStore key_file_path properly limit filenames to 255 characters. [phuibonhoa]
+
+* Fix Hash#to_query edge case with html_safe strings. [brainopia]
+
+* Allow asset tag helper methods to accept :digest => false option in order to completely avoid the digest generation.
+Useful for linking assets from static html files or from emails when the user
+could probably look at an older html email with an older asset. [Santiago Pastorino]
+
+* Don't mount Sprockets server at config.assets.prefix if config.assets.compile is false. [Mark J. Titorenko]
+
+* Set relative url root in assets when controller isn't available for Sprockets (eg. Sass files using asset_path). Fixes #2435 [Guillermo Iguaran]
+
+* Fix basic auth credential generation to not make newlines. GH #2882
+
+* Fixed the behavior of asset pipeline when config.assets.digest and config.assets.compile are false and requested asset isn't precompiled.
+ Before the requested asset were compiled anyway ignoring that the config.assets.compile flag is false. [Guillermo Iguaran]
+
+* CookieJar is now Enumerable. Fixes #2795
+
+* Fixed AssetNotPrecompiled error raised when rake assets:precompile is compiling certain .erb files. See GH #2763 #2765 #2805 [Guillermo Iguaran]
+
+* Manifest is correctly placed in assets path when default assets prefix is changed. Fixes #2776 [Guillermo Iguaran]
+
+* Fixed stylesheet_link_tag and javascript_include_tag to respect additional options passed by the users when debug is on. [Guillermo Iguaran]
+
+
+*Rails 3.1.0 (August 30, 2011)*
+
+* Param values are `paramified` in controller tests. [David Chelimsky]
+
+* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. [Santiago Pastorino]
+
+* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti]
+
+* Make sure respond_with with :js tries to render a template in all cases [José Valim]
+
+* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
+
+* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist]
+
+* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher]
+
+* Brought back alternative convention for namespaced models in i18n [thoefer]
+
+ Now the key can be either "namespace.model" or "namespace/model" until further deprecation.
+
+* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove]
+
+ The old behavior of SafeBuffer allowed you to mutate string in place via
+ method like `sub!`. These methods can add unsafe strings to a safe buffer,
+ and the safe buffer will continue to be marked as safe.
+
+ An example problem would be something like this:
+
+ <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %>
+
+ In the above example, an untrusted string (`params[:xss]`) is added to the
+ safe buffer returned by `link_to`, and the untrusted content is successfully
+ sent to the client without being escaped. To prevent this from happening
+ `sub!` and other similar methods will now raise an exception when they are called on a safe buffer.
+
+ In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example:
+
+ <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %>
+
+ The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe.
+
+ You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
* Warn if we cannot verify CSRF token authenticity [José Valim]
@@ -36,11 +207,11 @@
For example if you have this route:
- map '*pages' => 'pages#show'
+ match '*pages' => 'pages#show'
by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
- map '*pages' => 'pages#show', :format => false
+ match '*pages' => 'pages#show', :format => false
* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH]
@@ -2203,7 +2374,7 @@ superclass' view_paths. [Rick Olson]
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
@@ -2800,7 +2971,7 @@ superclass' view_paths. [Rick Olson]
* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 5919b5c6d4..95301c21ee 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -283,7 +283,7 @@ methods:
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).
+request from the web-server (e.g., Apache).
And the templates look like this:
@@ -316,13 +316,13 @@ an URL such as /weblog/5 (where 5 is the id of the post).
== Download and installation
-The latest version of Action Pack can be installed with Rubygems:
+The latest version of Action Pack can be installed with RubyGems:
% [sudo] gem install actionpack
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/actionpack/
+* https://github.com/rails/rails/tree/master/actionpack
== License
@@ -334,7 +334,7 @@ Action Pack is released under the MIT license.
API documentation is at
-* http://api.rubyonrails.com
+* http://api.rubyonrails.org
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS
index 1e3ba7abe7..1b29abd2d1 100644
--- a/actionpack/RUNNING_UNIT_TESTS
+++ b/actionpack/RUNNING_UNIT_TESTS
@@ -18,7 +18,7 @@ which can be further narrowed down to one test:
== Dependency on Active Record and database setup
-Test cases in the test/controller/active_record/ directory depend on having
+Test cases in the test/active_record/ directory depend on having
activerecord and sqlite installed. If Active Record is not in
actionpack/../activerecord directory, or the sqlite rubygem is not installed,
these tests are skipped.
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 9030db9f7a..d9e3e56fcc 100755
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,7 +1,7 @@
#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
-require 'rake/gempackagetask'
+require 'rubygems/package_task'
desc "Default Task"
task :default => :test
@@ -26,6 +26,11 @@ namespace :test do
Rake::TestTask.new(:isolated) do |t|
t.pattern = 'test/ts_isolated.rb'
end
+
+ Rake::TestTask.new(:template) do |t|
+ t.libs << 'test'
+ t.pattern = 'test/template/**/*.rb'
+ end
end
desc 'ActiveRecord Integration Tests'
@@ -36,7 +41,7 @@ end
spec = eval(File.read('actionpack.gemspec'))
-Rake::GemPackageTask.new(spec) do |p|
+Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 0b4e7027ed..c2eac234b0 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -11,21 +11,21 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.rubyforge_project = 'actionpack'
s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
- s.add_dependency('activesupport', version)
- s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.0.1')
- s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.6.0beta1')
- s.add_dependency('rack', '~> 1.3.0.beta')
- s.add_dependency('rack-test', '~> 0.6.0')
- s.add_dependency('rack-mount', '~> 0.8.1')
- s.add_dependency('sprockets', '~> 2.0.0.beta.3')
- s.add_dependency('tzinfo', '~> 0.3.27')
- s.add_dependency('erubis', '~> 2.7.0')
+ s.add_dependency('activesupport', version)
+ s.add_dependency('activemodel', version)
+ s.add_dependency('rack-cache', '~> 1.1')
+ s.add_dependency('builder', '~> 3.0.0')
+ s.add_dependency('i18n', '~> 0.6')
+ s.add_dependency('rack', '~> 1.3.5')
+ s.add_dependency('rack-test', '~> 0.6.1')
+ s.add_dependency('journey', '~> 1.0.0')
+ s.add_dependency('sprockets', '~> 2.0.3')
+ s.add_dependency('erubis', '~> 2.7.0')
+
+ s.add_development_dependency('tzinfo', '~> 0.3.29')
end
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index ad14cd6d87..c2a6809f58 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,8 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir, :use_sprockets
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir,
+ :stylesheets_dir, :default_asset_host_protocol
end
end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index f67d0e558e..fd6a46fbec 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -10,7 +10,7 @@ module AbstractController
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
# expected to provide their own +render+ method, since rendering means
- # different things depending on the context.
+ # different things depending on the context.
class Base
attr_internal :response_body
attr_internal :action_name
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index e8426bc52b..7004e607a1 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -75,38 +75,122 @@ module AbstractController
end
end
+ ##
+ # :method: before_filter
+ #
+ # :call-seq: before_filter(names, block)
+ #
+ # Append a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_before_filter
+ #
+ # :call-seq: prepend_before_filter(names, block)
+ #
+ # Prepend a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_before_filter
+ #
+ # :call-seq: skip_before_filter(names, block)
+ #
+ # Skip a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_before_filter
+ #
+ # :call-seq: append_before_filter(names, block)
+ #
+ # Append a before filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: after_filter
+ #
+ # :call-seq: after_filter(names, block)
+ #
+ # Append an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_after_filter
+ #
+ # :call-seq: prepend_after_filter(names, block)
+ #
+ # Prepend an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_after_filter
+ #
+ # :call-seq: skip_after_filter(names, block)
+ #
+ # Skip an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_after_filter
+ #
+ # :call-seq: append_after_filter(names, block)
+ #
+ # Append an after filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: around_filter
+ #
+ # :call-seq: around_filter(names, block)
+ #
+ # Append an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: prepend_around_filter
+ #
+ # :call-seq: prepend_around_filter(names, block)
+ #
+ # Prepend an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: skip_around_filter
+ #
+ # :call-seq: skip_around_filter(names, block)
+ #
+ # Skip an around filter. See _insert_callbacks for parameter details.
+
+ ##
+ # :method: append_around_filter
+ #
+ # :call-seq: append_around_filter(names, block)
+ #
+ # Append an around filter. See _insert_callbacks for parameter details.
+
# set up before_filter, prepend_before_filter, skip_before_filter, etc.
# for each of before, after, and around.
[:before, :after, :around].each do |filter|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
# Append a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def #{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
- set_callback(:process_action, :#{filter}, name, options)
- end
- end
+ def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
+ set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# Prepend a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def prepend_#{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
- set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true))
- end
- end
+ def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
+ set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
+ end # end
+ end # end
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options)
- end
- end
+ def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# *_filter is the same as append_*_filter
- alias_method :append_#{filter}_filter, :#{filter}_filter
+ alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
RUBY_EVAL
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index dc9778a416..77cc4c07d9 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -67,7 +67,7 @@ module AbstractController
# helper FooHelper # => includes FooHelper
#
# When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
- # and include the module in the template class. The second form illustrates how to include custom helpers
+ # and include the module in the template class. The second form illustrates how to include custom helpers
# when working with namespaced controllers, or other cases where the file containing the helper definition is not
# in one of Rails' standard load paths:
# helper :foo # => requires 'foo_helper' and includes FooHelper
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 8f73e244d7..10aa34c76b 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -81,11 +81,12 @@ module AbstractController
# class EmployeeController < BankController
# layout nil
#
- # The InformationController uses "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.
- #
- # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
- # uses it as well.
+ # In these examples:
+ # * The InformationController uses the "bank_standard" layout, inherited from BankController.
+ # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+.
+ # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well.
+ # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
+ # * The EmployeeController does not use a layout at all.
#
# == Types of layouts
#
@@ -138,8 +139,8 @@ module AbstractController
#
# end
#
- # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
- # around the rendered view.
+ # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
+ # be rendered directly, without wrapping a layout around the rendered view.
#
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
@@ -158,7 +159,7 @@ module AbstractController
# end
# end
#
- # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
+ # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
module Layouts
extend ActiveSupport::Concern
@@ -166,6 +167,7 @@ module AbstractController
included do
class_attribute :_layout_conditions
+ remove_possible_method :_layout_conditions
delegate :_layout_conditions, :to => :'self.class'
self._layout_conditions = {}
_write_layout_method
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index ab2c532859..41fdc11196 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -120,8 +120,6 @@ module AbstractController
view_renderer.render(view_context, options)
end
- private
-
DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
@_action_name @_response_body @_formats @_prefixes @_config
@_view_context_class @_view_renderer @_lookup_context
@@ -139,6 +137,8 @@ module AbstractController
hash
end
+ private
+
# Normalize args and options.
# :api: private
def _normalize_render(*args, &block)
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 6b7aae8c74..e8394447a7 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -1,3 +1,5 @@
+require 'action_view/base'
+
module AbstractController
module ViewPaths
extend ActiveSupport::Concern
@@ -63,7 +65,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def append_view_path(path)
- self.view_paths = view_paths.dup + Array(path)
+ self._view_paths = view_paths + Array(path)
end
# Prepend a path to the list of view paths for this controller.
@@ -73,7 +75,7 @@ module AbstractController
# the default view path. You may also provide a custom view path
# (see ActionView::PathSet for more information)
def prepend_view_path(path)
- self.view_paths = Array(path) + view_paths.dup
+ self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
end
# A list of all of the default view paths for this controller.
@@ -87,8 +89,7 @@ module AbstractController
# * <tt>paths</tt> - If a PathSet is provided, use that;
# otherwise, process the parameter into a PathSet.
def view_paths=(paths)
- self._view_paths = ActionView::Base.process_view_paths(paths)
- self._view_paths.freeze
+ self._view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index eba5e9377b..f4eaa2fd1b 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -37,30 +37,16 @@ module ActionController
autoload :UrlFor
end
- autoload :Integration, 'action_controller/deprecated/integration_test'
- autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
- autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :UrlWriter, 'action_controller/deprecated'
- autoload :Routing, 'action_controller/deprecated'
- autoload :TestCase, 'action_controller/test_case'
+ autoload :Integration, 'action_controller/deprecated/integration_test'
+ autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
+ autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
+ autoload :UrlWriter, 'action_controller/deprecated'
+ autoload :Routing, 'action_controller/deprecated'
+ autoload :TestCase, 'action_controller/test_case'
+ autoload :TemplateAssertions, 'action_controller/test_case'
eager_autoload do
autoload :RecordIdentifier
-
- # TODO: Don't autoload exceptions, setup explicit
- # requires for files that need them
- autoload_at "action_controller/metal/exceptions" do
- autoload :ActionControllerError
- autoload :RenderError
- autoload :RoutingError
- autoload :MethodNotAllowed
- autoload :NotImplemented
- autoload :UnknownController
- autoload :MissingFile
- autoload :RenderError
- autoload :SessionOverflowError
- autoload :UnknownHttpMethod
- end
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c03c77cb4a..98bfe72fef 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -31,7 +31,7 @@ module ActionController
# "302 Moved" HTTP response that takes the user to the index action.
#
# These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
- # Most actions are variations of these themes.
+ # Most actions are variations on these themes.
#
# == Requests
#
@@ -50,7 +50,7 @@ module ActionController
#
# All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
# which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include
- # <tt>{ "category" => "All", "limit" => 5 }</tt> in params.
+ # <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
@@ -63,7 +63,7 @@ module ActionController
#
# == Sessions
#
- # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
+ # Sessions allow you to store objects in 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.
@@ -116,8 +116,8 @@ module ActionController
#
# 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:
+ # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
+ # will use the manual rendering methods:
#
# def search
# @results = Search.find(params[:query])
@@ -132,9 +132,9 @@ module ActionController
#
# == Redirects
#
- # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database,
- # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to)
- # a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
+ # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
+ # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
+ # going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
#
# def create
# @entry = Entry.new(params[:entry])
@@ -146,7 +146,9 @@ module ActionController
# end
# end
#
- # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed.
+ # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed.
+ # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action),
+ # and not some internal re-routing which calls both "create" and then "show" within one request.
#
# Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
#
@@ -210,16 +212,16 @@ module ActionController
# also include them at the bottom.
AbstractController::Callbacks,
+ # Append rescue at the bottom to wrap as much as possible.
+ Rescue,
+
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
Instrumentation,
# Params wrapper should come before instrumentation so they are
# properly showed in logs
- ParamsWrapper,
-
- # The same with rescue, append it at the end to wrap as much as possible.
- Rescue
+ ParamsWrapper
]
MODULES.each do |mod|
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 5fc6956266..f988de39dd 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -38,9 +38,9 @@ module ActionController #:nodoc:
# <tt>:action => 'lists'</tt> is not the same as
# <tt>:action => 'list', :format => :xml</tt>.
#
- # You can set modify the default action cache path by passing a
- # <tt>:cache_path</tt> option. This will be passed directly to
- # <tt>ActionCachePath.path_for</tt>. This is handy for actions with
+ # You can modify the default action cache path by passing a
+ # <tt>:cache_path</tt> option. This will be passed directly to
+ # <tt>ActionCachePath.path_for</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
# block is given, it is called with the current controller instance.
#
@@ -175,7 +175,7 @@ module ActionController #:nodoc:
private
def normalize!(path)
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.ends_with?(extension)
+ path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}")
URI.parser.unescape(path)
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 0be04b70a1..abeb49d16f 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -12,13 +12,13 @@ module ActionController #:nodoc:
#
# <% cache do %>
# All the topics in the system:
- # <%= render :partial => "topic", :collection => Topic.find(:all) %>
+ # <%= render :partial => "topic", :collection => Topic.all %>
# <% end %>
#
# This cache will bind the name of the action that called it, so if
# this code was part of the view for the topics/list action, you
# would be able to invalidate it using:
- #
+ #
# expire_fragment(:controller => "topics", :action => "list")
#
# This default behavior is limited if you need to cache multiple
@@ -109,7 +109,6 @@ module ActionController #:nodoc:
def expire_fragment(key, options = nil)
return unless cache_configured?
key = fragment_cache_key(key) unless key.is_a?(Regexp)
- message = nil
instrument_fragment_cache :expire_fragment, key do
if key.is_a?(Regexp)
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 8c583c7ce0..957bb7de6b 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -16,9 +16,10 @@ module ActionController #:nodoc:
# caches_page :show, :new
# end
#
- # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
- # which match the URLs used to trigger the dynamic generation. This is how the web server is able
- # pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
+ # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
+ # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the
+ # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack.
+ # This is much faster than handling the full dynamic request in the usual way.
#
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
@@ -121,7 +122,7 @@ module ActionController #:nodoc:
if options.is_a?(Hash)
if options[:action].is_a?(Array)
- options[:action].dup.each do |action|
+ options[:action].each do |action|
self.class.expire_page(url_for(options.merge(:only_path => true, :action => action)))
end
else
@@ -132,8 +133,8 @@ module ActionController #:nodoc:
end
end
- # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
- # If no options are provided, the requested url is used. Example:
+ # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used.
+ # If no options are provided, the url of the current request being handled is used. Example:
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
def cache_page(content = nil, options = nil)
return unless self.class.perform_caching && caching_allowed?
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index e9db0d97b6..49cf70ec21 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -61,6 +61,7 @@ module ActionController #:nodoc:
end
def after(controller)
+ self.controller = controller
callback(:after) if controller.perform_caching
# Clean up, so that the controller can be collected after this request
self.controller = nil
@@ -87,7 +88,7 @@ module ActionController #:nodoc:
end
def method_missing(method, *arguments, &block)
- return if @controller.nil?
+ return unless @controller
@controller.__send__(method, *arguments, &block)
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 8d813a8e38..35e29398e6 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -10,7 +10,7 @@ module ActionController
format = payload[:format]
format = format.to_s.upcase if format.is_a?(Symbol)
- info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
+ info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
@@ -20,10 +20,11 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
- end
+ status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
+ end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << "\n"
info(message)
end
@@ -59,4 +60,4 @@ module ActionController
end
end
-ActionController::LogSubscriber.attach_to :action_controller \ No newline at end of file
+ActionController::LogSubscriber.attach_to :action_controller
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 997bc6e958..0670a58d97 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/file/path'
+require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
@@ -16,7 +17,7 @@ module ActionController #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
+ # config.action_dispatch.x_sendfile_header.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
@@ -26,18 +27,21 @@ module ActionController #:nodoc:
# Options:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# Defaults to <tt>File.basename(path)</tt>.
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # You can specify either a string or a symbol for a registered type register with
+ # <tt>Mime::Type.register</tt>, for example :json
+ # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
# the URL, which is necessary for i18n filenames on certain browsers
# (setting <tt>:filename</tt> overrides this option).
#
# 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
+ # 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:
@@ -58,8 +62,8 @@ module ActionController #:nodoc:
#
# 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
+ # 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.
@@ -84,9 +88,11 @@ module ActionController #:nodoc:
# * <tt>:filename</tt> - suggests a filename for the browser to use.
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
+ # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
+ # If no content type is registered for the extension, default type 'application/octet-stream' will be used.
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
# Valid values are 'inline' and 'attachment' (default).
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
#
# Generic data download:
#
@@ -108,6 +114,8 @@ module ActionController #:nodoc:
private
def send_file_headers!(options)
+ type_provided = options.has_key?(:type)
+
options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
[:type, :disposition].each do |arg|
raise ArgumentError, ":#{arg} option required" if options[arg].nil?
@@ -123,6 +131,10 @@ module ActionController #:nodoc:
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
self.content_type = extension
else
+ if !type_provided && options[:filename]
+ # If type wasn't provided, try guessing from file extension.
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type
+ end
self.content_type = content_type
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index eb8ed7dfbd..0fd42f9d8a 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -1,12 +1,12 @@
module ActionController
- # This module provides a method which will redirects browser to use HTTPS
+ # This module provides a method which will redirect browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
# transferred safely over the internet. You _should_ always force browser
# to use HTTPS when you're transferring sensitive information such as
# user authentication, account information, or credit card information.
#
- # Note that if you really concern about your application safety, you might
- # consider using +config.force_ssl+ in your configuration config file instead.
+ # Note that if you are really concerned about your application security,
+ # you might consider using +config.force_ssl+ in your config file instead.
# That will ensure all the data transferred via HTTPS protocol and prevent
# user from getting session hijacked when accessing the site under unsecured
# HTTP protocol.
@@ -24,12 +24,15 @@ module ActionController
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except<tt> - The callback should be run for all actions except this action
def force_ssl(options = {})
+ host = options.delete(:host)
before_filter(options) do
if !request.ssl? && !Rails.env.development?
- redirect_to :protocol => 'https://', :status => :moved_permanently
+ redirect_options = {:protocol => 'https://', :status => :moved_permanently}
+ redirect_options.merge!(:host => host) if host
+ redirect_to redirect_options
end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 8abcad55a2..a618533d09 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -9,6 +9,8 @@ module ActionController
#
# head :created, :location => person_path(@person)
#
+ # head :created, :location => @person
+ #
# It can also be used to return exceptional conditions:
#
# return head(:method_not_allowed) unless request.post?
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 75757db564..bd515bba82 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -7,9 +7,12 @@ module ActionController
# by default.
#
# In addition to using the standard template helpers provided, creating custom helpers to
- # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
- # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
- # include <tt>MyHelper</tt>.
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
+ # will include all helpers.
+ #
+ # In previous versions of \Rails the controller will include a helper whose
+ # name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
#
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
# controller which inherits from it.
@@ -29,7 +32,7 @@ module ActionController
# class EventsController < ActionController::Base
# helper FormattedTimeHelper
# def index
- # @events = Event.find(:all)
+ # @events = Event.all
# end
# end
#
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 1d6df89007..264806cd36 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -106,7 +106,7 @@ module ActionController
module ControllerMethods
extend ActiveSupport::Concern
-
+
module ClassMethods
def http_basic_authenticate_with(options = {})
before_filter(options.except(:name, :password, :realm)) do
@@ -116,7 +116,7 @@ module ActionController
end
end
end
-
+
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
end
@@ -145,7 +145,7 @@ module ActionController
end
def encode_credentials(user_name, password)
- "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
+ "Basic #{ActiveSupport::Base64.encode64s("#{user_name}:#{password}")}"
end
def authentication_request(controller, realm)
@@ -400,7 +400,7 @@ module ActionController
# the present token and options.
#
# controller - ActionController::Base instance for the current request.
- # login_procedure - Proc to call if a token is present. The Proc should
+ # login_procedure - Proc to call if a token is present. The Proc should
# take 2 arguments:
# authenticate(controller) { |token, options| ... }
#
@@ -413,7 +413,7 @@ module ActionController
end
end
- # Parses the token and options out of the token authorization header. If
+ # Parses the token and options out of the token authorization header. If
# the header looks like this:
# Authorization: Token token="abc", nonce="def"
# Then the returned token is "abc", and the options is {:nonce => "def"}
@@ -423,7 +423,7 @@ module ActionController
# Returns an Array of [String, Hash] if a token is present.
# Returns nil if no token is found.
def token_and_options(request)
- if header = request.authorization.to_s[/^Token (.*)/]
+ if request.authorization.to_s[/^Token (.*)/]
values = Hash[$1.split(',').map do |value|
value.strip! # remove any spaces between commas and values
key, value = value.split(/\=\"?/) # split key=value pairs
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 16cbbce2fb..777a0ab343 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,7 +19,7 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :format => request.format.ref,
+ :format => request.format.try(:ref),
:method => request.method,
:path => (request.fullpath rescue "unknown")
}
@@ -58,8 +58,8 @@ module ActionController
def redirect_to(*args)
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
- payload[:status] = self.status
- payload[:location] = self.location
+ payload[:status] = response.status
+ payload[:location] = response.location
result
end
end
@@ -97,4 +97,4 @@ module ActionController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f10287afb4..00bd1706e7 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -42,8 +42,8 @@ module ActionController #:nodoc:
def respond_to(*mimes)
options = mimes.extract_options!
- only_actions = Array(options.delete(:only))
- except_actions = Array(options.delete(:except))
+ only_actions = Array(options.delete(:only)).map(&:to_s)
+ except_actions = Array(options.delete(:except)).map(&:to_s)
new = mimes_for_respond_to.dup
mimes.each do |mime|
@@ -245,7 +245,7 @@ module ActionController #:nodoc:
# current action.
#
def collect_mimes_from_class_level #:nodoc:
- action = action_name.to_sym
+ action = action_name.to_s
self.class.mimes_for_respond_to.keys.select do |mime|
config = self.class.mimes_for_respond_to[mime]
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 5500f88930..e0d8e1c992 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -6,31 +6,30 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_types'
module ActionController
- # Wraps parameters hash into nested hash. This will allow client to submit
- # POST request without having to specify a root element in it.
+ # Wraps the parameters hash into a nested hash. This will allow clients to submit
+ # POST requests without having to specify any root elements.
#
- # By default this functionality won't be enabled. You can enable
- # it globally by setting +ActionController::Base.wrap_parameters+:
- #
- # ActionController::Base.wrap_parameters = [:json]
+ # This functionality is enabled in +config/initializers/wrap_parameters.rb+
+ # and can be customized. If you are upgrading to \Rails 3.1, this file will
+ # need to be created for the functionality to be enabled.
#
# You could also turn it on per controller by setting the format array to
- # non-empty array:
+ # a non-empty array:
#
# class UsersController < ApplicationController
# wrap_parameters :format => [:json, :xml]
# end
#
- # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
# send JSON parameters like this:
#
# {"user": {"name": "Konata"}}
#
- # You can now just send a parameters like this:
+ # You can send parameters like this:
#
# {"name": "Konata"}
#
- # And it will be wrapped into a nested hash with the key name matching
+ # And it will be wrapped into a nested hash with the key name matching the
# controller's name. For example, if you're posting to +UsersController+,
# your new +params+ hash will look like this:
#
@@ -64,7 +63,7 @@ module ActionController
# end
#
# will try to check if +Admin::User+ or +User+ model exists, and use it to
- # determine the wrapper key respectively. If both of the model doesn't exists,
+ # determine the wrapper key respectively. If both models don't exist,
# it will then fallback to use +user+ as the key.
module ParamsWrapper
extend ActiveSupport::Concern
@@ -82,20 +81,20 @@ module ActionController
#
# ==== Examples
# wrap_parameters :format => :xml
- # # enables the parmeter wrapper for XML format
+ # # enables the parameter wrapper for XML format
#
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
#
# wrap_parameters Person
- # # wraps parameters by determine the wrapper key from Person class
+ # # wraps parameters by determining the wrapper key from Person class
# (+person+, in this case) and the list of attribute names
#
# wrap_parameters :include => [:username, :title]
# # wraps only +:username+ and +:title+ attributes from parameters.
#
# wrap_parameters false
- # # disable parameters wrapping for this controller altogether.
+ # # disables parameters wrapping for this controller altogether.
#
# ==== Options
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
@@ -142,19 +141,16 @@ module ActionController
# try to find Foo::Bar::User, Foo::User and finally User.
def _default_wrap_model #:nodoc:
return nil if self.anonymous?
-
model_name = self.name.sub(/Controller$/, '').singularize
begin
- model_klass = model_name.constantize
- rescue NameError, ArgumentError => e
- if e.message =~ /is not missing constant|uninitialized constant #{model_name}/
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
namespaces = model_name.split("::")
namespaces.delete_at(-2)
break if namespaces.last == model_name
model_name = namespaces.join("::")
- else
- raise
end
end until model_klass
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 55c650df6c..0355c9f458 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -43,8 +43,9 @@ module ActionController
#
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
# integer, or a symbol representing the downcased, underscored and symbolized description.
+ # Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
- # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
+ # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
# Examples:
@@ -53,10 +54,10 @@ module ActionController
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
#
- # When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing RedirectBackError.
+ # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
+ # behavior for this case by rescuing ActionController::RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
- raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
+ raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 13044a7450..bc22e39efb 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
+require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
@@ -7,17 +8,16 @@ module ActionController #:nodoc:
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
# by including a token in the rendered html for your application. This token is
# stored as a random string in the session, to which an attacker does not have
- # access. When a request reaches your application, \Rails then verifies the received
- # token with the token in the session. Only HTML and javascript requests are checked,
+ # access. When a request reaches your application, \Rails verifies the received
+ # token with the token in the session. Only HTML and JavaScript requests are checked,
# so this will not protect your XML API (presumably you'll have a different
# authentication scheme there anyway). Also, GET requests are not protected as these
# should be idempotent.
#
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
- # which will check the token and raise an ActionController::InvalidAuthenticityToken
- # if it doesn't match what was expected. A call to this method is generated for new
- # \Rails applications by default. You can customize the error message by editing
- # public/422.html.
+ # which checks the token and resets the session if it doesn't match what was expected.
+ # A call to this method is generated for new \Rails applications by default.
+ # You can customize the error message by editing public/422.html.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -63,7 +63,7 @@ module ActionController #:nodoc:
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
prepend_before_filter :verify_authenticity_token, options
@@ -71,19 +71,21 @@ module ActionController #:nodoc:
end
protected
- # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ # The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
- logger.debug "WARNING: Can't verify CSRF token authenticity" if logger
+ logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
handle_unverified_request
end
end
+ # This is the method that defines the application behavior when a request is found to be unverified.
+ # By default, \Rails resets the session when it finds an unverified request.
def handle_unverified_request
reset_session
end
- # Returns true or false if a request is verified. Checks:
+ # Returns true or false if a request is verified. Checks:
#
# * is it a GET request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
@@ -96,7 +98,7 @@ module ActionController #:nodoc:
# Sets the token value for the current session.
def form_authenticity_token
- session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
+ session[:_csrf_token] ||= SecureRandom.base64(32)
end
# The form's authenticity parameter. Override to provide your own.
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index ebadb29ea7..b932302a60 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -9,7 +9,7 @@ module ActionController #:nodoc:
# respond_to :html, :xml, :json
#
# def index
- # @people = Person.find(:all)
+ # @people = Person.all
# respond_with(@people)
# end
# end
@@ -162,6 +162,11 @@ module ActionController #:nodoc:
navigation_behavior(e)
end
+ # to_js simply tries to render a template. If no template is found, raises the error.
+ def to_js
+ default_render
+ end
+
# All other formats follow the procedure below. First we try to render a
# template, if the template is not available, we verify if the resource
# responds to :to_format and display it.
@@ -197,10 +202,8 @@ module ActionController #:nodoc:
display resource
elsif post?
display resource, :status => :created, :location => api_location
- elsif has_empty_resource_definition?
- display empty_resource, :status => :ok
else
- head :ok
+ head :no_content
end
end
@@ -248,7 +251,7 @@ module ActionController #:nodoc:
end
def display_errors
- controller.render format => resource.errors, :status => :unprocessable_entity
+ controller.render format => resource_errors, :status => :unprocessable_entity
end
# Check whether the resource has errors.
@@ -264,22 +267,12 @@ module ActionController #:nodoc:
@action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
end
- # Check whether resource needs a specific definition of empty resource to be valid
- #
- def has_empty_resource_definition?
- respond_to?("empty_#{format}_resource")
- end
-
- # Delegate to proper empty resource method
- #
- def empty_resource
- send("empty_#{format}_resource")
+ def resource_errors
+ respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
end
- # Return a valid empty JSON resource
- #
- def empty_json_resource
- "{}"
+ def json_resource_errors
+ {:errors => resource.errors}
end
end
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 3892a12407..5fe5334458 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -24,20 +24,8 @@ module ActionController #:nodoc:
#
# == Examples
#
- # Streaming can be added to a controller easily, all you need to do is
- # call +stream+ in the controller class:
- #
- # class PostsController
- # stream
- # end
- #
- # The +stream+ method accepts the same options as +before_filter+ and friends:
- #
- # class PostsController
- # stream :only => :index
- # end
- #
- # You can also selectively turn on streaming for specific actions:
+ # Streaming can be added to a given template easily, all you need to do is
+ # to pass the :stream option.
#
# class PostsController
# def index
@@ -72,6 +60,9 @@ module ActionController #:nodoc:
# render :stream => true
# end
#
+ # Notice that :stream only works with templates. Rendering :json
+ # or :xml with :stream won't work.
+ #
# == Communication between layout and template
#
# When streaming, rendering happens top-down instead of inside-out.
@@ -209,33 +200,9 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
include AbstractController::Rendering
- attr_internal :stream
-
- module ClassMethods
- # Render streaming templates. It accepts :only, :except, :if and :unless as options
- # to specify when to stream, as in ActionController filters.
- def stream(options={})
- if defined?(Fiber)
- before_filter :_stream_filter, options
- else
- raise "You cannot use streaming if Fiber is not available."
- end
- end
- end
protected
- # Mark following render calls as streaming.
- def _stream_filter #:nodoc:
- self.stream = true
- end
-
- # Consider the stream option when normalazing options.
- def _normalize_options(options) #:nodoc:
- super
- options[:stream] = self.stream unless options.key?(:stream)
- end
-
# Set proper cache control and transfer encoding when streaming
def _process_options(options) #:nodoc:
super
@@ -260,4 +227,3 @@ module ActionController #:nodoc:
end
end
end
- \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index f4efeb33ba..d1813ee745 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -4,6 +4,11 @@ module ActionController
include RackDelegation
+ def recycle!
+ @_url_options = nil
+ end
+
+
# TODO: Clean this up
def process_with_new_base_test(request, response)
@_request = request
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 08132b1900..0b40b1fc4c 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -18,7 +18,7 @@
# @url = root_path # named route from the application.
# end
# end
-# =>
+#
module ActionController
module UrlFor
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index d2ba052c8d..f0c29825ba 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,7 +4,6 @@ require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/paths"
-require "sprockets/railtie"
module ActionController
class Railtie < Rails::Railtie
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 2def78b51a..9c38ff44d8 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -14,9 +14,9 @@ module ActionController
# <% end %> </div>
#
# # controller
- # def destroy
+ # def update
# post = Post.find(params[:id])
- # post.destroy
+ # post.update_attributes(params[:post])
#
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
@@ -40,7 +40,7 @@ module ActionController
# dom_class(post, :edit) # => "edit_post"
# dom_class(Person, :edit) # => "edit_person"
def dom_class(record_or_class, prefix = nil)
- singular = ActiveModel::Naming.singular(record_or_class)
+ singular = ActiveModel::Naming.param_key(record_or_class)
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
end
@@ -67,7 +67,7 @@ module ActionController
# This can be overwritten to customize the default generated string representation if desired.
# If you need to read back a key from a dom_id in order to query for the underlying database record,
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
- # on the default implementation (which just joins all key attributes with '-') or on your own
+ # on the default implementation (which just joins all key attributes with '_') or on your own
# overwritten version of the method. By default, this implementation passes the key string through a
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 89ff5ba174..6913c1ef4a 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -79,10 +79,10 @@ module ActionController
"expecting <?> but rendering with <?>",
options, rendered.keys.join(', '))
assert_block(msg) do
- if options.nil?
- @templates.blank?
- else
+ if options
rendered.any? { |t,num| t.match(options) }
+ else
+ @templates.blank?
end
end
when Hash
@@ -130,7 +130,7 @@ module ActionController
super
self.session = TestSession.new
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
+ self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
end
class Result < ::Array #:nodoc:
@@ -175,17 +175,21 @@ module ActionController
end
def recycle!
- write_cookies!
- @env.delete('HTTP_COOKIE') if @cookies.blank?
- @env.delete('action_dispatch.cookies')
- @cookies = nil
@formats = nil
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
@symbolized_path_params = nil
@method = @request_method = nil
- @fullpath = @ip = @remote_ip = nil
+ @fullpath = @ip = @remote_ip = @protocol = nil
@env['action_dispatch.request.query_parameters'] = {}
+ @set_cookies ||= {}
+ @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
+ deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
+ @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
+ cookie_jar.update(rack_cookies)
+ cookie_jar.update(cookies)
+ cookie_jar.update(@set_cookies)
+ cookie_jar.recycle!
end
end
@@ -206,7 +210,7 @@ module ActionController
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
def initialize(session = {})
- @env, @by = nil, nil
+ super(nil, nil)
replace(session.stringify_keys)
@loaded = true
end
@@ -301,18 +305,17 @@ module ActionController
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
#
- # == Manipulating the request collections
+ # == Manipulating session and cookie variables
#
- # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
- # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
- # and cookies, though. For sessions, you just do:
+ # Sometimes you need to set up the session and cookie variables for a test.
+ # To do this just assign a value to the session or cookie collection:
#
- # @request.session[:key] = "value"
- # @request.cookies[:key] = "value"
+ # session[:key] = "value"
+ # cookies[:key] = "value"
#
- # To clear the cookies for a test just clear the request's cookies hash:
+ # To clear the cookies for a test just clear the cookie collection:
#
- # @request.cookies.clear
+ # cookies.clear
#
# == \Testing named routes
#
@@ -330,9 +333,21 @@ module ActionController
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
- # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
+ # Normalizes +controller_class+ before using. Examples:
+ #
+ # tests WidgetController
+ # tests :widget
+ # tests 'widget'
+ #
def tests(controller_class)
- self.controller_class = controller_class
+ case controller_class
+ when String, Symbol
+ self.controller_class = "#{controller_class.to_s.underscore}_controller".camelize.constantize
+ when Class
+ self.controller_class = controller_class
+ else
+ raise ArgumentError, "controller class must be a String, Symbol, or Class"
+ end
end
def controller_class=(new_class)
@@ -349,9 +364,7 @@ module ActionController
end
def determine_default_controller_class(name)
- name.sub(/Test$/, '').constantize
- rescue NameError
- nil
+ name.sub(/Test$/, '').safe_constantize
end
def prepare_controller_class(new_class)
@@ -395,7 +408,24 @@ module ActionController
end
alias xhr :xml_http_request
+ def paramify_values(hash_or_array_or_value)
+ case hash_or_array_or_value
+ when Hash
+ Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
+ when Array
+ hash_or_array_or_value.map {|i| paramify_values(i)}
+ when Rack::Test::UploadedFile
+ hash_or_array_or_value
+ else
+ hash_or_array_or_value.to_param
+ end
+ end
+
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
+ # Ensure that numbers and symbols passed as params are converted to
+ # proper params, as is the case when engaging rack.
+ parameters = paramify_values(parameters)
+
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@routes @controller @request @response).each do |iv_name|
@@ -428,10 +458,10 @@ module ActionController
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
+ @controller.recycle!
@controller.process_with_new_base_test(@request, @response)
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
@request.session.delete('flash') if @request.session['flash'].blank?
- @request.cookies.merge!(@response.cookies)
@response
end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 7fa3aead82..386820300a 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -4,7 +4,7 @@ require 'html/selector'
require 'html/sanitizer'
module HTML #:nodoc:
- # A top-level HTMl document. You give it a body of text, and it will parse that
+ # A top-level HTML document. You give it a body of text, and it will parse that
# text into a tree of nodes.
class Document #:nodoc:
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
index 22b3243104..4e1f016431 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
@@ -156,7 +156,7 @@ module HTML #:nodoc:
end
closing = ( scanner.scan(/\//) ? :close : nil )
- return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:-]+/)
+ return Text.new(parent, line, pos, content) unless name = scanner.scan(/[^\s!>\/]+/)
name.downcase!
unless closing
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index 91a97c02ff..af06bffa16 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -1,4 +1,5 @@
require 'set'
+require 'cgi'
require 'active_support/core_ext/class/attribute'
module HTML
@@ -103,7 +104,7 @@ module HTML
# Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers.
self.shorthand_css_properties = Set.new(%w(background border margin padding))
- # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
+ # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
def sanitize_css(style)
# disallow urls
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 7f972fc281..1e92d14542 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -47,6 +47,7 @@ module ActionDispatch
end
autoload_under 'middleware' do
+ autoload :RequestId
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
@@ -82,6 +83,7 @@ module ActionDispatch
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
autoload_under 'testing' do
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 1e43104f0a..505d5560b1 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -3,9 +3,10 @@ require 'active_support/memoizable'
module ActionDispatch
module Http
class Headers < ::Hash
- extend ActiveSupport::Memoizable
+ @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" }
def initialize(*args)
+
if args.size == 1 && args[0].is_a?(Hash)
super()
update(args[0])
@@ -25,9 +26,8 @@ module ActionDispatch
private
# Converts a HTTP header name to an environment variable name.
def env_name(header_name)
- "HTTP_#{header_name.upcase.gsub(/-/, '_')}"
+ @@env_cache[header_name]
end
- memoize :env_name
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 980c658ab7..5c48a60469 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -98,7 +98,8 @@ module ActionDispatch
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def valid_accept_header
- xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS)
+ (xhr? && (accept || content_mime_type)) ||
+ (accept && accept !~ BROWSER_LIKE_ACCEPTS)
end
def use_accept_header
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 7c9ebe7c7b..8a9f9c4315 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -47,7 +47,7 @@ module Mime
cattr_reader :html_types
# These are the content types which browsers can generate without using ajax, flash, etc
- # i.e. following a link, getting an image or posting a form. CSRF protection
+ # i.e. following a link, getting an image or posting a form. CSRF protection
# only needs to protect against these types.
@@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text]
cattr_reader :browser_generated_types
@@ -246,7 +246,7 @@ module Mime
end
end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
+ # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
# ActionController::RequestForgeryProtection.
def verify_request?
@@browser_generated_types.include?(to_sym)
@@ -256,6 +256,10 @@ module Mime
@@html_types.include?(to_sym) || @string =~ /html/
end
+ def respond_to?(method, include_private = false) #:nodoc:
+ super || method.to_s =~ /(\w+)\?$/
+ end
+
private
def method_missing(method, *args)
if method.to_s =~ /(\w+)\?$/
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 68f37d2f65..3da4f91051 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -7,6 +7,15 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
+
+Mime::Type.register "image/png", :png, [], %w(png)
+Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe)
+Mime::Type.register "image/gif", :gif, [], %w(gif)
+Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
+Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
+
+Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
+
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
@@ -19,5 +28,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.json.org/JSONRequest.html
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
+Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
+Mime::Type.register "application/zip", :zip, [], %w(zip)
+
# Create Mime::ALL but do not add it to the SET.
Mime::ALL = Mime::Type.new("*/*", :all, [])
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index ccb866f4f7..7a5237dcf3 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -2,11 +2,11 @@ require 'tempfile'
require 'stringio'
require 'strscan'
-require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
+require 'action_controller/metal/exceptions'
module ActionDispatch
class Request < Rack::Request
@@ -26,12 +26,12 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
-
+
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{env.sub(/^HTTP_/n, '').downcase}
- @env["#{env}"]
- end
+ def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
+ @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
+ end # end
METHOD
end
@@ -134,11 +134,6 @@ module ActionDispatch
@fullpath ||= super
end
- def forgery_whitelisted?
- get?
- end
- deprecate :forgery_whitelisted? => "it is just an alias for 'get?' now, update your code"
-
def media_type
content_mime_type.to_s
end
@@ -172,16 +167,26 @@ module ActionDispatch
)\.
}x
- # Determines originating IP address. REMOTE_ADDR is the standard
- # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
+ # Determines 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 if
- # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
+ # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
# delimited list in the case of multiple chained proxies; the last
# address which is not trusted is the originating IP.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # be generated by a firewall, load balancer, or web server or by the RequestId middleware
+ # (which sets the action_dispatch.request_id environment variable).
+ #
+ # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
+ # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
+ def uuid
+ @uuid ||= env["action_dispatch.request_id"]
+ end
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3a6b1da4fd..f1e85559a3 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -33,7 +33,8 @@ module ActionDispatch # :nodoc:
# end
# end
class Response
- attr_accessor :request, :header, :status
+ attr_accessor :request, :header
+ attr_reader :status
attr_writer :sending_file
alias_method :headers=, :header=
@@ -115,32 +116,9 @@ module ActionDispatch # :nodoc:
EMPTY = " "
- class BodyBuster #:nodoc:
- def initialize(response)
- @response = response
- @body = ""
- end
-
- def bust(body)
- body.call(@response, self)
- body.close if body.respond_to?(:close)
- @body
- end
-
- def write(string)
- @body << string.to_s
- end
- end
-
def body=(body)
@blank = true if body == EMPTY
- if body.respond_to?(:call)
- ActiveSupport::Deprecation.warn "Setting a Proc or an object that responds to call " \
- "in response_body is no longer supported", caller
- body = BodyBuster.new(self).bust(body)
- end
-
# Explicitly check for strings. This is *wrong* theoretically
# but if we don't check this, the performance on string bodies
# is bad on Ruby 1.8 (because strings responds to each then).
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 37effade4f..94fa747a79 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -4,31 +4,30 @@ module ActionDispatch
attr_accessor :original_filename, :content_type, :tempfile, :headers
def initialize(hash)
- @original_filename = hash[:filename]
+ @original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
@tempfile = hash[:tempfile]
raise(ArgumentError, ':tempfile is required') unless @tempfile
end
- def open
- @tempfile.open
- end
-
- def path
- @tempfile.path
- end
-
def read(*args)
@tempfile.read(*args)
end
- def rewind
- @tempfile.rewind
+ # Delegate these methods to the tempfile.
+ [:open, :path, :rewind, :size].each do |method|
+ class_eval "def #{method}; @tempfile.#{method}; end"
end
-
- def size
- @tempfile.size
+
+ private
+ def encode_filename(filename)
+ # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
+ if "ruby".encoding_aware? && filename
+ filename.force_encoding("UTF-8").encode!
+ else
+ filename
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 8487b0fc8c..c8ddd07bfa 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -45,7 +45,7 @@ module ActionDispatch
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
+ rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
@@ -64,14 +64,16 @@ module ActionDispatch
end
def host_or_subdomain_and_domain(options)
- return options[:host] unless options[:subdomain] || options[:domain]
+ return options[:host] if options[:subdomain].nil? && options[:domain].nil?
tld_length = options[:tld_length] || @@tld_length
host = ""
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
- host << "."
- host << (options[:domain] || extract_domain(options[:host], tld_length))
+ unless options[:subdomain] == false
+ host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
+ host << "."
+ end
+ host << (options[:domain] || extract_domain(options[:host], tld_length))
host
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 1bb2ad7f67..8c0f4052ec 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -19,8 +19,7 @@ module ActionDispatch
set_callback(:call, :after, *args, &block)
end
- def initialize(app, unused = nil)
- ActiveSupport::Deprecation.warn "Passing a second argument to ActionDispatch::Callbacks.new is deprecated." unless unused.nil?
+ def initialize(app)
@app = app
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 0057f64dd3..a4ffd40a66 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,4 +1,5 @@
-require "active_support/core_ext/object/blank"
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
module ActionDispatch
class Request
@@ -59,7 +60,7 @@ module ActionDispatch
# The option symbols for setting cookies are:
#
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
- # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
+ # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
# of the application.
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
# restrict to the domain level. If you use a schema like www.example.com
@@ -84,6 +85,7 @@ module ActionDispatch
class CookieOverflow < StandardError; end
class CookieJar #:nodoc:
+ include Enumerable
# This regular expression is used to split the levels of a domain.
# The top level domain can be any string without a period or
@@ -123,13 +125,22 @@ module ActionDispatch
alias :closed? :closed
def close!; @closed = true end
+ def each(&block)
+ @cookies.each(&block)
+ end
+
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
def [](name)
@cookies[name.to_s]
end
+ def key?(name)
+ @cookies.key?(name.to_s)
+ end
+ alias :has_key? :key?
+
def update(other_hash)
- @cookies.update other_hash
+ @cookies.update other_hash.stringify_keys
self
end
@@ -163,7 +174,7 @@ module ActionDispatch
options = { :value => value }
end
- value = @cookies[key.to_s] = value
+ @cookies[key.to_s] = value
handle_options(options)
@@ -185,6 +196,11 @@ module ActionDispatch
value
end
+ # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
+ def clear(options = {})
+ @cookies.each_key{ |k| delete(k, options) }
+ end
+
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
#
# cookies.permanent[:prefers_open_id] = true
@@ -222,6 +238,11 @@ module ActionDispatch
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end
+ def recycle! #:nodoc:
+ @set_cookies.clear
+ @delete_cookies.clear
+ end
+
private
def write_cookie?(cookie)
@@ -305,7 +326,7 @@ module ActionDispatch
if secret.length < SECRET_MIN_LENGTH
raise ArgumentError, "Secret should be something secure, " +
- "like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
+ "like \"#{SecureRandom.hex(16)}\". The value you " +
"provided, \"#{secret}\", is shorter than the minimum length " +
"of #{SECRET_MIN_LENGTH} characters"
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 2adbce031b..e59404ef68 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -70,6 +70,10 @@ module ActionDispatch
end
end
+ # Implementation detail: please do not change the signature of the
+ # FlashHash class. Doing that will likely affect all Rails apps in
+ # production as the FlashHash currently stored in their sessions will
+ # become invalid.
class FlashHash
include Enumerable
diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb
index 56e2d2f2a8..f1906a3ab3 100644
--- a/actionpack/lib/action_dispatch/middleware/head.rb
+++ b/actionpack/lib/action_dispatch/middleware/head.rb
@@ -8,7 +8,7 @@ module ActionDispatch
if env["REQUEST_METHOD"] == "HEAD"
env["REQUEST_METHOD"] = "GET"
env["rack.methodoverride.original_method"] = "HEAD"
- status, headers, body = @app.call(env)
+ status, headers, _ = @app.call(env)
[status, headers, []]
else
@app.call(env)
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
new file mode 100644
index 0000000000..bee446c8a5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -0,0 +1,39 @@
+require 'securerandom'
+require 'active_support/core_ext/string/access'
+require 'active_support/core_ext/object/blank'
+
+module ActionDispatch
+ # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
+ # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
+ #
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
+ #
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
+ # from multiple pieces of the stack.
+ class RequestId
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
+ status, headers, body = @app.call(env)
+
+ headers["X-Request-Id"] = env["action_dispatch.request_id"]
+ [ status, headers, body ]
+ end
+
+ private
+ def external_request_id(env)
+ if request_id = env["HTTP_X_REQUEST_ID"].presence
+ request_id.gsub(/[^\w\-]/, "").first(255)
+ end
+ end
+
+ def internal_request_id
+ SecureRandom.hex(16)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 1a811ce1b1..6bcf099d2c 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -29,7 +29,7 @@ module ActionDispatch
end
def generate_sid
- sid = ActiveSupport::SecureRandom.hex(16)
+ sid = SecureRandom.hex(16)
sid.encode!('UTF-8') if sid.respond_to?(:encode!)
sid
end
@@ -59,7 +59,10 @@ module ActionDispatch
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ raise ActionDispatch::Session::SessionRestoreError,
+ "Session contains objects whose class definition isn't available.\n" +
+ "Remember to require the classes for all objects kept in the session.\n" +
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n"
end
retry
else
@@ -73,13 +76,7 @@ module ActionDispatch
include StaleSessionCheck
def destroy_session(env, sid, options)
- ActiveSupport::Deprecation.warn "Implementing #destroy in session stores is deprecated. " <<
- "Please implement destroy_session(env, session_id, options) instead."
- destroy(env)
- end
-
- def destroy(env)
- raise '#destroy needs to be implemented.'
+ raise '#destroy_session needs to be implemented.'
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
new file mode 100644
index 0000000000..d3b6fd12fa
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -0,0 +1,50 @@
+require 'action_dispatch/middleware/session/abstract_store'
+require 'rack/session/memcache'
+
+module ActionDispatch
+ module Session
+ # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
+ # if you don't store critical data in your sessions and you don't need them to live for extended periods
+ # of time.
+ class CacheStore < AbstractStore
+ # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
+ # not specified, <tt>Rails.cache</tt> will be used.
+ def initialize(app, options = {})
+ @cache = options[:cache] || Rails.cache
+ options[:expire_after] ||= @cache.options[:expires_in]
+ super
+ end
+
+ # Get a session from the cache.
+ def get_session(env, sid)
+ sid ||= generate_sid
+ session = @cache.read(cache_key(sid))
+ session ||= {}
+ [sid, session]
+ end
+
+ # Set a session in the cache.
+ def set_session(env, sid, session, options)
+ key = cache_key(sid)
+ if session
+ @cache.write(key, session, :expires_in => options[:expire_after])
+ else
+ @cache.delete(key)
+ end
+ sid
+ end
+
+ # Remove a session from the cache.
+ def destroy_session(env, sid, options)
+ @cache.delete(cache_key(sid))
+ generate_sid
+ end
+
+ private
+ # Turn the session id into a cache key.
+ def cache_key(sid)
+ "_session_id:#{sid}"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index c17c746096..2fa68c64c5 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/exception'
+require 'action_controller/metal/exceptions'
require 'active_support/notifications'
require 'action_dispatch/http/request'
@@ -85,8 +86,8 @@ module ActionDispatch
:framework_trace => framework_trace(exception),
:full_trace => full_trace(exception)
)
- file = "rescues/#{@@rescue_templates[exception.class.name]}.erb"
- body = template.render(:file => file, :layout => 'rescues/layout.erb')
+ file = "rescues/#{@@rescue_templates[exception.class.name]}"
+ body = template.render(:template => file, :layout => 'rescues/layout')
render(status_code(exception), body)
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index f51cc3711b..1af89858d1 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,10 +1,9 @@
require "action_dispatch"
-require "rails"
module ActionDispatch
class Railtie < Rails::Railtie
config.action_dispatch = ActiveSupport::OrderedOptions.new
- config.action_dispatch.x_sendfile_header = ""
+ config.action_dispatch.x_sendfile_header = nil
config.action_dispatch.ip_spoofing_check = true
config.action_dispatch.show_exceptions = true
config.action_dispatch.best_standards_support = true
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 74c090f260..1dcd83ceb5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -161,7 +161,7 @@ module ActionDispatch
# Consider the following route, which you will find commented out at the
# bottom of your generated <tt>config/routes.rb</tt>:
#
- # match ':controller(/:action(/:id(.:format)))'
+ # match ':controller(/:action(/:id))(.:format)'
#
# This route states that it expects requests to consist of a
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 3ba6094fbb..09a8c10043 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,4 +1,3 @@
-require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/inclusion'
@@ -49,6 +48,9 @@ module ActionDispatch
class Mapping #:nodoc:
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
+ ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ SHORTHAND_REGEX = %r{/[\w/]+$}
+ WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
def initialize(set, scope, path, options)
@set, @scope = set, scope
@@ -68,7 +70,7 @@ module ActionDispatch
if using_match_shorthand?(path_without_format, @options)
to_shorthand = @options[:to].blank?
- @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
+ @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1')
end
@options.merge!(default_controller_and_action(to_shorthand))
@@ -77,7 +79,7 @@ module ActionDispatch
# segment_keys.include?(k.to_s) || k == :controller
next unless Regexp === requirement && !constraints[name]
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
if requirement.multiline?
@@ -88,7 +90,7 @@ module ActionDispatch
# match "account/overview"
def using_match_shorthand?(path, options)
- path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$}
+ path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
end
def normalize_path(path)
@@ -102,13 +104,13 @@ module ActionDispatch
# controllers with default routes like :controller/:action/:id(.:format), e.g:
# GET /admin/products/show/1
# => { :controller => 'admin/products', :action => 'show', :id => '1' }
- @options.reverse_merge!(:controller => /.+?/)
+ @options[:controller] ||= /.+?/
end
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
- if path.match(/\*([^\/]+)$/) && @options[:format] != false
- @options.reverse_merge!(:"#{$1}" => /.+?/)
+ if path.match(WILDCARD_PATH) && @options[:format] != false
+ @options[$1.to_sym] ||= /.+?/
end
if @options[:format] == false
@@ -116,6 +118,8 @@ module ActionDispatch
path
elsif path.include?(":format") || path.end_with?('/')
path
+ elsif @options[:format] == true
+ "#{path}.:format"
else
"#{path}(.:format)"
end
@@ -187,13 +191,12 @@ module ActionDispatch
end
def blocks
- block = @scope[:blocks] || []
-
- if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
- block << @options[:constraints]
+ constraints = @options[:constraints]
+ if constraints.present? && !constraints.is_a?(Hash)
+ [constraints]
+ else
+ @scope[:blocks] || []
end
-
- block
end
def constraints
@@ -210,8 +213,8 @@ module ActionDispatch
end
def segment_keys
- @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
- Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
+ @segment_keys ||= Journey::Path::Pattern.new(
+ Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
).names
end
@@ -220,19 +223,11 @@ module ActionDispatch
end
def default_controller
- if @options[:controller]
- @options[:controller]
- elsif @scope[:controller]
- @scope[:controller]
- end
+ @options[:controller] || @scope[:controller]
end
def default_action
- if @options[:action]
- @options[:action]
- elsif @scope[:action]
- @scope[:action]
- end
+ @options[:action] || @scope[:action]
end
end
@@ -240,7 +235,7 @@ module ActionDispatch
# (:locale) becomes (/:locale) instead of /(:locale). Except
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
- path = Rack::Mount::Utils.normalize_path(path)
+ path = Journey::Router::Utils.normalize_path(path)
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$}
path
end
@@ -260,7 +255,7 @@ module ActionDispatch
# because this means it will be matched first. As this is the most popular route
# of most Rails applications, this is beneficial.
def root(options = {})
- match '/', options.reverse_merge(:as => :root)
+ match '/', { :as => :root }.merge(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -335,7 +330,7 @@ module ActionDispatch
#
# [:on]
# Shorthand for wrapping routes in a specific RESTful context. Valid
- # values are +:member+, +:collection+, and +:new+. Only use within
+ # values are +:member+, +:collection+, and +:new+. Only use within
# <tt>resource(s)</tt> block. For example:
#
# resource :bar do
@@ -457,7 +452,9 @@ module ActionDispatch
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
- _routes.url_helpers.send("#{name}_path", prefix_options)
+ prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
+ prefix = '' if prefix == '/'
+ prefix
end
end
end
@@ -578,8 +575,8 @@ module ActionDispatch
# end
#
# This generates helpers such as +account_projects_path+, just like +resources+ does.
- # The difference here being that the routes generated are like /rails/projects/2,
- # rather than /accounts/rails/projects/2.
+ # The difference here being that the routes generated are like /:account_id/projects,
+ # rather than /accounts/:account_id/projects.
#
# === Options
#
@@ -656,13 +653,13 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) {:action=>"index", :controller=>"admin/posts"}
- # admin_posts POST /admin/posts(.:format) {:action=>"create", :controller=>"admin/posts"}
- # new_admin_post GET /admin/posts/new(.:format) {:action=>"new", :controller=>"admin/posts"}
- # edit_admin_post GET /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"}
- # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"}
- # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"}
- # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"}
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
+ # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@@ -699,7 +696,7 @@ module ActionDispatch
# Allows you to constrain the nested routes based on a set of rules.
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
#
- # constraints(:id => /\d+\.\d+) do
+ # constraints(:id => /\d+\.\d+/) do
# resources :posts
# end
#
@@ -709,7 +706,7 @@ module ActionDispatch
# You may use this to also restrict other parameters:
#
# resources :posts do
- # constraints(:post_id => /\d+\.\d+) do
+ # constraints(:post_id => /\d+\.\d+/) do
# resources :comments
# end
# end
@@ -876,9 +873,9 @@ module ActionDispatch
def initialize(entities, options = {})
@name = entities.to_s
- @path = (options.delete(:path) || @name).to_s
- @controller = (options.delete(:controller) || @name).to_s
- @as = options.delete(:as)
+ @path = (options[:path] || @name).to_s
+ @controller = (options[:controller] || @name).to_s
+ @as = options[:as]
@options = options
end
@@ -910,7 +907,7 @@ module ActionDispatch
alias :member_name :singular
- # Checks for uncountable plurals, and appends "_index" if the plural
+ # Checks for uncountable plurals, and appends "_index" if the plural
# and singular form are the same.
def collection_name
singular == plural ? "#{plural}_index" : plural
@@ -940,12 +937,11 @@ module ActionDispatch
DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
def initialize(entities, options)
+ super
+
@as = nil
- @name = entities.to_s
- @path = (options.delete(:path) || @name).to_s
- @controller = (options.delete(:controller) || plural).to_s
- @as = options.delete(:as)
- @options = options
+ @controller = (options[:controller] || plural).to_s
+ @as = options[:as]
end
def plural
@@ -1042,12 +1038,12 @@ module ActionDispatch
#
# This generates the following comments routes:
#
- # GET /photos/:id/comments/new
- # POST /photos/:id/comments
- # GET /photos/:id/comments/:id
- # GET /photos/:id/comments/:id/edit
- # PUT /photos/:id/comments/:id
- # DELETE /photos/:id/comments/:id
+ # GET /photos/:photo_id/comments/new
+ # POST /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments/:id/edit
+ # PUT /photos/:photo_id/comments/:id
+ # DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
@@ -1083,24 +1079,28 @@ module ActionDispatch
# Is the same as:
#
# resources :posts do
- # resources :comments
+ # resources :comments, :except => [:show, :edit, :update, :destroy]
# end
- # resources :comments
+ # resources :comments, :only => [:show, :edit, :update, :destroy]
+ #
+ # This allows URLs for resources that otherwise would be deeply nested such
+ # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
+ # to be shortened to just <tt>/comments/1234</tt>.
#
# [:shallow_path]
# Prefixes nested shallow routes with the specified path.
#
- # scope :shallow_path => "sekret" do
- # resources :posts do
- # resources :comments, :shallow => true
+ # scope :shallow_path => "sekret" do
+ # resources :posts do
+ # resources :comments, :shallow => true
+ # end
# end
- # end
#
# The +comments+ resource here will have the following routes generated for it:
#
- # post_comments GET /sekret/posts/:post_id/comments(.:format)
- # post_comments POST /sekret/posts/:post_id/comments(.:format)
- # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format)
+ # post_comments GET /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
# edit_comment GET /sekret/comments/:id/edit(.:format)
# comment GET /sekret/comments/:id(.:format)
# comment PUT /sekret/comments/:id(.:format)
@@ -1419,7 +1419,9 @@ module ActionDispatch
end
def action_path(name, path = nil) #:nodoc:
- path || @scope[:path_names][name.to_sym] || name.to_s
+ # Ruby 1.8 can't transform empty strings to symbols
+ name = name.to_sym if name.is_a?(String) && !name.empty?
+ path || @scope[:path_names][name] || name.to_s
end
def prefix_name_for_action(as, action) #:nodoc:
@@ -1436,7 +1438,7 @@ module ActionDispatch
name_prefix = @scope[:as]
if parent_resource
- return nil if as.nil? && action.nil?
+ return nil unless as || action
collection_name = parent_resource.collection_name
member_name = parent_resource.member_name
@@ -1463,9 +1465,9 @@ module ActionDispatch
end
module Shorthand #:nodoc:
- def match(*args)
- if args.size == 1 && args.last.is_a?(Hash)
- options = args.pop
+ def match(path, *rest)
+ if rest.empty? && Hash === path
+ options = path
path, to = options.find { |name, value| name.is_a?(String) }
options.merge!(:to => to).delete(path)
super(path, options)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 82c4fadb50..e989a38d8b 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -124,14 +124,7 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
- if proxy
- proxy.send(named_route, *args)
- else
- # we need to use url_for, because polymorphic_url can be used in context of other than
- # current routes (e.g. engine's routes). As named routes from engine are not included
- # calling engine's named route directly would fail.
- url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args)
- end
+ (proxy || self).send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
@@ -182,10 +175,12 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
- else
+ elsif record
route << ActiveModel::Naming.route_key(record)
route = [route.join("_").singularize] if inflection == :singular
route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
+ else
+ raise ArgumentError, "Nil location provided. Can't build URI."
end
route << routing_type(options)
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
deleted file mode 100644
index a049510182..0000000000
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'active_support/core_ext/module/deprecation'
-
-module ActionDispatch
- module Routing
- class Route #:nodoc:
- attr_reader :app, :conditions, :defaults, :name
- attr_reader :path, :requirements, :set
-
- def initialize(set, app, conditions, requirements, defaults, name, anchor)
- @set = set
- @app = app
- @defaults = defaults
- @name = name
-
- # FIXME: we should not be doing this much work in a constructor.
-
- @requirements = requirements.merge(defaults)
- @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
- @requirements.delete_if { |k, v|
- v == Regexp.compile("[^#{SEPARATORS.join}]+")
- }
-
- if path = conditions[:path_info]
- @path = path
- conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
- end
-
- @verbs = conditions[:request_method] || []
-
- @conditions = conditions.dup
-
- # Rack-Mount requires that :request_method be a regular expression.
- # :request_method represents the HTTP verb that matches this route.
- #
- # Here we munge values before they get sent on to rack-mount.
- @conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty?
- @conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info]
- @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
- @requirements.delete_if{ |k,v| !valid_condition?(k) }
- end
-
- def verb
- @verbs.join '|'
- end
-
- def segment_keys
- @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
- end
-
- def to_a
- [@app, @conditions, @defaults, @name]
- end
- deprecate :to_a
-
- def to_s
- @to_s ||= begin
- "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect]
- end
- end
-
- private
- def valid_condition?(method)
- segment_keys.include?(method) || set.valid_conditions.include?(method)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 5097f6732d..e7bc431783 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,9 +1,10 @@
-require 'rack/mount'
+require 'journey'
require 'forwardable'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
+require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
@@ -164,13 +165,14 @@ module ActionDispatch
remove_possible_method :#{selector}
def #{selector}(*args)
options = args.extract_options!
+ result = #{options.inspect}
if args.any?
- options[:_positional_args] = args
- options[:_positional_keys] = #{route.segment_keys.inspect}
+ result[:_positional_args] = args
+ result[:_positional_keys] = #{route.segment_keys.inspect}
end
- options ? #{options.inspect}.merge(options) : #{options.inspect}
+ result.merge(options)
end
protected :#{selector}
END_EVAL
@@ -204,29 +206,42 @@ module ActionDispatch
end
end
- attr_accessor :set, :routes, :named_routes, :default_scope
+ attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ alias :routes :set
+
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
end
def initialize(request_class = ActionDispatch::Request)
- self.routes = []
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
self.request_class = request_class
- self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym }
+ @valid_conditions = {}
+
+ request_class.public_instance_methods.each { |m|
+ @valid_conditions[m.to_sym] = true
+ }
+ @valid_conditions[:controller] = true
+ @valid_conditions[:action] = true
+
self.valid_conditions.delete(:id)
- self.valid_conditions.push(:controller, :action)
- @append = []
- @prepend = []
+ @append = []
+ @prepend = []
@disable_clear_and_finalize = false
- clear!
+ @finalized = false
+
+ @set = Journey::Routes.new
+ @router = Journey::Router.new(@set, {
+ :parameters_key => PARAMETERS_KEY,
+ :request_class => request_class})
+ @formatter = Journey::Formatter.new @set
end
def draw(&block)
@@ -262,17 +277,13 @@ module ActionDispatch
return if @finalized
@append.each { |blk| eval_block(blk) }
@finalized = true
- @set.freeze
end
def clear!
@finalized = false
- routes.clear
named_routes.clear
- @set = ::Rack::Mount::RouteSet.new(
- :parameters_key => PARAMETERS_KEY,
- :request_class => request_class
- )
+ set.clear
+ formatter.clear
@prepend.each { |blk| eval_block(blk) }
end
@@ -340,26 +351,54 @@ module ActionDispatch
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
- route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
- @set.add_route(route.app, route.conditions, route.defaults, route.name)
+
+ path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
+ conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
+
+ route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name
- routes << route
route
end
+ def build_path(path, requirements, separators, anchor)
+ strexp = Journey::Router::Strexp.new(
+ path,
+ requirements,
+ SEPARATORS,
+ anchor)
+
+ Journey::Path::Pattern.new(strexp)
+ end
+ private :build_path
+
+ def build_conditions(current_conditions, req_predicates, path_values)
+ conditions = current_conditions.dup
+
+ verbs = conditions[:request_method] || []
+
+ # Rack-Mount requires that :request_method be a regular expression.
+ # :request_method represents the HTTP verb that matches this route.
+ #
+ # Here we munge values before they get sent on to rack-mount.
+ unless verbs.empty?
+ conditions[:request_method] = %r[^#{verbs.join('|')}$]
+ end
+ conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) }
+
+ conditions
+ end
+ private :build_conditions
+
class Generator #:nodoc:
- PARAMETERIZE = {
- :parameterize => lambda do |name, value|
- if name == :controller
- value
- elsif value.is_a?(Array)
- value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
- else
- return nil unless param = value.to_param
- param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/")
- end
+ PARAMETERIZE = lambda do |name, value|
+ if name == :controller
+ value
+ elsif value.is_a?(Array)
+ value.map { |v| v.to_param }.join('/')
+ elsif param = value.to_param
+ param
end
- }
+ end
attr_reader :options, :recall, :set, :named_route
@@ -449,14 +488,14 @@ module ActionDispatch
end
def generate
- path, params = @set.set.generate(:path_info, named_route, options, recall, PARAMETERIZE)
+ path, params = @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
raise_routing_error unless path
return [path, params.keys] if @extras
[path, params]
- rescue Rack::Mount::RoutingError
+ rescue Journey::Router::RoutingError
raise_routing_error
end
@@ -528,12 +567,12 @@ module ActionDispatch
def call(env)
finalize!
- @set.call(env)
+ @router.call(env)
end
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
- path = Rack::Mount::Utils.normalize_path(path) unless path =~ %r{://}
+ path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -542,7 +581,7 @@ module ActionDispatch
end
req = @request_class.new(env)
- @set.recognize(req) do |route, matches, params|
+ @router.recognize(req) do |route, matches, params|
params.each do |key, value|
if value.is_a?(String)
value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 5893f86798..8fc8dc191b 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -108,7 +108,7 @@ module ActionDispatch
end
# Generate a url based on the options provided, default_url_options and the
- # routes defined in routes.rb. The following options are supported:
+ # routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
@@ -116,9 +116,10 @@ module ActionDispatch
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:subdomain</tt> - Specifies the subdomain of the link, using the +tld_length+
- # to split the domain from the host.
- # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+
# to split the subdomain from the host.
+ # If false, removes all subdomains from the host part of the link.
+ # * <tt>:domain</tt> - Specifies the domain of the link, using the +tld_length+
+ # to split the domain from the host.
# * <tt>:tld_length</tt> - Number of labels the TLD id composed of, only used if
# <tt>:subdomain</tt> or <tt>:domain</tt> are supplied. Defaults to
# <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
@@ -131,16 +132,20 @@ module ActionDispatch
#
# Examples:
#
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
+ # # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
+ # # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true
+ # # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33'
+ # # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
when String
options
when nil, Hash
- _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys)
+ _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 822150b768..226baf9ad0 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -8,12 +8,11 @@ module ActionDispatch
extend ActiveSupport::Concern
- included do
- include DomAssertions
- include ResponseAssertions
- include RoutingAssertions
- include SelectorAssertions
- include TagAssertions
- end
+ include DomAssertions
+ include ResponseAssertions
+ include RoutingAssertions
+ include SelectorAssertions
+ include TagAssertions
end
end
+
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 3335742d47..7381617dd7 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -6,13 +6,6 @@ module ActionDispatch
module ResponseAssertions
extend ActiveSupport::Concern
- included do
- # TODO: Need to pull in AV::Template monkey patches that track which
- # templates are rendered. assert_template should probably be part
- # of AV instead of AD.
- require 'action_view/test_case'
- end
-
# Asserts that the response is one of the following types:
#
# * <tt>:success</tt> - Status code was 200
@@ -62,16 +55,14 @@ module ActionDispatch
# assert_redirected_to @customer
#
def assert_redirected_to(options = {}, message=nil)
- validate_request!
-
assert_response(:redirect, message)
return true if options == @response.location
- redirected_to_after_normalisation = normalize_argument_to_redirection(@response.location)
- options_after_normalisation = normalize_argument_to_redirection(options)
+ redirect_is = normalize_argument_to_redirection(@response.location)
+ redirect_expected = normalize_argument_to_redirection(options)
- if redirected_to_after_normalisation != options_after_normalisation
- flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
+ if redirect_is != redirect_expected
+ flunk "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index b760db42e2..b10aab9029 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,24 +1,25 @@
require 'uri'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/indifferent_access'
+require 'action_controller/metal/exceptions'
module ActionDispatch
module Assertions
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
module RoutingAssertions
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
- # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
+ # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
#
- # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
- # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
+ # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
+ # requiring a specific HTTP method. The hash should contain a :path with the incoming request path
# and a :method containing the required HTTP verb.
#
# # assert that POSTing to /items will call the create action on ItemsController
# assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
#
- # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
- # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
- # extras argument, appending the query string on the path directly will not work. For example:
+ # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
+ # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
+ # extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
@@ -49,7 +50,7 @@ module ActionDispatch
assert_equal(expected_options, request.path_parameters, msg)
end
- # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
+ # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
#
@@ -92,10 +93,10 @@ module ActionDispatch
end
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
- # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
+ # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
# and +assert_generates+ into one step.
#
- # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
+ # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
# ==== Examples
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 5fa91d1a76..b4555f4f59 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -415,9 +415,9 @@ module ActionDispatch
assert !deliveries.empty?, "No e-mail in delivery list"
for delivery in deliveries
- for part in delivery.parts
+ for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
if part["Content-Type"].to_s =~ /^text\/html\W/
- root = HTML::Document.new(part.body).root
+ root = HTML::Document.new(part.body.to_s).root
assert_select root, ":root", &block
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 7d707d03a9..0f1bb9f260 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -62,7 +62,7 @@ module ActionDispatch
#
# The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the
# parameters are +nil+, a hash, or a url-encoded or multipart string;
- # the headers are a hash. Keys are automatically upcased and prefixed
+ # the headers are a hash. Keys are automatically upcased and prefixed
# with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
@@ -207,9 +207,6 @@ module ActionDispatch
"*/*;q=0.5"
unless defined? @named_routes_configured
- # install the named routes in this session instance.
- klass = singleton_class
-
# the helpers are made protected by default--we make them public for
# easier access during testing and troubleshooting.
@named_routes_configured = true
@@ -244,8 +241,8 @@ module ActionDispatch
end
# Performs the actual request.
- def process(method, path, parameters = nil, env = nil)
- env ||= {}
+ def process(method, path, parameters = nil, rack_env = nil)
+ rack_env ||= {}
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -261,7 +258,7 @@ module ActionDispatch
hostname, port = host.split(':')
- default_env = {
+ env = {
:method => method,
:params => parameters,
@@ -279,7 +276,7 @@ module ActionDispatch
session = Rack::Test::Session.new(_mock_session)
- env.reverse_merge!(default_env)
+ env.merge!(rack_env)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 397bda41d5..b08ff41950 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,15 +1,11 @@
+require 'action_dispatch/middleware/cookies'
require 'action_dispatch/middleware/flash'
require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = {}.with_indifferent_access
- @controller.instance_variable_names.each do |ivar|
- next if ActionController::Base.protected_instance_variables.include?(ivar)
- assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar)
- end
-
+ assigns = @controller.view_assigns.with_indifferent_access
key.nil? ? assigns : assigns[key]
end
@@ -22,7 +18,7 @@ module ActionDispatch
end
def cookies
- @request.cookies.merge(@response.cookies).with_indifferent_access
+ @request.cookie_jar
end
def redirect_to_url
@@ -38,7 +34,7 @@ module ActionDispatch
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
- fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
+ fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path)
Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 822adb6a47..7280e9a93b 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/reverse_merge'
require 'rack/utils'
@@ -14,18 +15,11 @@ module ActionDispatch
env = Rails.application.env_config.merge(env) if defined?(Rails.application)
super(DEFAULT_ENV.merge(env))
- @cookies = nil
self.host = 'test.host'
self.remote_addr = '0.0.0.0'
self.user_agent = 'Rails Testing'
end
- def env
- write_cookies!
- delete_nil_values!
- super
- end
-
def request_method=(method)
@env['REQUEST_METHOD'] = method.to_s.upcase
end
@@ -71,23 +65,10 @@ module ActionDispatch
@env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
end
+ alias :rack_cookies :cookies
+
def cookies
- @cookies ||= super
+ @cookies ||= {}.with_indifferent_access
end
-
- private
- def write_cookies!
- unless @cookies.blank?
- @env['HTTP_COOKIE'] = @cookies.map { |name, value| escape_cookie(name, value) }.join('; ')
- end
- end
-
- def escape_cookie(name, value)
- "#{Rack::Utils.escape(name)}=#{Rack::Utils.escape(value)}"
- end
-
- def delete_nil_values!
- @env.delete_if { |k, v| v.nil? }
- end
end
end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 584e5c3791..add6b56425 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,9 +1,9 @@
module ActionPack
module VERSION #:nodoc:
MAJOR = 3
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "beta"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index a67b61c1ef..d7229419a9 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -30,6 +30,7 @@ module ActionView
extend ActiveSupport::Autoload
eager_autoload do
+ autoload :AssetPaths
autoload :Base
autoload :Context
autoload :Helpers
@@ -71,11 +72,6 @@ module ActionView
autoload :TemplateError
autoload :WrongEncodingError
end
-
- autoload_at "action_view/template" do
- autoload :TemplateHandler
- autoload :TemplateHandlers
- end
end
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
new file mode 100644
index 0000000000..1d16e34df6
--- /dev/null
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -0,0 +1,136 @@
+require 'zlib'
+require 'active_support/core_ext/file'
+require 'action_controller/metal/exceptions'
+
+module ActionView
+ class AssetPaths #:nodoc:
+ attr_reader :config, :controller
+
+ def initialize(config, controller = nil)
+ @config = config
+ @controller = controller
+ end
+
+ # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
+ # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
+ # roots. Rewrite the asset path for cache-busting asset ids. Include
+ # asset host, if configured, with the correct request protocol.
+ #
+ # When :relative (default), the protocol will be determined by the client using current protocol
+ # When :request, the protocol will be the request protocol
+ # Otherwise, the protocol is used (E.g. :http, :https, etc)
+ def compute_public_path(source, dir, options = {})
+ source = source.to_s
+ return source if is_uri?(source)
+
+ source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
+ source = rewrite_asset_path(source, dir, options)
+ source = rewrite_relative_url_root(source, relative_url_root)
+ source = rewrite_host_and_protocol(source, options[:protocol])
+ source
+ end
+
+ # Return the filesystem path for the source
+ def compute_source_path(source, dir, ext)
+ source = rewrite_extension(source, dir, ext) if ext
+ File.join(config.assets_dir, dir, source)
+ end
+
+ def is_uri?(path)
+ path =~ %r{^[-a-z]+://|^cid:|^//}
+ end
+
+ private
+
+ def rewrite_extension(source, dir, ext)
+ raise NotImplementedError
+ end
+
+ def rewrite_asset_path(source, path = nil)
+ raise NotImplementedError
+ end
+
+ def rewrite_relative_url_root(source, relative_url_root)
+ relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
+ end
+
+ def has_request?
+ controller.respond_to?(:request)
+ end
+
+ def rewrite_host_and_protocol(source, protocol = nil)
+ host = compute_asset_host(source)
+ if host && !is_uri?(host)
+ if (protocol || default_protocol) == :request && !has_request?
+ host = nil
+ else
+ host = "#{compute_protocol(protocol)}#{host}"
+ end
+ end
+ host ? "#{host}#{source}" : source
+ end
+
+ def compute_protocol(protocol)
+ protocol ||= default_protocol
+ case protocol
+ when :relative
+ "//"
+ when :request
+ unless @controller
+ invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
+ end
+ @controller.request.protocol
+ else
+ "#{protocol}://"
+ end
+ end
+
+ def default_protocol
+ @config.default_asset_host_protocol || (has_request? ? :request : :relative)
+ end
+
+ def invalid_asset_host!(help_message)
+ raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}"
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking call on an object responding to call
+ # (proc or otherwise).
+ def compute_asset_host(source)
+ if host = asset_host_config
+ if host.respond_to?(:call)
+ args = [source]
+ arity = arity_of(host)
+ if arity > 1 && !has_request?
+ invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.")
+ end
+ args << current_request if (arity > 1 || arity < 0) && has_request?
+ host.call(*args)
+ else
+ (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
+ end
+ end
+ end
+
+ def relative_url_root
+ config.relative_url_root
+ end
+
+ def asset_host_config
+ config.asset_host
+ end
+
+ # Returns the current request if one exists.
+ def current_request
+ controller.request if has_request?
+ end
+
+ # Returns the arity of a callable
+ def arity_of(callable)
+ callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
+ end
+
+ end
+end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index fd2970b8e2..36c49d9c91 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/wrap'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
+require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
@@ -115,7 +116,7 @@ module ActionView #:nodoc:
# xml.language "en-us"
# xml.ttl "40"
#
- # for item in @recent_items
+ # @recent_items.each do |item|
# xml.item do
# xml.title(item_title(item))
# xml.description(item_description(item)) if item_description(item)
@@ -161,6 +162,7 @@ module ActionView #:nodoc:
value.is_a?(PathSet) ?
value.dup : ActionView::PathSet.new(Array.wrap(value))
end
+ deprecate :process_view_paths
def xss_safe? #:nodoc:
true
@@ -194,7 +196,7 @@ module ActionView #:nodoc:
end
def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
- @_config = {}
+ @_config = ActiveSupport::InheritableOptions.new
# Handle all these for backwards compatibility.
# TODO Provide a new API for AV::Base and deprecate this one.
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
index 089fc68706..be7f65c2ce 100644
--- a/actionpack/lib/action_view/buffers.rb
+++ b/actionpack/lib/action_view/buffers.rb
@@ -35,9 +35,9 @@ module ActionView
def html_safe?
true
end
-
+
def html_safe
self
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 78a68db282..262e0f1010 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -22,7 +22,6 @@ module ActionView #:nodoc:
autoload :RecordTagHelper
autoload :RenderingHelper
autoload :SanitizeHelper
- autoload :SprocketsHelper
autoload :TagHelper
autoload :TextHelper
autoload :TranslationHelper
@@ -53,7 +52,6 @@ module ActionView #:nodoc:
include RecordTagHelper
include RenderingHelper
include SanitizeHelper
- include SprocketsHelper
include TagHelper
include TextHelper
include TranslationHelper
diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb
index 958f0e0a10..fae2e4fc1c 100644
--- a/actionpack/lib/action_view/helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_paths.rb
@@ -1,79 +1,7 @@
-require 'active_support/core_ext/file'
-require 'action_view/helpers/asset_paths'
+ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead."
module ActionView
module Helpers
-
- class AssetPaths #:nodoc:
- attr_reader :config, :controller
-
- def initialize(config, controller)
- @config = config
- @controller = controller
- end
-
- # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- def compute_public_path(source, dir, ext = nil, include_host = true)
- source = source.to_s
- return source if is_uri?(source)
-
- source = rewrite_extension(source, dir, ext) if ext
- source = rewrite_asset_path(source, dir)
-
- if controller && include_host
- has_request = controller.respond_to?(:request)
- source = rewrite_host_and_protocol(source, has_request)
- end
-
- source
- end
-
- def is_uri?(path)
- path =~ %r{^[-a-z]+://|^cid:|^//}
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- raise NotImplementedError
- end
-
- def rewrite_asset_path(source, path = nil)
- raise NotImplementedError
- end
-
- def rewrite_host_and_protocol(source, has_request)
- host = compute_asset_host(source)
- if has_request && host && !is_uri?(host)
- host = "#{controller.request.protocol}#{host}"
- end
- "#{host}#{source}"
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking the proc if it's a proc or the value from
- # invoking call if it's an object responding to call.
- def compute_asset_host(source)
- if host = config.asset_host
- if host.is_a?(Proc) || host.respond_to?(:call)
- case host.is_a?(Proc) ? host.arity : host.method(:call).arity
- when 2
- request = controller.respond_to?(:request) && controller.request
- host.call(source, request)
- else
- host.call(source)
- end
- else
- (host =~ /%d/) ? host % (source.hash % 4) : host
- end
- end
- end
- end
-
+ AssetPaths = ::ActionView::AssetPaths
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 9bc847a1ab..7d01e5ddb8 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,6 +1,7 @@
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
+require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Asset Tag Helpers
@@ -153,7 +154,7 @@ module ActionView
# "/release-#{RELEASE_NUMBER}#{asset_path}"
# }
#
- # This example would cause the following behaviour on all servers no
+ # This example would cause the following behavior on all servers no
# matter when they were deployed:
#
# image_tag("rails.png")
@@ -191,6 +192,7 @@ module ActionView
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
+ include TagHelper
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
@@ -274,11 +276,7 @@ module ActionView
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source)
- if config.use_sprockets
- asset_path(source)
- else
- asset_paths.compute_public_path(source, 'images')
- end
+ asset_paths.compute_public_path(source, 'images')
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -293,11 +291,7 @@ module ActionView
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
def video_path(source)
- if config.use_sprockets
- asset_path(source)
- else
- asset_paths.compute_public_path(source, 'videos')
- end
+ asset_paths.compute_public_path(source, 'videos')
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
@@ -312,11 +306,7 @@ module ActionView
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
def audio_path(source)
- if config.use_sprockets
- asset_path(source)
- else
- asset_paths.compute_public_path(source, 'audios')
- end
+ asset_paths.compute_public_path(source, 'audios')
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
@@ -359,7 +349,7 @@ module ActionView
src = options[:src] = path_to_image(source)
unless src =~ /^cid:/
- options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize }
+ options[:alt] = options.fetch(:alt){ image_alt(src) }
end
if size = options.delete(:size)
@@ -374,6 +364,10 @@ module ActionView
tag("img", options)
end
+ def image_alt(src)
+ File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
+ end
+
# Returns an html video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index e4662a2919..05d5f1870a 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -60,12 +60,16 @@ module ActionView
private
- def path_to_asset(source, include_host = true)
- asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host)
+ def path_to_asset(source, options = {})
+ asset_paths.compute_public_path(source, asset_name.to_s.pluralize, options.merge(:ext => extension))
+ end
+
+ def path_to_asset_source(source)
+ asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension)
end
def compute_paths(*args)
- expand_sources(*args).collect { |source| asset_paths.compute_public_path(source, asset_name.pluralize, extension, false) }
+ expand_sources(*args).collect { |source| path_to_asset_source(source) }
end
def expand_sources(sources, recursive)
@@ -92,7 +96,7 @@ module ActionView
def ensure_sources!(sources)
sources.each do |source|
- asset_file_path!(path_to_asset(source, false))
+ asset_file_path!(path_to_asset_source(source))
end
end
@@ -123,19 +127,14 @@ module ActionView
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
- mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
+ mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max
File.utime(mt, mt, joined_asset_path)
end
- def asset_file_path(path)
- File.join(config.assets_dir, path.split('?').first)
- end
-
- def asset_file_path!(path, error_if_file_is_uri = false)
- if asset_paths.is_uri?(path)
+ def asset_file_path!(absolute_path, error_if_file_is_uri = false)
+ if asset_paths.is_uri?(absolute_path)
raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri
else
- absolute_path = asset_file_path(path)
raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
return absolute_path
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
index cd0f8c8878..dd4e9ae4cc 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -1,11 +1,11 @@
+require 'thread'
require 'active_support/core_ext/file'
-require 'action_view/helpers/asset_paths'
module ActionView
module Helpers
module AssetTagHelper
- class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
+ class AssetPaths < ::ActionView::AssetPaths #:nodoc:
# You can enable or disable the asset tag ids cache.
# With the cache enabled, the asset tag helper methods will make fewer
# expensive file system calls (the default implementation checks the file
@@ -41,7 +41,7 @@ module ActionView
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
- def rewrite_asset_path(source, dir)
+ def rewrite_asset_path(source, dir, options = nil)
source = "/#{dir}/#{source}" unless source[0] == ?/
path = config.asset_path
@@ -85,17 +85,8 @@ module ActionView
end
end
end
-
- def rewrite_relative_url_root(source, relative_url_root)
- relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
- end
-
- def rewrite_host_and_protocol(source, has_request)
- source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request
- super(source, has_request)
- end
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index e1ee0d0e1a..09700bd0c5 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -83,11 +83,7 @@ module ActionView
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source)
- if config.use_sprockets
- asset_path(source, 'js')
- else
- asset_paths.compute_public_path(source, 'javascripts', 'js')
- end
+ asset_paths.compute_public_path(source, 'javascripts', :ext => 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -187,12 +183,8 @@ module ActionView
#
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
- if config.use_sprockets
- sprockets_javascript_include_tag(*sources)
- else
- @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
- @javascript_include.include_tag(*sources)
- end
+ @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths)
+ @javascript_include.include_tag(*sources)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index a95eb221be..343153c8c5 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -16,7 +16,8 @@ module ActionView
end
def asset_tag(source, options)
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source)) }.merge(options), false, false)
+ # We force the :request protocol here to avoid a double-download bug in IE7 and IE8
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
end
def custom_dir
@@ -52,7 +53,7 @@ module ActionView
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
+ #
# ==== Examples
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
@@ -60,11 +61,7 @@ module ActionView
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source)
- if config.use_sprockets
- asset_path(source, 'css')
- else
- asset_paths.compute_public_path(source, 'stylesheets', 'css')
- end
+ asset_paths.compute_public_path(source, 'stylesheets', :ext => 'css', :protocol => :request)
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -137,12 +134,8 @@ module ActionView
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
- if config.use_sprockets
- sprockets_stylesheet_link_tag(*sources)
- else
- @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
- @stylesheet_include.include_tag(*sources)
- end
+ @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths)
+ @stylesheet_include.include_tag(*sources)
end
end
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index a087688a2c..39c37b25dc 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -20,7 +20,7 @@ module ActionView
# # GET /posts.html
# # GET /posts.atom
# def index
- # @posts = Post.find(:all)
+ # @posts = Post.all
#
# respond_to do |format|
# format.html
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index e81d03b537..850dd5f448 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -51,7 +51,11 @@ module ActionView
# This dance is needed because Builder can't use capture
pos = output_buffer.length
yield
+ output_safe = output_buffer.html_safe?
fragment = output_buffer.slice!(pos..-1)
+ if output_safe
+ self.output_buffer = output_buffer.class.new(output_buffer)
+ end
controller.write_fragment(name, fragment, options)
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 3b5f4e694f..8abd85c3a3 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -27,7 +27,7 @@ module ActionView
# "The current timestamp is #{Time.now}."
# end
#
- # You can then use that variable anywhere else. For example:
+ # You can then use that variable anywhere else. For example:
#
# <html>
# <head><title><%= @greeting %></title></head>
@@ -76,7 +76,7 @@ module ActionView
#
# <%= stored_content %>
#
- # You can use the <tt>yield</tt> syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
+ # You can use the <tt>yield</tt> syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
#
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
@@ -134,9 +134,9 @@ module ActionView
# WARNING: content_for is ignored in caches. So you shouldn't use it
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
- content = capture(&block) if block_given?
- if content
- @view_flow.append(name, content)
+ if content || block_given?
+ content = capture(&block) if block_given?
+ @view_flow.append(name, content) if content
nil
else
@view_flow.get(name)
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
index e22331cb3c..1a583e62ae 100644
--- a/actionpack/lib/action_view/helpers/controller_helper.rb
+++ b/actionpack/lib/action_view/helpers/controller_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attr_internal'
+
module ActionView
module Helpers
# This module keeps all methods and behavior in ActionView
@@ -18,4 +20,4 @@ module ActionView
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index c78c03a5eb..4deb87180c 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -8,8 +8,8 @@ module ActionView
module Helpers
# = Action View Date 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:
+ # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
+ # 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 <tt>select_month</tt> method.
@@ -18,7 +18,7 @@ module ActionView
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
# of "date[month]".
module DateHelper
- # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
+ # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
@@ -176,37 +176,37 @@ module ActionView
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
#
# ==== Examples
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute.
- # date_select("post", "written_on")
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
+ # date_select("article", "written_on")
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995.
- # date_select("post", "written_on", :start_year => 1995)
+ # date_select("article", "written_on", :start_year => 1995)
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
# # and without a day select box.
- # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
+ # date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
- # date_select("post", "written_on", :order => [:day, :month, :year])
+ # date_select("article", "written_on", :order => [:day, :month, :year])
#
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
# # lacking a year field.
# date_select("user", "birthday", :order => [:month, :day])
#
- # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # which is initially set to the date 3 days from the current date
- # date_select("post", "written_on", :default => 3.days.from_now)
+ # date_select("article", "written_on", :default => 3.days.from_now)
#
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
# # that will have a default day of 20.
# date_select("credit_card", "bill_due", :default => { :day => 20 })
#
# # Generates a date select with custom prompts.
- # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
+ # date_select("article", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' })
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
@@ -228,20 +228,20 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute.
- # time_select("post", "sunrise")
+ # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
+ # time_select("article", "sunrise")
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
# # the sunrise attribute.
- # time_select("post", "start_time", :include_seconds => true)
+ # time_select("article", "start_time", :include_seconds => true)
#
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
# time_select 'game', 'game_time', {:minute_step => 15}
#
# # Creates a time select tag with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
- # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # time_select("post", "written_on", :prompt => true) # generic prompts for all
+ # time_select("article", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
+ # time_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # time_select("article", "written_on", :prompt => true) # generic prompts for all
#
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
# time_select 'game', 'game_time', {:ampm => true}
@@ -261,36 +261,36 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on
+ # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
# # attribute.
- # datetime_select("post", "written_on")
+ # datetime_select("article", "written_on")
#
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
- # # post variable in the written_on attribute.
- # datetime_select("post", "written_on", :start_year => 1995)
+ # # article variable in the written_on attribute.
+ # datetime_select("article", "written_on", :start_year => 1995)
#
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
# # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
# # Generate a datetime select with hours in the AM/PM format
- # datetime_select("post", "written_on", :ampm => true)
+ # datetime_select("article", "written_on", :ampm => true)
#
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
# # as the written_on attribute.
- # datetime_select("post", "written_on", :discard_type => true)
+ # datetime_select("article", "written_on", :discard_type => true)
#
# # Generates a datetime select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts.
- # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
- # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
- # datetime_select("post", "written_on", :prompt => true) # generic prompts for all
+ # datetime_select("article", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
+ # datetime_select("article", "written_on", :prompt => {:hour => true}) # generic prompt for hours
+ # datetime_select("article", "written_on", :prompt => true) # generic prompts for all
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
- # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the
+ # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
@@ -343,15 +343,15 @@ module ActionView
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
- # it will be appended onto the <tt>:order</tt> passed in.
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
+ # If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # my_date = Time.today + 6.days
+ # my_date = Time.now + 6.days
#
- # # Generates a date select that defaults to the date in my_date (six days afteri today).
+ # # Generates a date select that defaults to the date in my_date (six days after today).
# select_date(my_date)
#
# # Generates a date select that defaults to today (no specified date).
@@ -422,7 +422,7 @@ module ActionView
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
- # The <tt>second</tt> can also be substituted for a second number.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
# ==== Examples
@@ -440,7 +440,7 @@ module ActionView
#
# # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
- # select_minute(14, :prompt => 'Choose seconds')
+ # select_second(14, :prompt => 'Choose seconds')
#
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
@@ -448,21 +448,21 @@ module ActionView
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
- # selected. The <tt>minute</tt> can also be substituted for a minute number.
+ # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
# ==== Examples
# my_time = Time.now + 6.hours
#
- # # Generates a select field for minutes that defaults to the minutes for the time in my_tiime.
+ # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
# select_minute(my_time)
#
# # Generates a select field for minutes that defaults to the number given.
# select_minute(14)
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
- # # that is named 'stride' rather than 'second'.
- # select_minute(my_time, :field_name => 'stride')
+ # # that is named 'moment' rather than 'minute'.
+ # select_minute(my_time, :field_name => 'moment')
#
# # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
@@ -473,7 +473,7 @@ module ActionView
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.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
#
# ==== Examples
@@ -485,8 +485,8 @@ module ActionView
# # Generates a select field for hours that defaults to the number given.
# select_hour(13)
#
- # # Generates a select field for hours that defaults to the minutes for the time in my_time
- # # that is named 'stride' rather than 'second'.
+ # # Generates a select field for hours that defaults to the hour for the time in my_time
+ # # that is named 'stride' rather than 'hour'.
# select_hour(my_time, :field_name => 'stride')
#
# # Generates a select field for hours with a custom prompt. Use <tt>:prompt => true</tt> for a
@@ -501,11 +501,11 @@ module ActionView
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.
+ # The <tt>date</tt> can also be substituted for a day number.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
# ==== Examples
- # my_date = Time.today + 2.days
+ # my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
# select_day(my_time)
@@ -517,7 +517,7 @@ module ActionView
# # that is named 'due' rather than 'day'.
# select_day(my_time, :field_name => 'due')
#
- # # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a
+ # # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_day(5, :prompt => 'Choose day')
#
@@ -621,7 +621,6 @@ module ActionView
end
class DateTimeSelector #:nodoc:
- extend ActiveSupport::Memoizable
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date'.freeze
@@ -766,11 +765,16 @@ module ActionView
if @options[:use_hidden] || @options[:discard_year]
build_hidden(:year, val)
else
- options = {}
- options[:start] = @options[:start_year] || middle_year - 5
- options[:end] = @options[:end_year] || middle_year + 5
- options[:step] = options[:start] < options[:end] ? 1 : -1
- options[:leading_zeros] = false
+ options = {}
+ options[:start] = @options[:start_year] || middle_year - 5
+ options[:end] = @options[:end_year] || middle_year + 5
+ options[:step] = options[:start] < options[:end] ? 1 : -1
+ options[:leading_zeros] = false
+ options[:max_years_allowed] = @options[:max_years_allowed] || 1000
+
+ if (options[:end] - options[:start]).abs > options[:max_years_allowed]
+ raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter"
+ end
build_options_and_select(:year, val, options)
end
@@ -786,11 +790,12 @@ module ActionView
# Returns translated month names, but also ensures that a custom month
# name array has a leading nil element.
def month_names
- month_names = @options[:use_month_names] || translated_month_names
- month_names.unshift(nil) if month_names.size < 13
- month_names
+ @month_names ||= begin
+ month_names = @options[:use_month_names] || translated_month_names
+ month_names.unshift(nil) if month_names.size < 13
+ month_names
+ end
end
- memoize :month_names
# Returns translated month names.
# => [nil, "January", "February", "March",
@@ -825,9 +830,8 @@ module ActionView
end
def date_order
- @options[:order] || translated_date_order
+ @date_order ||= @options[:order] || translated_date_order
end
- memoize :date_order
def translated_date_order
I18n.translate(:'date.order', :locale => @options[:locale]) || []
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index cd67851642..c0cc7d347c 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -30,7 +30,7 @@ module ActionView
begin
Marshal::dump(object)
"<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>".html_safe
- rescue Exception => e # errors from Marshal or YAML
+ rescue Exception # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
"<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 07e2c8d341..f22c466666 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/module/method_names'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
@@ -48,7 +49,7 @@ module ActionView
# <label for="person_last_name">Last name</label>:
# <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
#
- # <input id="person_submit" name="commit" type="submit" value="Create Person" />
+ # <input name="commit" type="submit" value="Create Person" />
# </form>
#
# As you see, the HTML reflects knowledge about the resource in several spots,
@@ -79,7 +80,7 @@ module ActionView
# <label for="person_last_name">Last name</label>:
# <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
#
- # <input id="person_submit" name="commit" type="submit" value="Update Person" />
+ # <input name="commit" type="submit" value="Update Person" />
# </form>
#
# Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
@@ -202,13 +203,13 @@ module ActionView
#
# is equivalent to something like:
#
- # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :class => "new_post", :id => "new_post" } do |f| %>
+ # <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
# ...
# <% end %>
#
# You can also overwrite the individual conventions, like this:
#
- # <%= form_for(@post, :url => super_post_path(@post)) do |f| %>
+ # <%= form_for(@post, :url => super_posts_path) do |f| %>
# ...
# <% end %>
#
@@ -219,9 +220,9 @@ module ActionView
# <% end %>
#
# If you have an object that needs to be represented as a different
- # parameter, like a Client that acts as a Person:
+ # parameter, like a Person that acts as a Client:
#
- # <%= form_for(@post, :as => :client) do |f| %>
+ # <%= form_for(@person, :as => :client) do |f| %>
# ...
# <% end %>
#
@@ -232,7 +233,7 @@ module ActionView
# <% end %>
#
# If your resource has associations defined, for example, you want to add comments
- # to the post given that the routes are set correctly:
+ # to the document given that the routes are set correctly:
#
# <%= form_for([@document, @comment]) do |f| %>
# ...
@@ -258,8 +259,8 @@ module ActionView
# :remote => true
#
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
- # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular
- # POST arrangement, but ultimately the behaviour is the choice of the JavaScript driver implementor.
+ # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
+ # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
# a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
#
@@ -290,7 +291,7 @@ module ActionView
#
# Example:
#
- # <%= form(@post) do |f| %>
+ # <%= form_for(@post) do |f| %>
# <% f.fields_for(:comments, :include_id => false) do |cf| %>
# ...
# <% end %>
@@ -364,7 +365,7 @@ module ActionView
apply_form_for_options!(record, options)
end
- options[:html][:remote] = options.delete(:remote)
+ options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
@@ -517,6 +518,18 @@ module ActionView
# end
# end
#
+ # Note that the <tt>projects_attributes=</tt> writer method is in fact
+ # required for fields_for to correctly identify <tt>:projects</tt> as a
+ # collection, and the correct indices to be set in the form markup.
+ #
+ # When projects is already an association on Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects
+ # end
+ #
# This model can now be used with a nested fields_for. The block given to
# the nested fields_for call will be repeated for each instance in the
# collection:
@@ -846,7 +859,28 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
- # Returns a text_field of type "search".
+ # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
+ # some browsers.
+ #
+ # ==== Examples
+ #
+ # search_field(:user, :name)
+ # # => <input id="user_name" name="user[name]" size="30" type="search" />
+ # search_field(:user, :name, :autosave => false)
+ # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
+ # search_field(:user, :name, :results => 3)
+ # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
+ # # Assume request.host returns "www.example.com"
+ # search_field(:user, :name, :autosave => true)
+ # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
+ # search_field(:user, :name, :onsearch => true)
+ # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # search_field(:user, :name, :autosave => false, :onsearch => true)
+ # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # search_field(:user, :name, :autosave => true, :onsearch => true)
+ # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
+ #
def search_field(object_name, method, options = {})
options = options.stringify_keys
@@ -865,17 +899,29 @@ module ActionView
end
# Returns a text_field of type "tel".
+ #
+ # telephone_field("user", "phone")
+ # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
+ #
def telephone_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
end
alias phone_field telephone_field
# Returns a text_field of type "url".
+ #
+ # url_field("user", "homepage")
+ # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
+ #
def url_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
end
# Returns a text_field of type "email".
+ #
+ # email_field("user", "address")
+ # # => <input id="user_address" size="30" name="user[address]" type="email" />
+ #
def email_field(object_name, method, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
end
@@ -1136,7 +1182,7 @@ module ActionView
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
- options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
+ options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
options["id"] = options.fetch("id"){ tag_id }
end
end
@@ -1181,8 +1227,12 @@ module ActionView
parent_builder.multipart = multipart if parent_builder
end
- def self.model_name
- @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
+ def self._to_partial_path
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
+ end
+
+ def to_partial_path
+ self.class._to_partial_path
end
def to_model
@@ -1217,7 +1267,7 @@ module ActionView
end
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
- fields_options, record_object = record_object, nil if record_object.is_a?(Hash)
+ fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 0aaa690129..f895cad058 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -105,7 +105,10 @@ module ActionView
# Create a select tag and a series of contained option tags for the provided object and method.
# The option currently held by the object will be selected, provided that the object is available.
- # See options_for_select for the required format of the choices parameter.
+ #
+ # There are two possible formats for the choices parameter, corresponding to other helpers' output:
+ # * A flat collection: see options_for_select
+ # * A nested collection: see grouped_options_for_select
#
# Example with @post.person_id => 1:
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
@@ -125,9 +128,31 @@ module ActionView
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
#
- # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
+ # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
# or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
+ # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
+ # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
+ # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
+ # any mass-assignment idiom like
+ #
+ # @user.update_attributes(params[:user])
+ #
+ # wouldn't update roles.
+ #
+ # To prevent this the helper generates an auxiliary hidden field before
+ # every multiple select. The hidden field has the same name as multiple select and blank value.
+ #
+ # This way, the client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. Since the HTML specification
+ # says key/value pairs have to be sent in the same order they appear in the
+ # form, and parameters extraction gets the last occurrence of any repeated
+ # key in the query string, that works for ordinary forms.
+ #
def select(object, method, choices, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
@@ -255,7 +280,7 @@ module ActionView
# 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+
+ # 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):
@@ -298,12 +323,12 @@ module ActionView
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do | r |
- Array.wrap(r).map(&:to_s)
+ Array.wrap(r).map { |item| item.to_s }
end
container.map do |element|
html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map(&:to_s)
+ text, value = option_text_and_value(element).map { |item| item.to_s }
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
%(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
@@ -406,13 +431,13 @@ module ActionView
# wraps them with <tt><optgroup></tt> tags.
#
# Parameters:
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
# <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
# nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
# Ex. ["North America",[["United States","US"],["Canada","CA"]]]
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
# * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
#
@@ -427,7 +452,7 @@ module ActionView
#
# Sample usage (Hash):
# grouped_options = {
- # 'North America' => [['United States','US], 'Canada'],
+ # 'North America' => [['United States','US'], 'Canada'],
# 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
@@ -552,43 +577,38 @@ module ActionView
include FormOptionsHelper
def to_select_tag(choices, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- selected_value = options.has_key?(:selected) ? options[:selected] : value
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
- content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
+
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ if !choices.empty? && Array === choices.first.last
+ option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
+ else
+ option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
+ end
+
+ select_content_tag(option_tags, options, html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
- selected_value = options.has_key?(:selected) ? options[:selected] : value
- content_tag(
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
+ select_content_tag(
+ options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
)
end
def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag(
- "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
+ select_content_tag(
+ option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
)
end
def to_time_zone_select_tag(priority_zones, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- value = value(object)
- content_tag("select",
- add_options(
- time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
- options, value
- ), html_options
+ select_content_tag(
+ time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
)
end
@@ -603,6 +623,17 @@ module ActionView
end
option_tags.html_safe
end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ if html_options["multiple"]
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
end
class FormBuilder
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 65a98fb27a..1424a3584d 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -30,7 +30,7 @@ module ActionView
# (by passing <tt>false</tt>).
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
- # submit behaviour. By default this behaviour is an ajax submit.
+ # submit behavior. By default this behavior is an ajax submit.
#
# ==== Examples
# form_tag('/posts')
@@ -56,8 +56,8 @@ module ActionView
# form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
# # form with custom authenticity token
#
- def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
- html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
+ def form_tag(url_for_options = {}, options = {}, &block)
+ html_options = html_options_for_form(url_for_options, options)
if block_given?
form_tag_in_block(html_options, &block)
else
@@ -177,9 +177,12 @@ module ActionView
# label_tag 'name', nil, :class => 'small_label'
# # => <label for="name" class="small_label">Name</label>
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options.stringify_keys!
+ if block_given? && content_or_options.is_a?(Hash)
+ options = content_or_options = content_or_options.stringify_keys
+ else
+ options ||= {}
+ options = options.stringify_keys
+ end
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
content_tag :label, content_or_options || name.to_s.humanize, options, &block
end
@@ -204,7 +207,7 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
end
- # Creates a file upload field. If you are using file uploads then you will also need
+ # Creates a file upload field. If you are using file uploads then you will also need
# to set the multipart option for the form tag:
#
# <%= form_tag '/upload', :multipart => true do %>
@@ -304,7 +307,7 @@ module ActionView
# text_area_tag 'comment', nil, :class => 'comment_input'
# # => <textarea class="comment_input" id="comment" name="comment"></textarea>
def text_area_tag(name, content = nil, options = {})
- options.stringify_keys!
+ options = options.stringify_keys
if size = options.delete("size")
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
@@ -407,7 +410,7 @@ module ActionView
# data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
- options.stringify_keys!
+ options = options.stringify_keys
if disable_with = options.delete("disable_with")
options["data-disable-with"] = disable_with
@@ -417,7 +420,7 @@ module ActionView
options["data-confirm"] = confirm
end
- tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
+ tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
end
# Creates a button element that defines a <tt>submit</tt> button,
@@ -458,7 +461,7 @@ module ActionView
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
- options.stringify_keys!
+ options = options.stringify_keys
if disable_with = options.delete("disable_with")
options["data-disable-with"] = disable_with
@@ -497,13 +500,13 @@ module ActionView
# image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
# # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
def image_submit_tag(source, options = {})
- options.stringify_keys!
+ options = options.stringify_keys
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
+ tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
end
# Creates a field set for grouping HTML form elements.
@@ -579,7 +582,7 @@ module ActionView
#
# ==== Examples
# number_field_tag 'quantity', nil, :in => 1...10
- # => <input id="quantity" name="quantity" min="1" max="9" />
+ # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
@@ -597,13 +600,19 @@ module ActionView
number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
end
+ # Creates the hidden UTF8 enforcer tag. Override this method in a helper
+ # to customize the tag.
+ def utf8_enforcer_tag
+ tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
+ end
+
private
- def html_options_for_form(url_for_options, options, *parameters_for_url)
+ def html_options_for_form(url_for_options, options)
options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
# responsibility of the caller to escape all the values.
- html_options["action"] = url_for(url_for_options, *parameters_for_url)
+ html_options["action"] = url_for(url_for_options)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
@@ -611,9 +620,6 @@ module ActionView
end
def extra_tags_for_form(html_options)
- snowman_tag = tag(:input, :type => "hidden",
- :name => "utf8", :value => "&#x2713;".html_safe)
-
authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s
@@ -629,7 +635,7 @@ module ActionView
tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
end
- tags = snowman_tag << method_tag
+ tags = utf8_enforcer_tag << method_tag
content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
end
@@ -650,7 +656,7 @@ module ActionView
if token == false || !protect_against_forgery?
''
else
- token = form_authenticity_token if token.nil?
+ token ||= form_authenticity_token
tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
end
end
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index d7228bab67..1adcd716f8 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,4 +1,5 @@
require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
@@ -10,15 +11,24 @@ module ActionView
"\n" => '\n',
"\r" => '\n',
'"' => '\\"',
- "'" => "\\'" }
+ "'" => "\\'"
+ }
- # Escape carrier returns and single and double quotes for JavaScript segments.
+ if "ruby".encoding_aware?
+ JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
+ else
+ JS_ESCAPE_MAP["\342\200\250"] = '&#x2028;'
+ end
+
+ # Escapes carriage returns and single and double quotes for JavaScript segments.
+ #
# Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
#
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
+ javascript.html_safe? ? result.html_safe : result
else
''
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 63d13a0f0b..7031694af4 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
@@ -67,7 +69,7 @@ module ActionView
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.starts_with?('-')
+ number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
end
str = []
@@ -211,7 +213,7 @@ module ActionView
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
options = options.reverse_merge(defaults)
- parts = number.to_s.split('.')
+ parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
parts.join(options[:separator]).html_safe
@@ -342,7 +344,7 @@ module ActionView
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
-
+
base = options[:prefix] == :si ? 1000 : 1024
if number.to_i < base
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 142a25f118..b351302d01 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -17,6 +17,19 @@ module ActionView
#
# <div id="person_123" class="person foo"> Joe Bloggs </div>
#
+ # You can also pass an array of Active Record objects, which will then
+ # get iterated over and yield each record as an argument for the block.
+ # For example:
+ #
+ # <%= div_for(@people, :class => "foo") do |person| %>
+ # <%= person.name %>
+ # <% end %>
+ #
+ # produces:
+ #
+ # <div id="person_123" class="person foo"> Joe Bloggs </div>
+ # <div id="person_124" class="person foo"> Jane Bloggs </div>
+ #
def div_for(record, *args, &block)
content_tag_for(:div, record, *args, &block)
end
@@ -42,6 +55,21 @@ module ActionView
#
# <tr id="foo_person_123" class="person">...
#
+ # You can also pass an array of objects which this method will loop through
+ # and yield the current object to the supplied block, reducing the need for
+ # having to iterate through the object (using <tt>each</tt>) beforehand.
+ # For example (assuming @people is an array of Person objects):
+ #
+ # <%= content_tag_for(:tr, @people) do |person| %>
+ # <td><%= person.first_name %></td>
+ # <td><%= person.last_name %></td>
+ # <% end %>
+ #
+ # produces:
+ #
+ # <tr id="person_123" class="person">...</tr>
+ # <tr id="person_124" class="person">...</tr>
+ #
# content_tag_for also accepts a hash of options, which will be converted to
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
# with the default class name for your object. For example:
@@ -52,12 +80,30 @@ module ActionView
#
# <li id="person_123" class="person bar">...
#
- def content_tag_for(tag_name, record, prefix = nil, options = nil, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
- options ||= {}
- options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
- content_tag(tag_name, options, &block)
+ def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
+ if single_or_multiple_records.respond_to?(:to_ary)
+ single_or_multiple_records.to_ary.map do |single_record|
+ capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
+ end.join("\n").html_safe
+ else
+ content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
+ end
end
+
+ private
+
+ # Called by <tt>content_tag_for</tt> internally to render a content tag
+ # for each record.
+ def content_tag_for_single_record(tag_name, record, prefix, options, &block)
+ options, prefix = prefix, nil if prefix.is_a?(Hash)
+ options ||= {}
+ options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
+ if block.arity == 0
+ content_tag(tag_name, capture(&block), options)
+ else
+ content_tag(tag_name, capture(record, &block), options)
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb
index 47efdded42..626e1a1ab7 100644
--- a/actionpack/lib/action_view/helpers/rendering_helper.rb
+++ b/actionpack/lib/action_view/helpers/rendering_helper.rb
@@ -8,7 +8,7 @@ module ActionView
module RenderingHelper
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
- # * <tt>:partial</tt> - See ActionView::Partials.
+ # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
@@ -87,4 +87,4 @@ module ActionView
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index 841be0a567..bcc8f6fbcb 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -14,13 +14,13 @@ module ActionView
#
# It also strips href/src tags with invalid protocols, like javascript: especially.
# It does its best to counter any tricks that hackers may use, like throwing in
- # unicode/ascii/hex values to get past the javascript: filters. Check out
+ # unicode/ascii/hex values to get past the javascript: filters. Check out
# the extensive test suite.
#
# <%= sanitize @article.body %>
#
# You can add or remove tags/attributes if you want to customize it a bit.
- # See ActionView::Base for full docs on the available options. You can add
+ # See ActionView::Base for full docs on the available options. You can add
# tags/attributes for single uses of +sanitize+ by passing either the
# <tt>:attributes</tt> or <tt>:tags</tt> options:
#
@@ -66,7 +66,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses the
+ # Strips all HTML tags from the +html+, including comments. This uses the
# html-scanner tokenizer and so its HTML parsing ability is limited by
# that of html-scanner.
#
@@ -142,7 +142,7 @@ module ActionView
white_list_sanitizer.protocol_separator = value
end
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
+ # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -153,7 +153,7 @@ module ActionView
@full_sanitizer ||= HTML::FullSanitizer.new
end
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
+ # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb
deleted file mode 100644
index ab98da9624..0000000000
--- a/actionpack/lib/action_view/helpers/sprockets_helper.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'uri'
-require 'action_view/helpers/asset_paths'
-
-module ActionView
- module Helpers
- module SprocketsHelper
- def asset_path(source, default_ext = nil)
- sprockets_asset_paths.compute_public_path(source, 'assets', default_ext, true)
- end
-
- def sprockets_javascript_include_tag(source, options = {})
- options = {
- 'type' => "text/javascript",
- 'src' => asset_path(source, 'js')
- }.merge(options.stringify_keys)
-
- content_tag 'script', "", options
- end
-
- def sprockets_stylesheet_link_tag(source, options = {})
- options = {
- 'rel' => "stylesheet",
- 'type' => "text/css",
- 'media' => "screen",
- 'href' => asset_path(source, 'css')
- }.merge(options.stringify_keys)
-
- tag 'link', options
- end
-
- private
-
- def sprockets_asset_paths
- @sprockets_asset_paths ||= begin
- config = self.config if respond_to?(:config)
- controller = self.controller if respond_to?(:controller)
- SprocketsHelper::AssetPaths.new(config, controller)
- end
- end
-
- class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
- def rewrite_asset_path(source, dir)
- if source[0] == ?/
- source
- else
- assets.path(source, performing_caching?, dir)
- end
- end
-
- def rewrite_extension(source, dir, ext)
- if ext && File.extname(source).empty?
- "#{source}.#{ext}"
- else
- source
- end
- end
-
- def assets
- Rails.application.assets
- end
-
- # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available
- def performing_caching?
- @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 786af5ca58..8c33ef09fa 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -94,7 +94,7 @@ module ActionView
end
end
- # Returns a CDATA section with the given +content+. CDATA sections
+ # Returns a CDATA section with the given +content+. CDATA sections
# are used to escape blocks of text containing characters which would
# otherwise be recognized as markup. CDATA sections begin with the string
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index ca09c77b5c..21074efe86 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -232,7 +232,7 @@ module ActionView
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
# method does not remove the newlines from the +text+.
#
- # You can pass any HTML attributes into <tt>html_options</tt>. These
+ # You can pass any HTML attributes into <tt>html_options</tt>. These
# will be added to all created paragraphs.
#
# ==== Options
@@ -255,9 +255,11 @@ module ActionView
# simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options={}, options={})
- text = ''.html_safe if text.nil?
+ text = '' if text.nil?
+ text = text.dup
start_tag = tag('p', html_options, true)
text = sanitize(text) unless options[:sanitize] == false
+ text = text.to_str
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
@@ -267,7 +269,7 @@ module ActionView
# Creates a Cycle object whose _to_s_ method cycles through elements of an
# array every time it is called. This can be used for example, to alternate
- # classes for table rows. You can use named cycles to allow nesting in loops.
+ # classes for table rows. You can use named cycles to allow nesting in loops.
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
# named cycle. The default name for a cycle without a +:name+ key is
# <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
@@ -279,7 +281,7 @@ module ActionView
# @items = [1,2,3,4]
# <table>
# <% @items.each do |item| %>
- # <tr class="<%= cycle("even", "odd") -%>">
+ # <tr class="<%= cycle("odd", "even") -%>">
# <td>item</td>
# </tr>
# <% end %>
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index fd8fe417d0..be64dc823e 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -5,7 +5,7 @@ module I18n
class ExceptionHandler
include Module.new {
def call(exception, locale, key, options)
- exception.is_a?(MissingTranslation) ? super.html_safe : super
+ exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super
end
}
end
@@ -25,7 +25,7 @@ module ActionView
# * a titleized version of the last key segment as a text.
#
# E.g. the value returned for a missing translation key :"blog.post.title" will be
- # <span class="translation_missing" title="translation missing: blog.post.title">Title</span>.
+ # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
# This way your views will display rather reasonable strings but it will still
# be easy to spot missing translations.
#
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 5488c752cc..0c2e1aa3a9 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -55,9 +55,9 @@ module ActionView
#
# ==== Relying on named routes
#
- # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
- # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
- # a Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
+ # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will
+ # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
+ # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
#
# ==== Examples
@@ -112,13 +112,13 @@ module ActionView
end
end
- # Creates a link tag of the given +name+ using a URL created by the set
- # of +options+. See the valid options in the documentation for
- # +url_for+. It's also possible to pass a string instead
- # of an options hash to get a link tag that uses the value of the string as the
- # href for the link, or use <tt>:back</tt> to link to the referrer - a JavaScript back
- # link will be used in place of a referrer if none exists. If +nil+ is passed as
- # a name, the link itself will become the name.
+ # Creates a link tag of the given +name+ using a URL created by the set of +options+.
+ # See the valid options in the documentation for +url_for+. It's also possible to
+ # pass a String instead of an options hash, which generates a link tag that uses the
+ # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
+ # of an options hash will generate a link to the referrer (a JavaScript back link
+ # will be used in place of a referrer if none exists). If +nil+ is passed as the name
+ # the value of the link itself will become the name.
#
# ==== Signatures
#
@@ -160,7 +160,7 @@ module ActionView
#
# ==== Examples
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
- # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
+ # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
# your application on resources and use
#
# link_to "Profile", profile_path(@profile)
@@ -268,7 +268,7 @@ module ActionView
# to change the HTTP verb used to submit the form.
#
# ==== Options
- # The +options+ hash accepts the same options as url_for.
+ # The +options+ hash accepts the same options as +url_for+.
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
@@ -278,7 +278,8 @@ module ActionView
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
- # submit behaviour. By default this behaviour is an ajax submit.
+ # submit behavior. By default this behavior is an ajax submit.
+ # * <tt>:form</tt> - This hash will be form attributes
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
#
@@ -295,6 +296,12 @@ module ActionView
# # </form>"
#
#
+ # <%= button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" } %>
+ # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
+ # # <div><input value="Create" type="submit" /></div>
+ # # </form>"
+ #
+ #
# <%= button_to "Delete Image", { :action => "delete", :id => @image.id },
# :confirm => "Are you sure?", :method => :delete %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
@@ -324,10 +331,11 @@ module ActionView
end
form_method = method.to_s == 'get' ? 'get' : 'post'
- form_class = html_options.delete('form_class') || 'button_to'
-
+ form_options = html_options.delete('form') || {}
+ form_options[:class] ||= html_options.delete('form_class') || 'button_to'
+
remote = html_options.delete('remote')
-
+
request_token_tag = ''
if form_method == 'post' && protect_against_forgery?
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
@@ -340,15 +348,17 @@ module ActionView
html_options.merge!("type" => "submit", "value" => name)
- ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"#{ERB::Util.html_escape(form_class)}\"><div>" +
- method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
+ form_options.merge!(:method => form_method, :action => url)
+ form_options.merge!("data-remote" => "true") if remote
+
+ "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe
end
# Creates a link tag of the given +name+ using a URL created by the set of
# +options+ unless the current request URI is the same as the links, in
# which case only the name is returned (or the given block is yielded, if
- # one exists). You can give +link_to_unless_current+ a block which will
+ # one exists). You can give +link_to_unless_current+ a block which will
# specialize the default behavior (e.g., show a "Start Here" link rather
# than the link's text).
#
@@ -375,7 +385,7 @@ module ActionView
# </ul>
#
# The implicit block given to +link_to_unless_current+ is evaluated if the current
- # action is the action given. So, if we had a comments page and wanted to render a
+ # action is the action given. So, if we had a comments page and wanted to render a
# "Go Back" link instead of a link to the comments page, we could do something like this...
#
# <%=
@@ -497,14 +507,14 @@ module ActionView
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
- email_address_obfuscated = email_address.dup
+ email_address_obfuscated = email_address.to_str
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
case encode
when "javascript"
string = ''
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
- html = escape_javascript(html)
+ html = escape_javascript(html.to_str)
"document.write('#{html}');".each_byte do |c|
string << sprintf("%%%x", c)
end
@@ -569,6 +579,12 @@ module ActionView
#
# current_page?(:controller => 'library', :action => 'checkout')
# # => false
+ #
+ # Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product.
+ #
+ # current_page?(:controller => 'product', :action => 'index')
+ # # => false
+ #
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " \
@@ -576,10 +592,12 @@ module ActionView
"in a #request method"
end
+ return false unless request.get?
+
url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
- # submitted url doesn't have any either. This lets the function
+ # submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
if url_string.index("?")
request_uri = request.fullpath
@@ -596,9 +614,7 @@ module ActionView
private
def convert_options_to_data_attributes(options, html_options)
- if html_options.nil?
- link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
- else
+ if html_options
html_options = html_options.stringify_keys
html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
@@ -611,6 +627,8 @@ module ActionView
add_method_to_attributes!(html_options, method) if method
html_options
+ else
+ link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
end
end
@@ -619,7 +637,9 @@ module ActionView
end
def add_method_to_attributes!(html_options, method)
- html_options["rel"] = "nofollow" if method.to_s.downcase != "get"
+ if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
+ html_options["rel"] = "#{html_options["rel"]} nofollow".strip
+ end
html_options["data-method"] = method
end
@@ -641,7 +661,7 @@ module ActionView
# Processes the +html_options+ hash, converting the boolean
# attributes from true/false form into the form required by
- # HTML/XHTML. (An attribute is considered to be boolean if
+ # HTML/XHTML. (An attribute is considered to be boolean if
# its name is listed in the given +bool_attrs+ array.)
#
# More specifically, for each boolean attribute in +html_options+
@@ -651,7 +671,7 @@ module ActionView
#
# if the associated +bool_value+ evaluates to true, it is
# replaced with the attribute's name; otherwise the attribute is
- # removed from the +html_options+ hash. (See the XHTML 1.0 spec,
+ # removed from the +html_options+ hash. (See the XHTML 1.0 spec,
# section 4.5 "Attribute Minimization" for more:
# http://www.w3.org/TR/xhtml1/#h-4.5)
#
diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb
index 29ffbd6fdd..bf90d012bf 100644
--- a/actionpack/lib/action_view/log_subscriber.rb
+++ b/actionpack/lib/action_view/log_subscriber.rb
@@ -4,7 +4,7 @@ module ActionView
# Provides functionality so that Rails can output logs from Action View.
class LogSubscriber < ActiveSupport::LogSubscriber
def render_template(event)
- message = "Rendered #{from_rails_root(event.payload[:identifier])}"
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
message << (" (%.1fms)" % event.duration)
info(message)
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index f0ed3425de..fa4bf70f77 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/remove_method'
module ActionView
# = Action View Lookup Context
@@ -10,30 +11,32 @@ module ActionView
# this key is generated just once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
attr_accessor :prefixes
-
+
mattr_accessor :fallbacks
@@fallbacks = FallbackFileSystemResolver.instances
mattr_accessor :registered_details
self.registered_details = []
- mattr_accessor :registered_detail_setters
- self.registered_detail_setters = []
-
def self.register_detail(name, options = {}, &block)
self.registered_details << name
- self.registered_detail_setters << [name, "#{name}="]
+ initialize = registered_details.map { |n| "self.#{n} = details[:#{n}]" }
- Accessors.send :define_method, :"_#{name}_defaults", &block
+ Accessors.send :define_method, :"default_#{name}", &block
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{name}
@details[:#{name}]
end
def #{name}=(value)
- value = Array.wrap(value.presence || _#{name}_defaults)
+ value = Array.wrap(value.presence || default_#{name})
_set_detail(:#{name}, value) if value != @details[:#{name}]
end
+
+ remove_possible_method :initialize_details
+ def initialize_details(details)
+ #{initialize.join("\n")}
+ end
METHOD
end
@@ -41,8 +44,9 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:formats) { Mime::SET.symbols }
register_detail(:locale) { [I18n.locale, I18n.default_locale] }
+ register_detail(:formats) { Mime::SET.symbols }
+ register_detail(:handlers){ Template::Handlers.extensions }
class DetailsKey #:nodoc:
alias :eql? :equal?
@@ -60,38 +64,54 @@ module ActionView
end
end
- def initialize(view_paths, details = {}, prefixes = [])
- @details, @details_key = { :handlers => default_handlers }, nil
- @frozen_formats, @skip_default_locale = false, false
- @cache = true
- @prefixes = prefixes
+ # Add caching behavior on top of Details.
+ module DetailsCache
+ attr_accessor :cache
- self.view_paths = view_paths
- self.registered_detail_setters.each do |key, setter|
- send(setter, details[key])
+ # Calculate the details key. Remove the handlers from calculation to improve performance
+ # since the user cannot modify it explicitly.
+ def details_key #:nodoc:
+ @details_key ||= DetailsKey.get(@details) if @cache
+ end
+
+ # Temporary skip passing the details_key forward.
+ def disable_cache
+ old_value, @cache = @cache, false
+ yield
+ ensure
+ @cache = old_value
+ end
+
+ protected
+
+ def _set_detail(key, value)
+ @details_key = nil
+ @details = @details.dup if @details.frozen?
+ @details[key] = value.freeze
end
end
+ # Helpers related to template lookup using the lookup context information.
module ViewPaths
attr_reader :view_paths
# Whenever setting view paths, makes a copy so we can manipulate then in
# instance objects as we wish.
def view_paths=(paths)
- @view_paths = ActionView::Base.process_view_paths(paths)
+ @view_paths = ActionView::PathSet.new(Array.wrap(paths))
end
- def find(name, prefixes = [], partial = false, keys = [])
- @view_paths.find(*args_for_lookup(name, prefixes, partial, keys))
+ def find(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
end
alias :find_template :find
- def find_all(name, prefixes = [], partial = false, keys = [])
- @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys))
+ def find_all(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
end
- def exists?(name, prefixes = [], partial = false, keys = [])
- @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys))
+ def exists?(name, prefixes = [], partial = false, keys = [], options = {})
+ @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
end
alias :template_exists? :exists?
@@ -110,16 +130,29 @@ module ActionView
protected
- def args_for_lookup(name, prefixes, partial, keys) #:nodoc:
+ def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
name, prefixes = normalize_name(name, prefixes)
- [name, prefixes, partial || false, @details, details_key, keys]
+ details, details_key = detail_args_for(details_options)
+ [name, prefixes, partial || false, details, details_key, keys]
+ end
+
+ # Compute details hash and key according to user options (e.g. passed from #render).
+ def detail_args_for(options)
+ return @details, details_key if options.empty? # most common path.
+ user_details = @details.merge(options)
+ [user_details, DetailsKey.get(user_details)]
end
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
- name = name.to_s.gsub(handlers_regexp, '')
+ name = name.to_s.sub(handlers_regexp) do |match|
+ ActiveSupport::Deprecation.warn "Passing a template handler in the template name is deprecated. " \
+ "You can simply remove the handler name or pass render :handlers => [:#{match[1..-1]}] instead.", caller
+ ""
+ end
+
parts = name.split('/')
name = parts.pop
@@ -132,120 +165,82 @@ module ActionView
return name, prefixes
end
- def default_handlers #:nodoc:
- @@default_handlers ||= Template::Handlers.extensions
- end
-
def handlers_regexp #:nodoc:
@@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
end
end
- module Details
- attr_accessor :cache
-
- # Calculate the details key. Remove the handlers from calculation to improve performance
- # since the user cannot modify it explicitly.
- def details_key #:nodoc:
- @details_key ||= DetailsKey.get(@details) if @cache
- end
-
- # Temporary skip passing the details_key forward.
- def disable_cache
- old_value, @cache = @cache, false
- yield
- ensure
- @cache = old_value
- end
+ include Accessors
+ include DetailsCache
+ include ViewPaths
- # Freeze the current formats in the lookup context. By freezing them, you are guaranteeing
- # that next template lookups are not going to modify the formats. The controller can also
- # use this, to ensure that formats won't be further modified (as it does in respond_to blocks).
- def freeze_formats(formats, unless_frozen=false) #:nodoc:
- return if unless_frozen && @frozen_formats
- self.formats = formats
- @frozen_formats = true
- end
+ def initialize(view_paths, details = {}, prefixes = [])
+ @details, @details_key = {}, nil
+ @frozen_formats, @skip_default_locale = false, false
+ @cache = true
+ @prefixes = prefixes
- # Overload formats= to expand ["*/*"] values and automatically
- # add :html as fallback to :js.
- def formats=(values)
- if values
- values.concat(_formats_defaults) if values.delete "*/*"
- values << :html if values == [:js]
- end
- super(values)
- end
+ self.view_paths = view_paths
+ initialize_details(details)
+ end
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
+ # Freeze the current formats in the lookup context. By freezing them, you
+ # that next template lookups are not going to modify the formats. The con
+ # use this, to ensure that formats won't be further modified (as it does
+ def freeze_formats(formats, unless_frozen=false) #:nodoc:
+ return if unless_frozen && @frozen_formats
+ self.formats = formats
+ @frozen_formats = true
+ end
- # Overload locale to return a symbol instead of array.
- def locale
- @details[:locale].first
+ # Override formats= to expand ["*/*"] values and automatically
+ # add :html as fallback to :js.
+ def formats=(values)
+ if values
+ values.concat(default_formats) if values.delete "*/*"
+ values << :html if values == [:js]
end
+ super(values)
+ end
- # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
- # to original_config, it means that it's has a copy of the original I18n configuration and it's
- # acting as proxy, which we need to skip.
- def locale=(value)
- if value
- config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
- config.locale = value
- end
+ # Do not use the default locale on template lookup.
+ def skip_default_locale!
+ @skip_default_locale = true
+ self.locale = nil
+ end
- super(@skip_default_locale ? I18n.locale : _locale_defaults)
- end
+ # Override locale to return a symbol instead of array.
+ def locale
+ @details[:locale].first
+ end
- # A method which only uses the first format in the formats array for layout lookup.
- # This method plays straight with instance variables for performance reasons.
- def with_layout_format
- if formats.size == 1
- yield
- else
- old_formats = formats
- _set_detail(:formats, formats[0,1])
-
- begin
- yield
- ensure
- _set_detail(:formats, old_formats)
- end
- end
+ # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
+ # to original_config, it means that it's has a copy of the original I18n configuration and it's
+ # acting as proxy, which we need to skip.
+ def locale=(value)
+ if value
+ config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
+ config.locale = value
end
- # Update the details keys by merging the given hash into the current
- # details hash. If a block is given, the details are modified just during
- # the execution of the block and reverted to the previous value after.
- def update_details(new_details)
- old_details = @details.dup
+ super(@skip_default_locale ? I18n.locale : default_locale)
+ end
- registered_detail_setters.each do |key, setter|
- send(setter, new_details[key]) if new_details.key?(key)
- end
+ # A method which only uses the first format in the formats array for layout lookup.
+ # This method plays straight with instance variables for performance reasons.
+ def with_layout_format
+ if formats.size == 1
+ yield
+ else
+ old_formats = formats
+ _set_detail(:formats, formats[0,1])
begin
yield
ensure
- @details_key = nil
- @details = old_details
+ _set_detail(:formats, old_formats)
end
end
-
- protected
-
- def _set_detail(key, value)
- @details_key = nil
- @details = @details.dup if @details.frozen?
- @details[key] = value.freeze
- end
end
-
- include Accessors
- include Details
- include ViewPaths
end
end
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index e0cb5d6a70..bbb1af8154 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -1,11 +1,55 @@
module ActionView #:nodoc:
# = Action View PathSet
- class PathSet < Array #:nodoc:
- %w(initialize << concat insert push unshift).each do |method|
+ class PathSet #:nodoc:
+ include Enumerable
+
+ attr_reader :paths
+
+ def initialize(paths = [])
+ @paths = typecast paths
+ end
+
+ def initialize_copy(other)
+ @paths = other.paths.dup
+ self
+ end
+
+ def [](i)
+ paths[i]
+ end
+
+ def to_ary
+ paths.dup
+ end
+
+ def include?(item)
+ paths.include? item
+ end
+
+ def pop
+ paths.pop
+ end
+
+ def size
+ paths.size
+ end
+
+ def each(&block)
+ paths.each(&block)
+ end
+
+ def compact
+ PathSet.new paths.compact
+ end
+
+ def +(array)
+ PathSet.new(paths + array)
+ end
+
+ %w(<< concat push insert unshift).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(*args)
- super
- typecast!
+ paths.#{method}(*typecast(args))
end
METHOD
end
@@ -17,7 +61,7 @@ module ActionView #:nodoc:
def find_all(path, prefixes = [], *args)
prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
- each do |resolver|
+ paths.each do |resolver|
templates = resolver.find_all(path, prefix, *args)
return templates unless templates.empty?
end
@@ -25,17 +69,20 @@ module ActionView #:nodoc:
[]
end
- def exists?(*args)
- find_all(*args).any?
+ def exists?(path, prefixes, *args)
+ find_all(path, prefixes, *args).any?
end
- protected
+ private
- def typecast!
- each_with_index do |path, i|
- path = path.to_s if path.is_a?(Pathname)
- next unless path.is_a?(String)
- self[i] = OptimizedFileSystemResolver.new(path)
+ def typecast(paths)
+ paths.map do |path|
+ case path
+ when Pathname, String
+ OptimizedFileSystemResolver.new path.to_s
+ else
+ path
+ end
end
end
end
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index 60c527beeb..c0936441ac 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -11,15 +11,22 @@ module ActionView
raise NotImplementedError
end
- # Checks if the given path contains a format and if so, change
- # the lookup context to take this new format into account.
- def wrap_formats(value)
- return yield unless value.is_a?(String)
-
- if value.sub!(formats_regexp, "")
- update_details(:formats => [$1.to_sym]){ yield }
- else
- yield
+ protected
+
+ def extract_details(options)
+ details = {}
+ @lookup_context.registered_details.each do |key|
+ next unless value = options[key]
+ details[key] = Array.wrap(value)
+ end
+ details
+ end
+
+ def extract_format(value, details)
+ if value.is_a?(String) && value.sub!(formats_regexp, "")
+ ActiveSupport::Deprecation.warn "Passing the format in the template name is deprecated. " \
+ "Please pass render with :formats => [:#{$1}] instead.", caller
+ details[:formats] ||= [$1.to_sym]
end
end
@@ -27,8 +34,6 @@ module ActionView
@@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/
end
- protected
-
def instrument(name, options={})
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index a351fbc04f..15cb9d0f76 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -12,8 +12,7 @@ module ActionView
#
# <%= render :partial => "account" %>
#
- # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable
- # +account+ to the template for display.
+ # This would render "advertiser/_account.html.erb".
#
# In another template for Advertiser#buy, we could have:
#
@@ -28,32 +27,24 @@ module ActionView
#
# == The :as and :object options
#
- # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same
- # name as the template. So, given
+ # By default <tt>ActionView::PartialRenderer</tt> doesn't have any local variables.
+ # The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
#
- # <%= render :partial => "contract" %>
+ # <%= render :partial => "account", :object => @buyer %>
#
- # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written
+ # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # equivalent to:
#
- # <%= render :partial => "contract", :locals => { :contract => @contract } %>
+ # <%= render :partial => "account", :locals => { :account => @buyer } %>
#
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
- # wanted it to be +agreement+ instead of +contract+ we'd do:
- #
- # <%= render :partial => "contract", :as => 'agreement' %>
+ # wanted it to be +user+ instead of +account+ we'd do:
#
- # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;
- # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance.
+ # <%= render :partial => "account", :object => @buyer, :as => 'user' %>
#
- # Revisiting a previous example we could have written this code:
+ # This is equivalent to
#
- # <%= render :partial => "account", :object => @buyer %>
- #
- # <% @advertisements.each do |ad| %>
- # <%= render :partial => "ad", :object => ad %>
- # <% end %>
- #
- # The <tt>:object</tt> and <tt>:as</tt> options can be used together.
+ # <%= render :partial => "account", :locals => { :user => @buyer } %>
#
# == Rendering a collection of partials
#
@@ -215,27 +206,25 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer #:nodoc:
- PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
+ PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
- @partial_names = PARTIAL_NAMES[@lookup_context.prefixes.first]
+ @context_prefix = @lookup_context.prefixes.first
+ @partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
setup(context, options, block)
+ identifier = (@template = find_partial) ? @template.identifier : @path
- wrap_formats(@path) do
- identifier = ((@template = find_partial) ? @template.identifier : @path)
-
- if @collection
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
- render_collection
- end
- else
- instrument(:partial, :identifier => identifier) do
- render_partial
- end
+ if @collection
+ instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
+ render_collection
+ end
+ else
+ instrument(:partial, :identifier => identifier) do
+ render_partial
end
end
end
@@ -279,6 +268,7 @@ module ActionView
@options = options
@locals = options[:locals] || {}
@block = block
+ @details = extract_details(options)
if String === partial
@object = options[:object]
@@ -301,6 +291,13 @@ module ActionView
paths.map! { |path| retrieve_variable(path).unshift(path) }
end
+ if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
+ raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a letter or underscore, " +
+ "and is followed by any combinations of letters, numbers, or underscores.")
+ end
+
+ extract_format(@path, @details)
self
end
@@ -328,7 +325,7 @@ module ActionView
def find_template(path=@path, locals=@locals.keys)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
- @lookup_context.find_template(path, prefixes, true, locals)
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
@@ -364,13 +361,32 @@ module ActionView
end
def partial_path(object = @object)
- @partial_names[object.class.name] ||= begin
- object = object.to_model if object.respond_to?(:to_model)
+ object = object.to_model if object.respond_to?(:to_model)
+
+ path = if object.respond_to?(:to_partial_path)
+ object.to_partial_path
+ else
+ ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
+ object.class.model_name.partial_path
+ end
- object.class.model_name.partial_path.dup.tap do |partial|
- path = @lookup_context.prefixes.first
- partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
+ @partial_names[path] ||= path.dup.tap do |object_path|
+ merge_prefix_into_object_path(@context_prefix, object_path)
+ end
+ end
+
+ def merge_prefix_into_object_path(prefix, object_path)
+ if prefix.include?(?/) && object_path.include?(?/)
+ overlap = []
+ prefix_array = File.dirname(prefix).split('/')
+ object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
+
+ prefix_array.each_with_index do |dir, index|
+ overlap << dir if dir == object_path_array[index]
end
+
+ object_path.gsub!(/^#{overlap.join('/')}\//,'')
+ object_path.insert(0, "#{File.dirname(prefix)}/")
end
end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index a09cef8fef..ac91d333ba 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -4,13 +4,12 @@ require 'active_support/core_ext/array/wrap'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
def render(context, options)
- @view = context
-
- wrap_formats(options[:template] || options[:file]) do
- template = determine_template(options)
- freeze_formats(template.formats, true)
- render_template(template, options[:layout], options[:locals])
- end
+ @view = context
+ @details = extract_details(options)
+ extract_format(options[:file] || options[:template], @details)
+ template = determine_template(options)
+ freeze_formats(template.formats, true)
+ render_template(template, options[:layout], options[:locals])
end
# Determine the template to be rendered using the given options.
@@ -20,13 +19,13 @@ module ActionView
if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
- with_fallbacks { find_template(options[:file], nil, false, keys) }
+ with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, :locals => keys)
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
- options[:template] : find_template(options[:template], options[:prefixes], false, keys)
+ options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details)
end
end
@@ -62,12 +61,11 @@ module ActionView
begin
with_layout_format do
layout =~ /^\// ?
- with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys)
- end
- rescue ActionView::MissingTemplate => e
- update_details(:formats => nil) do
- raise unless template_exists?(layout)
+ with_fallbacks { find_template(layout, nil, false, keys, @details) } : find_template(layout, nil, false, keys, @details)
end
+ rescue ActionView::MissingTemplate
+ all_details = @details.merge(:formats => @lookup_context.default_formats)
+ raise unless template_exists?(layout, nil, false, keys, all_details)
end
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index b99d24d281..10797c010f 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -91,7 +91,6 @@ module ActionView
eager_autoload do
autoload :Error
- autoload :Handler
autoload :Handlers
autoload :Text
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index d4448a7b33..587e37a84f 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -31,7 +31,6 @@ module ActionView
def initialize(paths, path, prefixes, partial, details, *)
@path = path
prefixes = Array.wrap(prefixes)
- display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
"partial"
elsif path =~ /layouts/i
diff --git a/actionpack/lib/action_view/template/handler.rb b/actionpack/lib/action_view/template/handler.rb
deleted file mode 100644
index 636f3ebbad..0000000000
--- a/actionpack/lib/action_view/template/handler.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'action_dispatch/http/mime_type'
-require 'active_support/core_ext/class/attribute'
-
-# Legacy TemplateHandler stub
-module ActionView
- class Template
- module Handlers #:nodoc:
- module Compilable
- def self.included(base)
- ActiveSupport::Deprecation.warn "Including Compilable in your template handler is deprecated. " <<
- "Since Rails 3, all the API your template handler needs to implement is to respond to #call."
- base.extend(ClassMethods)
- end
-
- module ClassMethods
- def call(template)
- new.compile(template)
- end
- end
-
- def compile(template)
- raise "Need to implement #{self.class.name}#compile(template)"
- end
- end
- end
-
- class Template::Handler
- class_attribute :default_format
- self.default_format = Mime::HTML
-
- def self.inherited(base)
- ActiveSupport::Deprecation.warn "Inheriting from ActionView::Template::Handler is deprecated. " <<
- "Since Rails 3, all the API your template handler needs to implement is to respond to #call."
- super
- end
-
- def self.call(template)
- raise "Need to implement #{self.class.name}#call(template)"
- end
-
- def render(template, local_assigns)
- raise "Need to implement #{self.class.name}#render(template, local_assigns)"
- end
- end
- end
-
- TemplateHandlers = Template::Handlers
- TemplateHandler = Template::Handler
-end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 959afa734e..aa693335e3 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -41,12 +41,6 @@ module ActionView #:nodoc:
@@default_template_handlers = klass
end
- def handler_class_for_extension(extension)
- ActiveSupport::Deprecation.warn "handler_class_for_extension is deprecated. " <<
- "Please use handler_for_extension instead", caller
- handler_for_extension(extension)
- end
-
def handler_for_extension(extension)
registered_template_handler(extension) || @@default_template_handlers
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 7e9e4e518a..77720e2bc8 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,6 +1,5 @@
+require 'action_dispatch/http/mime_type'
require 'active_support/core_ext/class/attribute_accessors'
-require 'action_view/template'
-require 'action_view/template/handler'
require 'erubis'
module ActionView
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 2b9427ace5..f855ea257c 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,5 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
+require "active_support/core_ext/io"
require "action_view/template"
module ActionView
@@ -68,7 +69,7 @@ module ActionView
# before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = sort_locals(locals)
+ locals = locals.map { |x| x.to_s }.sort!
if key && caching?
@cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
@@ -97,18 +98,6 @@ module ActionView
t.virtual_path ||= (cached ||= build_path(*path_info))
end
end
-
- if :symbol.respond_to?("<=>")
- def sort_locals(locals) #:nodoc:
- locals.sort.freeze
- end
- else
- def sort_locals(locals) #:nodoc:
- locals = locals.map{ |l| l.to_s }
- locals.sort!
- locals.freeze
- end
- end
end
# An abstract class that implements a Resolver with path semantics.
@@ -130,27 +119,35 @@ module ActionView
def query(path, details, formats)
query = build_query(path, details)
- templates = []
- sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
- Dir[query].each do |p|
- next if File.directory?(p) || !sanitizer[p].include?(p)
+ # deals with case-insensitive file systems.
+ sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
- handler, format = extract_handler_and_format(p, formats)
- contents = File.open(p, "rb") { |io| io.read }
+ template_paths = Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ !sanitizer[File.dirname(filename)].include?(filename)
+ }
- templates << Template.new(contents, File.expand_path(p), handler,
- :virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
- end
+ template_paths.map { |template|
+ handler, format = extract_handler_and_format(template, formats)
+ contents = File.binread template
- templates
+ Template.new(contents, File.expand_path(template), handler,
+ :virtual_path => path.virtual,
+ :format => format,
+ :updated_at => mtime(template))
+ }
end
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
- query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
- query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
+
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
+ query.gsub!(/\:prefix(\/)?/, prefix)
+
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
+ query.gsub!(/\:action/, partial)
details.each do |ext, variants|
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
@@ -159,9 +156,13 @@ module ActionView
File.expand_path(query, @path)
end
+ def escape_entry(entry)
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ end
+
# Returns the file mtime from the filesystem.
def mtime(p)
- File.stat(p).mtime
+ File.mtime(p)
end
# Extract handler and formats from path. If a format cannot be a found neither
@@ -235,15 +236,11 @@ module ActionView
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
def build_query(path, details)
exts = EXTENSIONS.map { |ext| details[ext] }
- query = File.join(@path, path)
-
- exts.each do |ext|
- query << "{"
- ext.compact.each { |e| query << ".#{e}," }
- query << "}"
- end
+ query = escape_entry(File.join(@path, path))
- query
+ query + exts.map { |ext|
+ "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
+ }.join
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index d0317a148b..9ebe498192 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -50,14 +50,17 @@ module ActionView
module ClassMethods
def tests(helper_class)
- self.helper_class = helper_class
+ case helper_class
+ when String, Symbol
+ self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
+ when Module
+ self.helper_class = helper_class
+ end
end
def determine_default_helper_class(name)
- mod = name.sub(/Test$/, '').constantize
+ mod = name.sub(/Test$/, '').safe_constantize
mod.is_a?(Class) ? nil : mod
- rescue NameError
- nil
end
def helper_method(*methods)
@@ -65,7 +68,7 @@ module ActionView
methods.flatten.each do |method|
_helpers.module_eval <<-end_eval
def #{method}(*args, &block) # def current_user(*args, &block)
- _test_case.send(%(#{method}), *args, &block) # test_case.send(%(current_user), *args, &block)
+ _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
end # end
end_eval
end
@@ -218,12 +221,6 @@ module ActionView
end]
end
- def _assigns
- ActiveSupport::Deprecation.warn "ActionView::TestCase#_assigns is deprecated and will be removed in future versions. " <<
- "Please use view_assigns instead."
- view_assigns
- end
-
def _routes
@controller._routes if @controller.respond_to?(:_routes)
end
diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb
index 773dfcbb1d..7afa2fa613 100644
--- a/actionpack/lib/action_view/testing/resolvers.rb
+++ b/actionpack/lib/action_view/testing/resolvers.rb
@@ -34,7 +34,7 @@ module ActionView #:nodoc:
templates << Template.new(source, _path, handler,
:virtual_path => path.virtual, :format => format, :updated_at => updated_at)
end
-
+
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
new file mode 100644
index 0000000000..a61a121d55
--- /dev/null
+++ b/actionpack/lib/sprockets/assets.rake
@@ -0,0 +1,95 @@
+require "fileutils"
+
+namespace :assets do
+ def ruby_rake_task(task)
+ env = ENV['RAILS_ENV'] || 'production'
+ groups = ENV['RAILS_GROUPS'] || 'assets'
+ args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
+ args << "--trace" if Rake.application.options.trace
+ ruby(*args)
+ end
+
+ # We are currently running with no explicit bundler group
+ # and/or no explicit environment - we have to reinvoke rake to
+ # execute this task.
+ def invoke_or_reboot_rake_task(task)
+ if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty?
+ ruby_rake_task task
+ else
+ Rake::Task[task].invoke
+ end
+ end
+
+ desc "Compile all the assets named in config.assets.precompile"
+ task :precompile do
+ invoke_or_reboot_rake_task "assets:precompile:all"
+ end
+
+ namespace :precompile do
+ def internal_precompile(digest=nil)
+ unless Rails.application.config.assets.enabled
+ warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true"
+ exit
+ end
+
+ # Ensure that action view is loaded and the appropriate
+ # sprockets hooks get executed
+ _ = ActionView::Base
+
+ config = Rails.application.config
+ config.assets.compile = true
+ config.assets.digest = digest unless digest.nil?
+ config.assets.digests = {}
+
+ env = Rails.application.assets
+ target = File.join(Rails.public_path, config.assets.prefix)
+ compiler = Sprockets::StaticCompiler.new(env,
+ target,
+ config.assets.precompile,
+ :manifest_path => config.assets.manifest,
+ :digest => config.assets.digest,
+ :manifest => digest.nil?)
+ compiler.compile
+ end
+
+ task :all do
+ Rake::Task["assets:precompile:primary"].invoke
+ # We need to reinvoke in order to run the secondary digestless
+ # asset compilation run - a fresh Sprockets environment is
+ # required in order to compile digestless assets as the
+ # environment has already cached the assets on the primary
+ # run.
+ ruby_rake_task "assets:precompile:nondigest" if Rails.application.config.assets.digest
+ end
+
+ task :primary => ["assets:environment", "tmp:cache:clear"] do
+ internal_precompile
+ end
+
+ task :nondigest => ["assets:environment", "tmp:cache:clear"] do
+ internal_precompile(false)
+ end
+ end
+
+ desc "Remove compiled assets"
+ task :clean do
+ invoke_or_reboot_rake_task "assets:clean:all"
+ end
+
+ namespace :clean do
+ task :all => ["assets:environment", "tmp:cache:clear"] do
+ config = Rails.application.config
+ public_asset_path = File.join(Rails.public_path, config.assets.prefix)
+ rm_rf public_asset_path, :secure => true
+ end
+ end
+
+ task :environment do
+ if Rails.application.config.assets.initialize_on_precompile
+ Rake::Task["environment"].invoke
+ else
+ Rails.application.initialize!(:assets)
+ Sprockets::Bootstrap.new(Rails.application).run
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/bootstrap.rb b/actionpack/lib/sprockets/bootstrap.rb
new file mode 100644
index 0000000000..395b264fe7
--- /dev/null
+++ b/actionpack/lib/sprockets/bootstrap.rb
@@ -0,0 +1,37 @@
+module Sprockets
+ class Bootstrap
+ def initialize(app)
+ @app = app
+ end
+
+ # TODO: Get rid of config.assets.enabled
+ def run
+ app, config = @app, @app.config
+ return unless app.assets
+
+ config.assets.paths.each { |path| app.assets.append_path(path) }
+
+ if config.assets.compress
+ # temporarily hardcode default JS compressor to uglify. Soon, it will work
+ # the same as SCSS, where a default plugin sets the default.
+ unless config.assets.js_compressor == false
+ app.assets.js_compressor = LazyCompressor.new { Sprockets::Compressors.registered_js_compressor(config.assets.js_compressor || :uglifier) }
+ end
+
+ unless config.assets.css_compressor == false
+ app.assets.css_compressor = LazyCompressor.new { Sprockets::Compressors.registered_css_compressor(config.assets.css_compressor) }
+ end
+ end
+
+ if config.assets.compile
+ app.routes.prepend do
+ mount app.assets => config.assets.prefix
+ end
+ end
+
+ if config.assets.digest
+ app.assets = app.assets.index
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
new file mode 100644
index 0000000000..cb3e13314b
--- /dev/null
+++ b/actionpack/lib/sprockets/compressors.rb
@@ -0,0 +1,83 @@
+module Sprockets
+ module Compressors
+ @@css_compressors = {}
+ @@js_compressors = {}
+ @@default_css_compressor = nil
+ @@default_js_compressor = nil
+
+ def self.register_css_compressor(name, klass, options = {})
+ @@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil?
+ @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ end
+
+ def self.register_js_compressor(name, klass, options = {})
+ @@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil?
+ @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
+ end
+
+ def self.registered_css_compressor(name)
+ if name.respond_to?(:to_sym)
+ compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor]
+ require compressor[:require] if compressor[:require]
+ compressor[:klass].constantize.new
+ else
+ name
+ end
+ end
+
+ def self.registered_js_compressor(name)
+ if name.respond_to?(:to_sym)
+ compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor]
+ require compressor[:require] if compressor[:require]
+ compressor[:klass].constantize.new
+ else
+ name
+ end
+ end
+
+ # The default compressors must be registered in default plugins (ex. Sass-Rails)
+ register_css_compressor(:scss, 'Sass::Rails::Compressor', :require => 'sass/rails/compressor', :default => true)
+ register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier', :default => true)
+
+ # Automaticaly register some compressors
+ register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor')
+ register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler')
+ register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor')
+ end
+
+ # An asset compressor which does nothing.
+ #
+ # This compressor simply returns the asset as-is, without any compression
+ # whatsoever. It is useful in development mode, when compression isn't
+ # needed but using the same asset pipeline as production is desired.
+ class NullCompressor #:nodoc:
+ def compress(content)
+ content
+ end
+ end
+
+ # An asset compressor which only initializes the underlying compression
+ # engine when needed.
+ #
+ # This postpones the initialization of the compressor until
+ # <code>#compress</code> is called the first time.
+ class LazyCompressor #:nodoc:
+ # Initializes a new LazyCompressor.
+ #
+ # The block should return a compressor when called, i.e. an object
+ # which responds to <code>#compress</code>.
+ def initialize(&block)
+ @block = block
+ end
+
+ def compress(content)
+ compressor.compress(content)
+ end
+
+ private
+
+ def compressor
+ @compressor ||= (@block.call || NullCompressor.new)
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/helpers.rb b/actionpack/lib/sprockets/helpers.rb
new file mode 100644
index 0000000000..fee48386e0
--- /dev/null
+++ b/actionpack/lib/sprockets/helpers.rb
@@ -0,0 +1,6 @@
+module Sprockets
+ module Helpers
+ autoload :RailsHelper, "sprockets/helpers/rails_helper"
+ autoload :IsolatedHelper, "sprockets/helpers/isolated_helper"
+ end
+end
diff --git a/actionpack/lib/sprockets/helpers/isolated_helper.rb b/actionpack/lib/sprockets/helpers/isolated_helper.rb
new file mode 100644
index 0000000000..3adb928c45
--- /dev/null
+++ b/actionpack/lib/sprockets/helpers/isolated_helper.rb
@@ -0,0 +1,13 @@
+module Sprockets
+ module Helpers
+ module IsolatedHelper
+ def controller
+ nil
+ end
+
+ def config
+ Rails.application.config.action_controller
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
new file mode 100644
index 0000000000..ddf9b08b54
--- /dev/null
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -0,0 +1,166 @@
+require "action_view"
+
+module Sprockets
+ module Helpers
+ module RailsHelper
+ extend ActiveSupport::Concern
+ include ActionView::Helpers::AssetTagHelper
+
+ def asset_paths
+ @asset_paths ||= begin
+ paths = RailsHelper::AssetPaths.new(config, controller)
+ paths.asset_environment = asset_environment
+ paths.asset_digests = asset_digests
+ paths.compile_assets = compile_assets?
+ paths.digest_assets = digest_assets?
+ paths
+ end
+ end
+
+ def javascript_include_tag(*sources)
+ options = sources.extract_options!
+ debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
+ body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
+
+ sources.collect do |source|
+ if debug && asset = asset_paths.asset_for(source, 'js')
+ asset.to_a.map { |dep|
+ super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
+ }
+ else
+ super(source.to_s, { :src => asset_path(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
+ end
+ end.join("\n").html_safe
+ end
+
+ def stylesheet_link_tag(*sources)
+ options = sources.extract_options!
+ debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
+ body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
+
+ sources.collect do |source|
+ if debug && asset = asset_paths.asset_for(source, 'css')
+ asset.to_a.map { |dep|
+ super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
+ }
+ else
+ super(source.to_s, { :href => asset_path(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
+ end
+ end.join("\n").html_safe
+ end
+
+ def asset_path(source, options = {})
+ source = source.logical_path if source.respond_to?(:logical_path)
+ path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
+ options[:body] ? "#{path}?body=1" : path
+ end
+
+ def image_path(source)
+ asset_path(source)
+ end
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+
+ def javascript_path(source)
+ asset_path(source)
+ end
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route
+
+ def stylesheet_path(source)
+ asset_path(source)
+ end
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route
+
+ private
+ def debug_assets?
+ compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets])
+ rescue NoMethodError
+ false
+ end
+
+ # Override to specify an alternative prefix for asset path generation.
+ # When combined with a custom +asset_environment+, this can be used to
+ # implement themes that can take advantage of the asset pipeline.
+ #
+ # If you only want to change where the assets are mounted, refer to
+ # +config.assets.prefix+ instead.
+ def asset_prefix
+ Rails.application.config.assets.prefix
+ end
+
+ def asset_digests
+ Rails.application.config.assets.digests
+ end
+
+ def compile_assets?
+ Rails.application.config.assets.compile
+ end
+
+ def digest_assets?
+ Rails.application.config.assets.digest
+ end
+
+ # Override to specify an alternative asset environment for asset
+ # path generation. The environment should already have been mounted
+ # at the prefix returned by +asset_prefix+.
+ def asset_environment
+ Rails.application.assets
+ end
+
+ class AssetPaths < ::ActionView::AssetPaths #:nodoc:
+ attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
+
+ class AssetNotPrecompiledError < StandardError; end
+
+ # Return the filesystem path for the source
+ def compute_source_path(source, ext)
+ asset_for(source, ext)
+ end
+
+ def asset_for(source, ext)
+ source = source.to_s
+ return nil if is_uri?(source)
+ source = rewrite_extension(source, nil, ext)
+ asset_environment[source]
+ rescue Sprockets::FileOutsidePaths
+ nil
+ end
+
+ def digest_for(logical_path)
+ if digest_assets && asset_digests && (digest = asset_digests[logical_path])
+ return digest
+ end
+
+ if compile_assets
+ if digest_assets && asset = asset_environment[logical_path]
+ return asset.digest_path
+ end
+ return logical_path
+ else
+ raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
+ end
+ end
+
+ def rewrite_asset_path(source, dir, options = {})
+ if source[0] == ?/
+ source
+ else
+ source = digest_for(source) unless options[:digest] == false
+ source = File.join(dir, source)
+ source = "/#{source}" unless source =~ /^\//
+ source
+ end
+ end
+
+ def rewrite_extension(source, dir, ext)
+ if ext && File.extname(source).empty?
+ "#{source}.#{ext}"
+ else
+ source
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index 8cee3babe2..3d330bd91a 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -1,100 +1,61 @@
+require "action_controller/railtie"
+
module Sprockets
- class Railtie < Rails::Railtie
- def self.using_coffee?
- require 'coffee-script'
- defined?(CoffeeScript)
- rescue LoadError
- false
+ autoload :Bootstrap, "sprockets/bootstrap"
+ autoload :Helpers, "sprockets/helpers"
+ autoload :Compressors, "sprockets/compressors"
+ autoload :LazyCompressor, "sprockets/compressors"
+ autoload :NullCompressor, "sprockets/compressors"
+ autoload :StaticCompiler, "sprockets/static_compiler"
+
+ # TODO: Get rid of config.assets.enabled
+ class Railtie < ::Rails::Railtie
+ config.action_controller.default_asset_host_protocol = :relative
+
+ rake_tasks do
+ load "sprockets/assets.rake"
end
- def self.using_scss?
- require 'sass'
- defined?(Sass)
- rescue LoadError
- false
- end
+ initializer "sprockets.environment", :group => :all do |app|
+ config = app.config
+ next unless config.assets.enabled
- config.app_generators.javascript_engine :coffee if using_coffee?
- config.app_generators.stylesheet_engine :scss if using_scss?
+ require 'sprockets'
- # Configure ActionController to use sprockets.
- initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app|
- ActiveSupport.on_load(:action_controller) do
- self.use_sprockets = app.config.assets.enabled
- end
- end
+ app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
+ env.logger = ::Rails.logger
+ env.version = ::Rails.env + "-#{config.assets.version}"
- # We need to configure this after initialization to ensure we collect
- # paths from all engines. This hook is invoked exactly before routes
- # are compiled.
- config.after_initialize do |app|
- assets = app.config.assets
- next unless assets.enabled
-
- app.assets = asset_environment(app)
-
- ActiveSupport.on_load(:action_view) do
- app.assets.context_class.instance_eval do
- include ::ActionView::Helpers::SprocketsHelper
+ if config.assets.cache_store != false
+ env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
end
end
- app.routes.prepend do
- mount app.assets => assets.prefix
+ if config.assets.manifest
+ path = File.join(config.assets.manifest, "manifest.yml")
+ else
+ path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
end
- if config.action_controller.perform_caching
- app.assets = app.assets.index
+ if File.exist?(path)
+ config.assets.digests = YAML.load_file(path)
end
- end
- protected
-
- def asset_environment(app)
- require "sprockets"
- assets = app.config.assets
- env = Sprockets::Environment.new(app.root.to_s)
- env.static_root = File.join(app.root.join("public"), assets.prefix)
- env.paths.concat assets.paths
- env.logger = Rails.logger
- env.js_compressor = expand_js_compressor(assets.js_compressor)
- env.css_compressor = expand_css_compressor(assets.css_compressor)
- env
- end
-
- def expand_js_compressor(sym)
- case sym
- when :closure
- require 'closure-compiler'
- Closure::Compiler.new
- when :uglifier
- require 'uglifier'
- Uglifier.new
- when :yui
- require 'yui/compressor'
- YUI::JavaScriptCompressor.new
- else
- sym
+ ActiveSupport.on_load(:action_view) do
+ include ::Sprockets::Helpers::RailsHelper
+ app.assets.context_class.instance_eval do
+ include ::Sprockets::Helpers::IsolatedHelper
+ include ::Sprockets::Helpers::RailsHelper
+ end
end
end
- def expand_css_compressor(sym)
- case sym
- when :scss
- require 'sass'
- compressor = Object.new
- def compressor.compress(source)
- Sass::Engine.new(source,
- :syntax => :scss, :style => :compressed
- ).render
- end
- compressor
- when :yui
- require 'yui/compressor'
- YUI::CssCompressor.new
- else
- sym
- end
+ # We need to configure this after initialization to ensure we collect
+ # paths from all engines. This hook is invoked exactly before routes
+ # are compiled, and so that other Railties have an opportunity to
+ # register compressors.
+ config.after_initialize do |app|
+ Sprockets::Bootstrap.new(app).run
end
end
end
diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb
new file mode 100644
index 0000000000..32a9d66e6e
--- /dev/null
+++ b/actionpack/lib/sprockets/static_compiler.rb
@@ -0,0 +1,61 @@
+require 'fileutils'
+
+module Sprockets
+ class StaticCompiler
+ attr_accessor :env, :target, :paths
+
+ def initialize(env, target, paths, options = {})
+ @env = env
+ @target = target
+ @paths = paths
+ @digest = options.key?(:digest) ? options.delete(:digest) : true
+ @manifest = options.key?(:manifest) ? options.delete(:manifest) : true
+ @manifest_path = options.delete(:manifest_path) || target
+ end
+
+ def compile
+ manifest = {}
+ env.each_logical_path do |logical_path|
+ next unless compile_path?(logical_path)
+ if asset = env.find_asset(logical_path)
+ manifest[logical_path] = write_asset(asset)
+ end
+ end
+ write_manifest(manifest) if @manifest
+ end
+
+ def write_manifest(manifest)
+ FileUtils.mkdir_p(@manifest_path)
+ File.open("#{@manifest_path}/manifest.yml", 'wb') do |f|
+ YAML.dump(manifest, f)
+ end
+ end
+
+ def write_asset(asset)
+ path_for(asset).tap do |path|
+ filename = File.join(target, path)
+ FileUtils.mkdir_p File.dirname(filename)
+ asset.write_to(filename)
+ asset.write_to("#{filename}.gz") if filename.to_s =~ /\.(css|js)$/
+ end
+ end
+
+ def compile_path?(logical_path)
+ paths.each do |path|
+ case path
+ when Regexp
+ return true if path.match(logical_path)
+ when Proc
+ return true if path.call(logical_path)
+ else
+ return true if File.fnmatch(path.to_s, logical_path)
+ end
+ end
+ false
+ end
+
+ def path_for(asset)
+ @digest ? asset.digest_path : asset.logical_path
+ end
+ end
+end
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index 981c023d38..bf068aedcd 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -50,7 +50,7 @@ module AbstractController
end
def index_to_string
- self.response_body = render_to_string "index.erb"
+ self.response_body = render_to_string "index"
end
def action_with_ivars
@@ -63,11 +63,11 @@ module AbstractController
end
def rendering_to_body
- self.response_body = render_to_body :template => "naked_render.erb"
+ self.response_body = render_to_body :template => "naked_render"
end
def rendering_to_string
- self.response_body = render_to_string :template => "naked_render.erb"
+ self.response_body = render_to_string :template => "naked_render"
end
end
@@ -178,16 +178,10 @@ module AbstractController
end
end
- class Me5 < WithLayouts
- def index
- render
- end
- end
-
class TestLayouts < ActiveSupport::TestCase
test "layouts are included" do
controller = Me4.new
- result = controller.process(:index)
+ controller.process(:index)
assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body
end
end
@@ -241,11 +235,11 @@ module AbstractController
assert_dispatch ActionMissingRespondToActionController, "success", :ohai
end
- test "a method is available as an action if respond_to_action? returns true" do
+ test "a method is available as an action if method_for_action returns true" do
assert_dispatch RespondToActionController, "success", :index
end
- test "raises ActionNotFound if method is defined but respond_to_action? returns false" do
+ test "raises ActionNotFound if method is defined but method_for_action returns false" do
assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) }
end
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 3bdde86291..5d1a703c55 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -131,7 +131,7 @@ module AbstractController
end
def authenticate
- @list = []
+ @list ||= []
@authenticated = "true"
end
end
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 5ed6aa68b5..86208899f8 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -87,18 +87,6 @@ module AbstractControllerTests
end
end
- class WithSymbolReturningString < Base
- layout :no_hello
-
- def index
- render :template => ActionView::Template::Text.new("Hello missing symbol!")
- end
- private
- def no_hello
- nil
- end
- end
-
class WithSymbolReturningNil < Base
layout :nilz
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 60534a9746..24d071df39 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -151,6 +151,7 @@ class BasicController
config.assets_dir = public_dir
config.javascripts_dir = "#{public_dir}/javascripts"
config.stylesheets_dir = "#{public_dir}/stylesheets"
+ config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" })
config
end
end
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index f0fb113860..768ac713ca 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -225,6 +225,36 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
assert_equal session_id, cookies['_session_id']
end
end
+
+ def test_incoming_invalid_session_id_via_cookie_should_be_ignored
+ with_test_route_set do
+ open_session do |sess|
+ sess.cookies['_session_id'] = 'INVALID'
+
+ sess.get '/set_session_value'
+ new_session_id = sess.cookies['_session_id']
+ assert_not_equal 'INVALID', new_session_id
+
+ sess.get '/get_session_value'
+ new_session_id_2 = sess.cookies['_session_id']
+ assert_equal new_session_id, new_session_id_2
+ end
+ end
+ end
+
+ def test_incoming_invalid_session_id_via_parameter_should_be_ignored
+ with_test_route_set(:cookie_only => false) do
+ open_session do |sess|
+ sess.get '/set_session_value', :_session_id => 'INVALID'
+ new_session_id = sess.cookies['_session_id']
+ assert_not_equal 'INVALID', new_session_id
+
+ sess.get '/get_session_value'
+ new_session_id_2 = sess.cookies['_session_id']
+ assert_equal new_session_id, new_session_id_2
+ end
+ end
+ end
private
@@ -247,6 +277,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
session_class, ActiveRecord::SessionStore.session_class =
ActiveRecord::SessionStore.session_class, "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
yield
+ ensure
ActiveRecord::SessionStore.session_class = session_class
end
end
diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb
index b87b9f9c47..2d789395ce 100644
--- a/actionpack/test/activerecord/controller_runtime_test.rb
+++ b/actionpack/test/activerecord/controller_runtime_test.rb
@@ -15,6 +15,17 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
def zero
render :inline => "Zero DB runtime"
end
+
+ def redirect
+ Project.all
+ redirect_to :action => 'show'
+ end
+
+ def db_after_render
+ render :inline => "Hello world"
+ Project.all
+ ActiveRecord::LogSubscriber.runtime += 100
+ end
end
include ActiveSupport::LogSubscriber::TestHelper
@@ -52,4 +63,19 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
assert_equal 2, @logger.logged(:info).size
assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0.0ms\)/, @logger.logged(:info)[1])
end
+
+ def test_log_with_active_record_when_redirecting
+ get :redirect
+ wait
+ assert_equal 3, @logger.logged(:info).size
+ assert_match(/\(ActiveRecord: [\d.]+ms\)/, @logger.logged(:info)[2])
+ end
+
+ def test_include_time_query_time_after_rendering
+ get :db_after_render
+ wait
+
+ assert_equal 2, @logger.logged(:info).size
+ assert_match(/\(Views: [\d.]+ms \| ActiveRecord: ([1-9][\d.]+)ms\)/, @logger.logged(:info)[1])
+ end
end
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index f9e47d5118..20d11377f6 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -87,6 +87,14 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_nil
+ with_test_routes do
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ polymorphic_url(nil)
+ end
+ end
+ end
+
def test_with_record
with_test_routes do
@project.save
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 7f3d943bba..a714e8bbcc 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -100,9 +100,6 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base
end
end
-class UserController < ActionController::Base
-end
-
module Admin
class InnerModuleController < ActionController::Base
def index
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 878484eb57..5eef8a32d7 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -20,6 +20,15 @@ class AssertSelectTest < ActionController::TestCase
end
end
+ class AssertMultipartSelectMailer < ActionMailer::Base
+ def test(options)
+ mail :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>" do |format|
+ format.text { render :text => options[:text] }
+ format.html { render :text => options[:html] }
+ end
+ end
+ end
+
class AssertSelectController < ActionController::Base
def response_with=(content)
@content = content
@@ -313,6 +322,16 @@ EOF
end
end
+ def test_assert_select_email_multipart
+ AssertMultipartSelectMailer.test(:html => "<div><p>foo</p><p>bar</p></div>", :text => 'foo bar').deliver
+ assert_select_email do
+ assert_select "div:root" do
+ assert_select "p:first-child", "foo"
+ assert_select "p:last-child", "bar"
+ end
+ end
+ end
+
protected
def render_html(html)
@controller.response_with = html
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index fada0c7748..2364bbf3a3 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -127,7 +127,7 @@ class PageCachingTest < ActionController::TestCase
assert_equal 'I am xml', @response.body
end
- def test_should_cache_with_trailing_slash_on_url
+ def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/'
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
end
@@ -141,7 +141,7 @@ class PageCachingTest < ActionController::TestCase
[:ok, :no_content, :found, :not_found].each do |status|
[:get, :post, :put, :delete].each do |method|
- unless method == :get and status == :ok
+ unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
assert_response status
@@ -187,14 +187,18 @@ class ActionCachingTestController < CachingController
rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
end
+ # Eliminate uninitialized ivar warning
+ before_filter { @title = nil }
+
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
+ caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
- layout 'talk_from_action.erb'
+ layout 'talk_from_action'
def index
@cache_this = MockTime.now.to_f.to_s
@@ -216,6 +220,11 @@ class ActionCachingTestController < CachingController
render :text => @cache_this, :layout => true
end
+ def with_format_and_http_param
+ @cache_this = MockTime.now.to_f.to_s
+ render :text => @cache_this
+ end
+
def record_not_found
raise ActiveRecord::RecordNotFound, "oops!"
end
@@ -356,6 +365,13 @@ class ActionCacheTest < ActionController::TestCase
assert !fragment_exist?('hostname.com/action_caching_test')
end
+ def test_action_cache_with_format_and_http_param
+ get :with_format_and_http_param, :format => 'json'
+ assert_response :success
+ assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json')
+ assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value')
+ end
+
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@@ -742,6 +758,7 @@ class FunctionalFragmentCachingTest < ActionController::TestCase
expected_body = <<-CACHED
Hello
This bit's fragment cached
+Ciao
CACHED
assert_equal expected_body, @response.body
@@ -783,3 +800,51 @@ CACHED
assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
end
end
+
+class CacheHelperOutputBufferTest < ActionController::TestCase
+
+ class MockController
+ def read_fragment(name, options)
+ return false
+ end
+
+ def write_fragment(name, fragment, options)
+ fragment
+ end
+ end
+
+ def setup
+ super
+ end
+
+ def test_output_buffer
+ output_buffer = ActionView::OutputBuffer.new
+ controller = MockController.new
+ cache_helper = Object.new
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+ cache_helper.expects(:controller).returns(controller).at_least(0)
+ cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0)
+ # if the output_buffer is changed, the new one should be html_safe and of the same type
+ cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0)
+
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+
+ def test_safe_buffer
+ output_buffer = ActiveSupport::SafeBuffer.new
+ controller = MockController.new
+ cache_helper = Object.new
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+ cache_helper.expects(:controller).returns(controller).at_least(0)
+ cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0)
+ # if the output_buffer is changed, the new one should be html_safe and of the same type
+ cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0)
+
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+
+end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index b12c798302..d51882066d 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -74,6 +74,7 @@ class ContentTypeTest < ActionController::TestCase
get :render_defaults
assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type
+ ensure
OldContentTypeController.default_charset = "utf-8"
end
diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_filter_test.rb
new file mode 100644
index 0000000000..3bbb981040
--- /dev/null
+++ b/actionpack/test/controller/default_url_options_with_filter_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+
+
+class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base
+
+ before_filter { I18n.locale = params[:locale] }
+ after_filter { I18n.locale = "en" }
+
+ def target
+ render :text => "final response"
+ end
+
+ def redirect
+ redirect_to :action => "target"
+ end
+
+ def default_url_options
+ {:locale => "de"}
+ end
+end
+
+class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase
+
+ # This test has it´s roots in issue #1872
+ test "should redirect with correct locale :de" do
+ get :redirect, :locale => "de"
+ assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de"
+ end
+end
diff --git a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
deleted file mode 100644
index 0c02afea36..0000000000
--- a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'abstract_unit'
-
-class DeprecatedBaseMethodsTest < ActionController::TestCase
- class Target < ActionController::Base
- def home_url(greeting)
- "http://example.com/#{greeting}"
- end
-
- def raises_name_error
- this_method_doesnt_exist
- end
-
- def rescue_action(e) raise e end
- end
-
- tests Target
-
- if defined? Test::Unit::Error
- def test_assertion_failed_error_silences_deprecation_warnings
- get :raises_name_error
- rescue => e
- error = Test::Unit::Error.new('testing ur doodz', e)
- assert_not_deprecated { error.message }
- end
- end
-end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 9e44e8e088..d5e3da4d88 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -530,6 +530,11 @@ class FilterTest < ActionController::TestCase
assert sweeper.before(TestController.new)
end
+ def test_after_method_of_sweeper_should_always_return_nil
+ sweeper = ActionController::Caching::Sweeper.send(:new)
+ assert_nil sweeper.after(TestController.new)
+ end
+
def test_non_yielding_around_filters_not_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 3e723e20d9..43b20fdead 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -14,6 +14,10 @@ class ForceSSLControllerLevel < ForceSSLController
force_ssl
end
+class ForceSSLCustomDomain < ForceSSLController
+ force_ssl :host => "secure.test.host"
+end
+
class ForceSSLOnlyAction < ForceSSLController
force_ssl :only => :cheeseburger
end
@@ -38,6 +42,22 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
end
end
+class ForceSSLCustomDomainTest < ActionController::TestCase
+ tests ForceSSLCustomDomain
+
+ def test_banana_redirects_to_https_with_custom_host
+ get :banana
+ assert_response 301
+ assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url
+ end
+
+ def test_cheeseburger_redirects_to_https_with_custom_host
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://secure.test.host/force_ssl_custom_domain/cheeseburger", redirect_to_url
+ end
+end
+
class ForceSSLOnlyActionTest < ActionController::TestCase
tests ForceSSLOnlyAction
@@ -80,4 +100,4 @@ class ForceSSLExcludeDevelopmentTest < ActionController::TestCase
get :banana
assert_response 200
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 9f0670ffdf..35a87c1aae 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/kernel/reporting'
ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
@@ -77,7 +76,7 @@ class HelperTest < ActiveSupport::TestCase
self.test_helper = LocalAbcHelper
end
- def test_deprecated_helper
+ def test_helper
assert_equal expected_helper_methods, missing_methods
assert_nothing_raised { @controller_class.helper TestHelper }
assert_equal [], missing_methods
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index bd3e13e6fa..364e96d4f6 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -85,6 +85,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
end
end
+ def test_encode_credentials_has_no_newline
+ username = 'laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh'
+ password = 'kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf'
+ result = ActionController::HttpAuthentication::Basic.encode_credentials(
+ username, password)
+ assert_no_match(/\n/, result)
+ end
+
test "authentication request without credential" do
get :display
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 01dc2f2091..2ad95f5c29 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -492,6 +492,8 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
end
routes.draw do
+ match '', :to => 'application_integration_test/test#index', :as => :empty_string
+
match 'foo', :to => 'application_integration_test/test#index', :as => :foo
match 'bar', :to => 'application_integration_test/test#index', :as => :bar
end
@@ -501,11 +503,15 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
end
test "includes route helpers" do
+ assert_equal '/', empty_string_path
assert_equal '/foo', foo_path
assert_equal '/bar', bar_path
end
test "route helpers after controller access" do
+ get '/'
+ assert_equal '/', empty_string_path
+
get '/foo'
assert_equal '/foo', foo_path
@@ -522,11 +528,10 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
assert_raise(NameError) { missing_path }
end
- test "process reuse the env we pass as argument" do
+ test "process do not modify the env passed as argument" do
env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
+ old_env = env.dup
get '/foo', nil, env
- assert_equal :get, env[:method]
- assert_equal 'server', env[:SERVER_NAME]
- assert_equal 'custom', env['action_dispatch.custom']
+ assert_equal old_env, env
end
end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index cafe2b9320..25299eb8b8 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -79,7 +79,7 @@ class DefaultLayoutController < LayoutTest
end
class AbsolutePathLayoutController < LayoutTest
- layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test.erb')
+ layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test')
end
class HasOwnLayoutController < LayoutTest
@@ -184,7 +184,7 @@ class RenderWithTemplateOptionController < LayoutTest
end
class SetsNonExistentLayoutFile < LayoutTest
- layout "nofile.erb"
+ layout "nofile"
end
class LayoutExceptionRaised < ActionController::TestCase
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 80c4fa2ee5..ccdfcb0b2c 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -6,6 +6,13 @@ module Another
class LogSubscribersController < ActionController::Base
wrap_parameters :person, :include => :name, :format => :json
+ class SpecialException < Exception
+ end
+
+ rescue_from SpecialException do
+ head :status => 406
+ end
+
def show
render :nothing => true
end
@@ -39,6 +46,10 @@ module Another
raise Exception
end
+ def with_rescued_exception
+ raise SpecialException
+ end
+
end
end
@@ -195,6 +206,14 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_match(/Completed 500/, logs.last)
end
+ def test_process_action_with_rescued_exception_includes_http_status_code
+ get :with_rescued_exception
+ wait
+
+ assert_equal 2, logs.size
+ assert_match(/Completed 406/, logs.last)
+ end
+
def logs
@logs ||= @logger.logged(:info)
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 4a5e597500..76a8c89e60 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -498,12 +498,18 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
end
+
+ def test_invalid_format
+ get :using_defaults, :format => "invalidformat"
+ assert_equal " ", @response.body
+ assert_equal "text/html", @response.content_type
+ end
end
class RespondWithController < ActionController::Base
respond_to :html, :json
respond_to :xml, :except => :using_resource_with_block
- respond_to :js, :only => [ :using_resource_with_block, :using_resource ]
+ respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]
def using_resource
respond_with(resource)
@@ -569,11 +575,6 @@ protected
def resource
Customer.new("david", request.delete? ? nil : 13)
end
-
- def _render_js(js, options)
- self.content_type ||= Mime::JS
- self.response_body = js.respond_to?(:to_js) ? js.to_js : js
- end
end
class InheritedRespondWithController < RespondWithController
@@ -632,6 +633,20 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_using_resource_with_js_simply_tries_to_render_the_template
+ @request.accept = "text/javascript"
+ get :using_resource
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "alert(\"Hi\");", @response.body
+ end
+
+ def test_using_hash_resource_with_js_raises_an_error_if_template_cant_be_found
+ @request.accept = "text/javascript"
+ assert_raise ActionView::MissingTemplate do
+ get :using_hash_resource
+ end
+ end
+
def test_using_hash_resource
@request.accept = "application/xml"
get :using_hash_resource
@@ -641,7 +656,16 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :using_hash_resource
assert_equal "application/json", @response.content_type
- assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body
+ assert @response.body.include?("result")
+ assert @response.body.include?('"name":"david"')
+ assert @response.body.include?('"id":13')
+ end
+
+ def test_using_hash_resource_with_post
+ @request.accept = "application/json"
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ post :using_hash_resource
+ end
end
def test_using_resource_with_block
@@ -723,6 +747,20 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failure
+ with_test_route_set do
+ @request.accept = "application/json"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal 422, @response.status
+ errors = {:errors => errors}
+ assert_equal errors.to_json, @response.body
+ assert_nil @response.location
+ end
+ end
+
def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do
put :using_resource
@@ -758,21 +796,21 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
- def test_using_resource_for_put_with_xml_yields_ok_on_success
+ def test_using_resource_for_put_with_xml_yields_no_content_on_success
@request.accept = "application/xml"
put :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal 200, @response.status
+ assert_equal 204, @response.status
assert_equal " ", @response.body
end
- def test_using_resource_for_put_with_json_yields_ok_on_success
+ def test_using_resource_for_put_with_json_yields_no_content_on_success
Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
@request.accept = "application/json"
put :using_resource
assert_equal "application/json", @response.content_type
- assert_equal 200, @response.status
- assert_equal "{}", @response.body
+ assert_equal 204, @response.status
+ assert_equal " ", @response.body
end
def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure
@@ -786,6 +824,18 @@ class RespondWithControllerTest < ActionController::TestCase
assert_nil @response.location
end
+ def test_using_resource_for_put_with_json_yields_unprocessable_entity_on_failure
+ @request.accept = "application/json"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal 422, @response.status
+ errors = {:errors => errors}
+ assert_equal errors.to_json, @response.body
+ assert_nil @response.location
+ end
+
def test_using_resource_for_delete_with_html_redirects_on_success
with_test_route_set do
Customer.any_instance.stubs(:destroyed?).returns(true)
@@ -796,23 +846,23 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
- def test_using_resource_for_delete_with_xml_yields_ok_on_success
+ def test_using_resource_for_delete_with_xml_yields_no_content_on_success
Customer.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/xml"
delete :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal 200, @response.status
+ assert_equal 204, @response.status
assert_equal " ", @response.body
end
- def test_using_resource_for_delete_with_json_yields_ok_on_success
+ def test_using_resource_for_delete_with_json_yields_no_content_on_success
Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
Customer.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/json"
delete :using_resource
assert_equal "application/json", @response.content_type
- assert_equal 200, @response.status
- assert_equal "{}", @response.body
+ assert_equal 204, @response.status
+ assert_equal " ", @response.body
end
def test_using_resource_for_delete_with_html_redirects_on_failure
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 8fa5d20372..ed244513a5 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -40,7 +40,7 @@ module Dispatching
class ContainedEmptyController < ActionController::Base ; end
class ContainedSubEmptyController < ContainedEmptyController ; end
class ContainedNonDefaultPathController < ActionController::Base
- def self.controller_path; "i_am_extremly_not_default"; end
+ def self.controller_path; "i_am_extremely_not_default"; end
end
end
@@ -89,7 +89,7 @@ module Dispatching
end
test "namespaced non-default controller path" do
- assert_equal 'i_am_extremly_not_default', Submodule::ContainedNonDefaultPathController.controller_path
+ assert_equal 'i_am_extremely_not_default', Submodule::ContainedNonDefaultPathController.controller_path
assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path
end
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index 8b2fdf8f96..a961cbf849 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -10,7 +10,7 @@ module RenderFile
def with_instance_variables
@secret = 'in the sauce'
- render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar.erb')
+ render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
end
def without_file_key
@@ -19,7 +19,7 @@ module RenderFile
def without_file_key_with_instance_variable
@secret = 'in the sauce'
- render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar.erb')
+ render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
end
def relative_path
@@ -34,16 +34,16 @@ module RenderFile
def pathname
@secret = 'in the sauce'
- render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar.erb])
+ render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
end
def with_locals
- path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals.erb')
+ path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')
render :file => path, :locals => {:secret => 'in the sauce'}
end
def without_file_key_with_locals
- path = FIXTURES.join('test/render_file_with_locals.erb').to_s
+ path = FIXTURES.join('test/render_file_with_locals').to_s
render path, :locals => {:secret => 'in the sauce'}
end
end
diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb
index 83b0d039ad..b4a25c49c9 100644
--- a/actionpack/test/controller/new_base/render_partial_test.rb
+++ b/actionpack/test/controller/new_base/render_partial_test.rb
@@ -7,12 +7,12 @@ module RenderPartial
self.view_paths = [ActionView::FixtureResolver.new(
"render_partial/basic/_basic.html.erb" => "BasicPartial!",
"render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
- "render_partial/basic/with_json.html.erb" => "<%= render 'with_json.json' %>",
- "render_partial/basic/_with_json.json.erb" => "<%= render 'final' %>",
+ "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
+ "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
"render_partial/basic/_final.json.erb" => "{ final: json }",
- "render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
- "render_partial/basic/_overriden.html.erb" => "ParentPartial!",
- "render_partial/child/_overriden.html.erb" => "OverridenPartial!"
+ "render_partial/basic/overriden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overriden' %><%= @test_unchanged %>",
+ "render_partial/basic/_overriden.html.erb" => "ParentPartial!",
+ "render_partial/child/_overriden.html.erb" => "OverridenPartial!"
)]
def html_with_json_inside_json
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 48cf0ab9cb..1a17e24914 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -10,9 +10,9 @@ module RenderStreaming
)]
layout "application"
- stream :only => [:hello_world, :skip]
def hello_world
+ render :stream => true
end
def layout_exception
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index 584f2d772c..ba804421da 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -10,8 +10,8 @@ module RenderTemplate
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
"with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %>",
- "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>",
- "test/with_json.json.erb" => "<%= render :template => 'test/final' %>",
+ "test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>",
+ "test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>",
"test/final.json.erb" => "{ final: json }",
"test/with_error.html.erb" => "<%= idontexist %>"
)]
@@ -117,7 +117,7 @@ module RenderTemplate
assert_response "{ final: json }"
end
- test "rendering a template with error properly exceprts the code" do
+ test "rendering a template with error properly excerts the code" do
get :with_error
assert_status 500
assert_match "undefined local variable or method `idontexist'", response.body
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 92d4a6d98b..79041055bd 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -4,6 +4,11 @@ class WorkshopsController < ActionController::Base
end
class RedirectController < ActionController::Base
+ # empty method not used anywhere to ensure methods like
+ # `status` and `location` aren't called on `redirect_to` calls
+ def status; render :text => 'called status'; end
+ def location; render :text => 'called location'; end
+
def simple_redirect
redirect_to :action => "hello_world"
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index e62f3155c5..aea603b014 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -14,6 +14,14 @@ module Fun
end
end
+module Quiz
+ class QuestionsController < ActionController::Base
+ def new
+ render :partial => Quiz::Question.new("Namespaced Partial")
+ end
+ end
+end
+
class TestController < ActionController::Base
protect_from_forgery
@@ -155,14 +163,14 @@ class TestController < ActionController::Base
# :ported:
def render_file_with_instance_variables
@secret = 'in the sauce'
- path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar')
render :file => path
end
# :ported:
def render_file_as_string_with_instance_variables
@secret = 'in the sauce'
- path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
+ path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar'))
render path
end
@@ -179,21 +187,21 @@ class TestController < ActionController::Base
def render_file_using_pathname
@secret = 'in the sauce'
- render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar.erb')
+ render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar')
end
def render_file_from_template
@secret = 'in the sauce'
- @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
+ @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar'))
end
def render_file_with_locals
- path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
+ path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals')
render :file => path, :locals => {:secret => 'in the sauce'}
end
def render_file_as_string_with_locals
- path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb'))
+ path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals'))
render path, :locals => {:secret => 'in the sauce'}
end
@@ -397,6 +405,14 @@ class TestController < ActionController::Base
render :template => "test/hello_world"
end
+ def render_with_explicit_unescaped_template
+ render :template => "test/h*llo_world"
+ end
+
+ def render_with_explicit_escaped_template
+ render :template => "test/hello,world"
+ end
+
def render_with_explicit_string_template
render "test/hello_world"
end
@@ -437,17 +453,13 @@ class TestController < ActionController::Base
render :action => "potential_conflicts"
end
- # :deprecated:
- # Tests being able to pick a .builder template over a .erb
- # For instance, being able to have hello.xml.builder and hello.xml.erb
- # and select one via "hello.builder" or "hello.erb"
def hello_world_from_rxml_using_action
- render :action => "hello_world_from_rxml.builder"
+ render :action => "hello_world_from_rxml", :handlers => [:builder]
end
# :deprecated:
def hello_world_from_rxml_using_template
- render :template => "test/hello_world_from_rxml.builder"
+ render :template => "test/hello_world_from_rxml", :handlers => [:builder]
end
def action_talk_to_layout
@@ -509,8 +521,8 @@ class TestController < ActionController::Base
render :action => "using_layout_around_block", :layout => "layouts/block_with_layout"
end
- def partial_dot_html
- render :partial => 'partial.html.erb'
+ def partial_formats_html
+ render :partial => 'partial', :formats => [:html]
end
def partial
@@ -781,7 +793,9 @@ class RenderTest < ActionController::TestCase
end
def test_render_file
- get :hello_world_file
+ assert_deprecated do
+ get :hello_world_file
+ end
assert_equal "Hello world!", @response.body
end
@@ -1023,11 +1037,6 @@ class RenderTest < ActionController::TestCase
assert_equal " ", @response.body
end
- def test_render_to_string_not_deprecated
- assert_not_deprecated { get :hello_in_a_string }
- assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
- end
-
def test_render_to_string_doesnt_break_assigns
get :render_to_string_with_assigns
assert_equal "i'm before the render", assigns(:before)
@@ -1054,6 +1063,12 @@ class RenderTest < ActionController::TestCase
assert_response :success
end
+ def test_render_with_explicit_unescaped_template
+ assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template }
+ get :render_with_explicit_escaped_template
+ assert_equal "Hello w*rld!", @response.body
+ end
+
def test_render_with_explicit_string_template
get :render_with_explicit_string_template
assert_equal "<html>Hello world!</html>", @response.body
@@ -1106,7 +1121,7 @@ class RenderTest < ActionController::TestCase
end
def test_yield_content_for
- assert_not_deprecated { get :yield_content_for }
+ get :yield_content_for
assert_equal "<title>Putting stuff in the title!</title>\nGreat stuff!\n", @response.body
end
@@ -1216,8 +1231,8 @@ class RenderTest < ActionController::TestCase
assert_equal 'partial html', @response.body
end
- def test_should_render_html_partial_with_dot
- get :partial_dot_html
+ def test_should_render_html_partial_with_formats
+ get :partial_formats_html
assert_equal 'partial html', @response.body
end
@@ -1256,6 +1271,12 @@ class RenderTest < ActionController::TestCase
assert_template('fun/games/_form')
end
+ def test_namespaced_object_partial
+ @controller = Quiz::QuestionsController.new
+ get :new
+ assert_equal "Namespaced Partial", @response.body
+ end
+
def test_partial_collection
get :partial_collection
assert_equal "Hello: davidHello: mary", @response.body
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index dea80ed887..fd5a41a0bb 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'digest/sha1'
require 'active_support/core_ext/string/strip'
+require "active_support/log_subscriber/test_helper"
# common controller actions
module RequestForgeryProtectionActions
@@ -80,7 +81,7 @@ module RequestForgeryProtectionTests
def setup
@token = "cf50faa3fe97702ca1ae"
- ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
+ SecureRandom.stubs(:base64).returns(@token)
ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
end
@@ -157,6 +158,21 @@ module RequestForgeryProtectionTests
assert_not_blocked { put :index }
end
+ def test_should_warn_on_missing_csrf_token
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
+ begin
+ assert_blocked { post :index }
+
+ assert_equal 1, logger.logged(:warn).size
+ assert_match(/CSRF token authenticity/, logger.logged(:warn).last)
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
def assert_blocked
session[:something_like_user_id] = 1
yield
@@ -184,7 +200,7 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase
end
test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
- ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
+ SecureRandom.stubs(:base64).returns(@token + '<=?')
get :meta
assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token'
assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
@@ -207,7 +223,7 @@ class FreeCookieControllerTest < ActionController::TestCase
@response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
- ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
+ SecureRandom.stubs(:base64).returns(@token)
end
def test_should_not_render_form_with_token_tag
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 6ea492cf8b..6b8a8f6161 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -91,6 +91,15 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_multiple_resources_with_options
+ expected_options = {:controller => 'threads', :action => 'index'}
+
+ with_restful_routing :messages, :comments, expected_options.slice(:controller) do
+ assert_recognizes(expected_options, :path => 'comments')
+ assert_recognizes(expected_options, :path => 'messages')
+ end
+ end
+
def test_with_custom_conditions
with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
assert @routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
@@ -523,7 +532,7 @@ class ResourcesTest < ActionController::TestCase
routes.each do |route|
routes.each do |r|
next if route === r # skip the comparison instance
- assert distinct_routes?(route, r), "Duplicate Route: #{route}"
+ assert_not_equal [route.conditions, route.path.spec.to_s], [r.conditions, r.path.spec.to_s]
end
end
end
@@ -1342,13 +1351,4 @@ class ResourcesTest < ActionController::TestCase
assert_recognizes(expected_options, path)
end
end
-
- def distinct_routes? (r1, r2)
- if r1.conditions == r2.conditions and r1.constraints == r2.constraints then
- if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then
- return false
- end
- end
- true
- end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index aa9d193436..5bf68decca 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,7 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
-require 'active_support/dependencies'
require 'active_support/core_ext/object/with_options'
class MilestonesController < ActionController::Base
@@ -1665,114 +1664,6 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) }
end
- def test_generate
- assert_equal '/admin/users', url_for(@routes, { :use_route => 'admin_users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'admin/users', :action => 'index' })
- assert_equal '/admin/users', url_for(@routes, { :action => 'index' }, { :controller => 'admin/users' })
- assert_equal '/admin/users', url_for(@routes, { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' })
- assert_equal '/people', url_for(@routes, { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' })
-
- assert_equal '/admin/posts', url_for(@routes, { :controller => 'admin/posts' })
- assert_equal '/admin/posts/new', url_for(@routes, { :controller => 'admin/posts', :action => 'new' })
-
- assert_equal '/blog/2009', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009 })
- assert_equal '/blog/2009/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 })
- assert_equal '/blog/2009/1/1', url_for(@routes, { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 })
-
- assert_equal '/archive/2010', url_for(@routes, { :controller => 'archive', :action => 'index', :year => '2010' })
- assert_equal '/archive', url_for(@routes, { :controller => 'archive', :action => 'index' })
- assert_equal '/archive?year=january', url_for(@routes, { :controller => 'archive', :action => 'index', :year => 'january' })
-
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people' })
- assert_equal '/people', url_for(@routes, { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, {}, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/new', url_for(@routes, { :use_route => 'new_person' })
- assert_equal '/people/new', url_for(@routes, { :controller => 'people', :action => 'new' })
- assert_equal '/people/1', url_for(@routes, { :use_route => 'person', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1.xml', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => 1 })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show', :id => Model.new('1') })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' })
- assert_equal '/people/1', url_for(@routes, { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people', url_for(@routes, { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, {}, { :controller => 'people', :action => 'show', :id => '1' })
- assert_equal '/people/1', url_for(@routes, { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' })
- assert_equal '/people/1/edit', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1' })
- assert_equal '/people/1/edit.xml', url_for(@routes, { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' })
- assert_equal '/people/1/edit', url_for(@routes, { :use_route => 'edit_person', :id => '1' })
- assert_equal '/people/1?legacy=true', url_for(@routes, { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' })
- assert_equal '/people?legacy=true', url_for(@routes, { :controller => 'people', :action => 'index', :legacy => 'true' })
-
- assert_equal '/id_default/2', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '2' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => '1' })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default', :id => 1 })
- assert_equal '/id_default', url_for(@routes, { :controller => 'foo', :action => 'id_default' })
- assert_equal '/optional/bar', url_for(@routes, { :controller => 'posts', :action => 'index', :optional => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
-
- assert_equal '/project', url_for(@routes, { :controller => 'project', :action => 'index' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :controller => 'project', :action => 'index'}, {:project_id => '1' })
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }) }
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' })
- assert_equal '/projects/1', url_for(@routes, { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' })
-
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' })
- assert_equal '/clients?project_id=1', url_for(@routes, { :controller => 'projects', :action => 'index', :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :controller => 'projects', :action => 'index' }, { :project_id => '1' })
- assert_equal '/clients', url_for(@routes, { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' })
-
- assert_equal '/comment/20', url_for(@routes, { :id => 20 }, { :controller => 'comments', :action => 'show' })
- assert_equal '/comment/20', url_for(@routes, { :controller => 'comments', :id => 20, :action => 'show' })
- assert_equal '/comments/boo', url_for(@routes, { :controller => 'comments', :action => 'boo' })
-
- assert_equal '/ws/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1', :ws => true })
- assert_equal '/ws/posts', url_for(@routes, { :controller => 'posts', :action => 'index', :ws => true })
-
- assert_equal '/account', url_for(@routes, { :controller => 'account', :action => 'subscription' })
- assert_equal '/account/billing', url_for(@routes, { :controller => 'account', :action => 'billing' })
-
- assert_equal '/pages/1/notes/show/1', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/pages/1/notes/list', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'list' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes', :action => 'index' })
- assert_equal '/pages/1/notes', url_for(@routes, { :page_id => '1', :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :page_id => nil, :controller => 'notes' })
- assert_equal '/notes', url_for(@routes, { :controller => 'notes' })
- assert_equal '/notes/print', url_for(@routes, { :controller => 'notes', :action => 'print' })
- assert_equal '/notes/print', url_for(@routes, {}, { :controller => 'notes', :action => 'print' })
-
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :action => 'index' }, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, {}, { :controller => 'notes', :id => '1' })
- assert_equal '/notes/show/1', url_for(@routes, {}, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/index/1', url_for(@routes, { :controller => 'notes', :id => '1' }, { :foo => 'bar' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' })
- assert_equal '/notes/list', url_for(@routes, { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' })
-
- assert_equal '/posts/ping', url_for(@routes, { :controller => 'posts', :action => 'ping' })
- assert_equal '/posts/show/1', url_for(@routes, { :controller => 'posts', :action => 'show', :id => '1' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts', url_for(@routes, { :controller => 'posts' }, { :controller => 'posts', :action => 'index' })
- assert_equal '/posts/create', url_for(@routes, { :action => 'create' }, { :controller => 'posts' })
- assert_equal '/posts?foo=bar', url_for(@routes, { :controller => 'posts', :foo => 'bar' })
- assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', url_for(@routes, { :controller => 'posts', :foo => ['bar', 'baz'] })
- assert_equal '/posts?page=2', url_for(@routes, { :controller => 'posts', :page => 2 })
- assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', url_for(@routes, { :controller => 'posts', :q => { :foo => { :a => 'b'}} })
-
- assert_equal '/news.rss', url_for(@routes, { :controller => 'news', :action => 'index', :format => 'rss' })
-
-
- assert_raise(ActionController::RoutingError) { url_for(@routes, { :action => 'index' }) }
- end
-
def test_generate_extras
assert_equal ['/people', []], @routes.generate_extras(:controller => 'people')
assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar')
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index c7c8360ae6..8f885ff28e 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -138,6 +138,25 @@ class SendFileTest < ActionController::TestCase
@controller.headers = {}
assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) }
end
+
+ def test_send_file_headers_guess_type_from_extension
+ {
+ 'image.png' => 'image/png',
+ 'image.jpeg' => 'image/jpeg',
+ 'image.jpg' => 'image/jpeg',
+ 'image.tif' => 'image/tiff',
+ 'image.gif' => 'image/gif',
+ 'movie.mpg' => 'video/mpeg',
+ 'file.zip' => 'application/zip',
+ 'file.unk' => 'application/octet-stream',
+ 'zip' => 'application/octet-stream'
+ }.each do |filename,expected_type|
+ options = { :filename => filename }
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ assert_equal expected_type, @controller.content_type
+ end
+ end
%w(file data).each do |method|
define_method "test_send_#{method}_status" do
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 5896222a0a..b64e275363 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -50,6 +50,10 @@ class TestTest < ActionController::TestCase
render :text => request.query_string
end
+ def test_protocol
+ render :text => request.protocol
+ end
+
def test_html_output
render :text => <<HTML
<html>
@@ -142,6 +146,17 @@ XML
end
end
+ class ViewAssignsController < ActionController::Base
+ def test_assigns
+ @foo = "foo"
+ render :nothing => true
+ end
+
+ def view_assigns
+ { "bar" => "bar" }
+ end
+ end
+
def test_raw_post_handling
params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123]
post :render_raw_post, params.dup
@@ -252,6 +267,15 @@ XML
assert_equal "foo", assigns["foo"]
end
+ def test_view_assigns
+ @controller = ViewAssignsController.new
+ process :test_assigns
+ assert_equal nil, assigns(:foo)
+ assert_equal nil, assigns[:foo]
+ assert_equal "bar", assigns(:bar)
+ assert_equal "bar", assigns[:bar]
+ end
+
def test_assert_tag_tag
process :test_html_output
@@ -493,6 +517,16 @@ XML
)
end
+ def test_params_passing_with_fixnums
+ get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_test/test', 'action' => 'test_params',
+ 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
+ parsed_params
+ )
+ end
+
def test_params_passing_with_frozen_values
assert_nothing_raised do
get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze
@@ -505,6 +539,12 @@ XML
)
end
+ def test_params_passing_doesnt_modify_in_place
+ page = {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ get :test_params, :page => page
+ assert_equal 2004, page[:year]
+ end
+
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
assert_kind_of String, @request.path_parameters['id']
@@ -582,14 +622,27 @@ XML
assert_nil @request.symbolized_path_parameters[:id]
end
+ def test_request_protocol_is_reset_after_request
+ get :test_protocol
+ assert_equal "http://", @response.body
+
+ @request.env["HTTPS"] = "on"
+ get :test_protocol
+ assert_equal "https://", @response.body
+
+ @request.env.delete("HTTPS")
+ get :test_protocol
+ assert_equal "http://", @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
- @request.cookies['foo'] = 'bar'
+ cookies['foo'] = 'bar'
get :no_op
assert_equal 'bar', cookies['foo']
end
def test_should_detect_if_cookie_is_deleted
- @request.cookies['foo'] = 'bar'
+ cookies['foo'] = 'bar'
get :delete_cookie
assert_nil cookies['foo']
end
@@ -602,7 +655,6 @@ XML
send(method, :test_remote_addr)
assert false, "expected RuntimeError, got nothing"
rescue RuntimeError => error
- assert true
assert_match(%r{@#{variable} is nil}, error.message)
rescue => error
assert false, "expected RuntimeError, got #{error.class}"
@@ -640,6 +692,13 @@ XML
end
+ def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
+ TestTest.stubs(:fixture_path).returns(FILES_DIR)
+
+ uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png')
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
def test_test_uploaded_file_with_binary
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
@@ -715,6 +774,22 @@ class CrazyNameTest < ActionController::TestCase
end
end
+class CrazySymbolNameTest < ActionController::TestCase
+ tests :content
+
+ def test_set_controller_class_using_symbol
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
+class CrazyStringNameTest < ActionController::TestCase
+ tests 'content'
+
+ def test_set_controller_class_using_string
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
class NamedRoutesControllerTest < ActionController::TestCase
tests ContentController
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
new file mode 100644
index 0000000000..7b734ff0fb
--- /dev/null
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -0,0 +1,183 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'active_support/core_ext/object/with_options'
+
+module RoutingTestHelpers
+ def url_for(set, options, recall = nil)
+ set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
+ end
+end
+
+module ActionPack
+ class URLForIntegrationTest < ActiveSupport::TestCase
+ include RoutingTestHelpers
+
+ Model = Struct.new(:to_param)
+
+ Mapping = lambda {
+ namespace :admin do
+ resources :users, :posts
+ end
+
+ namespace 'api' do
+ root :to => 'users#index'
+ end
+
+ match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ :constraints => {
+ :year => /(19|20)\d\d/,
+ :month => /[01]?\d/,
+ :day => /[0-3]?\d/
+ },
+ :day => nil,
+ :month => nil
+
+ match 'archive/:year', :controller => 'archive', :action => 'index',
+ :defaults => { :year => nil },
+ :constraints => { :year => /\d{4}/ },
+ :as => "blog"
+
+ resources :people
+ #match 'legacy/people' => "people#index", :legacy => "true"
+
+ match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ match 'id_default(/:id)' => "foo#id_default", :id => 1
+ match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
+ match 'optional/:optional' => "posts#index"
+ match 'projects/:project_id' => "project#index", :as => "project"
+ match 'clients' => "projects#index"
+
+ match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ :postalcode => /# Postcode format
+ \d{5} #Prefix
+ (-\d{4})? #Suffix
+ /x
+ }, :as => "geocode"
+
+ match 'news(.:format)' => "news#index"
+
+ match 'comment/:id(/:action)' => "comments#show"
+ match 'ws/:controller(/:action(/:id))', :ws => true
+ match 'account(/:action)' => "account#subscription"
+ match 'pages/:page_id/:controller(/:action(/:id))'
+ match ':controller/ping', :action => 'ping'
+ match ':controller(/:action(/:id))(.:format)'
+ root :to => "news#index"
+ }
+
+ def setup
+ @routes = ActionDispatch::Routing::RouteSet.new
+ @routes.draw(&Mapping)
+ end
+
+ [
+ ['/admin/users',[ { :use_route => 'admin_users' }]],
+ ['/admin/users',[ { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]],
+ ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' }]],
+ ['/people',[ { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' }]],
+
+ ['/admin/posts',[ { :controller => 'admin/posts' }]],
+ ['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]],
+
+ ['/blog/2009',[ { :controller => 'posts', :action => 'show_date', :year => 2009 }]],
+ ['/blog/2009/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 }]],
+ ['/blog/2009/1/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 }]],
+
+ ['/archive/2010',[ { :controller => 'archive', :action => 'index', :year => '2010' }]],
+ ['/archive',[ { :controller => 'archive', :action => 'index' }]],
+ ['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]],
+
+ ['/people',[ { :controller => 'people', :action => 'index' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ {}, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/new',[ { :use_route => 'new_person' }]],
+ ['/people/new',[ { :controller => 'people', :action => 'new' }]],
+ ['/people/1',[ { :use_route => 'person', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]],
+ ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }]],
+ ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }]],
+ ['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]],
+ ['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]],
+ ['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]],
+ ['/people/1?legacy=true',[ { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' }]],
+ ['/people?legacy=true',[ { :controller => 'people', :action => 'index', :legacy => 'true' }]],
+
+ ['/id_default/2',[ { :controller => 'foo', :action => 'id_default', :id => '2' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => '1' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => 1 }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default' }]],
+ ['/optional/bar',[ { :controller => 'posts', :action => 'index', :optional => 'bar' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+
+ ['/project',[ { :controller => 'project', :action => 'index' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' }]],
+
+ ['/clients',[ { :controller => 'projects', :action => 'index' }]],
+ ['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+ ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1' }]],
+ ['/clients',[ { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+
+ ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }]],
+ ['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]],
+ ['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]],
+
+ ['/ws/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :ws => true }]],
+ ['/ws/posts',[ { :controller => 'posts', :action => 'index', :ws => true }]],
+
+ ['/account',[ { :controller => 'account', :action => 'subscription' }]],
+ ['/account/billing',[ { :controller => 'account', :action => 'billing' }]],
+
+ ['/pages/1/notes/show/1',[ { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/pages/1/notes/list',[ { :page_id => '1', :controller => 'notes', :action => 'list' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes', :action => 'index' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes' }]],
+ ['/notes',[ { :page_id => nil, :controller => 'notes' }]],
+ ['/notes',[ { :controller => 'notes' }]],
+ ['/notes/print',[ { :controller => 'notes', :action => 'print' }]],
+ ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }]],
+
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' }]],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1' }]],
+ ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1' }]],
+ ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/index/1',[ { :controller => 'notes', :id => '1' }, { :foo => 'bar' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+
+ ['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]],
+ ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]],
+ ['/posts',[ { :controller => 'posts' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'posts', :action => 'index' }]],
+ ['/posts/create',[ { :action => 'create' }, { :controller => 'posts' }]],
+ ['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]],
+ ['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]],
+ ['/posts?page=2', [{ :controller => 'posts', :page => 2 }]],
+ ['/posts?q%5Bfoo%5D%5Ba%5D=b', [{ :controller => 'posts', :q => { :foo => { :a => 'b'}} }]],
+
+ ['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]],
+ ].each_with_index do |(url, params), i|
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ assert_equal url, url_for(@routes, *params), params.inspect
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 3f3d6dcc2f..11ced2df2a 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -67,6 +67,20 @@ module AbstractController
)
end
+ def test_subdomain_may_be_removed
+ add_host!
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_multiple_subdomains_may_be_removed
+ W.default_url_options[:host] = 'mobile.www.api.basecamphq.com'
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_domain_may_be_changed
add_host!
assert_equal('http://www.37signals.com/c/a/i',
@@ -293,8 +307,8 @@ module AbstractController
first_class.default_url_options[:host] = first_host
second_class.default_url_options[:host] = second_host
- assert_equal first_class.default_url_options[:host], first_host
- assert_equal second_class.default_url_options[:host], second_host
+ assert_equal first_host, first_class.default_url_options[:host]
+ assert_equal second_host, second_class.default_url_options[:host]
end
def test_with_stringified_keys
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 89de4c1da4..f88903b10e 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -70,9 +70,9 @@ class UrlRewriterTests < ActionController::TestCase
)
end
- def test_anchor_should_be_cgi_escaped
+ def test_anchor_should_be_uri_escaped
assert_equal(
- 'http://test.host/c/a/i#anc%2Fhor',
+ 'http://test.host/c/a/i#anc/hor',
@rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
)
end
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 3de1849db8..f5ac886c20 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -32,17 +32,11 @@ class ViewLoadPathsTest < ActionController::TestCase
@controller.send :assign_shortcuts, @request, @response
@controller.send :initialize_template_class, @response
- # Track the last warning.
- @old_behavior = ActiveSupport::Deprecation.behavior
- @last_message = nil
- ActiveSupport::Deprecation.behavior = Proc.new { |message, callback| @last_message = message }
-
@paths = TestController.view_paths
end
def teardown
TestController.view_paths = @paths
- ActiveSupport::Deprecation.behavior = @old_behavior
end
def expand(array)
@@ -179,7 +173,7 @@ class ViewLoadPathsTest < ActionController::TestCase
assert_nothing_raised { C.append_view_path 'c/path' }
assert_paths C, "c/path"
end
-
+
def test_lookup_context_accessor
assert_equal ["test"], TestController.new.lookup_context.prefixes
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 621fb79915..ae8588cbb0 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -30,7 +30,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_check_parameters
with_test_route_set do
get "/"
- assert_blank @controller.response.body
+ assert_equal '', @controller.response.body
end
end
@@ -162,7 +162,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_use_xml_ximple_with_empty_request
with_test_route_set do
assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} }
- assert_blank @controller.response.body
+ assert_equal '', @controller.response.body
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index e42c39f527..49da448001 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -148,6 +148,31 @@ class CookiesTest < ActionController::TestCase
@request.host = "www.nextangle.com"
end
+ def test_each
+ request.cookie_jar['foo'] = :bar
+ list = []
+ request.cookie_jar.each do |k,v|
+ list << [k, v]
+ end
+
+ assert_equal [['foo', :bar]], list
+ end
+
+ def test_enumerable
+ request.cookie_jar['foo'] = :bar
+ actual = request.cookie_jar.map { |k,v| [k.to_s, v.to_s] }
+ assert_equal [['foo', 'bar']], actual
+ end
+
+ def test_key_methods
+ assert !request.cookie_jar.key?(:foo)
+ assert !request.cookie_jar.has_key?("foo")
+
+ request.cookie_jar[:foo] = :bar
+ assert request.cookie_jar.key?(:foo)
+ assert request.cookie_jar.has_key?("foo")
+ end
+
def test_setting_cookie
get :authenticate
assert_cookie_header "user_name=david; path=/"
@@ -417,7 +442,7 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
-
+
def test_cookies_hash_is_indifferent_access
get :symbol_key
assert_equal "david", cookies[:user_name]
@@ -430,54 +455,95 @@ class CookiesTest < ActionController::TestCase
def test_setting_request_cookies_is_indifferent_access
- @request.cookies.clear
- @request.cookies[:user_name] = "andrew"
+ cookies.clear
+ cookies[:user_name] = "andrew"
get :string_key_mock
- assert_equal "david", cookies[:user_name]
+ assert_equal "david", cookies['user_name']
- @request.cookies.clear
- @request.cookies['user_name'] = "andrew"
+ cookies.clear
+ cookies['user_name'] = "andrew"
get :symbol_key_mock
- assert_equal "david", cookies['user_name']
+ assert_equal "david", cookies[:user_name]
end
def test_cookies_retained_across_requests
get :symbol_key
- assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
+ assert_cookie_header "user_name=david; path=/"
assert_equal "david", cookies[:user_name]
get :noop
assert_nil @response.headers["Set-Cookie"]
- assert_equal "user_name=david", @request.env['HTTP_COOKIE']
assert_equal "david", cookies[:user_name]
get :noop
assert_nil @response.headers["Set-Cookie"]
- assert_equal "user_name=david", @request.env['HTTP_COOKIE']
assert_equal "david", cookies[:user_name]
end
def test_cookies_can_be_cleared
get :symbol_key
- assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
assert_equal "david", cookies[:user_name]
- @request.cookies.clear
+ cookies.clear
get :noop
- assert_nil @response.headers["Set-Cookie"]
- assert_nil @request.env['HTTP_COOKIE']
assert_nil cookies[:user_name]
get :symbol_key
- assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"]
assert_equal "david", cookies[:user_name]
end
- def test_cookies_are_escaped
- @request.cookies[:user_ids] = '1;2'
+ def test_can_set_http_cookie_header
+ @request.env['HTTP_COOKIE'] = 'user_name=david'
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ @request.env['HTTP_COOKIE'] = 'user_name=andrew'
+ get :noop
+ assert_equal 'andrew', cookies['user_name']
+ assert_equal 'andrew', cookies[:user_name]
+ end
+
+ def test_can_set_request_cookies
+ @request.cookies['user_name'] = 'david'
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ @request.cookies[:user_name] = 'andrew'
+ get :noop
+ assert_equal 'andrew', cookies['user_name']
+ assert_equal 'andrew', cookies[:user_name]
+ end
+
+ def test_cookies_precedence_over_http_cookie
+ @request.env['HTTP_COOKIE'] = 'user_name=andrew'
+ get :authenticate
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+ end
+
+ def test_cookies_precedence_over_request_cookies
+ @request.cookies['user_name'] = 'andrew'
+ get :authenticate
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
get :noop
- assert_equal "user_ids=1%3B2", @request.env['HTTP_COOKIE']
- assert_equal "1;2", cookies[:user_ids]
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
end
private
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index b6c08ffc33..d3465589c1 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -5,6 +5,7 @@ module ActionDispatch
class MapperTest < ActiveSupport::TestCase
class FakeSet
attr_reader :routes
+ alias :set :routes
def initialize
@routes = []
@@ -35,6 +36,13 @@ module ActionDispatch
Mapper.new FakeSet.new
end
+ def test_mapping_requirements
+ options = { :controller => 'foo', :action => 'bar' }
+ m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options
+ _, _, requirements, _ = m.to_route
+ assert_equal(/.+?/, requirements[:rest])
+ end
+
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
@@ -83,6 +91,13 @@ module ActionDispatch
assert_equal '/*path', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
+
+ def test_map_wildcard_with_format_true
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show', :format => true
+ assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
+ end
end
end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 11cf68fdb3..08fe2127b9 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -52,7 +52,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse application with trailing star" do
accept = "application/*"
- expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF]
+ expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP]
parsed = Mime::Type.parse(accept)
assert_equal expect, parsed
end
@@ -86,12 +86,12 @@ class MimeTypeTest < ActiveSupport::TestCase
test "custom type" do
begin
- Mime::Type.register("image/gif", :gif)
+ Mime::Type.register("image/foo", :foo)
assert_nothing_raised do
- assert_equal Mime::GIF, Mime::SET.last
+ assert_equal Mime::FOO, Mime::SET.last
end
ensure
- Mime::Type.unregister(:gif)
+ Mime::Type.unregister(:FOO)
end
end
@@ -107,8 +107,10 @@ class MimeTypeTest < ActiveSupport::TestCase
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
+
types.each do |type|
mime = Mime.const_get(type.to_s.upcase)
+ assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
invalid_types.delete(:html) if Mime::Type.html_types.include?(type)
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index b28a058250..93eccaecbd 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -50,7 +50,9 @@ module TestGenerationPrefix
scope "/:omg", :omg => "awesome" do
mount BlogEngine => "/blog", :as => "blog_engine"
end
+ match "/posts/:id", :to => "outside_engine_generating#post", :as => :post
match "/generate", :to => "outside_engine_generating#index"
+ match "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
match "/conflicting_url", :to => "outside_engine_generating#conflicting"
@@ -101,6 +103,7 @@ module TestGenerationPrefix
class ::OutsideEngineGeneratingController < ActionController::Base
include BlogEngine.routes.mounted_helpers
+ include RailsApplication.routes.url_helpers
def index
render :text => blog_engine.post_path(:id => 1)
@@ -110,6 +113,10 @@ module TestGenerationPrefix
render :text => blog_engine.polymorphic_path(Post.new)
end
+ def polymorphic_path_for_app
+ render :text => polymorphic_path(Post.new)
+ end
+
def polymorphic_with_url_for
render :text => blog_engine.url_for(Post.new)
end
@@ -201,6 +208,11 @@ module TestGenerationPrefix
assert_equal "/awesome/blog/posts/1", last_response.body
end
+ test "polymorphic_path_for_app" do
+ get "/polymorphic_path_for_app"
+ assert_equal "/posts/1", last_response.body
+ end
+
test "[APP] generating engine's url with url_for(@post)" do
get "/polymorphic_with_url_for"
assert_equal "http://example.org/awesome/blog/posts/1", last_response.body
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
new file mode 100644
index 0000000000..b6e8b6c3ad
--- /dev/null
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -0,0 +1,65 @@
+require 'abstract_unit'
+
+class RequestIdTest < ActiveSupport::TestCase
+ test "passing on the request id from the outside" do
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
+ end
+
+ test "ensure that only alphanumeric uurids are accepted" do
+ assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid
+ end
+
+ test "ensure that 255 char limit on the request id is being enforced" do
+ assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid
+ end
+
+ test "generating a request id when none is supplied" do
+ assert_match(/\w+/, stub_request.uuid)
+ end
+
+ private
+
+ def stub_request(env = {})
+ ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestIdResponseTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def index
+ head :ok
+ end
+ end
+
+ test "request id is passed all the way to the response" do
+ with_test_route_set do
+ get '/'
+ assert_match(/\w+/, @response.headers["X-Request-Id"])
+ end
+ end
+
+ test "request id given on request is passed all the way to the response" do
+ with_test_route_set do
+ get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ assert_equal "X" * 255, @response.headers["X-Request-Id"]
+ end
+ end
+
+
+ private
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match '/', :to => ::RequestIdResponseTest::TestController.action(:index)
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::RequestId
+ end
+
+ yield
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 25b1b4f745..a611252b31 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -15,6 +15,7 @@ class RequestTest < ActiveSupport::TestCase
assert_equal 'http://www.example.com', url_for
assert_equal 'http://api.example.com', url_for(:subdomain => 'api')
+ assert_equal 'http://example.com', url_for(:subdomain => false)
assert_equal 'http://www.ror.com', url_for(:domain => 'ror.com')
assert_equal 'http://api.ror.co.uk', url_for(:host => 'www.ror.co.uk', :subdomain => 'api', :tld_length => 2)
assert_equal 'http://www.example.com:8080', url_for(:port => 8080)
@@ -468,6 +469,12 @@ class RequestTest < ActiveSupport::TestCase
assert request.formats.empty?
end
+ test "formats with xhr request" do
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [Mime::JS], request.formats
+ end
+
test "ignore_accept_header" do
ActionDispatch::Request.ignore_accept_header = true
diff --git a/actionpack/test/dispatch/response_body_is_proc_test.rb b/actionpack/test/dispatch/response_body_is_proc_test.rb
deleted file mode 100644
index fd94832624..0000000000
--- a/actionpack/test/dispatch/response_body_is_proc_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'abstract_unit'
-
-class ResponseBodyIsProcTest < ActionDispatch::IntegrationTest
- class TestController < ActionController::Base
- def test
- self.response_body = proc { |response, output|
- output.write 'Hello'
- }
- end
- end
-
- def test_simple_get
- with_test_route_set do
- assert_deprecated do
- get '/test'
- end
- assert_response :success
- assert_equal 'Hello', response.body
- end
- end
-
- private
-
- def with_test_route_set(options = {})
- with_routing do |set|
- set.draw do
- match ':action', :to => ::ResponseBodyIsProcTest::TestController
- end
-
- @app = self.class.build_app(set) do |middleware|
- middleware.delete "ActionDispatch::ShowExceptions"
- end
-
- yield
- end
- end
-end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index ba7506721f..cf22731823 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -339,6 +339,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
scope '(:locale)', :locale => /en|pl/ do
+ get "registrations/new"
resources :descriptions
root :to => 'projects#index'
end
@@ -504,6 +505,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+
+ scope '/italians' do
+ match '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ match '/sculptors', :to => 'italians#sculptors'
+ match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ end
end
end
@@ -845,6 +852,29 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ # tests the use of dup in url_for
+ def test_url_for_with_no_side_effects
+ # without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
+ original_options = {:controller => 'projects', :action => 'status'}
+ options = original_options.dup
+
+ url_for options
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
+ # tests the arguments modification free version of define_hash_access
+ def test_named_route_with_no_side_effects
+ original_options = { :host => 'test.host' }
+ options = original_options.dup
+
+ profile_customer_url("customer_model", options)
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
def test_projects_status
with_test_routes do
assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
@@ -1442,6 +1472,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_nested_optional_path_shorthand
+ with_test_routes do
+ get '/registrations/new'
+ assert @request.params[:locale].nil?
+
+ get '/en/registrations/new'
+ assert 'en', @request.params[:locale]
+ end
+ end
+
def test_default_params
with_test_routes do
get '/inline_pages'
@@ -2229,6 +2269,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
verify_redirect 'http://www.example.com/countries/all/cities'
end
+ def test_constraints_block_not_carried_to_following_routes
+ get '/italians/writers'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/sculptors'
+ assert_equal 'italians#sculptors', @response.body
+
+ get '/italians/painters/botticelli'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/painters/michelangelo'
+ assert_equal 'italians#painters', @response.body
+ end
+
def test_custom_resource_actions_defined_using_string
get '/customers/inactive'
assert_equal 'customers#inactive', @response.body
@@ -2474,3 +2528,40 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
end
end
end
+
+class TestUriPathEscaping < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ match '/:segment' => lambda { |env|
+ path_params = env['action_dispatch.request.path_parameters']
+ [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]]
+ }, :as => :segment
+
+ match '/*splat' => lambda { |env|
+ path_params = env['action_dispatch.request.path_parameters']
+ [200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]]
+ }, :as => :splat
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'escapes generated path segment' do
+ assert_equal '/a%20b/c+d', segment_path(:segment => 'a b/c+d')
+ end
+
+ test 'unescapes recognized path segment' do
+ get '/a%20b%2Fc+d'
+ assert_equal 'a b/c+d', @response.body
+ end
+
+ test 'escapes generated path splat' do
+ assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d')
+ end
+
+ test 'unescapes recognized path splat' do
+ get '/a%20b/c+d'
+ assert_equal 'a b/c+d', @response.body
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
new file mode 100644
index 0000000000..73e056de23
--- /dev/null
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -0,0 +1,181 @@
+require 'abstract_unit'
+
+class CacheStoreTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ head :ok
+ end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "#{request.session_options[:id]}"
+ end
+
+ def call_reset_session
+ session[:bar]
+ reset_session
+ session[:bar] = "baz"
+ head :ok
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ def test_setting_and_getting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ end
+
+ def test_getting_nil_session_value
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_not_equal session_id, response.body
+ end
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
+ end
+ end
+
+ def test_prevents_session_fixation
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ session_id = cookies['_session_id']
+
+ reset!
+
+ get '/set_session_value', :_session_id => session_id
+ assert_response :success
+ assert_not_equal session_id, cookies['_session_id']
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => ::CacheStoreTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ cache = ActiveSupport::Cache::MemoryStore.new
+ middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => cache
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index b0efbcef4a..92df6967d6 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -5,8 +5,8 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = '_myapp_session'
SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
- Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1')
- SignedBar = Verifier.generate(:foo => "bar", :session_id => ActiveSupport::SecureRandom.hex(16))
+ Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
+ SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
class TestController < ActionController::Base
def no_session_access
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
index 31ce97a25b..904398f563 100644
--- a/actionpack/test/dispatch/session/test_session_test.rb
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -29,7 +29,6 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase
end
def test_clear_emptys_session
- params = {:one => 'one', :two => 'two'}
session = ActionController::TestSession.new({:one => 'one', :two => 'two'})
session.clear
assert_nil(session[:one])
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index 81a8c24525..4ee1d61146 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -34,12 +34,29 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal({}, req.cookies)
assert_equal nil, req.env["HTTP_COOKIE"]
- req.cookies["user_name"] = "david"
- assert_equal({"user_name" => "david"}, req.cookies)
- assert_equal "user_name=david", req.env["HTTP_COOKIE"]
+ req.cookie_jar["user_name"] = "david"
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
- req.cookies["login"] = "XJ-122"
- assert_equal({"user_name" => "david", "login" => "XJ-122"}, req.cookies)
- assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; /).sort
+ req.cookie_jar["login"] = "XJ-122"
+ assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar)
+
+ assert_nothing_raised do
+ req.cookie_jar["login"] = nil
+ assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar)
+ end
+
+ req.cookie_jar.delete(:login)
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
+
+ req.cookie_jar.clear
+ assert_cookies({}, req.cookie_jar)
+
+ req.cookie_jar.update(:user_name => "david")
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
end
+
+ private
+ def assert_cookies(expected, cookie_jar)
+ assert_equal(expected, cookie_jar.instance_variable_get("@cookies"))
+ end
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index e2a7f1bad7..7e4a1519fb 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -12,6 +12,13 @@ module ActionDispatch
uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
assert_equal 'foo', uf.original_filename
end
+
+ if "ruby".encoding_aware?
+ def test_filename_should_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
+ end
+ end
def test_content_type
uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new)
diff --git a/actionpack/test/fixtures/comments/empty.de.html.erb b/actionpack/test/fixtures/comments/empty.de.html.erb
new file mode 100644
index 0000000000..cffd90dd26
--- /dev/null
+++ b/actionpack/test/fixtures/comments/empty.de.html.erb
@@ -0,0 +1 @@
+<h1>Kein Kommentar</h1> \ No newline at end of file
diff --git a/actionpack/test/fixtures/comments/empty.html.builder b/actionpack/test/fixtures/comments/empty.html.builder
new file mode 100644
index 0000000000..2b0c7207a3
--- /dev/null
+++ b/actionpack/test/fixtures/comments/empty.html.builder
@@ -0,0 +1 @@
+xml.h1 'No Comment' \ No newline at end of file
diff --git a/actionpack/test/fixtures/comments/empty.html.erb b/actionpack/test/fixtures/comments/empty.html.erb
new file mode 100644
index 0000000000..827f3861de
--- /dev/null
+++ b/actionpack/test/fixtures/comments/empty.html.erb
@@ -0,0 +1 @@
+<h1>No Comment</h1> \ No newline at end of file
diff --git a/actionpack/test/fixtures/comments/empty.xml.erb b/actionpack/test/fixtures/comments/empty.xml.erb
new file mode 100644
index 0000000000..db1027cd7d
--- /dev/null
+++ b/actionpack/test/fixtures/comments/empty.xml.erb
@@ -0,0 +1 @@
+<error>No Comment</error> \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
index c479adb897..fa5e6bd318 100644
--- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
@@ -1,2 +1,3 @@
Hello
<%= cache do %>This bit's fragment cached<% end %>
+<%= 'Ciao' %>
diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.erb b/actionpack/test/fixtures/respond_with/using_resource.js.erb
new file mode 100644
index 0000000000..4417680bce
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_resource.js.erb
@@ -0,0 +1 @@
+alert("Hi"); \ No newline at end of file
diff --git a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css
new file mode 100644
index 0000000000..bfb90bfa48
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css
@@ -0,0 +1 @@
+/* Different from other style.css */ \ No newline at end of file
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/application.js b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
index e69de29bb2..e611d2b129 100644
--- a/actionpack/test/fixtures/sprockets/app/javascripts/application.js
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
@@ -0,0 +1 @@
+//= require xmlhr
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
index e69de29bb2..2365eaa4cd 100644
--- a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
@@ -0,0 +1 @@
+/*= require style */
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css
diff --git a/actionpack/test/fixtures/test/_200.html.erb b/actionpack/test/fixtures/test/_200.html.erb
new file mode 100644
index 0000000000..c9f45675dc
--- /dev/null
+++ b/actionpack/test/fixtures/test/_200.html.erb
@@ -0,0 +1 @@
+<h1>Invalid partial</h1>
diff --git a/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb b/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb
index 5db0822f07..820e7db789 100644
--- a/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb
+++ b/actionpack/test/fixtures/test/_layout_with_partial_and_yield.html.erb
@@ -1,4 +1,4 @@
Before
-<%= render :partial => "test/partial.html.erb" %>
+<%= render :partial => "test/partial" %>
<%= yield %>
After
diff --git a/actionpack/test/fixtures/test/deprecated_nested_layout.erb b/actionpack/test/fixtures/test/deprecated_nested_layout.erb
deleted file mode 100644
index 7b6dcbb6c7..0000000000
--- a/actionpack/test/fixtures/test/deprecated_nested_layout.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<% content_for :title, "title" -%>
-<% content_for :column do -%>column<% end -%>
-<% render :layout => 'layouts/column' do -%>content<% end -%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello,world.erb b/actionpack/test/fixtures/test/hello,world.erb
new file mode 100644
index 0000000000..bc8fa5e0ca
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello,world.erb
@@ -0,0 +1 @@
+Hello w*rld! \ No newline at end of file
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 67baf369e2..363403092b 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -48,26 +48,19 @@ module Quiz
end
end
-class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
+class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
extend ActiveModel::Translation
alias_method :secret?, :secret
+ alias_method :persisted?, :persisted
def initialize(*args)
super
@persisted = false
end
- def persisted=(boolean)
- @persisted = boolean
- end
-
- def persisted?
- @persisted
- end
-
attr_accessor :author
def author_attributes=(attributes); end
@@ -170,6 +163,17 @@ class Author < Comment
def post_attributes=(attributes); end
end
+class HashBackedAuthor < Hash
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def persisted?; false; end
+
+ def name
+ "hash backed author"
+ end
+end
+
module Blog
def self._railtie
self
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 2abc806e97..aa7304b3ed 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -1,3 +1,4 @@
+require 'zlib'
require 'abstract_unit'
require 'active_support/ordered_options'
@@ -365,6 +366,10 @@ class AssetTagHelperTest < ActionView::TestCase
assert stylesheet_link_tag('dir/other/file', 'dir/file2').html_safe?
end
+ def test_stylesheet_link_tag_escapes_options
+ assert_dom_equal %(<link href="/file.css" media="&lt;script&gt;" rel="stylesheet" type="text/css" />), stylesheet_link_tag('/file', :media => '<script>')
+ end
+
def test_custom_stylesheet_expansions
ENV["RAILS_ASSET_ID"] = ''
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"]
@@ -424,6 +429,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_image_alt
+ [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix|
+ assert_equal 'Rails', image_alt("#{prefix}rails.png")
+ assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png")
+ assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png")
+ end
+ end
+
def test_image_tag
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -434,7 +447,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_image_tag_windows_behaviour
old_asset_id, ENV["RAILS_ASSET_ID"] = ENV["RAILS_ASSET_ID"], "1"
- # This simulates the behaviour of File#exist? on windows when testing a file ending in "."
+ # This simulates the behavior of File#exist? on windows when testing a file ending in "."
# If the file "rails.png" exists, windows will return true when asked if "rails.png." exists (notice trailing ".")
# OS X, linux etc will return false in this case.
File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true)
@@ -472,7 +485,7 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_timebased_asset_id
- expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
+ expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
@@ -503,7 +516,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_timebased_asset_id_with_relative_url_root
@controller.config.relative_url_root = "/collaboration/hieraki"
- expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
+ expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
@@ -681,9 +694,9 @@ class AssetTagHelperTest < ActionView::TestCase
@controller.config.asset_host = 'http://a%d.example.com'
config.perform_caching = true
- hash = '/javascripts/cache/money.js'.hash % 4
+ number = Zlib.crc32('/javascripts/cache/money.js') % 4
assert_dom_equal(
- %(<script src="http://a#{hash}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
+ %(<script src="http://a#{number}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => "cache/money")
)
@@ -854,7 +867,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_caching_stylesheet_link_tag_when_caching_on
ENV["RAILS_ASSET_ID"] = ""
- @controller.config.asset_host = 'http://a0.example.com'
+ @controller.config.asset_host = 'a0.example.com'
config.perform_caching = true
assert_dom_equal(
@@ -964,7 +977,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host
ENV["RAILS_ASSET_ID"] = ""
- @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" }
+ @controller.config.asset_host = Proc.new { |source| "a#{source.length}.example.com" }
config.perform_caching = true
assert_equal '/stylesheets/styles.css'.length, 23
@@ -1091,13 +1104,23 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
end
def test_should_compute_proper_path_with_asset_host
- @controller.config.asset_host = "http://assets.example.com"
+ @controller.config.asset_host = "assets.example.com"
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
- assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
- assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
- assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
+ assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
+ assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
+ end
+
+ def test_should_compute_proper_path_with_asset_host_and_default_protocol
+ @controller.config.asset_host = "assets.example.com"
+ @controller.config.default_asset_host_protocol = :request
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
+ assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
+ assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_ignore_asset_host_on_complete_url
@@ -1115,12 +1138,12 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png'))
end
- def test_asset_host_without_protocol_should_use_request_protocol
+ def test_asset_host_without_protocol_should_be_protocol_relative
@controller.config.asset_host = 'a.example.com'
assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
end
- def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present
+ def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present
@controller.config.asset_host = 'a.example.com/files/go/here'
assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
end
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index a9157e711c..13e2d5b595 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -46,6 +46,42 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal "bar", content_for(:bar)
end
+ def test_content_for_with_multiple_calls
+ assert ! content_for?(:title)
+ content_for :title, 'foo'
+ content_for :title, 'bar'
+ assert_equal 'foobar', content_for(:title)
+ end
+
+ def test_content_for_with_block
+ assert ! content_for?(:title)
+ content_for :title do
+ output_buffer << 'foo'
+ output_buffer << 'bar'
+ nil
+ end
+ assert_equal 'foobar', content_for(:title)
+ end
+
+ def test_content_for_with_whitespace_block
+ assert ! content_for?(:title)
+ content_for :title, 'foo'
+ content_for :title do
+ output_buffer << " \n "
+ nil
+ end
+ content_for :title, 'bar'
+ assert_equal 'foobar', content_for(:title)
+ end
+
+ def test_content_for_returns_nil_when_writing
+ assert ! content_for?(:title)
+ assert_equal nil, content_for(:title, 'foo')
+ assert_equal nil, content_for(:title) { output_buffer << 'bar'; nil }
+ assert_equal nil, content_for(:title) { output_buffer << " \n "; nil }
+ assert_equal 'foobar', content_for(:title)
+ end
+
def test_content_for_question_mark
assert ! content_for?(:title)
content_for :title, 'title'
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 3f31edd5ce..8fc78283d8 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -10,24 +10,24 @@ class CompiledTemplatesTest < Test::Unit::TestCase
end
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
- assert_equal "one", render(:file => "test/render_file_with_locals_and_default.erb")
- assert_equal "two", render(:file => "test/render_file_with_locals_and_default.erb", :locals => { :secret => "two" })
+ assert_equal "one", render(:file => "test/render_file_with_locals_and_default")
+ assert_equal "two", render(:file => "test/render_file_with_locals_and_default", :locals => { :secret => "two" })
end
def test_template_changes_are_not_reflected_with_cached_templates
- assert_equal "Hello world!", render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", render(:file => "test/hello_world")
modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Hello world!", render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", render(:file => "test/hello_world")
end
- assert_equal "Hello world!", render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", render(:file => "test/hello_world")
end
def test_template_changes_are_reflected_with_uncached_templates
- assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", render_without_cache(:file => "test/hello_world")
modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Goodbye world!", render_without_cache(:file => "test/hello_world.erb")
+ assert_equal "Goodbye world!", render_without_cache(:file => "test/hello_world")
end
- assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", render_without_cache(:file => "test/hello_world")
end
private
@@ -42,7 +42,7 @@ class CompiledTemplatesTest < Test::Unit::TestCase
def render_without_cache(*args)
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
ActionView::Base.new(view_paths, {}).render(*args)
end
diff --git a/actionpack/test/template/compressors_test.rb b/actionpack/test/template/compressors_test.rb
new file mode 100644
index 0000000000..a273f15bd7
--- /dev/null
+++ b/actionpack/test/template/compressors_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+require 'sprockets/compressors'
+
+class CompressorsTest < ActiveSupport::TestCase
+ def test_register_css_compressor
+ Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor)
+ compressor = Sprockets::Compressors.registered_css_compressor(:null)
+ assert_kind_of Sprockets::NullCompressor, compressor
+ end
+
+ def test_register_js_compressor
+ Sprockets::Compressors.register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier')
+ compressor = Sprockets::Compressors.registered_js_compressor(:uglifier)
+ assert_kind_of Uglifier, compressor
+ end
+
+ def test_register_default_css_compressor
+ Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor, :default => true)
+ compressor = Sprockets::Compressors.registered_css_compressor(:default)
+ assert_kind_of Sprockets::NullCompressor, compressor
+ end
+
+ def test_register_default_js_compressor
+ Sprockets::Compressors.register_js_compressor(:null, Sprockets::NullCompressor, :default => true)
+ compressor = Sprockets::Compressors.registered_js_compressor(:default)
+ assert_kind_of Sprockets::NullCompressor, compressor
+ end
+end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 09c53a36f0..af30ec9892 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -664,6 +664,15 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
end
+ def test_select_date_with_too_big_range_between_start_year_and_end_year
+ assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), :start_year => 2000, :end_year => 20000, :prefix => "date[first]", :order => [:month, :day, :year]) }
+ assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), :start_year => Date.today.year - 100.years, :end_year => 2000, :prefix => "date[first]", :order => [:month, :day, :year]) }
+ end
+
+ def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter
+ assert_nothing_raised { select_date(Time.mktime(2003, 8, 16), :start_year => 2000, :end_year => 3100, :max_years_allowed => 2000) }
+ end
+
def test_select_date_with_order
expected = %(<select id="date_first_month" 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)
diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb
index 30f6d1a213..790ab1c74c 100644
--- a/actionpack/test/template/erb_util_test.rb
+++ b/actionpack/test/template/erb_util_test.rb
@@ -16,6 +16,16 @@ class ErbUtilTest < Test::Unit::TestCase
end
end
+ def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings
+ value = json_escape("asdf")
+ assert !value.html_safe?
+ end
+
+ def test_json_escape_returns_safe_strings_when_passed_safe_strings
+ value = json_escape("asdf".html_safe)
+ assert value.html_safe?
+ end
+
def test_html_escape_is_html_safe
escaped = h("<p>")
assert_equal "&lt;p&gt;", escaped
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 286bfb4d04..e36d032f6c 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -696,13 +696,12 @@ class FormHelperTest < ActionView::TestCase
concat f.submit('Edit post')
end
- expected =
- "<form accept-charset='UTF-8' action='/posts/44' method='post'>" +
- snowman +
- "<label for='post_title'>The Title</label>" +
+ expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do
"<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
- "<input name='commit' id='post_submit' type='submit' value='Edit post' />" +
- "</form>"
+ "<input name='commit' type='submit' value='Edit post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
end
def test_form_for_with_symbol_object_name
@@ -791,6 +790,23 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_remote_in_html
+ form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :put }) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_remote_without_html
@post.persisted = false
form_for(@post, :remote => true) do |f|
@@ -1564,6 +1580,22 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_for_with_hash_like_model
+ @author = HashBackedAuthor.new
+
+ form_for(@post) do |f|
+ concat f.fields_for(:author, @author) { |af|
+ concat af.text_field(:name)
+ }
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_fields_for
output_buffer = fields_for(:post, @post) do |f|
concat f.text_field(:title)
@@ -1765,7 +1797,7 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- def snowman(method = nil)
+ def hidden_fields(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
if method && !method.to_s.in?(['get', 'post'])
@@ -1793,7 +1825,7 @@ class FormHelperTest < ActionView::TestCase
method = options
end
- form_text(action, id, html_class, remote, multipart, method) + snowman(method) + contents + "</form>"
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
end
def test_default_form_builder
@@ -1858,6 +1890,17 @@ class FormHelperTest < ActionView::TestCase
assert_equal LabelledFormBuilder, klass
end
+ def test_form_for_with_labelled_builder_path
+ path = nil
+
+ form_for(@post, :builder => LabelledFormBuilder) do |f|
+ path = f.to_partial_path
+ ''
+ end
+
+ assert_equal 'labelled_form', path
+ end
+
class LabelledFormBuilderSubclass < LabelledFormBuilder; end
def test_form_for_with_labelled_builder_with_nested_fields_for_with_custom_builder
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index b92e1d9890..469718e1bd 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -378,6 +378,49 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_without_multiple
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"></select>",
+ select(:post, :category, "", {}, :multiple => false)
+ )
+ end
+
+ def test_select_with_grouped_collection_as_nested_array
+ @post = Post.new
+
+ countries_by_continent = [
+ ["<Africa>", [["<South Africa>", "<sa>"], ["Somalia", "so"]]],
+ ["Europe", [["Denmark", "dk"], ["Ireland", "ie"]]],
+ ]
+
+ assert_dom_equal(
+ [
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>},
+ %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>},
+ %Q{<option value="ie">Ireland</option></optgroup></select>},
+ ].join("\n"),
+ select("post", "origin", countries_by_continent)
+ )
+ end
+
+ def test_select_with_grouped_collection_as_hash
+ @post = Post.new
+
+ countries_by_continent = {
+ "<Africa>" => [["<South Africa>", "<sa>"], ["Somalia", "so"]],
+ "Europe" => [["Denmark", "dk"], ["Ireland", "ie"]],
+ }
+
+ assert_dom_equal(
+ [
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>},
+ %Q{<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk">Denmark</option>},
+ %Q{<option value="ie">Ireland</option></optgroup></select>},
+ ].join("\n"),
+ select("post", "origin", countries_by_continent)
+ )
+ end
+
def test_select_with_boolean_method
@post = Post.new
@post.allow_comments = false
@@ -457,6 +500,22 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_multiple_to_add_hidden_input
+ output_buffer = select(:post, :category, "", {}, :multiple => true)
+ assert_dom_equal(
+ "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
+ def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
+ output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true)
+ assert_dom_equal(
+ "<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_blank
@post = Post.new
@post.category = "<mus>"
@@ -528,6 +587,24 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_empty
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n</select>",
+ select("post", "category", [], :prompt => true, :include_blank => true)
+ )
+ end
+
+ def test_list_of_lists
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"number\">Number</option>\n<option value=\"text\">Text</option>\n<option value=\"boolean\">Yes/No</option></select>",
+ select("post", "category", [["Number", "number"], ["Text", "text"], ["Yes/No", "boolean"]], :prompt => true, :include_blank => true)
+ )
+ end
+
def test_select_with_selected_value
@post = Post.new
@post.category = "<mus>"
@@ -649,11 +726,11 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_collection_select_with_multiple_option_appends_array_brackets
+ def test_collection_select_with_multiple_option_appends_array_brackets_and_hidden_input
@post = Post.new
@post.author_name = "Babe"
- expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></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>"
+ expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></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>"
# Should suffix default name with [].
assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index f95308b847..6eae9bf846 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -9,7 +9,7 @@ class FormTagHelperTest < ActionView::TestCase
@controller = BasicController.new
end
- def snowman(options = {})
+ def hidden_fields(options = {})
method = options[:method]
txt = %{<div style="margin:0;padding:0;display:inline">}
@@ -34,7 +34,7 @@ class FormTagHelperTest < ActionView::TestCase
end
def whole_form(action = "http://www.example.com", options = {})
- out = form_text(action, options) + snowman(options)
+ out = form_text(action, options) + hidden_fields(options)
if block_given?
out << yield << "</form>"
@@ -505,6 +505,36 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<fieldset class="format">Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
end
+
+ def test_text_area_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ text_area_tag "body", "hello world", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_submit_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ submit_tag "submit value", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_button_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ button_tag "button value", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_image_submit_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ image_submit_tag "submit source", options
+ assert_equal options, { :option => "random_option" }
+ end
+
+ def test_image_label_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ label_tag "submit source", "title", options
+ assert_equal options, { :option => "random_option" }
+ end
def protect_against_forgery?
false
diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionpack/test/template/html-scanner/document_test.rb
index ddfb351595..3db2fba783 100644
--- a/actionpack/test/template/html-scanner/document_test.rb
+++ b/actionpack/test/template/html-scanner/document_test.rb
@@ -123,7 +123,7 @@ HTML
def test_parse_invalid_document
assert_nothing_raised do
- doc = HTML::Document.new("<html>
+ HTML::Document.new("<html>
<table>
<tr>
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
@@ -135,7 +135,7 @@ HTML
def test_invalid_document_raises_exception_when_strict
assert_raise RuntimeError do
- doc = HTML::Document.new("<html>
+ HTML::Document.new("<html>
<table>
<tr>
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index 678cb9eeeb..62ad6be680 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -5,6 +5,13 @@ class SanitizerTest < ActionController::TestCase
@sanitizer = nil # used by assert_sanitizer
end
+ def test_strip_tags_with_quote
+ sanitizer = HTML::FullSanitizer.new
+ string = '<" <img src="trollface.gif" onload="alert(1)"> hi'
+
+ assert_equal ' hi', sanitizer.sanitize(string)
+ end
+
def test_strip_tags
sanitizer = HTML::FullSanitizer.new
assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
diff --git a/actionpack/test/template/html-scanner/tag_node_test.rb b/actionpack/test/template/html-scanner/tag_node_test.rb
index 0d87f1bd42..3b72243e7d 100644
--- a/actionpack/test/template/html-scanner/tag_node_test.rb
+++ b/actionpack/test/template/html-scanner/tag_node_test.rb
@@ -55,7 +55,12 @@ class TagNodeTest < Test::Unit::TestCase
def test_to_s
node = tag("<a b=c d='f' g=\"h 'i'\" />")
- assert_equal %(<a b="c" d="f" g="h 'i'" />), node.to_s
+ node = node.to_s
+ assert node.include?('a')
+ assert node.include?('b="c"')
+ assert node.include?('d="f"')
+ assert node.include?('g="h')
+ assert node.include?('i')
end
def test_tag
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 538e0e9874..4b9c3c97b1 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/core_ext/string/encoding'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
@@ -27,9 +28,23 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
+ if "ruby".encoding_aware?
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!)
+ else
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline))
+ end
assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
+ def test_escape_javascript_with_safebuffer
+ given = %('quoted' "double-quoted" new-line:\n </closed>)
+ expect = %(\\'quoted\\' \\"double-quoted\\" new-line:\\n <\\/closed>)
+ assert_equal expect, escape_javascript(given)
+ assert_equal expect, escape_javascript(ActiveSupport::SafeBuffer.new(given))
+ assert_instance_of String, escape_javascript(given)
+ assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given))
+ end
+
def test_button_to_function
assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
button_to_function("Greeting", "alert('Hello world!')")
diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb
index 50e1cccd3b..752b0f23a8 100644
--- a/actionpack/test/template/log_subscriber_test.rb
+++ b/actionpack/test/template/log_subscriber_test.rb
@@ -27,7 +27,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
def test_render_file_template
- @view.render(:file => "test/hello_world.erb")
+ @view.render(:file => "test/hello_world")
wait
assert_equal 1, @logger.logged(:info).size
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 47b70f05ab..bac2530e3d 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -31,16 +31,6 @@ class LookupContextTest < ActiveSupport::TestCase
assert @lookup_context.formats.frozen?
end
- test "allows me to change some details to execute an specific block of code" do
- formats = Mime::SET
- @lookup_context.update_details(:locale => :pt) do
- assert_equal formats, @lookup_context.formats
- assert_equal :pt, @lookup_context.locale
- end
- assert_equal formats, @lookup_context.formats
- assert_equal :en, @lookup_context.locale
- end
-
test "provides getters and setters for formats" do
@lookup_context.formats = [:html]
assert_equal [:html], @lookup_context.formats
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 63b92aadf4..8d679aac1d 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -19,15 +19,6 @@ class NumberHelperTest < ActionView::TestCase
gigabytes(number) * 1024
end
- def silence_deprecation_warnings
- @old_deprecatios_silenced = ActiveSupport::Deprecation.silenced
- ActiveSupport::Deprecation.silenced = true
- end
-
- def restore_deprecation_warnings
- ActiveSupport::Deprecation.silenced = @old_deprecatios_silenced
- end
-
def test_number_to_phone
assert_equal("555-1234", number_to_phone(5551234))
assert_equal("800-555-1212", number_to_phone(8005551212))
@@ -36,6 +27,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "}))
assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123}))
assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " "))
+ assert_equal("555.1212", number_to_phone(5551212, :delimiter => '.'))
assert_equal("800-555-1212", number_to_phone("8005551212"))
assert_equal("+1-800-555-1212", number_to_phone(8005551212, :country_code => 1))
assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
@@ -292,33 +284,40 @@ class NumberHelperTest < ActionView::TestCase
assert number_to_human(1).html_safe?
assert !number_to_human("<script></script>").html_safe?
assert number_to_human("asdf".html_safe).html_safe?
+ assert number_to_human("1".html_safe).html_safe?
assert number_to_human_size(1).html_safe?
assert number_to_human_size(1000000).html_safe?
assert !number_to_human_size("<script></script>").html_safe?
assert number_to_human_size("asdf".html_safe).html_safe?
+ assert number_to_human_size("1".html_safe).html_safe?
assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe?
assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe?
assert !number_with_precision("<script></script>").html_safe?
assert number_with_precision("asdf".html_safe).html_safe?
+ assert number_with_precision("1".html_safe).html_safe?
assert number_to_currency(1).html_safe?
assert !number_to_currency("<script></script>").html_safe?
assert number_to_currency("asdf".html_safe).html_safe?
+ assert number_to_currency("1".html_safe).html_safe?
assert number_to_percentage(1).html_safe?
assert !number_to_percentage("<script></script>").html_safe?
assert number_to_percentage("asdf".html_safe).html_safe?
+ assert number_to_percentage("1".html_safe).html_safe?
assert number_to_phone(1).html_safe?
assert_equal "&lt;script&gt;&lt;/script&gt;", number_to_phone("<script></script>")
assert number_to_phone("<script></script>").html_safe?
assert number_to_phone("asdf".html_safe).html_safe?
+ assert number_to_phone("1".html_safe).html_safe?
assert number_with_delimiter(1).html_safe?
assert !number_with_delimiter("<script></script>").html_safe?
assert number_with_delimiter("asdf".html_safe).html_safe?
+ assert number_with_delimiter("1".html_safe).html_safe?
end
def test_number_helpers_should_raise_error_if_invalid_when_specified
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 74d7bba4fe..7f23629e05 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -4,11 +4,20 @@ require 'controller/fake_models'
class Post
extend ActiveModel::Naming
include ActiveModel::Conversion
+ attr_writer :id, :body
+
+ def initialize
+ @id = nil
+ @body = nil
+ super
+ end
+
def id
- 45
+ @id || 45
end
+
def body
- super || "What a wonderful world!"
+ super || @body || "What a wonderful world!"
end
end
@@ -48,16 +57,42 @@ class RecordTagHelperTest < ActionView::TestCase
end
def test_block_works_with_content_tag_for_in_erb
- __in_erb_template = ''
expected = %(<tr class="post" id="post_45">#{@post.body}</tr>)
actual = content_tag_for(:tr, @post) { concat @post.body }
assert_dom_equal expected, actual
end
def test_div_for_in_erb
- __in_erb_template = ''
expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
actual = div_for(@post, :class => "bar") { concat @post.body }
assert_dom_equal expected, actual
end
+
+ def test_content_tag_for_collection
+ post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ expected = %(<li class="post" id="post_101">Hello!</li>\n<li class="post" id="post_102">World!</li>)
+ actual = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ assert_dom_equal expected, actual
+ end
+
+ def test_div_for_collection
+ post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ expected = %(<div class="post" id="post_101">Hello!</div>\n<div class="post" id="post_102">World!</div>)
+ actual = div_for([post_1, post_2]) { |post| concat post.body }
+ assert_dom_equal expected, actual
+ end
+
+ def test_content_tag_for_single_record_is_html_safe
+ result = div_for(@post, :class => "bar") { concat @post.body }
+ assert result.html_safe?
+ end
+
+ def test_content_tag_for_collection_is_html_safe
+ post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
+ post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
+ result = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ assert result.html_safe?
+ end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 86d08a43a5..77659918f7 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -21,18 +21,48 @@ module RenderTestCases
end
def test_render_file
- assert_equal "Hello world!", @view.render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
def test_render_file_not_using_full_path
- assert_equal "Hello world!", @view.render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
def test_render_file_without_specific_extension
assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
- def test_render_file_with_localization
+ # Test if :formats, :locale etc. options are passed correctly to the resolvers.
+ def test_render_file_with_format
+ assert_match "<h1>No Comment</h1>", @view.render(:file => "comments/empty", :formats => [:html])
+ assert_match "<error>No Comment</error>", @view.render(:file => "comments/empty", :formats => [:xml])
+ assert_match "<error>No Comment</error>", @view.render(:file => "comments/empty", :formats => :xml)
+ end
+
+ def test_render_template_with_format
+ assert_match "<h1>No Comment</h1>", @view.render(:template => "comments/empty", :formats => [:html])
+ assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml])
+ end
+
+ def test_render_file_with_locale
+ assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de])
+ assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de)
+ end
+
+ def test_render_template_with_locale
+ assert_equal "<h1>Kein Kommentar</h1>", @view.render(:template => "comments/empty", :locale => [:de])
+ end
+
+ def test_render_file_with_handlers
+ assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => [:builder])
+ assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => :builder)
+ end
+
+ def test_render_template_with_handlers
+ assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder])
+ end
+
+ def test_render_file_with_localization_on_context_level
old_locale, @view.locale = @view.locale, :da
assert_equal "Hey verden", @view.render(:file => "test/hello_world")
ensure
@@ -51,17 +81,17 @@ module RenderTestCases
end
def test_render_file_with_full_path
- template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world.erb')
+ template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world')
assert_equal "Hello world!", @view.render(:file => template_path)
end
def test_render_file_with_instance_variables
- assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_ivar.erb")
+ assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_ivar")
end
def test_render_file_with_locals
locals = { :secret => 'in the sauce' }
- assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_locals.erb", :locals => locals)
+ assert_equal "The secret is in the sauce\n", @view.render(:file => "test/render_file_with_locals", :locals => locals)
end
def test_render_file_not_using_full_path_with_dot_in_path
@@ -80,13 +110,18 @@ module RenderTestCases
assert_equal 'partial html', @view.render(:partial => 'test/partial')
end
+ def test_render_partial_with_selected_format
+ assert_equal 'partial html', @view.render(:partial => 'test/partial', :formats => :html)
+ assert_equal 'partial js', @view.render(:partial => 'test/partial', :formats => [:js])
+ end
+
def test_render_partial_at_top_level
- # file fixtures/_top_level_partial_only.erb (not fixtures/test)
+ # file fixtures/_top_level_partial_only (not fixtures/test)
assert_equal 'top level partial', @view.render(:partial => '/top_level_partial_only')
end
def test_render_partial_with_format_at_top_level
- # file fixtures/_top_level_partial.html.erb (not fixtures/test, with format extension)
+ # file fixtures/_top_level_partial.html (not fixtures/test, with format extension)
assert_equal 'top level partial html', @view.render(:partial => '/top_level_partial')
end
@@ -98,6 +133,15 @@ module RenderTestCases
assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5)
end
+ def test_render_partial_with_invalid_name
+ @view.render(:partial => "test/200")
+ flunk "Render did not raise ArgumentError"
+ rescue ArgumentError => e
+ assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a letter or underscore, " +
+ "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ end
+
def test_render_partial_with_errors
@view.render(:partial => "test/raise")
flunk "Render did not raise Template::Error"
@@ -192,6 +236,36 @@ module RenderTestCases
@controller_view.render(customers, :greeting => "Hello")
end
+ class CustomerWithDeprecatedPartialPath
+ attr_reader :name
+
+ def self.model_name
+ Struct.new(:partial_path).new("customers/customer")
+ end
+
+ def initialize(name)
+ @name = name
+ end
+ end
+
+ def test_render_partial_using_object_with_deprecated_partial_path
+ assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
+ assert_equal "Hello: nertzy",
+ @controller_view.render(CustomerWithDeprecatedPartialPath.new("nertzy"), :greeting => "Hello")
+ end
+ end
+
+ def test_render_partial_using_collection_with_deprecated_partial_path
+ assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
+ customers = [
+ CustomerWithDeprecatedPartialPath.new("nertzy"),
+ CustomerWithDeprecatedPartialPath.new("peeja")
+ ]
+ assert_equal "Hello: nertzyHello: peeja",
+ @controller_view.render(customers, :greeting => "Hello")
+ end
+ end
+
# TODO: The reason for this test is unclear, improve documentation
def test_render_partial_and_fallback_to_layout
assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
@@ -206,7 +280,7 @@ module RenderTestCases
end
def test_render_layout_with_block_and_other_partial_inside
- render = @view.render(:layout => "test/layout_with_partial_and_yield.html.erb") { "Yield!" }
+ render = @view.render(:layout => "test/layout_with_partial_and_yield") { "Yield!" }
assert_equal "Before\npartial html\nYield!\nAfter\n", render
end
@@ -243,24 +317,26 @@ module RenderTestCases
end
def test_render_ignores_templates_with_malformed_template_handlers
- %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
- assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
+ ActiveSupport::Deprecation.silence do
+ %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
+ assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
+ end
end
end
def test_render_with_layout
assert_equal %(<title></title>\nHello world!\n),
- @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield")
+ @view.render(:file => "test/hello_world", :layout => "layouts/yield")
end
def test_render_with_layout_which_has_render_inline
assert_equal %(welcome\nHello world!\n),
- @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside")
+ @view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_inline_inside")
end
def test_render_with_layout_which_renders_another_partial
assert_equal %(partial html\nHello world!\n),
- @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside")
+ @view.render(:file => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside")
end
def test_render_layout_with_block_and_yield
@@ -305,17 +381,17 @@ module RenderTestCases
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
- @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield")
+ @view.render(:file => "test/nested_layout", :layout => "layouts/yield")
end
def test_render_with_file_in_layout
assert_equal %(\n<title>title</title>\n\n),
- @view.render(:file => "test/layout_render_file.erb")
+ @view.render(:file => "test/layout_render_file")
end
def test_render_layout_with_object
assert_equal %(<title>David</title>),
- @view.render(:file => "test/layout_render_object.erb")
+ @view.render(:file => "test/layout_render_object")
end
end
@@ -341,7 +417,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
# is not eager loaded
def setup
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::Base.process_view_paths(path)
+ view_paths = ActionView::PathSet.new([path])
assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first
setup_view(view_paths)
end
@@ -353,7 +429,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
if '1.9'.respond_to?(:force_encoding)
def test_render_utf8_template_with_magic_comment
with_external_encoding Encoding::ASCII_8BIT do
- result = @view.render(:file => "test/utf8_magic.html.erb", :layouts => "layouts/yield")
+ result = @view.render(:file => "test/utf8_magic", :formats => [:html], :layouts => "layouts/yield")
assert_equal Encoding::UTF_8, result.encoding
assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
@@ -361,7 +437,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_default_external_encoding
with_external_encoding Encoding::UTF_8 do
- result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
+ result = @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield")
assert_equal Encoding::UTF_8, result.encoding
assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
@@ -370,7 +446,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_incompatible_external_encoding
with_external_encoding Encoding::SHIFT_JIS do
begin
- result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
+ @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield")
flunk 'Should have raised incompatible encoding error'
rescue ActionView::Template::Error => error
assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
@@ -381,7 +457,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_partial_with_incompatible_encoding
with_external_encoding Encoding::SHIFT_JIS do
begin
- result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield")
+ @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield")
flunk 'Should have raised incompatible encoding error'
rescue ActionView::Template::Error => error
assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
index 8d3be09a4f..db69f95130 100644
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ b/actionpack/test/template/sprockets_helper_test.rb
@@ -1,48 +1,66 @@
require 'abstract_unit'
require 'sprockets'
+require 'sprockets/helpers/rails_helper'
require 'mocha'
-module Rails; end
-
class SprocketsHelperTest < ActionView::TestCase
- tests ActionView::Helpers::SprocketsHelper
+ include Sprockets::Helpers::RailsHelper
attr_accessor :assets
+ class MockRequest
+ def protocol() 'http://' end
+ def ssl?() false end
+ def host_with_port() 'localhost' end
+ end
+
def setup
super
- @controller = BasicController.new
-
- @request = Class.new do
- def protocol() 'http://' end
- def ssl?() false end
- def host_with_port() 'localhost' end
- end.new
-
- @controller.request = @request
+ @controller = BasicController.new
+ @controller.request = MockRequest.new
@assets = Sprockets::Environment.new
- @assets.paths << FIXTURES.join("sprockets/app/javascripts")
- @assets.paths << FIXTURES.join("sprockets/app/stylesheets")
- @assets.paths << FIXTURES.join("sprockets/app/images")
+ @assets.append_path(FIXTURES.join("sprockets/app/javascripts"))
+ @assets.append_path(FIXTURES.join("sprockets/app/stylesheets"))
+ @assets.append_path(FIXTURES.join("sprockets/app/images"))
- application = Object.new
+ application = Struct.new(:config, :assets).new(config, @assets)
Rails.stubs(:application).returns(application)
- application.stubs(:config).returns(config)
- application.stubs(:assets).returns(@assets)
-
- config.perform_caching = true
+ @config = config
+ @config.perform_caching = true
+ @config.assets.digest = true
+ @config.assets.compile = true
end
def url_for(*args)
"http://www.example.com"
end
- test "asset path" do
- assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png",
+ def config
+ @controller ? @controller.config : @config
+ end
+
+ test "asset_path" do
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png", :digest => true)
+ assert_match %r{/assets/logo.png},
+ asset_path("logo.png", :digest => false)
+ end
+
+ test "custom_asset_path" do
+ @config.assets.prefix = '/s'
+ assert_match %r{/s/logo-[0-9a-f]+.png},
asset_path("logo.png")
+ assert_match %r{/s/logo-[0-9a-f]+.png},
+ asset_path("logo.png", :digest => true)
+ assert_match %r{/s/logo.png},
+ asset_path("logo.png", :digest => false)
+ end
+ test "asset_path with root relative assets" do
assert_equal "/images/logo",
asset_path("/images/logo")
assert_equal "/images/logo.gif",
@@ -50,70 +68,244 @@ class SprocketsHelperTest < ActionView::TestCase
assert_equal "/dir/audio",
asset_path("/dir/audio")
+ end
+ test "asset_path with absolute urls" do
assert_equal "http://www.example.com/video/play",
asset_path("http://www.example.com/video/play")
assert_equal "http://www.example.com/video/play.mp4",
asset_path("http://www.example.com/video/play.mp4")
end
- test "javascript path" do
- assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js",
- asset_path(:application, "js")
+ test "with a simple asset host the url should default to protocol relative" do
+ @controller.config.default_asset_host_protocol = :relative
+ @controller.config.asset_host = "assets-%d.example.com"
+ assert_match %r{^//assets-\d.example.com/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "with a simple asset host the url can be changed to use the request protocol" do
+ @controller.config.asset_host = "assets-%d.example.com"
+ @controller.config.default_asset_host_protocol = :request
+ assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "With a proc asset host that returns no protocol the url should be protocol relative" do
+ @controller.config.default_asset_host_protocol = :relative
+ @controller.config.asset_host = Proc.new do |asset|
+ "assets-999.example.com"
+ end
+ assert_match %r{^//assets-999.example.com/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "with a proc asset host that returns a protocol the url use it" do
+ @controller.config.asset_host = Proc.new do |asset|
+ "http://assets-999.example.com"
+ end
+ assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "stylesheets served with a controller in scope can access the request" do
+ config.asset_host = Proc.new do |asset, request|
+ assert_not_nil request
+ "http://assets-666.example.com"
+ end
+ assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "stylesheets served without a controller in scope cannot access the request" do
+ @controller = nil
+ @config.asset_host = Proc.new do |asset, request|
+ fail "This should not have been called."
+ end
+ assert_raises ActionController::RoutingError do
+ asset_path("logo.png")
+ end
+ end
+
+ test "image_tag" do
+ assert_dom_equal '<img alt="Xml" src="/assets/xml.png" />', image_tag("xml.png")
+ end
+
+ test "image_path" do
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ image_path("logo.png")
+
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ path_to_image("logo.png")
+ end
+
+ test "javascript_path" do
+ assert_match %r{/assets/application-[0-9a-f]+.js},
+ javascript_path("application.js")
+
+ assert_match %r{/assets/application-[0-9a-f]+.js},
+ path_to_javascript("application.js")
+ end
+
+ test "stylesheet_path" do
+ assert_match %r{/assets/application-[0-9a-f]+.css},
+ stylesheet_path("application.css")
+
+ assert_match %r{/assets/application-[0-9a-f]+.css},
+ path_to_stylesheet("application.css")
+ end
+
+ test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do
+ @controller = nil
+ @config.asset_host = "assets-%d.example.com"
+ @config.default_asset_host_protocol = :request
+ @config.perform_caching = true
+
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png")
+ end
+
+ test "asset path with relative url root" do
+ @controller.config.relative_url_root = "/collaboration/hieraki"
+ assert_equal "/collaboration/hieraki/images/logo.gif",
+ asset_path("/images/logo.gif")
+ end
+
+ test "asset path with relative url root when controller isn't present but relative_url_root is" do
+ @controller = nil
+ @config.relative_url_root = "/collaboration/hieraki"
+ assert_equal "/collaboration/hieraki/images/logo.gif",
+ asset_path("/images/logo.gif")
+ end
- assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
- asset_path("xmlhr", "js")
- assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js",
- asset_path("dir/xmlhr.js", "js")
+ test "javascript path through asset_path" do
+ assert_match %r{/assets/application-[0-9a-f]+.js},
+ asset_path(:application, :ext => "js")
+
+ assert_match %r{/assets/xmlhr-[0-9a-f]+.js},
+ asset_path("xmlhr", :ext => "js")
+ assert_match %r{/assets/dir/xmlhr-[0-9a-f]+.js},
+ asset_path("dir/xmlhr.js", :ext => "js")
assert_equal "/dir/xmlhr.js",
- asset_path("/dir/xmlhr", "js")
+ asset_path("/dir/xmlhr", :ext => "js")
assert_equal "http://www.example.com/js/xmlhr",
- asset_path("http://www.example.com/js/xmlhr", "js")
+ asset_path("http://www.example.com/js/xmlhr", :ext => "js")
assert_equal "http://www.example.com/js/xmlhr.js",
- asset_path("http://www.example.com/js/xmlhr.js", "js")
+ asset_path("http://www.example.com/js/xmlhr.js", :ext => "js")
end
test "javascript include tag" do
- assert_equal '<script src="/assets/application-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
- sprockets_javascript_include_tag(:application)
+ assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
+ javascript_include_tag(:application)
+ assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
+ javascript_include_tag(:application, :digest => true)
+ assert_match %r{<script src="/assets/application.js" type="text/javascript"></script>},
+ javascript_include_tag(:application, :digest => false)
- assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
- sprockets_javascript_include_tag("xmlhr")
- assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>',
- sprockets_javascript_include_tag("xmlhr.js")
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
+ javascript_include_tag("xmlhr")
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
+ javascript_include_tag("xmlhr.js")
assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>',
- sprockets_javascript_include_tag("http://www.example.com/xmlhr")
+ javascript_include_tag("http://www.example.com/xmlhr")
+
+ assert_match %r{<script src=\"/assets/xmlhr-[0-9a-f]+.js" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-[0-9a-f]+.js" type=\"text/javascript\"></script>},
+ javascript_include_tag("xmlhr", "extra")
+
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
+ javascript_include_tag(:application, :debug => true)
+
+ @config.assets.compile = true
+ @config.assets.debug = true
+ assert_match %r{<script src="/javascripts/application.js" type="text/javascript"></script>},
+ javascript_include_tag('/javascripts/application')
+ assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
+ javascript_include_tag(:application)
end
- test "stylesheet path" do
- assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", asset_path(:application, "css")
+ test "stylesheet path through asset_path" do
+ assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, :ext => "css")
- assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css")
- assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css")
- assert_equal "/dir/style.css", asset_path("/dir/style.css", "css")
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
+ assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", :ext => "css")
+ assert_equal "/dir/style.css", asset_path("/dir/style.css", :ext => "css")
assert_equal "http://www.example.com/css/style",
- asset_path("http://www.example.com/css/style", "css")
+ asset_path("http://www.example.com/css/style", :ext => "css")
assert_equal "http://www.example.com/css/style.css",
- asset_path("http://www.example.com/css/style.css", "css")
+ asset_path("http://www.example.com/css/style.css", :ext => "css")
end
test "stylesheet link tag" do
- assert_equal '<link href="/assets/application-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag(:application)
+ assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application)
+ assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :digest => true)
+ assert_match %r{<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :digest => false)
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag("style")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag("style.css")
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style")
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style.css")
assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag("http://www.example.com/style.css")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag("style", :media => "all")
- assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />',
- sprockets_stylesheet_link_tag("style", :media => "print")
+ stylesheet_link_tag("http://www.example.com/style.css")
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="all" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style", :media => "all")
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="print" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style", :media => "print")
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/extra-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag("style", "extra")
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :debug => true)
+
+ @config.assets.compile = true
+ @config.assets.debug = true
+ assert_match %r{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag('/stylesheets/application')
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application)
+
+ assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :media => "print")
+ end
+
+ test "alternate asset prefix" do
+ stubs(:asset_prefix).returns("/themes/test")
+ assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
+ end
+
+ test "alternate asset environment" do
+ assets = Sprockets::Environment.new
+ assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
+ stubs(:asset_environment).returns(assets)
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
+ end
+
+ test "alternate hash based on environment" do
+ assets = Sprockets::Environment.new
+ assets.version = 'development'
+ assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
+ stubs(:asset_environment).returns(assets)
+ dev_path = asset_path("style", :ext => "css")
+
+ assets.version = 'production'
+ prod_path = asset_path("style", :ext => "css")
+
+ assert_not_equal prod_path, dev_path
+ end
+
+ test "precedence of `config.digest = false` over manifest.yml asset digests" do
+ Rails.application.config.assets.digests = {'logo.png' => 'logo-d1g3st.png'}
+ @config.assets.digest = false
+
+ assert_equal '/assets/logo.png',
+ asset_path("logo.png")
end
end
diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb
index b2df8efee3..4d01352b43 100644
--- a/actionpack/test/template/streaming_render_test.rb
+++ b/actionpack/test/template/streaming_render_test.rb
@@ -8,7 +8,7 @@ end
class FiberedTest < ActiveSupport::TestCase
def setup
view_paths = ActionController::Base.view_paths
- @assigns = { :secret => 'in the sauce' }
+ @assigns = { :secret => 'in the sauce', :name => nil }
@view = ActionView::Base.new(view_paths, @assigns)
@controller_view = TestController.new.view_context
end
@@ -28,7 +28,7 @@ class FiberedTest < ActiveSupport::TestCase
def test_streaming_works
content = []
- body = render_body(:template => "test/hello_world.erb", :layout => "layouts/yield")
+ body = render_body(:template => "test/hello_world", :layout => "layouts/yield")
body.each do |piece|
content << piece
@@ -42,12 +42,12 @@ class FiberedTest < ActiveSupport::TestCase
end
def test_render_file
- assert_equal "Hello world!", buffered_render(:file => "test/hello_world.erb")
+ assert_equal "Hello world!", buffered_render(:file => "test/hello_world")
end
def test_render_file_with_locals
locals = { :secret => 'in the sauce' }
- assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals.erb", :locals => locals)
+ assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals", :locals => locals)
end
def test_render_partial
@@ -64,27 +64,27 @@ class FiberedTest < ActiveSupport::TestCase
def test_render_with_layout
assert_equal %(<title></title>\nHello world!\n),
- buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield")
+ buffered_render(:template => "test/hello_world", :layout => "layouts/yield")
end
def test_render_with_layout_which_has_render_inline
assert_equal %(welcome\nHello world!\n),
- buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside")
+ buffered_render(:template => "test/hello_world", :layout => "layouts/yield_with_render_inline_inside")
end
def test_render_with_layout_which_renders_another_partial
assert_equal %(partial html\nHello world!\n),
- buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside")
+ buffered_render(:template => "test/hello_world", :layout => "layouts/yield_with_render_partial_inside")
end
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
- buffered_render(:template => "test/nested_layout.erb", :layout => "layouts/yield")
+ buffered_render(:template => "test/nested_layout", :layout => "layouts/yield")
end
def test_render_with_file_in_layout
assert_equal %(\n<title>title</title>\n\n),
- buffered_render(:template => "test/layout_render_file.erb")
+ buffered_render(:template => "test/layout_render_file")
end
def test_render_with_handler_without_streaming_support
@@ -106,4 +106,4 @@ class FiberedTest < ActiveSupport::TestCase
buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming")
end
-end if defined?(Fiber) \ No newline at end of file
+end if defined?(Fiber)
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index 81fb34b80f..70ca876c67 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -8,6 +8,9 @@ class TestERBTemplate < ActiveSupport::TestCase
def disable_cache
yield
end
+
+ def find_template(*args)
+ end
end
class Context
@@ -153,7 +156,6 @@ class TestERBTemplate < ActiveSupport::TestCase
def test_encoding_can_be_specified_with_magic_comment_in_erb
with_external_encoding Encoding::UTF_8 do
@template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil)
- result = render
assert_equal Encoding::UTF_8, render.encoding
assert_equal "hello \u{fc}mlat", render
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index cd4618a505..a75f1bc981 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -45,6 +45,10 @@ module ActionView
assert_same _view, view
end
+ test "retrieve non existing config values" do
+ assert_equal nil, ActionView::Base.new.config.something_odd
+ end
+
test "works without testing a helper module" do
assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy'))
end
@@ -141,22 +145,6 @@ module ActionView
end
end
- class AssignsTest < ActionView::TestCase
- setup do
- ActiveSupport::Deprecation.stubs(:warn)
- end
-
- test "_assigns delegates to user_defined_ivars" do
- self.expects(:view_assigns)
- _assigns
- end
-
- test "_assigns is deprecated" do
- ActiveSupport::Deprecation.expects(:warn)
- _assigns
- end
- end
-
class ViewAssignsTest < ActionView::TestCase
test "view_assigns returns a Hash of user defined ivars" do
@a = 'b'
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index 3d0bbba435..adcbf1447f 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -39,7 +39,7 @@ class PeopleHelperTest < ActionView::TestCase
with_test_route_set do
person = mock(:name => "David")
person.class.extend ActiveModel::Naming
- _routes.url_helpers.expects(:hash_for_mocha_mock_path).with(person).returns("/people/1")
+ expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
end
@@ -62,3 +62,19 @@ class CrazyHelperTest < ActionView::TestCase
assert_equal PeopleHelper, self.class.helper_class
end
end
+
+class CrazySymbolHelperTest < ActionView::TestCase
+ tests :people
+
+ def test_set_helper_class_using_symbol
+ assert_equal PeopleHelper, self.class.helper_class
+ end
+end
+
+class CrazyStringHelperTest < ActionView::TestCase
+ tests 'people'
+
+ def test_set_helper_class_using_string
+ assert_equal PeopleHelper, self.class.helper_class
+ end
+end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 740f577a6e..02f9609483 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -36,8 +36,8 @@ class TextHelperTest < ActionView::TestCase
text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze
assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text)
- assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test')
- assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test')
+ assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test')
+ assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test')
end
def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false
@@ -48,6 +48,13 @@ class TextHelperTest < ActionView::TestCase
assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
end
+ def test_simple_format_should_not_change_the_text_passed
+ text = "<b>Ok</b><script>code!</script>"
+ text_clone = text.dup
+ simple_format(text)
+ assert_equal text_clone, text
+ end
+
def test_truncate_should_not_be_html_safe
assert !truncate("Hello World!", :length => 12).html_safe?
end
@@ -291,7 +298,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_cycle_class_with_no_arguments
- assert_raise(ArgumentError) { value = Cycle.new() }
+ assert_raise(ArgumentError) { Cycle.new }
end
def test_cycle
@@ -304,7 +311,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_cycle_with_no_arguments
- assert_raise(ArgumentError) { value = cycle() }
+ assert_raise(ArgumentError) { cycle }
end
def test_cycle_resets_with_new_values
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index 9b5c6d127c..cd9f54e04c 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -38,11 +38,19 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_returns_missing_translation_message_wrapped_into_span
expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
assert_equal expected, translate(:"translations.missing")
+ assert_equal true, translate(:"translations.missing").html_safe?
end
def test_returns_missing_translation_message_using_nil_as_rescue_format
expected = 'translation missing: en.translations.missing'
assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
+ assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
+ end
+
+ def test_i18n_translate_defaults_to_nil_rescue_format
+ expected = 'translation missing: en.translations.missing'
+ assert_equal expected, I18n.translate(:"translations.missing")
+ assert_equal false, I18n.translate(:"translations.missing").html_safe?
end
def test_translation_returning_an_array
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 8d0f0124c2..bc45fabf34 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -15,6 +15,7 @@ class UrlHelperTest < ActiveSupport::TestCase
routes.draw do
match "/" => "foo#bar"
match "/other" => "foo#other"
+ match "/article/:id" => "foo#article", :as => :article
end
include routes.url_helpers
@@ -25,6 +26,8 @@ class UrlHelperTest < ActiveSupport::TestCase
include ActionView::Context
include RenderERBUtils
+ setup :_prepare_context
+
def hash_for(opts = [])
ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))]
end
@@ -85,6 +88,10 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_with_remote_and_form_options
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\" data-remote=\"true\" data-type=\"json\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :remote => true, :form => { :class => "custom-class", "data-type" => "json" } )
+ end
+
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>",
@@ -242,6 +249,13 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_link_tag_using_post_javascript_and_rel
+ assert_dom_equal(
+ "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>",
+ link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example')
+ )
+ end
+
def test_link_tag_using_post_javascript_and_confirm
assert_dom_equal(
"<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>",
@@ -262,6 +276,13 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal '<a href="/">Example site</a>', out
end
+ def test_link_tag_with_html_safe_string
+ assert_dom_equal(
+ "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>",
+ link_to("Gerd Müller", article_path("Gerd_Müller".html_safe))
+ )
+ end
+
def test_link_to_unless
assert_equal "Showing", link_to_unless(true, "Showing", url_hash)
@@ -287,8 +308,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal "Showing", link_to_if(false, "Showing", url_hash)
end
- def request_for_url(url)
- env = Rack::MockRequest.env_for("http://www.example.com#{url}")
+ def request_for_url(url, opts = {})
+ env = Rack::MockRequest.env_for("http://www.example.com#{url}", opts)
ActionDispatch::Request.new(env)
end
@@ -312,6 +333,12 @@ class UrlHelperTest < ActiveSupport::TestCase
assert current_page?("http://www.example.com/?order=desc&page=1")
end
+ def test_current_page_with_not_get_verb
+ @request = request_for_url("/events", :method => :post)
+
+ assert !current_page?('/events')
+ end
+
def test_link_unless_current
@request = request_for_url("/")
@@ -369,13 +396,11 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_javascript
snippet = mail_to("me@domain.com", "My email", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- assert snippet.html_safe?
end
def test_mail_to_with_javascript_unicode
snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript")
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- assert snippet.html_safe
end
def test_mail_with_options
@@ -404,6 +429,12 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
+ def test_mail_to_returns_html_safe_string
+ assert mail_to("david@loudthinking.com").html_safe?
+ assert mail_to("me@domain.com", "My email", :encode => "javascript").html_safe?
+ assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe?
+ end
+
# TODO: button_to looks at this ... why?
def protect_against_forgery?
false