From b29f95ed9a4f09674a187b237acc143ac5f4ddde Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Nov 2008 18:12:32 +0100 Subject: Dont log the _method attribute either. Its already available in the header --- actionpack/lib/action_controller/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 74c04147fb..fc59668107 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1073,7 +1073,7 @@ module ActionController #:nodoc: status = 302 end - response.redirected_to= options + response.redirected_to = options logger.info("Redirected to #{options}") if logger && logger.info? case options @@ -1248,7 +1248,7 @@ module ActionController #:nodoc: def log_processing_for_parameters parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params - parameters = parameters.except(:controller, :action, :format) + parameters = parameters.except(:controller, :action, :format, :_method) logger.info " Parameters: #{parameters.inspect}" end -- cgit v1.2.3 From b2cd318c2e3f4d19813a5c62903319a6683aa561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20de=20P=C3=A1dua?= Date: Tue, 4 Nov 2008 00:28:17 -0200 Subject: Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed [#1289 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_view/helpers/date_helper.rb | 4 +-- actionpack/test/template/date_helper_test.rb | 40 +++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 17fada156d..3d1e5c13ec 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 [Bernardo Padua/Tor Erik] + * Simplified the logging format for parameters (don't include controller, action, and format as duplicates) [DHH] * Remove the logging of the Session ID when the session store is CookieStore [DHH] diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index d4d2c6ef53..919c937444 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -539,7 +539,7 @@ module ActionView # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are # valid (otherwise it could be 31 and february wouldn't be a valid date) - if @options[:discard_day] && !@options[:discard_month] + if @datetime && @options[:discard_day] && !@options[:discard_month] @datetime = @datetime.change(:day => 1) end @@ -567,7 +567,7 @@ module ActionView # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are # valid (otherwise it could be 31 and february wouldn't be a valid date) - if @options[:discard_day] && !@options[:discard_month] + if @datetime && @options[:discard_day] && !@options[:discard_month] @datetime = @datetime.change(:day => 1) end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 1a645bccc6..49ba140c23 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1149,6 +1149,46 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :include_blank => true) end + + def test_date_select_with_nil_and_blank_and_order + @post = Post.new + + start_year = Time.now.year-5 + end_year = Time.now.year+5 + + expected = '' + "\n" + expected << %{\n" + + expected << %{\n" + + assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true) + end + + def test_date_select_with_nil_and_blank_and_order + @post = Post.new + + start_year = Time.now.year-5 + end_year = Time.now.year+5 + + expected = '' + "\n" + expected << %{\n" + + expected << %{\n" + + assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true) + end def test_date_select_cant_override_discard_hour @post = Post.new -- cgit v1.2.3 From 5fad229e43e2b2541ed39c6ef571975176e6a8d2 Mon Sep 17 00:00:00 2001 From: Vladimir Dobriakov Date: Tue, 4 Nov 2008 13:46:36 +0100 Subject: Fixed that FormTagHelper generates illegal html if name contains e.g. square brackets [#1238 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ .../lib/action_view/helpers/form_tag_helper.rb | 14 ++++++--- actionpack/test/template/form_tag_helper_test.rb | 35 +++++++++++++++++++++- 3 files changed, 46 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 3d1e5c13ec..3ce6522535 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov] + * Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 [Bernardo Padua/Tor Erik] * Simplified the logging format for parameters (don't include controller, action, and format as duplicates) [DHH] diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 7492348c50..4646bc118b 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -78,7 +78,7 @@ module ActionView # # def select_tag(name, option_tags = nil, options = {}) html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name - content_tag :select, option_tags, { "name" => html_name, "id" => name }.update(options.stringify_keys) + content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) end # Creates a standard text field; use these text fields to input smaller chunks of text like a username @@ -112,7 +112,7 @@ module ActionView # text_field_tag 'ip', '0.0.0.0', :maxlength => 15, :size => 20, :class => "ip-input" # # => def text_field_tag(name, value = nil, options = {}) - tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) + tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) end # Creates a label field @@ -130,7 +130,7 @@ module ActionView # label_tag 'name', nil, :class => 'small_label' # # => def label_tag(name, text = nil, options = {}) - content_tag :label, text || name.to_s.humanize, { "for" => name }.update(options.stringify_keys) + content_tag :label, text || name.to_s.humanize, { "for" => sanitize_to_id(name) }.update(options.stringify_keys) end # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or @@ -282,7 +282,7 @@ module ActionView # check_box_tag 'eula', 'accepted', false, :disabled => true # # => def check_box_tag(name, value = "1", checked = false, options = {}) - html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) + html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) html_options["checked"] = "checked" if checked tag :input, html_options end @@ -470,6 +470,12 @@ module ActionView tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) end end + + # see http://www.w3.org/TR/html4/types.html#type-name + def sanitize_to_id(name) + name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_") + end + end end end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 1849a61f2f..de82647813 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -12,12 +12,19 @@ class FormTagHelperTest < ActionView::TestCase @controller = @controller.new end + VALID_HTML_ID = /^[A-Za-z][-_:.A-Za-z0-9]*$/ # see http://www.w3.org/TR/html4/types.html#type-name + def test_check_box_tag actual = check_box_tag "admin" expected = %() assert_dom_equal expected, actual end + def test_check_box_tag_id_sanitized + label_elem = root_elem(check_box_tag("project[2][admin]")) + assert_match VALID_HTML_ID, label_elem['id'] + end + def test_form_tag actual = form_tag expected = %(
) @@ -64,6 +71,11 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_hidden_field_tag_id_sanitized + input_elem = root_elem(hidden_field_tag("item[][title]")) + assert_match VALID_HTML_ID, input_elem['id'] + end + def test_file_field_tag assert_dom_equal "", file_field_tag("picsplz") end @@ -118,6 +130,11 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_select_tag_id_sanitized + input_elem = root_elem(select_tag("project[1]people", "")) + assert_match VALID_HTML_ID, input_elem['id'] + end + def test_text_area_tag_size_string actual = text_area_tag "body", "hello world", "size" => "20x40" expected = %() @@ -184,6 +201,11 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_text_field_tag_id_sanitized + input_elem = root_elem(text_field_tag("item[][title]")) + assert_match VALID_HTML_ID, input_elem['id'] + end + def test_label_tag_without_text actual = label_tag "title" expected = %() @@ -208,11 +230,16 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_label_tag_id_sanitized + label_elem = root_elem(label_tag("item[title]")) + assert_match VALID_HTML_ID, label_elem['for'] + end + def test_boolean_optios assert_dom_equal %(), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) assert_dom_equal %(), select_tag("people", "", :multiple => true) - assert_dom_equal %(), select_tag("people[]", "", :multiple => true) + assert_dom_equal %(), select_tag("people[]", "", :multiple => true) assert_dom_equal %(), select_tag("people", "", :multiple => nil) end @@ -283,4 +310,10 @@ class FormTagHelperTest < ActionView::TestCase def protect_against_forgery? false end + + private + + def root_elem(rendered_content) + HTML::Document.new(rendered_content).root.children[0] + end end -- cgit v1.2.3 From 55707da1a14814ad84f138ee98e5b5fb1a745afc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 09:59:11 +0100 Subject: Dont bother logging the parameters hash if there are no parameters --- actionpack/lib/action_controller/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3a7f6c0f3c..43f6c1be44 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1241,7 +1241,7 @@ module ActionController #:nodoc: parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup parameters = parameters.except!(:controller, :action, :format, :_method) - logger.info " Parameters: #{parameters.inspect}" + logger.info " Parameters: #{parameters.inspect}" unless parameters.empty? end def default_render #:nodoc: -- cgit v1.2.3 From a358d87e16fa876de29286b69474ab6aaee4a80b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Nov 2008 13:02:32 +0100 Subject: Fixed the sanitize helper to avoid double escaping already properly escaped entities [#683 state:committed] --- actionpack/CHANGELOG | 2 ++ .../lib/action_controller/vendor/html-scanner/html/sanitizer.rb | 2 +- actionpack/test/controller/html-scanner/sanitizer_test.rb | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 3ce6522535..e7d5031f1a 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 [antonmos/Ryan McGeary] + * Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov] * Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 [Bernardo Padua/Tor Erik] 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 12c8405101..ae20f9947c 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -160,7 +160,7 @@ module HTML if !options[:attributes].include?(attr_name) || contains_bad_protocols?(attr_name, value) node.attributes.delete(attr_name) else - node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(value) + node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(CGI::unescapeHTML(value)) end end end diff --git a/actionpack/test/controller/html-scanner/sanitizer_test.rb b/actionpack/test/controller/html-scanner/sanitizer_test.rb index a9e8447e32..bae0f5c9fd 100644 --- a/actionpack/test/controller/html-scanner/sanitizer_test.rb +++ b/actionpack/test/controller/html-scanner/sanitizer_test.rb @@ -253,6 +253,10 @@ class SanitizerTest < Test::Unit::TestCase assert_sanitized "neverending...", "<![CDATA[<span>neverending...]]>" end + def test_should_not_mangle_urls_with_ampersand + assert_sanitized %{my link} + end + protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new -- cgit v1.2.3 From af5b304a4002fe8ebeb8f31cd0a481dfa4a944db Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 6 Nov 2008 18:52:02 +0000 Subject: Fix stupid typo --- actionpack/lib/action_controller/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index a6d4abf029..c079895683 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -13,7 +13,7 @@ module ActionController ActiveSupport::Deprecation.warn( "ActionController::AbstractRequest.relative_url_root= has been renamed." + "You can now set it with config.action_controller.relative_url_root=", caller) - ActionController::base.relative_url_root=relative_url_root + ActionController::Base.relative_url_root=relative_url_root end HTTP_METHODS = %w(get head put post delete options) -- cgit v1.2.3 From 099f10679ec6d9ead9606cac2f843e854787db0c Mon Sep 17 00:00:00 2001 From: Aliaksey Kandratsenka Date: Sat, 1 Nov 2008 13:55:45 +0200 Subject: Don't eval recognize_optimized use __FILE__ and __LINE__ in the optimised recognition code. It produces meaningless line numbers. This also easily produces line numbers greater than recognition_optimization.rb have, which causes rcov to trash memory outside of it's coverage counting arrays. [#1319 state:committed] Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/routing/recognition_optimisation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb index 6c47ced6d1..3b98b16683 100644 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -148,7 +148,7 @@ module ActionController end nil end - }, __FILE__, __LINE__ + }, '(recognize_optimized)', 1 end def clear_recognize_optimized! -- cgit v1.2.3 From d355921709c6b21af3710de6f7b61a5b9c39314e Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 13:27:06 -0500 Subject: Remove controller assertions from Test::Unit::TestCase. Use ActionController::TestCase. --- actionpack/lib/action_controller/assertions.rb | 69 ------------------------ actionpack/lib/action_controller/test_case.rb | 59 +++++++++++++++++++- actionpack/lib/action_controller/test_process.rb | 1 - 3 files changed, 58 insertions(+), 71 deletions(-) delete mode 100644 actionpack/lib/action_controller/assertions.rb (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/assertions.rb b/actionpack/lib/action_controller/assertions.rb deleted file mode 100644 index 5b9a2b71f2..0000000000 --- a/actionpack/lib/action_controller/assertions.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'test/unit/assertions' - -module ActionController #:nodoc: - # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions - # can be used against. These collections are: - # - # * assigns: Instance variables assigned in the action that are available for the view. - # * session: Objects being saved in the session. - # * flash: The flash objects currently in the session. - # * cookies: Cookies being sent to the user on this request. - # - # These collections can be used just like any other hash: - # - # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set - # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" - # assert flash.empty? # makes sure that there's nothing in the flash - # - # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To - # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing. - # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work. - # - # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url. - # - # 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 - # - # 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: - # - # @request.session[:key] = "value" - # - # For cookies, you need to manually create the cookie, like this: - # - # @request.cookies["key"] = CGI::Cookie.new("key", "value") - # - # == Testing named routes - # - # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. - # Example: - # - # assert_redirected_to page_url(:title => 'foo') - module Assertions - def self.included(klass) - %w(response selector tag dom routing model).each do |kind| - require "action_controller/assertions/#{kind}_assertions" - klass.module_eval { include const_get("#{kind.camelize}Assertions") } - end - end - - def clean_backtrace(&block) - yield - rescue Test::Unit::AssertionFailedError => error - framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions")) - error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path } - raise - end - end -end - -module Test #:nodoc: - module Unit #:nodoc: - class TestCase #:nodoc: - include ActionController::Assertions - end - end -end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 4fc60f0697..a0bb3c562c 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -74,7 +74,56 @@ module ActionController # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase # tests WidgetController # end + # + # == Testing controller internals + # + # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions + # can be used against. These collections are: + # + # * assigns: Instance variables assigned in the action that are available for the view. + # * session: Objects being saved in the session. + # * flash: The flash objects currently in the session. + # * cookies: Cookies being sent to the user on this request. + # + # These collections can be used just like any other hash: + # + # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set + # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" + # assert flash.empty? # makes sure that there's nothing in the flash + # + # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To + # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing. + # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work. + # + # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url. + # + # 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 + # + # 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: + # + # @request.session[:key] = "value" + # + # For cookies, you need to manually create the cookie, like this: + # + # @request.cookies["key"] = CGI::Cookie.new("key", "value") + # + # == Testing named routes + # + # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. + # Example: + # + # assert_redirected_to page_url(:title => 'foo') class TestCase < ActiveSupport::TestCase + %w(response selector tag dom routing model).each do |kind| + require "action_controller/assertions/#{kind}_assertions" + include const_get("#{kind.camelize}Assertions") + end + # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else @@ -143,5 +192,13 @@ module ActionController def rescue_action_in_public! @request.remote_addr = '208.77.188.166' # example.com end - end + + def clean_backtrace(&block) + yield + rescue Assertion => error + framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions")) + error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path } + raise + end + end end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 7a31f0e8d5..a5ec23cf50 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -1,4 +1,3 @@ -require 'action_controller/assertions' require 'action_controller/test_case' module ActionController #:nodoc: -- cgit v1.2.3 From d3ec1d3c22904c8801945b56956466f8ead9f3c1 Mon Sep 17 00:00:00 2001 From: Rich Manalang Date: Thu, 6 Nov 2008 20:02:32 -0800 Subject: auto_link view helper was failing on URLs with colons after a query param Signed-off-by: Michael Koziarski [#1341 state:committed] --- actionpack/lib/action_view/helpers/text_helper.rb | 4 ++-- actionpack/test/template/text_helper_test.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index d80e7c6e57..36f7575652 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -559,7 +559,7 @@ module ActionView (?:\.[-\w]+)* # remaining subdomains or domain (?::\d+)? # port (?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path - (?:\?[\w\+@%&=.;-]+)? # query string + (?:\?[\w\+@%&=.;:-]+)? # query string (?:\#[\w\-]*)? # trailing anchor ) ([[:punct:]]|<|$|) # trailing text @@ -598,4 +598,4 @@ module ActionView end end end -end \ No newline at end of file +end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 5f9f715819..095c952d67 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -221,6 +221,7 @@ class TextHelperTest < ActionView::TestCase http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 http://en.wikipedia.org/wiki/Sprite_(computer_graphics) http://en.wikipedia.org/wiki/Texas_hold'em + https://www.google.com/doku.php?id=gps:resource:scs:start ) urls.each do |url| -- cgit v1.2.3 From c82e8e1f483ece1fbd2e9f73715fd211487620fc Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 15:42:34 -0500 Subject: Move controller assertions from base TestCase to AC:: and AV::TestCase --- actionpack/lib/action_controller/integration.rb | 4 +- actionpack/lib/action_controller/test_case.rb | 57 ++++----- actionpack/lib/action_controller/test_process.rb | 6 +- actionpack/lib/action_view/test_case.rb | 4 +- actionpack/test/active_record_unit.rb | 4 +- ...nder_partial_with_record_identification_test.rb | 7 -- .../test/controller/action_pack_assertions_test.rb | 26 ++-- actionpack/test/controller/assert_select_test.rb | 131 ++++++++++----------- actionpack/test/controller/base_test.rb | 2 +- actionpack/test/controller/caching_test.rb | 8 +- actionpack/test/controller/components_test.rb | 8 +- .../deprecation/deprecated_base_methods_test.rb | 20 ++-- .../test/controller/html-scanner/sanitizer_test.rb | 2 +- actionpack/test/controller/layout_test.rb | 33 +----- actionpack/test/controller/mime_responds_test.rb | 16 +-- .../test/controller/polymorphic_routes_test.rb | 3 +- actionpack/test/controller/redirect_test.rb | 16 +-- actionpack/test/controller/render_test.rb | 32 ++--- .../controller/request_forgery_protection_test.rb | 10 +- actionpack/test/controller/request_test.rb | 26 ++-- actionpack/test/controller/resources_test.rb | 2 +- actionpack/test/controller/test_test.rb | 22 ++-- actionpack/test/controller/url_rewriter_test.rb | 5 +- actionpack/test/controller/verification_test.rb | 2 +- actionpack/test/controller/view_paths_test.rb | 2 +- actionpack/test/template/atom_feed_helper_test.rb | 8 +- 26 files changed, 191 insertions(+), 265 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index fc473c269c..b3771c1ebc 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -1,4 +1,4 @@ -require 'active_support/test_case' +require 'action_controller/test_case' require 'action_controller/dispatcher' require 'action_controller/test_process' @@ -16,7 +16,7 @@ module ActionController # rather than instantiating Integration::Session directly. class Session include Test::Unit::Assertions - include ActionController::Assertions + include ActionController::TestCase::Assertions include ActionController::TestProcess # The integer HTTP status code of the last request. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a0bb3c562c..b925230118 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,20 +1,6 @@ require 'active_support/test_case' module ActionController - class NonInferrableControllerError < ActionControllerError - def initialize(name) - @name = name - super "Unable to determine the controller to test from #{name}. " + - "You'll need to specify it using 'tests YourController' in your " + - "test case definition. This could mean that #{inferred_controller_name} does not exist " + - "or it contains syntax errors" - end - - def inferred_controller_name - @name.sub(/Test$/, '') - end - end - # Superclass for ActionController functional tests. Functional tests allow you to # test a single controller action per test method. This should not be confused with # integration tests (see ActionController::IntegrationTest), which are more like @@ -119,10 +105,21 @@ module ActionController # # assert_redirected_to page_url(:title => 'foo') class TestCase < ActiveSupport::TestCase - %w(response selector tag dom routing model).each do |kind| - require "action_controller/assertions/#{kind}_assertions" - include const_get("#{kind.camelize}Assertions") + module Assertions + %w(response selector tag dom routing model).each do |kind| + require "action_controller/assertions/#{kind}_assertions" + include ActionController::Assertions.const_get("#{kind.camelize}Assertions") + end + + def clean_backtrace(&block) + yield + rescue ActiveSupport::TestCase::Assertion => error + framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions")) + error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path } + raise + end end + include Assertions # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular @@ -156,7 +153,7 @@ module ActionController end def controller_class=(new_class) - prepare_controller_class(new_class) + prepare_controller_class(new_class) if new_class write_inheritable_attribute(:controller_class, new_class) end @@ -171,7 +168,7 @@ module ActionController def determine_default_controller_class(name) name.sub(/Test$/, '').constantize rescue NameError - raise NonInferrableControllerError.new(name) + nil end def prepare_controller_class(new_class) @@ -180,25 +177,23 @@ module ActionController end def setup_controller_request_and_response - @controller = self.class.controller_class.new - @controller.request = @request = TestRequest.new + @request = TestRequest.new @response = TestResponse.new - @controller.params = {} - @controller.send(:initialize_current_url) + if klass = self.class.controller_class + @controller ||= klass.new rescue nil + end + + if @controller + @controller.request = @request + @controller.params = {} + @controller.send(:initialize_current_url) + end end # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local def rescue_action_in_public! @request.remote_addr = '208.77.188.166' # example.com end - - def clean_backtrace(&block) - yield - rescue Assertion => error - framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions")) - error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path } - raise - end end end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index a5ec23cf50..38e15baac2 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -462,9 +462,9 @@ module ActionController #:nodoc: html_document.find_all(conditions) end - def method_missing(selector, *args) - if ActionController::Routing::Routes.named_routes.helpers.include?(selector) - @controller.send(selector, *args) + def method_missing(selector, *args, &block) + if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector) + @controller.send(selector, *args, &block) else super end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index c69f9455b2..cdea1def92 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,7 +1,9 @@ -require 'active_support/test_case' +require 'action_controller/test_case' module ActionView class TestCase < ActiveSupport::TestCase + include ActionController::TestCase::Assertions + class_inheritable_accessor :helper_class @@helper_class = nil diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb index a377ccad24..8eca6cc9bd 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -82,7 +82,7 @@ class ActiveRecordTestConnector end end -class ActiveRecordTestCase < ActiveSupport::TestCase +class ActiveRecordTestCase < ActionController::TestCase # Set our fixture path if ActiveRecordTestConnector.able_to_connect self.fixture_path = [FIXTURE_LOAD_PATH] @@ -96,8 +96,6 @@ class ActiveRecordTestCase < ActiveSupport::TestCase def run(*args) super if ActiveRecordTestConnector.connected end - - def default_test; end end ActiveRecordTestConnector.setup diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index d75cb2b53a..822a739112 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -49,13 +49,6 @@ end class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots - def setup - @controller = RenderPartialWithRecordIdentificationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - def test_rendering_partial_with_has_many_and_belongs_to_association get :render_with_has_many_and_belongs_to_association assert_template 'projects/_project' diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 56ba36cee5..7050000dd8 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -165,13 +165,11 @@ module Admin end # a test case to exercise the new capabilities TestRequest & TestResponse -class ActionPackAssertionsControllerTest < Test::Unit::TestCase +class ActionPackAssertionsControllerTest < ActionController::TestCase # let's get this party started def setup ActionController::Routing::Routes.reload ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module user content admin/user)) - @controller = ActionPackAssertionsController.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new end def teardown @@ -235,13 +233,13 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase map.connect ':controller/:action/:id' end process :redirect_to_named_route - assert_raise(Test::Unit::AssertionFailedError) do + assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to 'http://test.host/route_two' end - assert_raise(Test::Unit::AssertionFailedError) do + assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' end - assert_raise(Test::Unit::AssertionFailedError) do + assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to route_two_url end end @@ -410,7 +408,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase def test_assert_redirection_fails_with_incorrect_controller process :redirect_to_controller - assert_raise(Test::Unit::AssertionFailedError) do + assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me" end end @@ -466,7 +464,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase begin assert_valid assigns('record') assert false - rescue Test::Unit::AssertionFailedError => e + rescue ActiveSupport::TestCase::Assertion => e end end @@ -475,7 +473,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase get :index assert_response :success flunk 'Expected non-success response' - rescue Test::Unit::AssertionFailedError => e + rescue ActiveSupport::TestCase::Assertion => e assert e.message.include?('FAIL') end @@ -484,17 +482,15 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase get :show assert_response :success flunk 'Expected non-success response' - rescue Test::Unit::AssertionFailedError + rescue ActiveSupport::TestCase::Assertion + # success rescue flunk "assert_response failed to handle failure response with missing, but optional, exception." end end -class ActionPackHeaderTest < Test::Unit::TestCase - def setup - @controller = ActionPackAssertionsController.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - end +class ActionPackHeaderTest < ActionController::TestCase + tests ActionPackAssertionsController def test_rendering_xml_sets_content_type process :hello_xml_world diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 08cbcbf302..a79278159d 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -19,7 +19,18 @@ end ActionMailer::Base.template_root = FIXTURE_LOAD_PATH -class AssertSelectTest < Test::Unit::TestCase +class AssertSelectTest < ActionController::TestCase + Assertion = ActiveSupport::TestCase::Assertion + + class AssertSelectMailer < ActionMailer::Base + def test(html) + recipients "test " + from "test@test.host" + subject "Test e-mail" + part :content_type=>"text/html", :body=>html + end + end + class AssertSelectController < ActionController::Base def response_with=(content) @content = content @@ -51,21 +62,9 @@ class AssertSelectTest < Test::Unit::TestCase end end - class AssertSelectMailer < ActionMailer::Base - def test(html) - recipients "test " - from "test@test.host" - subject "Test e-mail" - part :content_type=>"text/html", :body=>html - end - end - - AssertionFailedError = Test::Unit::AssertionFailedError + tests AssertSelectController def setup - @controller = AssertSelectController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new ActionMailer::Base.delivery_method = :test ActionMailer::Base.perform_deliveries = true ActionMailer::Base.deliveries = [] @@ -76,7 +75,7 @@ class AssertSelectTest < Test::Unit::TestCase end def assert_failure(message, &block) - e = assert_raises(AssertionFailedError, &block) + e = assert_raises(Assertion, &block) assert_match(message, e.message) if Regexp === message assert_equal(message, e.message) if String === message end @@ -94,43 +93,43 @@ class AssertSelectTest < Test::Unit::TestCase def test_equality_true_false render_html %Q{
} - assert_nothing_raised { assert_select "div" } - assert_raises(AssertionFailedError) { assert_select "p" } - assert_nothing_raised { assert_select "div", true } - assert_raises(AssertionFailedError) { assert_select "p", true } - assert_raises(AssertionFailedError) { assert_select "div", false } - assert_nothing_raised { assert_select "p", false } + assert_nothing_raised { assert_select "div" } + assert_raises(Assertion) { assert_select "p" } + assert_nothing_raised { assert_select "div", true } + assert_raises(Assertion) { assert_select "p", true } + assert_raises(Assertion) { assert_select "div", false } + assert_nothing_raised { assert_select "p", false } end def test_equality_string_and_regexp render_html %Q{
foo
foo
} - assert_nothing_raised { assert_select "div", "foo" } - assert_raises(AssertionFailedError) { assert_select "div", "bar" } - assert_nothing_raised { assert_select "div", :text=>"foo" } - assert_raises(AssertionFailedError) { assert_select "div", :text=>"bar" } - assert_nothing_raised { assert_select "div", /(foo|bar)/ } - assert_raises(AssertionFailedError) { assert_select "div", /foobar/ } - assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } - assert_raises(AssertionFailedError) { assert_select "div", :text=>/foobar/ } - assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ } + assert_nothing_raised { assert_select "div", "foo" } + assert_raises(Assertion) { assert_select "div", "bar" } + assert_nothing_raised { assert_select "div", :text=>"foo" } + assert_raises(Assertion) { assert_select "div", :text=>"bar" } + assert_nothing_raised { assert_select "div", /(foo|bar)/ } + assert_raises(Assertion) { assert_select "div", /foobar/ } + assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } + assert_raises(Assertion) { assert_select "div", :text=>/foobar/ } + assert_raises(Assertion) { assert_select "p", :text=>/foobar/ } end def test_equality_of_html render_html %Q{

\n"This is not a big problem," he said.\n

} text = "\"This is not a big problem,\" he said." html = "\"This is not a big problem,\" he said." - assert_nothing_raised { assert_select "p", text } - assert_raises(AssertionFailedError) { assert_select "p", html } - assert_nothing_raised { assert_select "p", :html=>html } - assert_raises(AssertionFailedError) { assert_select "p", :html=>text } + assert_nothing_raised { assert_select "p", text } + assert_raises(Assertion) { assert_select "p", html } + assert_nothing_raised { assert_select "p", :html=>html } + assert_raises(Assertion) { assert_select "p", :html=>text } # No stripping for pre. render_html %Q{
\n"This is not a big problem," he said.\n
} text = "\n\"This is not a big problem,\" he said.\n" html = "\n\"This is not a big problem,\" he said.\n" - assert_nothing_raised { assert_select "pre", text } - assert_raises(AssertionFailedError) { assert_select "pre", html } - assert_nothing_raised { assert_select "pre", :html=>html } - assert_raises(AssertionFailedError) { assert_select "pre", :html=>text } + assert_nothing_raised { assert_select "pre", text } + assert_raises(Assertion) { assert_select "pre", html } + assert_nothing_raised { assert_select "pre", :html=>html } + assert_raises(Assertion) { assert_select "pre", :html=>text } end def test_counts @@ -206,16 +205,16 @@ class AssertSelectTest < Test::Unit::TestCase def test_assert_select_text_match render_html %Q{
foo
bar
} assert_select "div" do - assert_nothing_raised { assert_select "div", "foo" } - assert_nothing_raised { assert_select "div", "bar" } - assert_nothing_raised { assert_select "div", /\w*/ } - assert_nothing_raised { assert_select "div", /\w*/, :count=>2 } - assert_raises(AssertionFailedError) { assert_select "div", :text=>"foo", :count=>2 } - assert_nothing_raised { assert_select "div", :html=>"bar" } - assert_nothing_raised { assert_select "div", :html=>"bar" } - assert_nothing_raised { assert_select "div", :html=>/\w*/ } - assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 } - assert_raises(AssertionFailedError) { assert_select "div", :html=>"foo", :count=>2 } + assert_nothing_raised { assert_select "div", "foo" } + assert_nothing_raised { assert_select "div", "bar" } + assert_nothing_raised { assert_select "div", /\w*/ } + assert_nothing_raised { assert_select "div", /\w*/, :count=>2 } + assert_raises(Assertion) { assert_select "div", :text=>"foo", :count=>2 } + assert_nothing_raised { assert_select "div", :html=>"bar" } + assert_nothing_raised { assert_select "div", :html=>"bar" } + assert_nothing_raised { assert_select "div", :html=>/\w*/ } + assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 } + assert_raises(Assertion) { assert_select "div", :html=>"foo", :count=>2 } end end @@ -323,7 +322,7 @@ class AssertSelectTest < Test::Unit::TestCase # Test that we fail if there is nothing to pick. def test_assert_select_rjs_fails_if_nothing_to_pick render_rjs { } - assert_raises(AssertionFailedError) { assert_select_rjs } + assert_raises(Assertion) { assert_select_rjs } end def test_assert_select_rjs_with_unicode @@ -338,10 +337,10 @@ class AssertSelectTest < Test::Unit::TestCase if str.respond_to?(:force_encoding) str.force_encoding(Encoding::UTF_8) assert_select str, /\343\203\201..\343\203\210/u - assert_raises(AssertionFailedError) { assert_select str, /\343\203\201.\343\203\210/u } + assert_raises(Assertion) { assert_select str, /\343\203\201.\343\203\210/u } else assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U') - assert_raises(AssertionFailedError) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } + assert_raises(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } end end end @@ -365,7 +364,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(AssertionFailedError) { assert_select_rjs "test4" } + assert_raises(Assertion) { assert_select_rjs "test4" } end def test_assert_select_rjs_for_replace @@ -383,7 +382,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(AssertionFailedError) { assert_select_rjs :replace, "test2" } + assert_raises(Assertion) { assert_select_rjs :replace, "test2" } # Replace HTML. assert_select_rjs :replace_html do assert_select "div", 1 @@ -393,7 +392,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(AssertionFailedError) { assert_select_rjs :replace_html, "test1" } + assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } end def test_assert_select_rjs_for_chained_replace @@ -411,7 +410,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(AssertionFailedError) { assert_select_rjs :chained_replace, "test2" } + assert_raises(Assertion) { assert_select_rjs :chained_replace, "test2" } # Replace HTML. assert_select_rjs :chained_replace_html do assert_select "div", 1 @@ -421,7 +420,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(AssertionFailedError) { assert_select_rjs :replace_html, "test1" } + assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } end # Simple remove @@ -440,8 +439,8 @@ class AssertSelectTest < Test::Unit::TestCase assert_select_rjs :remove, "test1" - rescue Test::Unit::AssertionFailedError - assert_equal "No RJS statement that removes 'test1' was rendered.", $!.message + rescue Assertion + assert_equal "No RJS statement that removes 'test1' was rendered.", $!.message end def test_assert_select_rjs_for_remove_ignores_block @@ -472,8 +471,8 @@ class AssertSelectTest < Test::Unit::TestCase assert_select_rjs :show, "test1" - rescue Test::Unit::AssertionFailedError - assert_equal "No RJS statement that shows 'test1' was rendered.", $!.message + rescue Assertion + assert_equal "No RJS statement that shows 'test1' was rendered.", $!.message end def test_assert_select_rjs_for_show_ignores_block @@ -504,8 +503,8 @@ class AssertSelectTest < Test::Unit::TestCase assert_select_rjs :hide, "test1" - rescue Test::Unit::AssertionFailedError - assert_equal "No RJS statement that hides 'test1' was rendered.", $!.message + rescue Assertion + assert_equal "No RJS statement that hides 'test1' was rendered.", $!.message end def test_assert_select_rjs_for_hide_ignores_block @@ -536,8 +535,8 @@ class AssertSelectTest < Test::Unit::TestCase assert_select_rjs :toggle, "test1" - rescue Test::Unit::AssertionFailedError - assert_equal "No RJS statement that toggles 'test1' was rendered.", $!.message + rescue Assertion + assert_equal "No RJS statement that toggles 'test1' was rendered.", $!.message end def test_assert_select_rjs_for_toggle_ignores_block @@ -567,7 +566,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(AssertionFailedError) { assert_select_rjs :insert_html, "test1" } + assert_raises(Assertion) { assert_select_rjs :insert_html, "test1" } end # Positioned insert. @@ -693,7 +692,7 @@ EOF # def test_assert_select_email - assert_raises(AssertionFailedError) { assert_select_email {} } + assert_raises(Assertion) { assert_select_email {} } AssertSelectMailer.deliver_test "

foo

bar

" assert_select_email do assert_select "div:root" do diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 738c016c6e..18d185b264 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -105,7 +105,7 @@ class ControllerInstanceTests < Test::Unit::TestCase end -class PerformActionTest < Test::Unit::TestCase +class PerformActionTest < ActionController::TestCase class MockLogger attr_reader :logged diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index b6cdd116e5..ad160970cc 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -42,7 +42,7 @@ class PageCachingTestController < ActionController::Base end end -class PageCachingTest < Test::Unit::TestCase +class PageCachingTest < ActionController::TestCase def setup ActionController::Base.perform_caching = true @@ -222,7 +222,7 @@ class ActionCachingMockController end end -class ActionCacheTest < Test::Unit::TestCase +class ActionCacheTest < ActionController::TestCase def setup reset! FileUtils.mkdir_p(FILE_STORE_PATH) @@ -469,7 +469,7 @@ class FragmentCachingTestController < ActionController::Base def some_action; end; end -class FragmentCachingTest < Test::Unit::TestCase +class FragmentCachingTest < ActionController::TestCase def setup ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new @@ -601,7 +601,7 @@ class FunctionalCachingController < ActionController::Base end end -class FunctionalFragmentCachingTest < Test::Unit::TestCase +class FunctionalFragmentCachingTest < ActionController::TestCase def setup ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new diff --git a/actionpack/test/controller/components_test.rb b/actionpack/test/controller/components_test.rb index 4d36fc411d..e7b17aa34e 100644 --- a/actionpack/test/controller/components_test.rb +++ b/actionpack/test/controller/components_test.rb @@ -69,12 +69,8 @@ class CalleeController < ActionController::Base def rescue_action(e) raise end end -class ComponentsTest < Test::Unit::TestCase - def setup - @controller = CallerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end +class ComponentsTest < ActionController::TestCase + tests CallerController def test_calling_from_controller assert_deprecated do diff --git a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb index 86555a77df..dd69a63020 100644 --- a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb +++ b/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class DeprecatedBaseMethodsTest < Test::Unit::TestCase +class DeprecatedBaseMethodsTest < ActionController::TestCase class Target < ActionController::Base def home_url(greeting) "http://example.com/#{greeting}" @@ -13,11 +13,7 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase def rescue_action(e) raise e end end - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = Target.new - end + tests Target def test_log_error_silences_deprecation_warnings get :raises_name_error @@ -25,10 +21,12 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase assert_not_deprecated { @controller.send :log_error, e } end - 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 } + 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/html-scanner/sanitizer_test.rb b/actionpack/test/controller/html-scanner/sanitizer_test.rb index bae0f5c9fd..e85a5c7abf 100644 --- a/actionpack/test/controller/html-scanner/sanitizer_test.rb +++ b/actionpack/test/controller/html-scanner/sanitizer_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class SanitizerTest < Test::Unit::TestCase +class SanitizerTest < ActionController::TestCase def setup @sanitizer = nil # used by assert_sanitizer end diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 1120fdbff5..61c20f8299 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -34,11 +34,8 @@ end ActionView::Template::register_template_handler :mab, lambda { |template| template.source.inspect } -class LayoutAutoDiscoveryTest < Test::Unit::TestCase +class LayoutAutoDiscoveryTest < ActionController::TestCase def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @request.host = "www.nextangle.com" end @@ -98,12 +95,7 @@ class RendersNoLayoutController < LayoutTest end end -class LayoutSetInResponseTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - +class LayoutSetInResponseTest < ActionController::TestCase def test_layout_set_when_using_default_layout @controller = DefaultLayoutController.new get :hello @@ -150,12 +142,7 @@ class SetsNonExistentLayoutFile < LayoutTest layout "nofile.rhtml" end -class LayoutExceptionRaised < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - +class LayoutExceptionRaised < ActionController::TestCase def test_exception_raised_when_layout_file_not_found @controller = SetsNonExistentLayoutFile.new get :hello @@ -170,12 +157,7 @@ class LayoutStatusIsRendered < LayoutTest end end -class LayoutStatusIsRenderedTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - +class LayoutStatusIsRenderedTest < ActionController::TestCase def test_layout_status_is_rendered @controller = LayoutStatusIsRendered.new get :hello @@ -187,12 +169,7 @@ class LayoutSymlinkedTest < LayoutTest layout "symlinked/symlinked_layout" end -class LayoutSymlinkedIsRenderedTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - +class LayoutSymlinkedIsRenderedTest < ActionController::TestCase def test_symlinked_layout_is_rendered @controller = LayoutSymlinkedTest.new get :hello diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 0d508eb8df..dc59180a68 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -162,13 +162,11 @@ class RespondToController < ActionController::Base end end -class MimeControllerTest < Test::Unit::TestCase +class MimeControllerTest < ActionController::TestCase + tests RespondToController + def setup ActionController::Base.use_accept_header = true - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - - @controller = RespondToController.new @request.host = "www.example.com" end @@ -509,12 +507,10 @@ class SuperPostController < PostController end end -class MimeControllerLayoutsTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new +class MimeControllerLayoutsTest < ActionController::TestCase + tests PostController - @controller = PostController.new + def setup @request.host = "www.example.com" end diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 6ddf2826cd..df49a37f89 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -22,8 +22,7 @@ end class Response::Nested < Response; end uses_mocha 'polymorphic URL helpers' do - class PolymorphicRoutesTest < Test::Unit::TestCase - + class PolymorphicRoutesTest < ActiveSupport::TestCase include ActionController::PolymorphicRoutes def setup diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index c55307d645..27cedc91d2 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -103,12 +103,8 @@ class RedirectController < ActionController::Base end end -class RedirectTest < Test::Unit::TestCase - def setup - @controller = RedirectController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end +class RedirectTest < ActionController::TestCase + tests RedirectController def test_simple_redirect get :simple_redirect @@ -256,12 +252,8 @@ module ModuleTest end end - class ModuleRedirectTest < Test::Unit::TestCase - def setup - @controller = ModuleRedirectController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end + class ModuleRedirectTest < ActionController::TestCase + tests ModuleRedirectController def test_simple_redirect get :simple_redirect diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index df9376727f..6a03466db1 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -641,12 +641,10 @@ class TestController < ActionController::Base end end -class RenderTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new +class RenderTest < ActionController::TestCase + tests TestController + def setup # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get # a more accurate simulation of what happens in "real life". @controller.logger = Logger.new(nil) @@ -1333,12 +1331,10 @@ class RenderTest < Test::Unit::TestCase end end -class EtagRenderTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new +class EtagRenderTest < ActionController::TestCase + tests TestController + def setup @request.host = "www.nextangle.com" @expected_bang_etag = etag_for(expand_key([:foo, 123])) end @@ -1430,12 +1426,10 @@ class EtagRenderTest < Test::Unit::TestCase end end -class LastModifiedRenderTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new +class LastModifiedRenderTest < ActionController::TestCase + tests TestController + def setup @request.host = "www.nextangle.com" @last_modified = Time.now.utc.beginning_of_day.httpdate end @@ -1487,12 +1481,10 @@ class LastModifiedRenderTest < Test::Unit::TestCase end end -class RenderingLoggingTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = TestController.new +class RenderingLoggingTest < ActionController::TestCase + tests TestController + def setup @request.host = "www.nextangle.com" end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index f7adaa7d4e..9dbfd120f2 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -222,7 +222,7 @@ end # OK let's get our test on -class RequestForgeryProtectionControllerTest < Test::Unit::TestCase +class RequestForgeryProtectionControllerTest < ActionController::TestCase include RequestForgeryProtectionTests def setup @controller = RequestForgeryProtectionController.new @@ -236,7 +236,7 @@ class RequestForgeryProtectionControllerTest < Test::Unit::TestCase end end -class RequestForgeryProtectionWithoutSecretControllerTest < Test::Unit::TestCase +class RequestForgeryProtectionWithoutSecretControllerTest < ActionController::TestCase def setup @controller = RequestForgeryProtectionWithoutSecretController.new @request = ActionController::TestRequest.new @@ -255,7 +255,7 @@ class RequestForgeryProtectionWithoutSecretControllerTest < Test::Unit::TestCase end end -class CsrfCookieMonsterControllerTest < Test::Unit::TestCase +class CsrfCookieMonsterControllerTest < ActionController::TestCase include RequestForgeryProtectionTests def setup @controller = CsrfCookieMonsterController.new @@ -271,7 +271,7 @@ class CsrfCookieMonsterControllerTest < Test::Unit::TestCase end end -class FreeCookieControllerTest < Test::Unit::TestCase +class FreeCookieControllerTest < ActionController::TestCase def setup @controller = FreeCookieController.new @request = ActionController::TestRequest.new @@ -296,7 +296,7 @@ class FreeCookieControllerTest < Test::Unit::TestCase end end -class SessionOffControllerTest < Test::Unit::TestCase +class SessionOffControllerTest < ActionController::TestCase def setup @controller = SessionOffController.new @request = ActionController::TestRequest.new diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index e79a0ea76b..2dc2ed9965 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' require 'action_controller/integration' -class RequestTest < Test::Unit::TestCase +class RequestTest < ActiveSupport::TestCase def setup ActionController::Base.relative_url_root = nil @request = ActionController::TestRequest.new @@ -400,7 +400,7 @@ class RequestTest < Test::Unit::TestCase end end -class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase +class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase def setup @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" @query_string_with_empty = "action=create_customer&full_name=" @@ -704,20 +704,20 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase end end -class MultipartRequestParameterParsingTest < Test::Unit::TestCase +class MultipartRequestParameterParsingTest < ActiveSupport::TestCase FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart' def test_single_parameter - params = process('single_parameter') + params = parse_multipart('single_parameter') assert_equal({ 'foo' => 'bar' }, params) end def test_bracketed_param - assert_equal({ 'foo' => { 'baz' => 'bar'}}, process('bracketed_param')) + assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param')) end def test_text_file - params = process('text_file') + params = parse_multipart('text_file') assert_equal %w(file foo), params.keys.sort assert_equal 'bar', params['foo'] @@ -729,7 +729,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end def test_boundary_problem_file - params = process('boundary_problem_file') + params = parse_multipart('boundary_problem_file') assert_equal %w(file foo), params.keys.sort file = params['file'] @@ -748,7 +748,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end def test_large_text_file - params = process('large_text_file') + params = parse_multipart('large_text_file') assert_equal %w(file foo), params.keys.sort assert_equal 'bar', params['foo'] @@ -774,7 +774,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end def test_binary_file - params = process('binary_file') + params = parse_multipart('binary_file') assert_equal %w(file flowers foo), params.keys.sort assert_equal 'bar', params['foo'] @@ -793,7 +793,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end def test_mixed_files - params = process('mixed_files') + params = parse_multipart('mixed_files') assert_equal %w(files foo), params.keys.sort assert_equal 'bar', params['foo'] @@ -805,7 +805,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end private - def process(name) + def parse_multipart(name) File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {}) assert_equal 0, file.pos # file was rewound after reading @@ -814,7 +814,7 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase end end -class XmlParamsParsingTest < Test::Unit::TestCase +class XmlParamsParsingTest < ActiveSupport::TestCase def test_hash_params person = parse_body("David")[:person] assert_kind_of Hash, person @@ -868,7 +868,7 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest end end -class JsonParamsParsingTest < Test::Unit::TestCase +class JsonParamsParsingTest < ActiveSupport::TestCase def test_hash_params_for_application_json person = parse_body({:person => {:name => "David"}}.to_json,'application/json')[:person] assert_kind_of Hash, person diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1fea82e564..3e656fa51b 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -27,7 +27,7 @@ module Backoffice end end -class ResourcesTest < Test::Unit::TestCase +class ResourcesTest < ActionController::TestCase # The assertions in these tests are incompatible with the hash method # optimisation. This could indicate user level problems def setup diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index a23428804a..02eb447f31 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' require 'controller/fake_controllers' -class TestTest < Test::Unit::TestCase +class TestTest < ActionController::TestCase class TestController < ActionController::Base def no_op render :text => 'dummy' @@ -24,7 +24,7 @@ class TestTest < Test::Unit::TestCase end def render_raw_post - raise Test::Unit::AssertionFailedError, "#raw_post is blank" if request.raw_post.blank? + raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank? render :text => request.raw_post end @@ -580,7 +580,7 @@ XML assert_equal @response.redirect_url, redirect_to_url # Must be a :redirect response. - assert_raise(Test::Unit::AssertionFailedError) do + assert_raise(ActiveSupport::TestCase::Assertion) do assert_redirected_to 'created resource' end end @@ -602,9 +602,9 @@ XML end end -class CleanBacktraceTest < Test::Unit::TestCase +class CleanBacktraceTest < ActionController::TestCase def test_should_reraise_the_same_object - exception = Test::Unit::AssertionFailedError.new('message') + exception = ActiveSupport::TestCase::Assertion.new('message') clean_backtrace { raise exception } rescue => caught assert_equal exception.object_id, caught.object_id @@ -613,7 +613,7 @@ class CleanBacktraceTest < Test::Unit::TestCase def test_should_clean_assertion_lines_from_backtrace path = File.expand_path("#{File.dirname(__FILE__)}/../../lib/action_controller") - exception = Test::Unit::AssertionFailedError.new('message') + exception = ActiveSupport::TestCase::Assertion.new('message') exception.set_backtrace ["#{path}/abc", "#{path}/assertions/def"] clean_backtrace { raise exception } rescue => caught @@ -629,21 +629,17 @@ class CleanBacktraceTest < Test::Unit::TestCase end end -class InferringClassNameTest < Test::Unit::TestCase +class InferringClassNameTest < ActionController::TestCase def test_determine_controller_class assert_equal ContentController, determine_class("ContentControllerTest") end def test_determine_controller_class_with_nonsense_name - assert_raises ActionController::NonInferrableControllerError do - determine_class("HelloGoodBye") - end + assert_nil determine_class("HelloGoodBye") end def test_determine_controller_class_with_sensible_name_where_no_controller_exists - assert_raises ActionController::NonInferrableControllerError do - determine_class("NoControllerWithThisNameTest") - end + assert_nil determine_class("NoControllerWithThisNameTest") end private diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 64e9a085ca..8bc343e2ea 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' ActionController::UrlRewriter -class UrlRewriterTests < Test::Unit::TestCase +class UrlRewriterTests < ActionController::TestCase def setup @request = ActionController::TestRequest.new @params = {} @@ -85,8 +85,7 @@ class UrlRewriterTests < Test::Unit::TestCase end end -class UrlWriterTests < Test::Unit::TestCase - +class UrlWriterTests < ActionController::TestCase class W include ActionController::UrlWriter end diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb index b289443129..418a81baa8 100644 --- a/actionpack/test/controller/verification_test.rb +++ b/actionpack/test/controller/verification_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class VerificationTest < Test::Unit::TestCase +class VerificationTest < ActionController::TestCase class TestController < ActionController::Base verify :only => :guarded_one, :params => "one", :add_flash => { :error => 'unguarded' }, diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index b859a92cbd..04e14d8eac 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class ViewLoadPathsTest < Test::Unit::TestCase +class ViewLoadPathsTest < ActionController::TestCase class TestController < ActionController::Base def self.controller_path() "test" end def rescue_action(e) raise end diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 9247a42d33..317a5cf28c 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -166,12 +166,10 @@ class ScrollsController < ActionController::Base end end -class AtomFeedTest < Test::Unit::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @controller = ScrollsController.new +class AtomFeedTest < ActionController::TestCase + tests ScrollsController + def setup @request.host = "www.nextangle.com" end -- cgit v1.2.3 From 4af46c4ba1c87333b24d820150f3ecc59165d92a Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 15:51:50 -0500 Subject: Update AR integration tests for TestCase changes --- actionpack/test/active_record_unit.rb | 2 ++ .../render_partial_with_record_identification_test.rb | 15 +++------------ 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb index 8eca6cc9bd..d8d2e00dc2 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -83,6 +83,8 @@ class ActiveRecordTestConnector end class ActiveRecordTestCase < ActionController::TestCase + include ActiveRecord::TestFixtures + # Set our fixture path if ActiveRecordTestConnector.able_to_connect self.fixture_path = [FIXTURE_LOAD_PATH] diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index 822a739112..147b270808 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -47,6 +47,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base end class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase + tests RenderPartialWithRecordIdentificationController fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots def test_rendering_partial_with_has_many_and_belongs_to_association @@ -155,12 +156,7 @@ module Fun end class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveRecordTestCase - def setup - @controller = Fun::NestedController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end + tests Fun::NestedController def test_render_with_record_in_nested_controller get :render_with_record_in_nested_controller @@ -176,12 +172,7 @@ class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveReco end class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < ActiveRecordTestCase - def setup - @controller = Fun::Serious::NestedDeeperController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end + tests Fun::Serious::NestedDeeperController def test_render_with_record_in_deeper_nested_controller get :render_with_record_in_deeper_nested_controller -- cgit v1.2.3 From 529c2716992490a6eab55486788ca0d35c17e60b Mon Sep 17 00:00:00 2001 From: Nick Sieger Date: Sat, 8 Nov 2008 03:49:25 +0530 Subject: Simplify dispatcher callbacks to eliminate unnecessary stale thread purging. [Nick Sieger, Pratik Naik] Signed-off-by: Pratik Naik --- actionpack/lib/action_controller/dispatcher.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index f3e173004a..2d5e80f0bb 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -23,7 +23,6 @@ module ActionController if defined?(ActiveRecord) after_dispatch :checkin_connections - before_dispatch { ActiveRecord::Base.verify_active_connections! } to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers } end -- cgit v1.2.3 From 110c044e20b0fd72f191299b89327fa1b1552c63 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:44:31 -0500 Subject: Use delete if the rhs is nil --- actionpack/test/template/asset_tag_helper_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index bade96fe17..1a3a6e86fa 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -239,7 +239,11 @@ class AssetTagHelperTest < ActionView::TestCase File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true) assert_equal 'Rails', image_tag('rails.png') ensure - ENV["RAILS_ASSET_ID"] = old_asset_id + if old_asset_id + ENV["RAILS_ASSET_ID"] = old_asset_id + else + ENV.delete("RAILS_ASSET_ID") + end end end -- cgit v1.2.3 From 99648c96721586b6dcdc1c0a9d5963991169b548 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:45:10 -0500 Subject: Don't worry about attribute ordering --- actionpack/test/template/atom_feed_helper_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 317a5cf28c..8a00a397ca 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -253,7 +253,8 @@ class AtomFeedTest < ActionController::TestCase def test_feed_xml_processing_instructions with_restful_routing(:scrolls) do get :index, :id => 'feed_with_xml_processing_instructions' - assert_match %r{<\?xml-stylesheet type="text/css" href="t.css"\?>}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body end end -- cgit v1.2.3 From c77e6ace66b34390e9c1e173c580d86e88915267 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:46:03 -0500 Subject: Check whether last arg is a Hash instead of duck-typing against [] --- actionpack/lib/action_view/helpers/atom_feed_helper.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index ccb7df212a..cd25684940 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -1,3 +1,5 @@ +require 'set' + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other # template languages). module ActionView @@ -121,6 +123,8 @@ module ActionView end class AtomBuilder + XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set + def initialize(xml) @xml = xml end @@ -140,14 +144,15 @@ module ActionView @xml.__send__(method, *arguments, &block) end end - + # True if the method name matches one of the five elements defined # in the Atom spec as potentially containing XHTML content and # if :type => 'xhtml' is, in fact, specified. def xhtml_block?(method, arguments) - %w( content rights title subtitle summary ).include?(method.to_s) && - arguments.last.respond_to?(:[]) && - arguments.last[:type].to_s == 'xhtml' + if XHTML_TAG_NAMES.include?(method.to_s) + last = arguments.last + last.is_a?(Hash) && last[:type].to_s == 'xhtml' + end end end -- cgit v1.2.3 From aaa2abf73fa39e0d455b4b781fb4d00e51d0bdc7 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:44:31 -0500 Subject: Use delete if the rhs is nil --- actionpack/test/template/asset_tag_helper_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index bade96fe17..1a3a6e86fa 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -239,7 +239,11 @@ class AssetTagHelperTest < ActionView::TestCase File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true) assert_equal 'Rails', image_tag('rails.png') ensure - ENV["RAILS_ASSET_ID"] = old_asset_id + if old_asset_id + ENV["RAILS_ASSET_ID"] = old_asset_id + else + ENV.delete("RAILS_ASSET_ID") + end end end -- cgit v1.2.3 From 07fe3370f8abe49b4c055d4eb8c39f1e73847eea Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:46:03 -0500 Subject: Check whether last arg is a Hash instead of duck-typing against [] --- actionpack/lib/action_view/helpers/atom_feed_helper.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index ccb7df212a..cd25684940 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -1,3 +1,5 @@ +require 'set' + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other # template languages). module ActionView @@ -121,6 +123,8 @@ module ActionView end class AtomBuilder + XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set + def initialize(xml) @xml = xml end @@ -140,14 +144,15 @@ module ActionView @xml.__send__(method, *arguments, &block) end end - + # True if the method name matches one of the five elements defined # in the Atom spec as potentially containing XHTML content and # if :type => 'xhtml' is, in fact, specified. def xhtml_block?(method, arguments) - %w( content rights title subtitle summary ).include?(method.to_s) && - arguments.last.respond_to?(:[]) && - arguments.last[:type].to_s == 'xhtml' + if XHTML_TAG_NAMES.include?(method.to_s) + last = arguments.last + last.is_a?(Hash) && last[:type].to_s == 'xhtml' + end end end -- cgit v1.2.3 From 425382d95fbc93efde52c7a1c369f3fbcba70b2e Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 17:45:10 -0500 Subject: Don't worry about attribute ordering --- actionpack/test/template/atom_feed_helper_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 9247a42d33..06af8d1d6a 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -255,7 +255,8 @@ class AtomFeedTest < Test::Unit::TestCase def test_feed_xml_processing_instructions with_restful_routing(:scrolls) do get :index, :id => 'feed_with_xml_processing_instructions' - assert_match %r{<\?xml-stylesheet type="text/css" href="t.css"\?>}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body + assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body end end -- cgit v1.2.3 From 0be5bc3f5981f11d81c24ccfb97863a69406cf9d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Nov 2008 21:50:39 -0500 Subject: Work around ruby 1.9 segfault --- actionpack/test/controller/session/cookie_store_test.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionpack') diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 010c00fa14..30422314a1 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -266,6 +266,7 @@ class CookieStoreTest < Test::Unit::TestCase @options = self.class.default_session_options.merge(options) session = CGI::Session.new(cgi, @options) + ObjectSpace.undefine_finalizer(session) assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}" assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}" -- cgit v1.2.3 From 5cc27f2b0302698ef517755df41f3212587bceb9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Nov 2008 18:45:19 +0530 Subject: Add some basic controller logging tests --- actionpack/test/controller/logging_test.rb | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 actionpack/test/controller/logging_test.rb (limited to 'actionpack') diff --git a/actionpack/test/controller/logging_test.rb b/actionpack/test/controller/logging_test.rb new file mode 100644 index 0000000000..3c936854dd --- /dev/null +++ b/actionpack/test/controller/logging_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' + +class LoggingController < ActionController::Base + def show + render :nothing => true + end +end + +class LoggingTest < ActionController::TestCase + tests LoggingController + + class MockLogger + attr_reader :logged + + def method_missing(method, *args) + @logged ||= [] + @logged << args.first + end + end + + setup :set_logger + + def test_logging_without_parameters + get :show + assert_equal 2, logs.size + assert_nil logs.detect {|l| l =~ /Parameters/ } + end + + def test_logging_with_parameters + get :show, :id => 10 + assert_equal 3, logs.size + + params = logs.detect {|l| l =~ /Parameters/ } + assert_equal 'Parameters: {"id"=>"10"}', params + end + + private + + def set_logger + @controller.logger = MockLogger.new + end + + def logs + @logs ||= @controller.logger.logged.compact.map {|l| l.strip} + end +end -- cgit v1.2.3 From 8a1f91338131c69b88fb32e7f0078cef28421437 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 22:35:30 -0500 Subject: Workaround lack of Mocha on 1.9 (hasn't been updated for minitest yet) --- actionpack/test/controller/cgi_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb index 813171857a..87fbf1a4cd 100644 --- a/actionpack/test/controller/cgi_test.rb +++ b/actionpack/test/controller/cgi_test.rb @@ -48,7 +48,8 @@ class BaseCgiTest < Test::Unit::TestCase # some developers have grown accustomed to using comma in cookie values. @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"} @cgi = CGI.new - @cgi.stubs(:env_table).returns(@request_hash) + class << @cgi; attr_accessor :env_table end + @cgi.env_table = @request_hash @request = ActionController::CgiRequest.new(@cgi) end -- cgit v1.2.3 From eda9f49d5781a5f9a5090492a76436598faf3948 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 22:43:29 -0500 Subject: Ruby 1.9 compat: CGI switched back to Tempfile --- actionpack/test/controller/request_test.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index 2dc2ed9965..7e264289e6 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -735,11 +735,7 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase file = params['file'] foo = params['foo'] - if RUBY_VERSION > '1.9' - assert_kind_of File, file - else - assert_kind_of Tempfile, file - end + assert_kind_of Tempfile, file assert_equal 'file.txt', file.original_filename assert_equal "text/plain", file.content_type @@ -753,11 +749,9 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase assert_equal 'bar', params['foo'] file = params['file'] - if RUBY_VERSION > '1.9' - assert_kind_of File, file - else - assert_kind_of Tempfile, file - end + + assert_kind_of Tempfile, file + assert_equal 'file.txt', file.original_filename assert_equal "text/plain", file.content_type assert ('a' * 20480) == file.read -- cgit v1.2.3 From 8bfd5edbcf842c32c5fa60d109cf7f4cd9cf60b4 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 22:43:56 -0500 Subject: Wrap straggling mocha user with uses_mocha block --- actionpack/test/controller/caching_test.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index ad160970cc..eb106c1eb4 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -291,11 +291,13 @@ class ActionCacheTest < ActionController::TestCase ActionController::Base.use_accept_header = old_use_accept_header 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 - @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once - get :index + uses_mocha 'test action cache' do + 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 + @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once + get :index + end end def test_action_cache_with_custom_cache_path -- cgit v1.2.3 From d87d3f76d547df7c956fe4fab324cad9396477ae Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 8 Nov 2008 22:46:13 -0500 Subject: Ruby 1.9 compat: rescue Exception since minitest's assertion doesn't subclass StandardError --- actionpack/test/controller/test_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 02eb447f31..ee7b8ade8c 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -606,7 +606,7 @@ class CleanBacktraceTest < ActionController::TestCase def test_should_reraise_the_same_object exception = ActiveSupport::TestCase::Assertion.new('message') clean_backtrace { raise exception } - rescue => caught + rescue Exception => caught assert_equal exception.object_id, caught.object_id assert_equal exception.message, caught.message end @@ -616,7 +616,7 @@ class CleanBacktraceTest < ActionController::TestCase exception = ActiveSupport::TestCase::Assertion.new('message') exception.set_backtrace ["#{path}/abc", "#{path}/assertions/def"] clean_backtrace { raise exception } - rescue => caught + rescue Exception => caught assert_equal ["#{path}/abc"], caught.backtrace end -- cgit v1.2.3 From 5db9f9b3ad47fadf0b3f12ada1c2ea7b9c15ded5 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 10 Nov 2008 17:41:07 -0800 Subject: Pare down object creation during route building --- .../lib/action_controller/routing/builder.rb | 53 ++++++++++------------ .../lib/action_controller/routing/routing_ext.rb | 4 ++ 2 files changed, 27 insertions(+), 30 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index 7b888fa8d2..d4e501e780 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -1,23 +1,16 @@ module ActionController module Routing class RouteBuilder #:nodoc: - attr_accessor :separators, :optional_separators + attr_reader :separators, :optional_separators + attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp def initialize - self.separators = Routing::SEPARATORS - self.optional_separators = %w( / ) - end - - def separator_pattern(inverted = false) - "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" - end - - def interval_regexp - Regexp.new "(.*?)(#{separators.source}|$)" - end + @separators = Routing::SEPARATORS + @optional_separators = %w( / ) - def multiline_regexp?(expression) - expression.options & Regexp::MULTILINE == Regexp::MULTILINE + @separator_regexp = /[#{Regexp.escape(separators.join)}]/ + @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/ + @interval_regexp = /(.*?)(#{separator_regexp}|$)/ end # Accepts a "route path" (a string defining a route), and returns the array @@ -30,7 +23,7 @@ module ActionController rest, segments = path, [] until rest.empty? - segment, rest = segment_for rest + segment, rest = segment_for(rest) segments << segment end segments @@ -39,20 +32,20 @@ module ActionController # A factory method that returns a new segment instance appropriate for the # format of the given string. def segment_for(string) - segment = case string - when /\A:(\w+)/ - key = $1.to_sym - case key - when :controller then ControllerSegment.new(key) - else DynamicSegment.new key - end - when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) - when /\A\?(.*?)\?/ - StaticSegment.new($1, :optional => true) - when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) - when Regexp.new(separator_pattern) then - DividerSegment.new($&, :optional => (optional_separators.include? $&)) - end + segment = + case string + when /\A:(\w+)/ + key = $1.to_sym + key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) + when /\A\*(\w+)/ + PathSegment.new($1.to_sym, :optional => true) + when /\A\?(.*?)\?/ + StaticSegment.new($1, :optional => true) + when nonseparator_regexp + StaticSegment.new($1) + when separator_regexp + DividerSegment.new($&, :optional => optional_separators.include?($&)) + end [segment, $~.post_match] end @@ -98,7 +91,7 @@ module ActionController if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end - if multiline_regexp?(requirement) + if requirement.multiline? raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" end segment.regexp = requirement diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb index 5f4ba90d0c..4a82b2af5f 100644 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ b/actionpack/lib/action_controller/routing/routing_ext.rb @@ -27,6 +27,10 @@ class Regexp #:nodoc: Regexp.new("|#{source}").match('').captures.length end + def multiline? + options & MULTILINE == MULTILINE + end + class << self def optionalize(pattern) case unoptionalize(pattern) -- cgit v1.2.3 From 278b6cd9529f33286449a9be18f1903687814d3f Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 10 Nov 2008 19:53:53 -0800 Subject: Eliminate excess Regexp creation due to capture counting --- actionpack/lib/action_controller/routing/route.rb | 2 +- .../lib/action_controller/routing/segments.rb | 28 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb index 3b2cb28545..a610ec7e84 100644 --- a/actionpack/lib/action_controller/routing/route.rb +++ b/actionpack/lib/action_controller/routing/route.rb @@ -219,7 +219,7 @@ module ActionController next_capture = 1 extraction = segments.collect do |segment| x = segment.match_extraction(next_capture) - next_capture += Regexp.new(segment.regexp_chunk).number_of_captures + next_capture += segment.number_of_captures x end extraction.compact diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index e5f174ae2c..f6b03edcca 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -13,6 +13,10 @@ module ActionController @is_optional = false end + def number_of_captures + Regexp.new(regexp_chunk).number_of_captures + end + def extraction_code nil end @@ -84,6 +88,10 @@ module ActionController optional? ? Regexp.optionalize(chunk) : chunk end + def number_of_captures + 0 + end + def build_pattern(pattern) escaped = Regexp.escape(value) if optional? && ! pattern.empty? @@ -194,10 +202,16 @@ module ActionController end end + def number_of_captures + if regexp + regexp.number_of_captures + 1 + else + 1 + end + end + def build_pattern(pattern) - chunk = regexp_chunk - chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 - pattern = "#{chunk}#{pattern}" + pattern = "#{regexp_chunk}#{pattern}" optional? ? Regexp.optionalize(pattern) : pattern end @@ -230,6 +244,10 @@ module ActionController "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" end + def number_of_captures + 1 + end + # Don't URI.escape the controller name since it may contain slashes. def interpolation_chunk(value_code = local_name) "\#{#{value_code}.to_s}" @@ -275,6 +293,10 @@ module ActionController regexp || "(.*)" end + def number_of_captures + regexp ? regexp.number_of_captures : 1 + end + def optionality_implied? true end -- cgit v1.2.3 From 44a3009ff068bf080de6764a8c884fbf0ceb920e Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Wed, 12 Nov 2008 11:00:17 +0000 Subject: Add :only/:except options to map.resources This allows people with huge numbers of resource routes to cut down on the memory consumption caused by the generated code. Signed-off-by: Michael Koziarski [#1215 state:committed] --- actionpack/lib/action_controller/resources.rb | 122 +++++++++++++------ actionpack/test/controller/resources_test.rb | 162 ++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 37 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 872b0dab3d..de529e23ff 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -42,7 +42,11 @@ module ActionController # # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer module Resources + INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except + class Resource #:nodoc: + DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy + attr_reader :collection_methods, :member_methods, :new_methods attr_reader :path_prefix, :name_prefix, :path_segment attr_reader :plural, :singular @@ -57,6 +61,7 @@ module ActionController arrange_actions add_default_actions + set_allowed_actions set_prefixes end @@ -113,6 +118,10 @@ module ActionController @singular.to_s == @plural.to_s end + def has_action?(action) + !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + end + protected def arrange_actions @collection_methods = arrange_actions_by_methods(options.delete(:collection)) @@ -125,6 +134,30 @@ module ActionController add_default_action(new_methods, :get, :new) end + def set_allowed_actions + only, except = @options.values_at(:only, :except) + @allowed_actions ||= {} + + if only == :all || except == :none + only = nil + except = [] + elsif only == :none || except == :all + only = [] + except = nil + end + + if only + @allowed_actions[:only] = Array(only).map(&:to_sym) + elsif except + @allowed_actions[:except] = Array(except).map(&:to_sym) + end + end + + def action_allowed?(action) + only, except = @allowed_actions.values_at(:only, :except) + (!only || only.include?(action)) && (!except || !except.include?(action)) + end + def set_prefixes @path_prefix = options.delete(:path_prefix) @name_prefix = options.delete(:name_prefix) @@ -353,6 +386,25 @@ module ActionController # # map.resources :users, :has_many => { :posts => :comments }, :shallow => true # + # * :only and :except - Specify which of the seven default actions should be routed to. + # + # :only and :except may be set to :all, :none, an action name or a + # list of action names. By default, routes are generated for all seven actions. + # + # For example: + # + # map.resources :posts, :only => [:index, :show] do |post| + # post.resources :comments, :except => [:update, :destroy] + # end + # # --> GET /posts (maps to the PostsController#index action) + # # --> POST /posts (fails) + # # --> GET /posts/1 (maps to the PostsController#show action) + # # --> DELETE /posts/1 (fails) + # # --> POST /posts/1/comments (maps to the CommentsController#create action) + # # --> PUT /posts/1/comments/1 (fails) + # + # The :only and :except options are inherited by any nested resource(s). + # # If map.resources is called with multiple resources, they all get the same options applied. # # Examples: @@ -478,7 +530,7 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end end end @@ -495,7 +547,7 @@ module ActionController map_associations(resource, options) if block_given? - with_options(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], &block) + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end end end @@ -507,7 +559,7 @@ module ActionController name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" Array(options[:has_one]).each do |association| - resource(association, :path_prefix => path_prefix, :name_prefix => name_prefix, :namespace => options[:namespace], :shallow => options[:shallow]) + resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) end end @@ -522,7 +574,7 @@ module ActionController map_has_many_associations(resource, association, options) end when Symbol, String - resources(associations, :path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :namespace => options[:namespace], :shallow => options[:shallow], :has_many => options[:has_many]) + resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) else end end @@ -531,41 +583,39 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - action_options = action_options_for(action, resource, m) - map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options) + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) end end end end def map_default_collection_actions(map, resource) - index_action_options = action_options_for("index", resource) index_route_name = "#{resource.name_prefix}#{resource.plural}" if resource.uncountable? index_route_name << "_index" end - map_named_routes(map, index_route_name, resource.path, index_action_options) - - create_action_options = action_options_for("create", resource) - map_unnamed_routes(map, resource.path, create_action_options) + map_resource_routes(map, resource, :index, resource.path, index_route_name) + map_resource_routes(map, resource, :create, resource.path) end def map_default_singleton_actions(map, resource) - create_action_options = action_options_for("create", resource) - map_unnamed_routes(map, resource.path, create_action_options) + map_resource_routes(map, resource, :create, resource.path) end def map_new_actions(map, resource) resource.new_methods.each do |method, actions| actions.each do |action| - action_options = action_options_for(action, resource, method) - if action == :new - map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options) - else - map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options) + route_path = resource.new_path + route_name = "new_#{resource.name_prefix}#{resource.singular}" + + unless action == :new + route_path = "#{route_path}#{resource.action_separator}#{action}" + route_name = "#{action}_#{route_name}" end + + map_resource_routes(map, resource, action, route_path, route_name, method) end end end @@ -574,34 +624,32 @@ module ActionController resource.member_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - action_options = action_options_for(action, resource, m) - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_named_routes(map, "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m) end end end - show_action_options = action_options_for("show", resource) - map_named_routes(map, "#{resource.shallow_name_prefix}#{resource.singular}", resource.member_path, show_action_options) - - update_action_options = action_options_for("update", resource) - map_unnamed_routes(map, resource.member_path, update_action_options) - - destroy_action_options = action_options_for("destroy", resource) - map_unnamed_routes(map, resource.member_path, destroy_action_options) + map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}") + map_resource_routes(map, resource, :update, resource.member_path) + map_resource_routes(map, resource, :destroy, resource.member_path) end - def map_unnamed_routes(map, path_without_format, options) - map.connect(path_without_format, options) - map.connect("#{path_without_format}.:format", options) - end - - def map_named_routes(map, name, path_without_format, options) - map.named_route(name, path_without_format, options) - map.named_route("formatted_#{name}", "#{path_without_format}.:format", options) + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) + if resource.has_action?(action) + action_options = action_options_for(action, resource, method) + formatted_route_path = "#{route_path}.:format" + + if route_name + map.named_route(route_name, route_path, action_options) + map.named_route("formatted_#{route_name}", formatted_route_path, action_options) + else + map.connect(route_path, action_options) + map.connect(formatted_route_path, action_options) + end + end end def add_conditions_for(conditions, method) diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1fea82e564..2a86577d8c 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -14,6 +14,8 @@ class LogosController < ResourcesController; end class AccountsController < ResourcesController; end class AdminController < ResourcesController; end +class ProductsController < ResourcesController; end +class ImagesController < ResourcesController; end module Backoffice class ProductsController < ResourcesController; end @@ -776,6 +778,121 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_resource_has_only_show_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + end + end + + def test_singleton_resource_has_only_show_action + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :show + end + + assert_singleton_resource_allowed_routes('accounts', {}, :show, [:index, :new, :create, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :show, [:index, :new, :create, :edit, :update, :destroy]) + end + end + + def test_resource_does_not_have_destroy_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :destroy + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy) + end + end + + def test_singleton_resource_does_not_have_destroy_action + with_routing do |set| + set.draw do |map| + map.resource :account, :except => :destroy + end + + assert_singleton_resource_allowed_routes('accounts', {}, [:new, :create, :show, :edit, :update], :destroy) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [:new, :create, :show, :edit, :update], :destroy) + end + end + + def test_resource_has_only_collection_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :all, :collection => { :sale => :get } + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'products', :action => 'sale' }, :path => 'products/sale', :method => :get) + assert_recognizes({ :controller => 'products', :action => 'sale', :format => 'xml' }, :path => 'products/sale.xml', :method => :get) + end + end + + def test_resource_has_only_member_action + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :all, :member => { :preview => :get } + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1' }, :path => 'products/1/preview', :method => :get) + assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1', :format => 'xml' }, :path => 'products/1/preview.xml', :method => :get) + end + end + + def test_singleton_resource_has_only_member_action + with_routing do |set| + set.draw do |map| + map.resource :account, :except => :all, :member => { :signup => :get } + end + + assert_singleton_resource_allowed_routes('accounts', {}, [], [:new, :create, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [], [:new, :create, :show, :edit, :update, :destroy]) + + assert_recognizes({ :controller => 'accounts', :action => 'signup' }, :path => 'account/signup', :method => :get) + assert_recognizes({ :controller => 'accounts', :action => 'signup', :format => 'xml' }, :path => 'account/signup.xml', :method => :get) + end + end + + def test_nested_resource_inherits_only_show_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show do |product| + product.resources :images + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + end + end + + def test_nested_resource_has_only_show_and_member_action + with_routing do |set| + set.draw do |map| + map.resources :products, :only => [:index, :show] do |product| + product.resources :images, :member => { :thumbnail => :get }, :only => :show + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images') + + assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2' }, :path => 'products/1/images/2/thumbnail', :method => :get) + assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2', :format => 'jpg' }, :path => 'products/1/images/2/thumbnail.jpg', :method => :get) + end + end + protected def with_restful_routing(*args) with_routing do |set| @@ -979,6 +1096,51 @@ class ResourcesTest < Test::Unit::TestCase end end + def assert_resource_allowed_routes(controller, options, shallow_options, allowed, not_allowed, path = controller) + shallow_path = "#{path}/#{shallow_options[:id]}" + format = options[:format] && ".#{options[:format]}" + options.merge!(:controller => controller) + shallow_options.merge!(options) + + assert_whether_allowed(allowed, not_allowed, options, 'index', "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'show', "#{shallow_path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit', "#{shallow_path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy', "#{shallow_path}#{format}", :delete) + end + + def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize) + format = options[:format] && ".#{options[:format]}" + options.merge!(:controller => controller) + + assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post) + assert_whether_allowed(allowed, not_allowed, options, 'show', "#{path}#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'edit', "#{path}/edit#{format}", :get) + assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :put) + assert_whether_allowed(allowed, not_allowed, options, 'destroy', "#{path}#{format}", :delete) + end + + def assert_whether_allowed(allowed, not_allowed, options, action, path, method) + action = action.to_sym + options = options.merge(:action => action.to_s) + path_options = { :path => path, :method => method } + + if Array(allowed).include?(action) + assert_recognizes options, path_options + elsif Array(not_allowed).include?(action) + assert_not_recognizes options, path_options + end + end + + def assert_not_recognizes(expected_options, path) + assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Test::Unit::AssertionFailedError do + assert_recognizes(expected_options, path) + end + end + def distinct_routes? (r1, r2) if r1.conditions == r2.conditions and r1.requirements == r2.requirements then if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then -- cgit v1.2.3 From fbbcd6f29aeccc938b97b5c01717365f8b67912c Mon Sep 17 00:00:00 2001 From: Jeff Cohen Date: Fri, 31 Oct 2008 23:10:44 -0500 Subject: Changed request forgery protection to only worry about HTML-formatted content requests. Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/mime_type.rb | 4 +- .../request_forgery_protection.rb | 2 +- actionpack/lib/action_controller/test_process.rb | 1 + .../controller/request_forgery_protection_test.rb | 118 ++++++++++++--------- 4 files changed, 70 insertions(+), 55 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 26edca3b69..f43ae721c6 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -19,7 +19,7 @@ module Mime # end # end class Type - @@html_types = Set.new [:html, :all] + @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] cattr_reader :html_types, :unverifiable_types @@ -167,7 +167,7 @@ module Mime # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See # ActionController::RequestForgerProtection. def verify_request? - !@@unverifiable_types.include?(to_sym) + html? end def html? diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 05a6d8bb79..3e0e94a06b 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: end def verifiable_request_format? - request.content_type.nil? || request.content_type.verify_request? + !request.content_type.nil? && request.content_type.verify_request? end # Sets the token value for the current session. Pass a :secret option diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 7a31f0e8d5..1e3a646bc9 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -395,6 +395,7 @@ module ActionController #:nodoc: @html_document = nil @request.env['REQUEST_METHOD'] ||= "GET" + @request.action = action.to_s parameters ||= {} diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index f7adaa7d4e..5669b8f358 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -77,57 +77,61 @@ module RequestForgeryProtectionTests ActionController::Base.request_forgery_protection_token = nil end + def test_should_render_form_with_token_tag - get :index - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + get :index + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + end + + def test_should_render_button_to_with_token_tag + get :show_button + assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + end + + def test_should_render_remote_form_with_only_one_token_parameter + get :remote_form + assert_equal 1, @response.body.scan(@token).size + end + + def test_should_allow_get + get :index + assert_response :success + end + + def test_should_allow_post_without_token_on_unsafe_action + post :unsafe + assert_response :success + end + + def test_should_not_allow_html_post_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } end - def test_should_render_button_to_with_token_tag - get :show_button - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token - end - - def test_should_render_remote_form_with_only_one_token_parameter - get :remote_form - assert_equal 1, @response.body.scan(@token).size - end - - def test_should_allow_get - get :index - assert_response :success + def test_should_not_allow_html_put_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } end - def test_should_allow_post_without_token_on_unsafe_action - post :unsafe - assert_response :success + def test_should_not_allow_html_delete_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raises(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } end - def test_should_not_allow_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { post :index } - end - - def test_should_not_allow_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { put :index } - end - - def test_should_not_allow_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { delete :index } - end - - def test_should_not_allow_api_formatted_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + def test_should_allow_api_formatted_post_without_token + assert_nothing_raised do post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_nothing_raised do put :index, :format => 'xml' end end - def test_should_not_allow_api_formatted_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + def test_should_allow_api_formatted_delete_without_token + assert_nothing_raised do delete :index, :format => 'xml' end end @@ -174,16 +178,20 @@ module RequestForgeryProtectionTests end end - def test_should_not_allow_xhr_post_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index } + def test_should_allow_xhr_post_without_token + assert_nothing_raised { xhr :post, :index } + end + def test_should_not_allow_xhr_post_with_html_without_token + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + assert_raise(ActionController::InvalidAuthenticityToken) { xhr :post, :index } end - def test_should_not_allow_xhr_put_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :put, :index } + def test_should_allow_xhr_put_without_token + assert_nothing_raised { xhr :put, :index } end - def test_should_not_allow_xhr_delete_without_token - assert_raises(ActionController::InvalidAuthenticityToken) { xhr :delete, :index } + def test_should_allow_xhr_delete_without_token + assert_nothing_raised { xhr :delete, :index } end def test_should_allow_post_with_token @@ -227,6 +235,7 @@ class RequestForgeryProtectionControllerTest < Test::Unit::TestCase def setup @controller = RequestForgeryProtectionController.new @request = ActionController::TestRequest.new + @request.format = :html @response = ActionController::TestResponse.new class << @request.session def session_id() '123' end @@ -248,11 +257,11 @@ class RequestForgeryProtectionWithoutSecretControllerTest < Test::Unit::TestCase ActionController::Base.request_forgery_protection_token = :authenticity_token end - def test_should_raise_error_without_secret - assert_raises ActionController::InvalidAuthenticityToken do - get :index - end - end + # def test_should_raise_error_without_secret + # assert_raises ActionController::InvalidAuthenticityToken do + # get :index + # end + # end end class CsrfCookieMonsterControllerTest < Test::Unit::TestCase @@ -304,10 +313,15 @@ class SessionOffControllerTest < Test::Unit::TestCase @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') end - def test_should_raise_correct_exception - @request.session = {} # session(:off) doesn't appear to work with controller tests - assert_raises(ActionController::InvalidAuthenticityToken) do - post :index, :authenticity_token => @token - end - end + # TODO: Rewrite this test. + # This test was passing but for the wrong reason. + # Sessions aren't really being turned off, so an exception was raised + # because sessions weren't on - not because the token didn't match. + # + # def test_should_raise_correct_exception + # @request.session = {} # session(:off) doesn't appear to work with controller tests + # assert_raises(ActionController::InvalidAuthenticityToken) do + # post :index, :authenticity_token => @token, :format => :html + # end + # end end -- cgit v1.2.3 From 00c46b5eeb858629ef1c7ab50f022aecccca42c3 Mon Sep 17 00:00:00 2001 From: rick Date: Wed, 12 Nov 2008 13:34:29 -0800 Subject: fix two MimeType failing test cases Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/mime_type.rb | 5 ++++- actionpack/test/controller/mime_type_test.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index f43ae721c6..48c4c1ee1e 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -20,8 +20,11 @@ module Mime # end class Type @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] + cattr_reader :html_types + + # UNUSED, deprecate? @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] - cattr_reader :html_types, :unverifiable_types + cattr_reader :unverifiable_types # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb index f16a3c68b4..4cfaf38ac7 100644 --- a/actionpack/test/controller/mime_type_test.rb +++ b/actionpack/test/controller/mime_type_test.rb @@ -61,7 +61,9 @@ class MimeTypeTest < Test::Unit::TestCase types.each do |type| mime = Mime.const_get(type.to_s.upcase) assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?" - (types - [type]).each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } + invalid_types = types - [type] + invalid_types.delete(:html) if Mime::Type.html_types.include?(type) + invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } end end @@ -71,14 +73,12 @@ class MimeTypeTest < Test::Unit::TestCase end def test_verifiable_mime_types - unverified_types = Mime::Type.unverifiable_types all_types = Mime::SET.to_a.map(&:to_sym) all_types.uniq! # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } - - unverified, verified = all_types.partition { |type| Mime::Type.unverifiable_types.include? type } - assert verified.all? { |type| Mime.const_get(type.to_s.upcase).verify_request? }, "Not all Mime Types are verified: #{verified.inspect}" - assert unverified.all? { |type| !Mime.const_get(type.to_s.upcase).verify_request? }, "Some Mime Types are verified: #{unverified.inspect}" + verified, unverified = all_types.partition { |type| Mime::Type.html_types.include? type } + assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is not verified: #{type.inspect}" } + assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is verified: #{type.inspect}" } end end -- cgit v1.2.3 From f1ad8b48aae3ee26613b3e77bc0056e120096846 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 13 Nov 2008 11:19:53 +0100 Subject: Instead of overriding html_types, base the verification on browser_generated_types. Also Deprecate the old unverifiable types. [#1145 state:committed] --- actionpack/lib/action_controller/mime_type.rb | 21 +++++++++++++++++---- actionpack/test/controller/mime_type_test.rb | 6 +++--- 2 files changed, 20 insertions(+), 7 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 48c4c1ee1e..8ca3a70341 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -19,12 +19,21 @@ module Mime # end # end class Type - @@html_types = Set.new [:html, :url_encoded_form, :multipart_form, :all] + @@html_types = Set.new [:html, :all] cattr_reader :html_types - # UNUSED, deprecate? + # 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 + # only needs to protect against these types. + @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form] + cattr_reader :browser_generated_types + + @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] - cattr_reader :unverifiable_types + def self.unverifiable_types + ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller) + @@unverifiable_types + end # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: @@ -170,13 +179,17 @@ module Mime # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See # ActionController::RequestForgerProtection. def verify_request? - html? + browser_generated? end def html? @@html_types.include?(to_sym) || @string =~ /html/ end + def browser_generated? + @@browser_generated_types.include?(to_sym) + end + private def method_missing(method, *args) if method.to_s =~ /(\w+)\?$/ diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb index 4cfaf38ac7..21ae0419f1 100644 --- a/actionpack/test/controller/mime_type_test.rb +++ b/actionpack/test/controller/mime_type_test.rb @@ -77,8 +77,8 @@ class MimeTypeTest < Test::Unit::TestCase all_types.uniq! # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } - verified, unverified = all_types.partition { |type| Mime::Type.html_types.include? type } - assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is not verified: #{type.inspect}" } - assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Mime Type is verified: #{type.inspect}" } + verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type } + assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" } + assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" } end end -- cgit v1.2.3 From 4c0921024471c0463d67f8b8fb6a115a94d343aa Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Thu, 13 Nov 2008 14:31:36 +0000 Subject: Fix map.resources to always generate named routes if they're needed Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/resources.rb | 13 ++--- actionpack/test/controller/resources_test.rb | 78 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index de529e23ff..d6cc4aa418 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -597,11 +597,11 @@ module ActionController end map_resource_routes(map, resource, :index, resource.path, index_route_name) - map_resource_routes(map, resource, :create, resource.path) + map_resource_routes(map, resource, :create, resource.path, index_route_name) end def map_default_singleton_actions(map, resource) - map_resource_routes(map, resource, :create, resource.path) + map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") end def map_new_actions(map, resource) @@ -632,9 +632,10 @@ module ActionController end end - map_resource_routes(map, resource, :show, resource.member_path, "#{resource.shallow_name_prefix}#{resource.singular}") - map_resource_routes(map, resource, :update, resource.member_path) - map_resource_routes(map, resource, :destroy, resource.member_path) + route_path = "#{resource.shallow_name_prefix}#{resource.singular}" + map_resource_routes(map, resource, :show, resource.member_path, route_path) + map_resource_routes(map, resource, :update, resource.member_path, route_path) + map_resource_routes(map, resource, :destroy, resource.member_path, route_path) end def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) @@ -642,7 +643,7 @@ module ActionController action_options = action_options_for(action, resource, method) formatted_route_path = "#{route_path}.:format" - if route_name + if route_name && @set.named_routes[route_name.to_sym].nil? map.named_route(route_name, route_path, action_options) map.named_route("formatted_#{route_name}", formatted_route_path, action_options) else diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 2a86577d8c..1f1f7b8a2c 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -822,6 +822,84 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_resource_has_only_create_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :create + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy]) + + assert_not_nil set.named_routes[:products] + end + end + + def test_resource_has_only_update_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :update + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy]) + + assert_not_nil set.named_routes[:product] + end + end + + def test_resource_has_only_destroy_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :destroy + end + + assert_resource_allowed_routes('products', {}, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) + assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update]) + + assert_not_nil set.named_routes[:product] + end + end + + def test_singleton_resource_has_only_create_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :create + end + + assert_singleton_resource_allowed_routes('accounts', {}, :create, [:new, :show, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :create, [:new, :show, :edit, :update, :destroy]) + + assert_not_nil set.named_routes[:account] + end + end + + def test_singleton_resource_has_only_update_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :update + end + + assert_singleton_resource_allowed_routes('accounts', {}, :update, [:new, :create, :show, :edit, :destroy]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :update, [:new, :create, :show, :edit, :destroy]) + + assert_not_nil set.named_routes[:account] + end + end + + def test_singleton_resource_has_only_destroy_action_and_named_route + with_routing do |set| + set.draw do |map| + map.resource :account, :only => :destroy + end + + assert_singleton_resource_allowed_routes('accounts', {}, :destroy, [:new, :create, :show, :edit, :update]) + assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :destroy, [:new, :create, :show, :edit, :update]) + + assert_not_nil set.named_routes[:account] + end + end + def test_resource_has_only_collection_action with_routing do |set| set.draw do |map| -- cgit v1.2.3 From 4e9abdd7f1b4e05f8d1b50ddaa080b3ff63b92d9 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 13 Nov 2008 21:49:23 +0100 Subject: Tag helper should output an attribute with the value 'false' instead of omitting the attribute, if the associated option is false but not nil. --- actionpack/lib/action_view/helpers/tag_helper.rb | 10 ++++++---- actionpack/test/template/form_tag_helper_test.rb | 2 +- actionpack/test/template/tag_helper_test.rb | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index de08672d2d..d37ca766af 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -133,10 +133,12 @@ module ActionView unless options.blank? attrs = [] if escape - options.each do |key, value| - next unless value - value = BOOLEAN_ATTRIBUTES.include?(key) ? key : escape_once(value) - attrs << %(#{key}="#{value}") + options.each_pair do |key, value| + if BOOLEAN_ATTRIBUTES.include?(key) + attrs << %(#{key}="#{key}") if value + else + attrs << %(#{key}="#{escape_once(value)}") if !value.nil? + end end else attrs = options.map { |key, value| %(#{key}="#{value}") } diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index de82647813..f8add0bab1 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -235,7 +235,7 @@ class FormTagHelperTest < ActionView::TestCase assert_match VALID_HTML_ID, label_elem['for'] end - def test_boolean_optios + def test_boolean_options assert_dom_equal %(), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) assert_dom_equal %(), select_tag("people", "", :multiple => true) diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index fc49d340ef..ef88cae5b8 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -19,6 +19,10 @@ class TagHelperTest < ActionView::TestCase assert_equal "

", tag("p", :ignored => nil) end + def test_tag_options_accepts_false_option + assert_equal "

", tag("p", :value => false) + end + def test_tag_options_accepts_blank_option assert_equal "

", tag("p", :included => '') end -- cgit v1.2.3 From 94d6716324126028b89dde886f160474049b1b0c Mon Sep 17 00:00:00 2001 From: hiroshi Date: Mon, 3 Nov 2008 14:09:07 +0900 Subject: Make polymorphic_url compact given array [#1317 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_controller/polymorphic_routes.rb | 2 +- actionpack/test/controller/polymorphic_routes_test.rb | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index e7d5031f1a..97389dfc20 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2 or 2.2 final]* +* Fixed that polymorphic_url should compact given array #1317 [hiroshi] + * Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 [antonmos/Ryan McGeary] * Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov] diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index cc228c4230..2644c7f7c7 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -73,7 +73,7 @@ module ActionController # def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) - record_or_hash_or_array = record_or_hash_or_array.dup + record_or_hash_or_array = record_or_hash_or_array.compact end record = extract_record(record_or_hash_or_array) diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 6ddf2826cd..620f2b3ab5 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -169,6 +169,17 @@ uses_mocha 'polymorphic URL helpers' do polymorphic_url([@article, :response, @tag], :format => :pdf) end + def test_nesting_with_array_containing_nil + expects(:article_response_url).with(@article) + polymorphic_url([@article, nil, :response]) + end + + def test_with_array_containing_single_object + @article.save + expects(:article_url).with(@article) + polymorphic_url([nil, @article]) + end + # TODO: Needs to be updated to correctly know about whether the object is in a hash or not def xtest_with_hash expects(:article_url).with(@article) -- cgit v1.2.3 From 2ecec6052f7f290252a9fd9cc27ec804c7aad36c Mon Sep 17 00:00:00 2001 From: Tom Stuart Date: Thu, 13 Nov 2008 20:00:11 +0000 Subject: Make inheritance of map.resources :only/:except options behave more predictably Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/resources.rb | 33 ++++++++++++--------------- actionpack/test/controller/resources_test.rb | 26 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 19 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index d6cc4aa418..7700b9d4d0 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -42,7 +42,7 @@ module ActionController # # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer module Resources - INHERITABLE_OPTIONS = :namespace, :shallow, :only, :except + INHERITABLE_OPTIONS = :namespace, :shallow, :actions class Resource #:nodoc: DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy @@ -119,7 +119,7 @@ module ActionController end def has_action?(action) - !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + !DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action) end protected @@ -135,29 +135,24 @@ module ActionController end def set_allowed_actions - only, except = @options.values_at(:only, :except) - @allowed_actions ||= {} + only = @options.delete(:only) + except = @options.delete(:except) - if only == :all || except == :none - only = nil - except = [] + if only && except + raise ArgumentError, 'Please supply either :only or :except, not both.' + elsif only == :all || except == :none + options[:actions] = DEFAULT_ACTIONS elsif only == :none || except == :all - only = [] - except = nil - end - - if only - @allowed_actions[:only] = Array(only).map(&:to_sym) + options[:actions] = [] + elsif only + options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym) elsif except - @allowed_actions[:except] = Array(except).map(&:to_sym) + options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym) + else + # leave options[:actions] alone end end - def action_allowed?(action) - only, except = @allowed_actions.values_at(:only, :except) - (!only || only.include?(action)) && (!except || !except.include?(action)) - end - def set_prefixes @path_prefix = options.delete(:path_prefix) @name_prefix = options.delete(:name_prefix) diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1f1f7b8a2c..04f7a0a528 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -971,6 +971,32 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_nested_resource_ignores_only_option + with_routing do |set| + set.draw do |map| + map.resources :products, :only => :show do |product| + product.resources :images, :except => :destroy + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images') + end + end + + def test_nested_resource_ignores_except_option + with_routing do |set| + set.draw do |map| + map.resources :products, :except => :show do |product| + product.resources :images, :only => :destroy + end + end + + assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') + assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images') + end + end + protected def with_restful_routing(*args) with_routing do |set| -- cgit v1.2.3 From 61e43700b85de753b6254893d5365e04d3465b9a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 14 Nov 2008 12:26:50 +0100 Subject: Prepare for RC2 --- actionpack/CHANGELOG | 4 +++- actionpack/lib/action_pack/version.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 97389dfc20..dc7ee64358 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,4 +1,6 @@ -*2.2.1 [RC2 or 2.2 final]* +*2.2.1 [RC2] (November 14th, 2008)* + +* Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] * Fixed that polymorphic_url should compact given array #1317 [hiroshi] diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 288b62778e..126d16e5f4 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 2 - TINY = 0 + TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end -- cgit v1.2.3 From c70b993a9e01547de88417cb8fa95b48acbed2db Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 14 Nov 2008 17:47:21 +0530 Subject: Merge docrails. --- actionpack/lib/action_controller/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 43f6c1be44..f35c42f929 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1029,10 +1029,10 @@ module ActionController #:nodoc: # # * Hash - The URL will be generated by calling url_for with the +options+. # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. - # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. - # * String not containing a protocol - The current protocol and host is prepended to the string. + # * String starting with protocol:// (like http://) - Is passed straight through as the target for redirection. + # * String not containing a protocol - The current protocol and host is prepended to the string. # * :back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. - # Short-hand for redirect_to(request.env["HTTP_REFERER"]) + # Short-hand for redirect_to(request.env["HTTP_REFERER"]) # # Examples: # redirect_to :action => "show", :id => 5 -- cgit v1.2.3 From 3be853b59d3175e74ef4564b78309c10bc0cc550 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 14 Nov 2008 14:08:26 +0100 Subject: A few more dependency updates --- actionpack/Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 73da8b1ce3..4020b4aa78 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.2.0' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.2.1' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' -- cgit v1.2.3 From 16ae82db1e2b830424aa3c94db57281da96aa62d Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Fri, 14 Nov 2008 16:04:19 +0100 Subject: Missing changelog for :only/:except on map.resources --- actionpack/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index dc7ee64358..0b3811c794 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2] (November 14th, 2008)* +* Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets. #1215 [Tom Stuart] + * Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] * Fixed that polymorphic_url should compact given array #1317 [hiroshi] -- cgit v1.2.3 From e4a345c1dc13f7de99b782f4a1115d58b7bf1b45 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Fri, 14 Nov 2008 16:07:26 +0100 Subject: Missing changelog for CSRF changes --- actionpack/CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 0b3811c794..5b9114755c 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,7 +1,13 @@ *2.2.1 [RC2] (November 14th, 2008)* +* Switched the CSRF module to use the request content type to decide if the request is forgeable. #1145 [Jeff Cohen] + * Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets. #1215 [Tom Stuart] + map.resources :products, :only => :show do |product| + product.resources :images, :except => :destroy + end + * Added render :js for people who want to render inline JavaScript replies without using RJS [DHH] * Fixed that polymorphic_url should compact given array #1317 [hiroshi] -- cgit v1.2.3 From 44c3b865ac52a7c9a6312982ba0f6c20d7ad41e1 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Fri, 14 Nov 2008 16:10:57 +0100 Subject: Missing changelogs for relative_url_root changes --- actionpack/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 5b9114755c..b7a824d559 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.2.1 [RC2] (November 14th, 2008)* +* Restore backwards compatible functionality for setting relative_url_root. Include deprecation + * Switched the CSRF module to use the request content type to decide if the request is forgeable. #1145 [Jeff Cohen] * Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets. #1215 [Tom Stuart] -- cgit v1.2.3 From c6c5cd554110f6e62290de3e3008076b2f69e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 12 Nov 2008 13:15:57 +0100 Subject: refactor autolink helper. change tests to expect HTML-escaped URLs Signed-off-by: Michael Koziarski --- actionpack/lib/action_view/helpers/text_helper.rb | 46 ++++++++++------------- actionpack/test/template/text_helper_test.rb | 37 +++++++++++------- 2 files changed, 44 insertions(+), 39 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 36f7575652..07f98158f7 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -545,38 +545,32 @@ module ActionView end AUTO_LINK_RE = %r{ - ( # leading text - <\w+.*?>| # leading HTML tag, or - [^=!:'"/]| # leading punctuation, or - ^ # beginning of line - ) - ( - (?:https?://)| # protocol spec, or - (?:www\.) # www.* - ) - ( - [-\w]+ # subdomain or domain - (?:\.[-\w]+)* # remaining subdomains or domain - (?::\d+)? # port - (?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path - (?:\?[\w\+@%&=.;:-]+)? # query string - (?:\#[\w\-]*)? # trailing anchor - ) - ([[:punct:]]|<|$|) # trailing text - }x unless const_defined?(:AUTO_LINK_RE) + ( https?:// | www\. ) + [^\s<]+ + }x unless const_defined?(:AUTO_LINK_RE) # Turns all urls into clickable links. If a block is given, each url # is yielded and the result is used as the link text. def auto_link_urls(text, html_options = {}) - extra_options = tag_options(html_options.stringify_keys) || "" + link_attributes = html_options.stringify_keys text.gsub(AUTO_LINK_RE) do - all, a, b, c, d = $&, $1, $2, $3, $4 - if a =~ /]*href="$/ + if href =~ /[^\w\/-]$/ + punctuation = href[-1, 1] + href = href[0, href.length - 1] + else + punctuation = '' + end + + link_text = block_given?? yield(href) : href + href = 'http://' + href unless href.index('http') == 0 + + content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation else - text = b + c - text = yield(text) if block_given? - %(#{a}#{text}#{d}) + # do not change string; URL is alreay linked + href end end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 095c952d67..42390d84c8 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -225,36 +225,41 @@ class TextHelperTest < ActionView::TestCase ) urls.each do |url| - assert_equal %(#{url}), auto_link(url) + assert_equal %(#{CGI::escapeHTML url}), auto_link(url) end end + def generate_result(link_text, href = nil) + href ||= link_text + %{#{CGI::escapeHTML link_text}} + end + def test_auto_linking email_raw = 'david@loudthinking.com' email_result = %{#{email_raw}} email2_raw = '+david@loudthinking.com' email2_result = %{#{email2_raw}} link_raw = 'http://www.rubyonrails.com' - link_result = %{#{link_raw}} + link_result = generate_result(link_raw) link_result_with_options = %{#{link_raw}} link2_raw = 'www.rubyonrails.com' - link2_result = %{#{link2_raw}} + link2_result = generate_result(link2_raw, "http://#{link2_raw}") link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281' - link3_result = %{#{link3_raw}} + link3_result = generate_result(link3_raw) link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123' - link4_result = %{#{link4_raw}} + link4_result = generate_result(link4_raw) link5_raw = 'http://foo.example.com:3000/controller/action' - link5_result = %{#{link5_raw}} + link5_result = generate_result(link5_raw) link6_raw = 'http://foo.example.com:3000/controller/action+pack' - link6_result = %{#{link6_raw}} + link6_result = generate_result(link6_raw) link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123' - link7_result = %{#{link7_raw}} + link7_result = generate_result(link7_raw) link8_raw = 'http://foo.example.com:3000/controller/action.html' - link8_result = %{#{link8_raw}} + link8_result = generate_result(link8_raw) link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html' - link9_result = %{#{link9_raw}} + link9_result = generate_result(link9_raw) link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/' - link10_result = %{#{link10_raw}} + link10_result = generate_result(link10_raw) assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses) assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls) @@ -299,7 +304,13 @@ class TextHelperTest < ActionView::TestCase assert_equal '', auto_link(nil) assert_equal '', auto_link('') assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}") - assert_equal 'Ruby On Rails', auto_link('Ruby On Rails') + end + + def test_auto_link_already_linked + linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com') + linked2 = generate_result('www.rubyonrails.com', 'http://www.rubyonrails.com') + assert_equal linked1, auto_link(linked1) + assert_equal linked2, auto_link(linked2) end def test_auto_link_at_eol @@ -317,7 +328,7 @@ class TextHelperTest < ActionView::TestCase end def test_auto_link_with_options_hash - assert_equal 'Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.', + assert_dom_equal 'Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.', auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.", :link => :all, :html => { :class => "menu", :target => "_blank" }) end -- cgit v1.2.3 From 4f984c9d0e66601a81cb5ae6e3b50582e6dc0c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 13 Nov 2008 22:39:16 +0100 Subject: auto_link helper: add intelligent ending closing bracket handling. add new tests and reorder new ones for readability Signed-off-by: Michael Koziarski [#1353 state:committed] --- actionpack/lib/action_view/helpers/text_helper.rb | 22 ++-- actionpack/test/template/text_helper_test.rb | 128 ++++++++++++++-------- 2 files changed, 94 insertions(+), 56 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 07f98158f7..9bd3d63423 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -549,28 +549,32 @@ module ActionView [^\s<]+ }x unless const_defined?(:AUTO_LINK_RE) + BRACKETS = { ']' => '[', ')' => '(', '}' => '{' } + # Turns all urls into clickable links. If a block is given, each url # is yielded and the result is used as the link text. def auto_link_urls(text, html_options = {}) link_attributes = html_options.stringify_keys text.gsub(AUTO_LINK_RE) do href = $& + punctuation = '' # detect already linked URLs - unless $` =~ /]*href="$/ - if href =~ /[^\w\/-]$/ - punctuation = href[-1, 1] - href = href[0, href.length - 1] - else - punctuation = '' + if $` =~ /]*href="$/ + # do not change string; URL is alreay linked + href + else + # don't include trailing punctuation character as part of the URL + if href.sub!(/[^\w\/-]$/, '') and punctuation = $& and opening = BRACKETS[punctuation] + if href.scan(opening).size > href.scan(punctuation).size + href << punctuation + punctuation = '' + end end link_text = block_given?? yield(href) : href href = 'http://' + href unless href.index('http') == 0 content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation - else - # do not change string; URL is alreay linked - href end end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 42390d84c8..3e7a8f3e44 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -205,27 +205,30 @@ class TextHelperTest < ActionView::TestCase end def test_auto_link_parsing - urls = %w(http://www.rubyonrails.com - http://www.rubyonrails.com:80 - http://www.rubyonrails.com/~minam - https://www.rubyonrails.com/~minam - http://www.rubyonrails.com/~minam/url%20with%20spaces - http://www.rubyonrails.com/foo.cgi?something=here - http://www.rubyonrails.com/foo.cgi?something=here&and=here - http://www.rubyonrails.com/contact;new - http://www.rubyonrails.com/contact;new%20with%20spaces - http://www.rubyonrails.com/contact;new?with=query&string=params - http://www.rubyonrails.com/~minam/contact;new?with=query&string=params - http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007 - http://www.mail-archive.com/rails@lists.rubyonrails.org/ - http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 - http://en.wikipedia.org/wiki/Sprite_(computer_graphics) - http://en.wikipedia.org/wiki/Texas_hold'em - https://www.google.com/doku.php?id=gps:resource:scs:start - ) + urls = %w( + http://www.rubyonrails.com + http://www.rubyonrails.com:80 + http://www.rubyonrails.com/~minam + https://www.rubyonrails.com/~minam + http://www.rubyonrails.com/~minam/url%20with%20spaces + http://www.rubyonrails.com/foo.cgi?something=here + http://www.rubyonrails.com/foo.cgi?something=here&and=here + http://www.rubyonrails.com/contact;new + http://www.rubyonrails.com/contact;new%20with%20spaces + http://www.rubyonrails.com/contact;new?with=query&string=params + http://www.rubyonrails.com/~minam/contact;new?with=query&string=params + http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007 + http://www.mail-archive.com/rails@lists.rubyonrails.org/ + http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 + http://en.wikipedia.org/wiki/Texas_hold'em + https://www.google.com/doku.php?id=gps:resource:scs:start + http://connect.oraclecorp.com/search?search[q]=green+france&search[type]=Group + http://of.openfoundry.org/projects/492/download#4th.Release.3 + http://maps.google.co.uk/maps?f=q&q=the+london+eye&ie=UTF8&ll=51.503373,-0.11939&spn=0.007052,0.012767&z=16&iwloc=A + ) urls.each do |url| - assert_equal %(#{CGI::escapeHTML url}), auto_link(url) + assert_equal generate_result(url), auto_link(url) end end @@ -237,29 +240,13 @@ class TextHelperTest < ActionView::TestCase def test_auto_linking email_raw = 'david@loudthinking.com' email_result = %{#{email_raw}} - email2_raw = '+david@loudthinking.com' - email2_result = %{#{email2_raw}} link_raw = 'http://www.rubyonrails.com' link_result = generate_result(link_raw) - link_result_with_options = %{#{link_raw}} - link2_raw = 'www.rubyonrails.com' - link2_result = generate_result(link2_raw, "http://#{link2_raw}") - link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281' - link3_result = generate_result(link3_raw) - link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123' - link4_result = generate_result(link4_raw) - link5_raw = 'http://foo.example.com:3000/controller/action' - link5_result = generate_result(link5_raw) - link6_raw = 'http://foo.example.com:3000/controller/action+pack' - link6_result = generate_result(link6_raw) - link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123' - link7_result = generate_result(link7_raw) - link8_raw = 'http://foo.example.com:3000/controller/action.html' - link8_result = generate_result(link8_raw) - link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html' - link9_result = generate_result(link9_raw) - link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/' - link10_result = generate_result(link10_raw) + link_result_with_options = %{#{link_raw}} + + assert_equal '', auto_link(nil) + assert_equal '', auto_link('') + assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}") assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses) assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls) @@ -270,40 +257,70 @@ class TextHelperTest < ActionView::TestCase assert_equal %(

Link #{link_result_with_options}

), auto_link("

Link #{link_raw}

", :all, {:target => "_blank"}) assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.)) assert_equal %(

Go to #{link_result}, then say hello to #{email_result}.

), auto_link(%(

Go to #{link_raw}, then say hello to #{email_raw}.

)) + + email2_raw = '+david@loudthinking.com' + email2_result = %{#{email2_raw}} + assert_equal email2_result, auto_link(email2_raw) + + link2_raw = 'www.rubyonrails.com' + link2_result = generate_result(link2_raw, "http://#{link2_raw}") assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls) assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses) assert_equal %(

Link #{link2_result}

), auto_link("

Link #{link2_raw}

") assert_equal %(

#{link2_result} Link

), auto_link("

#{link2_raw} Link

") assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.)) assert_equal %(

Say hello to #{email_result}, then go to #{link2_result}.

), auto_link(%(

Say hello to #{email_raw}, then go to #{link2_raw}.

)) + + link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281' + link3_result = generate_result(link3_raw) assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls) assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses) assert_equal %(

Link #{link3_result}

), auto_link("

Link #{link3_raw}

") assert_equal %(

#{link3_result} Link

), auto_link("

#{link3_raw} Link

") assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.)) - assert_equal %(

Go to #{link3_result}. seriously, #{link3_result}? i think I'll say hello to #{email_result}. instead.

), auto_link(%(

Go to #{link3_raw}. seriously, #{link3_raw}? i think I'll say hello to #{email_raw}. instead.

)) + assert_equal %(

Go to #{link3_result}. Seriously, #{link3_result}? I think I'll say hello to #{email_result}. Instead.

), + auto_link(%(

Go to #{link3_raw}. Seriously, #{link3_raw}? I think I'll say hello to #{email_raw}. Instead.

)) + + link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123' + link4_result = generate_result(link4_raw) assert_equal %(

Link #{link4_result}

), auto_link("

Link #{link4_raw}

") assert_equal %(

#{link4_result} Link

), auto_link("

#{link4_raw} Link

") + + link5_raw = 'http://foo.example.com:3000/controller/action' + link5_result = generate_result(link5_raw) assert_equal %(

#{link5_result} Link

), auto_link("

#{link5_raw} Link

") + + link6_raw = 'http://foo.example.com:3000/controller/action+pack' + link6_result = generate_result(link6_raw) assert_equal %(

#{link6_result} Link

), auto_link("

#{link6_raw} Link

") + + link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123' + link7_result = generate_result(link7_raw) assert_equal %(

#{link7_result} Link

), auto_link("

#{link7_raw} Link

") + + link8_raw = 'http://foo.example.com:3000/controller/action.html' + link8_result = generate_result(link8_raw) assert_equal %(Go to #{link8_result}), auto_link("Go to #{link8_raw}", :urls) assert_equal %(Go to #{link8_raw}), auto_link("Go to #{link8_raw}", :email_addresses) assert_equal %(

Link #{link8_result}

), auto_link("

Link #{link8_raw}

") assert_equal %(

#{link8_result} Link

), auto_link("

#{link8_raw} Link

") assert_equal %(Go to #{link8_result}.), auto_link(%(Go to #{link8_raw}.)) - assert_equal %(

Go to #{link8_result}. seriously, #{link8_result}? i think I'll say hello to #{email_result}. instead.

), auto_link(%(

Go to #{link8_raw}. seriously, #{link8_raw}? i think I'll say hello to #{email_raw}. instead.

)) + assert_equal %(

Go to #{link8_result}. Seriously, #{link8_result}? I think I'll say hello to #{email_result}. Instead.

), + auto_link(%(

Go to #{link8_raw}. Seriously, #{link8_raw}? I think I'll say hello to #{email_raw}. Instead.

)) + + link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html' + link9_result = generate_result(link9_raw) assert_equal %(Go to #{link9_result}), auto_link("Go to #{link9_raw}", :urls) assert_equal %(Go to #{link9_raw}), auto_link("Go to #{link9_raw}", :email_addresses) assert_equal %(

Link #{link9_result}

), auto_link("

Link #{link9_raw}

") assert_equal %(

#{link9_result} Link

), auto_link("

#{link9_raw} Link

") assert_equal %(Go to #{link9_result}.), auto_link(%(Go to #{link9_raw}.)) - assert_equal %(

Go to #{link9_result}. seriously, #{link9_result}? i think I'll say hello to #{email_result}. instead.

), auto_link(%(

Go to #{link9_raw}. seriously, #{link9_raw}? i think I'll say hello to #{email_raw}. instead.

)) + assert_equal %(

Go to #{link9_result}. Seriously, #{link9_result}? I think I'll say hello to #{email_result}. Instead.

), + auto_link(%(

Go to #{link9_raw}. Seriously, #{link9_raw}? I think I'll say hello to #{email_raw}. Instead.

)) + + link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/' + link10_result = generate_result(link10_raw) assert_equal %(

#{link10_result} Link

), auto_link("

#{link10_raw} Link

") - assert_equal email2_result, auto_link(email2_raw) - assert_equal '', auto_link(nil) - assert_equal '', auto_link('') - assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}") end def test_auto_link_already_linked @@ -313,6 +330,23 @@ class TextHelperTest < ActionView::TestCase assert_equal linked2, auto_link(linked2) end + def test_auto_link_with_brackets + link1_raw = 'http://en.wikipedia.org/wiki/Sprite_(computer_graphics)' + link1_result = generate_result(link1_raw) + assert_equal link1_result, auto_link(link1_raw) + assert_equal "(link: #{link1_result})", auto_link("(link: #{link1_raw})") + + link2_raw = 'http://en.wikipedia.org/wiki/Sprite_[computer_graphics]' + link2_result = generate_result(link2_raw) + assert_equal link2_result, auto_link(link2_raw) + assert_equal "[link: #{link2_result}]", auto_link("[link: #{link2_raw}]") + + link3_raw = 'http://en.wikipedia.org/wiki/Sprite_{computer_graphics}' + link3_result = generate_result(link3_raw) + assert_equal link3_result, auto_link(link3_raw) + assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") + end + def test_auto_link_at_eol url1 = "http://api.rubyonrails.com/Foo.html" url2 = "http://www.ruby-doc.org/core/Bar.html" -- cgit v1.2.3 From 8c197fb4ab4fa432a6e9421e0339a17a7ec296f1 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Sun, 16 Nov 2008 20:19:02 +0100 Subject: Add text/plain to the browser_generated_types array as webkit and gecko can submit them. For more information see: http://pseudo-flaw.net/content/web-browsers/form-data-encoding-roundup/ --- actionpack/lib/action_controller/mime_type.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 8ca3a70341..6923a13f3f 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -25,7 +25,7 @@ module Mime # 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 # only needs to protect against these types. - @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form] + @@browser_generated_types = Set.new [:html, :url_encoded_form, :multipart_form, :text] cattr_reader :browser_generated_types @@ -177,7 +177,7 @@ module Mime end # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See - # ActionController::RequestForgerProtection. + # ActionController::RequestForgeryProtection. def verify_request? browser_generated? end -- cgit v1.2.3 From 4b33fae1f52325d22083de2e83d827b924d1c616 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 17 Nov 2008 18:31:36 +0100 Subject: Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [DHH] --- actionpack/CHANGELOG | 5 + actionpack/lib/action_view/helpers/text_helper.rb | 150 ++++++++++------------ 2 files changed, 74 insertions(+), 81 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index b7a824d559..4ed39133db 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,3 +1,8 @@ +*2.3.0/3.0* + +* Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [DHH] + + *2.2.1 [RC2] (November 14th, 2008)* * Restore backwards compatible functionality for setting relative_url_root. Include deprecation diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 9bd3d63423..510c1a6a76 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -226,91 +226,79 @@ module ActionView end * "\n" end - begin - require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth) - - # Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags. - # - # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. - # This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/] - # is available. - # - # ==== Examples - # textilize("*This is Textile!* Rejoice!") - # # => "

This is Textile! Rejoice!

" - # - # textilize("I _love_ ROR(Ruby on Rails)!") - # # => "

I love ROR!

" - # - # textilize("h2. Textile makes markup -easy- simple!") - # # => "

Textile makes markup easy simple!

" - # - # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.) - # # => "

Visit the Rails website here.

" - def textilize(text) - if text.blank? - "" - else - textilized = RedCloth.new(text, [ :hard_breaks ]) - textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=) - textilized.to_html - end + # Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags. + # + # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. + # This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/] + # is available. + # + # ==== Examples + # textilize("*This is Textile!* Rejoice!") + # # => "

This is Textile! Rejoice!

" + # + # textilize("I _love_ ROR(Ruby on Rails)!") + # # => "

I love ROR!

" + # + # textilize("h2. Textile makes markup -easy- simple!") + # # => "

Textile makes markup easy simple!

" + # + # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.) + # # => "

Visit the Rails website here.

" + def textilize(text) + if text.blank? + "" + else + textilized = RedCloth.new(text, [ :hard_breaks ]) + textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=) + textilized.to_html end + end - # Returns the text with all the Textile codes turned into HTML tags, - # but without the bounding

tag that RedCloth adds. - # - # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. - # This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/] - # is available. - # - # ==== Examples - # textilize_without_paragraph("*This is Textile!* Rejoice!") - # # => "This is Textile! Rejoice!" - # - # textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!") - # # => "I love ROR!" - # - # textilize_without_paragraph("h2. Textile makes markup -easy- simple!") - # # => "

Textile makes markup easy simple!

" - # - # textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.) - # # => "Visit the Rails website here." - def textilize_without_paragraph(text) - textiled = textilize(text) - if textiled[0..2] == "

" then textiled = textiled[3..-1] end - if textiled[-4..-1] == "

" then textiled = textiled[0..-5] end - return textiled - end - rescue LoadError - # We can't really help what's not there + # Returns the text with all the Textile codes turned into HTML tags, + # but without the bounding

tag that RedCloth adds. + # + # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. + # This method is requires RedCloth[http://whytheluckystiff.net/ruby/redcloth/] + # to be available. + # + # ==== Examples + # textilize_without_paragraph("*This is Textile!* Rejoice!") + # # => "This is Textile! Rejoice!" + # + # textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!") + # # => "I love ROR!" + # + # textilize_without_paragraph("h2. Textile makes markup -easy- simple!") + # # => "

Textile makes markup easy simple!

" + # + # textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.) + # # => "Visit the Rails website here." + def textilize_without_paragraph(text) + textiled = textilize(text) + if textiled[0..2] == "

" then textiled = textiled[3..-1] end + if textiled[-4..-1] == "

" then textiled = textiled[0..-5] end + return textiled end - begin - require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth) - - # Returns the text with all the Markdown codes turned into HTML tags. - # This method is only available if BlueCloth[http://www.deveiate.org/projects/BlueCloth] - # is available. - # - # ==== Examples - # markdown("We are using __Markdown__ now!") - # # => "

We are using Markdown now!

" - # - # markdown("We like to _write_ `code`, not just _read_ it!") - # # => "

We like to write code, not just read it!

" - # - # markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.") - # # => "

The Markdown website - # # has more information.

" - # - # markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")') - # # => '

The ROR logo

' - def markdown(text) - text.blank? ? "" : BlueCloth.new(text).to_html - end - rescue LoadError - # We can't really help what's not there + # Returns the text with all the Markdown codes turned into HTML tags. + # This method requires BlueCloth[http://www.deveiate.org/projects/BlueCloth] + # to be available. + # + # ==== Examples + # markdown("We are using __Markdown__ now!") + # # => "

We are using Markdown now!

" + # + # markdown("We like to _write_ `code`, not just _read_ it!") + # # => "

We like to write code, not just read it!

" + # + # markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.") + # # => "

The Markdown website + # # has more information.

" + # + # markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")') + # # => '

The ROR logo

' + def markdown(text) + text.blank? ? "" : BlueCloth.new(text).to_html end # Returns +text+ transformed into HTML using simple formatting rules. -- cgit v1.2.3 From fcce1f17eaf9993b0210fe8e2a8117b61a1f0f69 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 17 Nov 2008 19:16:31 +0100 Subject: BACKWARDS INCOMPATIBLE: Renamed application.rb to application_controller.rb and removed all the special casing that was in place to support the former. You must do this rename in your own application when you upgrade to this version [DHH] --- actionpack/lib/action_controller/dispatcher.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 2d5e80f0bb..d93edf067c 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -12,14 +12,7 @@ module ActionController after_dispatch :cleanup_application end - # Common callbacks - to_prepare :load_application_controller do - begin - require_dependency 'application' unless defined?(::ApplicationController) - rescue LoadError => error - raise unless error.message =~ /application\.rb/ - end - end + to_prepare(:load_application_controller) { ApplicationController } if defined?(ActiveRecord) after_dispatch :checkin_connections -- cgit v1.2.3 From 75fb8dfb996f5c5d8b64d10ce7b27eeb681d5316 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Mon, 17 Nov 2008 22:09:22 -0600 Subject: Prevent assert_template failures when a render :inline is called before rendering a file-based template [#1383 state:resolved] Signed-off-by: Joshua Peek --- actionpack/lib/action_view/renderable.rb | 4 +++- actionpack/test/controller/render_test.rb | 28 +++++++++++++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index c23b8cde89..5ff5569db6 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -29,7 +29,9 @@ module ActionView stack.push(self) # This is only used for TestResponse to set rendered_template - view.instance_variable_set(:@_first_render, self) unless view.instance_variable_get(:@_first_render) + unless is_a?(InlineTemplate) || view.instance_variable_get(:@_first_render) + view.instance_variable_set(:@_first_render, self) + end view.send(:_evaluate_assigns_and_ivars) view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 6a03466db1..462fba7045 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -39,7 +39,7 @@ class TestController < ActionController::Base render :action => 'hello_world' end before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs - + def handle_last_modified_and_etags fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) end @@ -337,6 +337,11 @@ class TestController < ActionController::Base render :text => "Hi web users! #{@stuff}" end + def render_to_string_with_inline_and_render + render_to_string :inline => "<%= 'dlrow olleh'.reverse %>" + render :template => "test/hello_world" + end + def rendering_with_conflicting_local_vars @name = "David" def @template.name() nil end @@ -906,6 +911,11 @@ class RenderTest < ActionController::TestCase assert_equal "The value of foo is: ::this is a test::\n", @response.body end + def test_render_to_string_inline + get :render_to_string_with_inline_and_render + assert_template "test/hello_world" + end + def test_nested_rendering @controller = Fun::GamesController.new get :hello_world @@ -1364,7 +1374,7 @@ class EtagRenderTest < ActionController::TestCase assert_equal "200 OK", @response.status assert !@response.body.empty? end - + def test_render_should_not_set_etag_when_last_modified_has_been_specified get :render_hello_world_with_last_modified_set assert_equal "200 OK", @response.status @@ -1378,7 +1388,7 @@ class EtagRenderTest < ActionController::TestCase expected_etag = etag_for('hello david') assert_equal expected_etag, @response.headers['ETag'] @response = ActionController::TestResponse.new - + @request.if_none_match = expected_etag get :render_hello_world_from_variable assert_equal "304 Not Modified", @response.status @@ -1403,24 +1413,24 @@ class EtagRenderTest < ActionController::TestCase assert_equal "\n\n

Hello

\n

This is grand!

\n\n
\n", @response.body assert_equal etag_for("\n\n

Hello

\n

This is grand!

\n\n
\n"), @response.headers['ETag'] end - + def test_etag_with_bang_should_set_etag get :conditional_hello_with_bangs assert_equal @expected_bang_etag, @response.headers["ETag"] assert_response :success end - + def test_etag_with_bang_should_obey_if_none_match @request.if_none_match = @expected_bang_etag get :conditional_hello_with_bangs assert_response :not_modified end - + protected def etag_for(text) %("#{Digest::MD5.hexdigest(text)}") end - + def expand_key(args) ActiveSupport::Cache.expand_cache_key(args) end @@ -1461,13 +1471,13 @@ class LastModifiedRenderTest < ActionController::TestCase assert !@response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end - + def test_request_with_bang_gets_last_modified get :conditional_hello_with_bangs assert_equal @last_modified, @response.headers['Last-Modified'] assert_response :success end - + def test_request_with_bang_obeys_last_modified @request.if_modified_since = @last_modified get :conditional_hello_with_bangs -- cgit v1.2.3 From 12118963acacc9c869bdd41ef8480a1a4e06d358 Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Tue, 18 Nov 2008 09:59:46 +0100 Subject: use :en as a default locale (in favor of :en-US) Signed-off-by: David Heinemeier Hansson --- actionpack/lib/action_view.rb | 2 +- actionpack/lib/action_view/helpers/date_helper.rb | 2 +- actionpack/lib/action_view/locale/en-US.yml | 91 ---------------------- actionpack/lib/action_view/locale/en.yml | 91 ++++++++++++++++++++++ .../template/active_record_helper_i18n_test.rb | 24 +++--- actionpack/test/template/date_helper_i18n_test.rb | 22 +++--- .../test/template/number_helper_i18n_test.rb | 30 +++---- .../test/template/translation_helper_test.rb | 6 +- 8 files changed, 134 insertions(+), 134 deletions(-) delete mode 100644 actionpack/lib/action_view/locale/en-US.yml create mode 100644 actionpack/lib/action_view/locale/en.yml (limited to 'actionpack') diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 7cd9b633ac..7b1d3f8e7c 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -43,7 +43,7 @@ require 'action_view/base' require 'action_view/partials' require 'action_view/template_error' -I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml" +I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" require 'action_view/helpers' diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 919c937444..22108dd99d 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -131,7 +131,7 @@ module ActionView # * :order - Set to an array containing :day, :month and :year do # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective # select will not be shown (like when you set :discard_xxx => true. Defaults to the order defined in - # the respective locale (e.g. [:year, :month, :day] in the en-US locale that ships with Rails). + # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails). # * :include_blank - Include a blank option in every select field so it's possible to set empty # dates. # * :default - Set a default date if the affected date isn't set or is nil. diff --git a/actionpack/lib/action_view/locale/en-US.yml b/actionpack/lib/action_view/locale/en-US.yml deleted file mode 100644 index 818f2f93bd..0000000000 --- a/actionpack/lib/action_view/locale/en-US.yml +++ /dev/null @@ -1,91 +0,0 @@ -"en-US": - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "." - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "," - # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) - precision: 3 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%u%n" - unit: "$" - # These three are to override number.format and are optional - separator: "." - delimiter: "," - precision: 2 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 1 - - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than {{count}} seconds" - x_seconds: - one: "1 second" - other: "{{count}} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than {{count}} minutes" - x_minutes: - one: "1 minute" - other: "{{count}} minutes" - about_x_hours: - one: "about 1 hour" - other: "about {{count}} hours" - x_days: - one: "1 day" - other: "{{count}} days" - about_x_months: - one: "about 1 month" - other: "about {{count}} months" - x_months: - one: "1 month" - other: "{{count}} months" - about_x_years: - one: "about 1 year" - other: "about {{count}} years" - over_x_years: - one: "over 1 year" - other: "over {{count}} years" - - activerecord: - errors: - template: - header: - one: "1 error prohibited this {{model}} from being saved" - other: "{{count}} errors prohibited this {{model}} from being saved" - # The variable :count is also available - body: "There were problems with the following fields:" - diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml new file mode 100644 index 0000000000..002226fd9c --- /dev/null +++ b/actionpack/lib/action_view/locale/en.yml @@ -0,0 +1,91 @@ +"en": + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%u%n" + unit: "$" + # These three are to override number.format and are optional + separator: "." + delimiter: "," + precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 1 + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than {{count}} seconds" + x_seconds: + one: "1 second" + other: "{{count}} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than {{count}} minutes" + x_minutes: + one: "1 minute" + other: "{{count}} minutes" + about_x_hours: + one: "about 1 hour" + other: "about {{count}} hours" + x_days: + one: "1 day" + other: "{{count}} days" + about_x_months: + one: "about 1 month" + other: "about {{count}} months" + x_months: + one: "1 month" + other: "{{count}} months" + about_x_years: + one: "about 1 year" + other: "about {{count}} years" + over_x_years: + one: "over 1 year" + other: "over {{count}} years" + + activerecord: + errors: + template: + header: + one: "1 error prohibited this {{model}} from being saved" + other: "{{count}} errors prohibited this {{model}} from being saved" + # The variable :count is also available + body: "There were problems with the following fields:" + diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb index 7ba9659814..7e6bf70706 100644 --- a/actionpack/test/template/active_record_helper_i18n_test.rb +++ b/actionpack/test/template/active_record_helper_i18n_test.rb @@ -10,37 +10,37 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase @object_name = 'book' stubs(:content_tag).returns 'content_tag' - I18n.stubs(:t).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" - I18n.stubs(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' + I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" + I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' end def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message - I18n.expects(:translate).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never - error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US') + I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never + error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') end def test_error_messages_for_given_no_header_option_it_translates_header_message - I18n.expects(:t).with(:'header', :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message' + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message' I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en-US') + error_messages_for(:object => @object, :locale => 'en') end def test_error_messages_for_given_a_message_option_it_does_not_translate_message - I18n.expects(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).never + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :message => 'message', :locale => 'en-US') + error_messages_for(:object => @object, :message => 'message', :locale => 'en') end def test_error_messages_for_given_no_message_option_it_translates_message - I18n.expects(:t).with(:'body', :locale => 'en-US', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en-US') + error_messages_for(:object => @object, :locale => 'en') end def test_error_messages_for_given_object_name_it_translates_object_name - I18n.expects(:t).with(:header, :locale => 'en-US', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name).returns "1 error prohibited this #{@object_name} from being saved" + I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name).returns "1 error prohibited this #{@object_name} from being saved" I18n.expects(:t).with(@object_name, :default => @object_name, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name - error_messages_for(:object => @object, :locale => 'en-US', :object_name => @object_name) + error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name) end end end diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index bf3b2588c8..dc9616db3b 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -41,11 +41,11 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase key, count = *expected to = @from + diff - options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'} + options = {:locale => 'en', :scope => :'datetime.distance_in_words'} options[:count] = count if count I18n.expects(:t).with(key, options) - distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US') + distance_of_time_in_words(@from, to, include_seconds, :locale => 'en') end def test_distance_of_time_pluralizations @@ -78,36 +78,36 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase uses_mocha 'date_helper_select_tags_i18n_tests' do def setup - I18n.stubs(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES + I18n.stubs(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES end # select_month def test_select_month_given_use_month_names_option_does_not_translate_monthnames I18n.expects(:translate).never - select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES) + select_month(8, :locale => 'en', :use_month_names => Date::MONTHNAMES) end def test_select_month_translates_monthnames - I18n.expects(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES - select_month(8, :locale => 'en-US') + I18n.expects(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES + select_month(8, :locale => 'en') end def test_select_month_given_use_short_month_option_translates_abbr_monthnames - I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en-US').returns Date::ABBR_MONTHNAMES - select_month(8, :locale => 'en-US', :use_short_month => true) + I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en').returns Date::ABBR_MONTHNAMES + select_month(8, :locale => 'en', :use_short_month => true) end # date_or_time_select def test_date_or_time_select_given_an_order_options_does_not_translate_order I18n.expects(:translate).never - datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US') + datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en') end def test_date_or_time_select_given_no_order_options_translates_order - I18n.expects(:translate).with(:'date.order', :locale => 'en-US').returns [:year, :month, :day] - datetime_select('post', 'updated_at', :locale => 'en-US') + I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day] + datetime_select('post', 'updated_at', :locale => 'en') end end end \ No newline at end of file diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb index 2ee7f43a65..67c61a5f2e 100644 --- a/actionpack/test/template/number_helper_i18n_test.rb +++ b/actionpack/test/template/number_helper_i18n_test.rb @@ -13,42 +13,42 @@ class NumberHelperI18nTests < Test::Unit::TestCase @percentage_defaults = { :delimiter => '' } @precision_defaults = { :delimiter => '' } - I18n.backend.store_translations 'en-US', :number => { :format => @number_defaults, + I18n.backend.store_translations 'en', :number => { :format => @number_defaults, :currency => { :format => @currency_defaults }, :human => @human_defaults } end def test_number_to_currency_translates_currency_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.currency.format', :locale => 'en-US', + I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) + I18n.expects(:translate).with(:'number.currency.format', :locale => 'en', :raise => true).returns(@currency_defaults) - number_to_currency(1, :locale => 'en-US') + number_to_currency(1, :locale => 'en') end def test_number_with_precision_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.precision.format', :locale => 'en-US', + I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) + I18n.expects(:translate).with(:'number.precision.format', :locale => 'en', :raise => true).returns(@precision_defaults) - number_with_precision(1, :locale => 'en-US') + number_with_precision(1, :locale => 'en') end def test_number_with_delimiter_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults) - number_with_delimiter(1, :locale => 'en-US') + I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) + number_with_delimiter(1, :locale => 'en') end def test_number_to_percentage_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en-US', + I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) + I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en', :raise => true).returns(@percentage_defaults) - number_to_percentage(1, :locale => 'en-US') + number_to_percentage(1, :locale => 'en') end def test_number_to_human_size_translates_human_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en-US', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.human.format', :locale => 'en-US', + I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) + I18n.expects(:translate).with(:'number.human.format', :locale => 'en', :raise => true).returns(@human_defaults) # can't be called with 1 because this directly returns without calling I18n.translate - number_to_human_size(1025, :locale => 'en-US') + number_to_human_size(1025, :locale => 'en') end end end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 1b28a59288..d0d65cb450 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -10,12 +10,12 @@ class TranslationHelperTest < Test::Unit::TestCase end def test_delegates_to_i18n_setting_the_raise_option - I18n.expects(:translate).with(:foo, :locale => 'en-US', :raise => true) - translate :foo, :locale => 'en-US' + I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true) + translate :foo, :locale => 'en' end def test_returns_missing_translation_message_wrapped_into_span - expected = 'en-US, foo' + expected = 'en, foo' assert_equal expected, translate(:foo) end -- cgit v1.2.3 From 252ca3e3e7e609a179f7559624811550bde05749 Mon Sep 17 00:00:00 2001 From: Thomas Fuchs Date: Tue, 18 Nov 2008 19:24:22 +0100 Subject: Update Prototype to 1.6.0.3 and update script.aculo.us to 1.8.2 --- .../action_view/helpers/javascripts/controls.js | 144 ++++----- .../action_view/helpers/javascripts/dragdrop.js | 329 ++++++++++---------- .../lib/action_view/helpers/javascripts/effects.js | 338 +++++++++++---------- .../action_view/helpers/javascripts/prototype.js | 337 ++++++++++++-------- 4 files changed, 628 insertions(+), 520 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js index 5aaf0bb2b7..ca29aefdd1 100644 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -1,22 +1,22 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ -// Autocompleter.Base handles all the autocompletion functionality +// Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // -// Specific autocompleters need to provide, at the very least, +// Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method +// the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized @@ -30,23 +30,23 @@ // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); -var Autocompleter = { } +var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { - element = $(element) - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; @@ -59,28 +59,28 @@ Autocompleter.Base = Class.create({ this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ + this.options.onShow = this.options.onShow || + function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { - setHeight: false, + setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; - this.options.onHide = this.options.onHide || + this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - if(typeof(this.options.tokens) == 'string') + if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; - + this.element.setAttribute('autocomplete','off'); Element.hide(this.update); @@ -91,10 +91,10 @@ Autocompleter.Base = Class.create({ show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && + if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, + new Insertion.After(this.update, ''); @@ -102,7 +102,7 @@ Autocompleter.Base = Class.create({ } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, - + fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; @@ -150,15 +150,15 @@ Autocompleter.Base = Class.create({ Event.stop(event); return; } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); - this.observer = + this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, @@ -170,35 +170,35 @@ Autocompleter.Base = Class.create({ onHover: function(event) { var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) + if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, - + onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, - + onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; - this.active = false; - }, - + this.active = false; + }, + render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { + if(this.hasFocus) { this.show(); this.active = true; } @@ -207,27 +207,27 @@ Autocompleter.Base = Class.create({ this.hide(); } }, - + markPrevious: function() { - if(this.index > 0) this.index-- + if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, - + markNext: function() { - if(this.index < this.entryCount-1) this.index++ + if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, - + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - + getCurrentEntry: function() { return this.getEntry(this.index); }, - + selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); @@ -244,7 +244,7 @@ Autocompleter.Base = Class.create({ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - + var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); @@ -257,7 +257,7 @@ Autocompleter.Base = Class.create({ } this.oldElementValue = this.element.value; this.element.focus(); - + if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, @@ -269,20 +269,20 @@ Autocompleter.Base = Class.create({ Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = + this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } - } else { + } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; - + if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); @@ -298,7 +298,7 @@ Autocompleter.Base = Class.create({ }, onObserverEvent: function() { - this.changed = false; + this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); @@ -351,16 +351,16 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { getUpdatedChoices: function() { this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + + + var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - if(this.options.defaultParams) + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; - + new Ajax.Request(this.url, this.options); }, @@ -382,7 +382,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the +// text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to @@ -399,7 +399,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // -// It's possible to pass in a custom function as the 'selector' +// It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. @@ -427,20 +427,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { var entry = instance.getToken(); var count = 0; - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; - } else if (entry.length >= instance.options.partialChars && + } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + @@ -450,14 +450,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { } } - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "
      " + ret.join('') + "
    "; } }, options || { }); @@ -474,7 +474,7 @@ Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); -} +}; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { @@ -604,7 +604,7 @@ Ajax.InPlaceEditor = Class.create({ this.triggerCallback('onEnterHover'); }, getText: function() { - return this.element.innerHTML; + return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); @@ -780,7 +780,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw 'Server returned an invalid collection representation.'; + throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), @@ -937,7 +937,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; -// Delayed observer, like Form.Element.Observer, +// Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields @@ -947,7 +947,7 @@ Form.Element.DelayedObserver = Class.create({ this.element = $(element); this.callback = callback; this.timer = null; - this.lastValue = $F(this.element); + this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { @@ -960,4 +960,4 @@ Form.Element.DelayedObserver = Class.create({ this.timer = null; this.callback(this.element, $F(this.element)); } -}); +}); \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index bf5cfea66c..07229f986f 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -1,6 +1,6 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) -// +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ @@ -32,7 +32,7 @@ var Droppables = { options._containers.push($(containment)); } } - + if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE @@ -40,34 +40,34 @@ var Droppables = { this.drops.push(options); }, - + findDeepestChild: function(drops) { deepest = drops[0]; - + for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; - + return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { - containmentNode = element.treeNode; + containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, - + isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || - (Element.classNames(element).detect( + (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, @@ -87,12 +87,12 @@ var Droppables = { show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; - + this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); - + if(affected.length>0) drop = Droppables.findDeepestChild(affected); @@ -101,7 +101,7 @@ var Droppables = { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - + if (drop != this.last_active) Droppables.activate(drop); } }, @@ -112,8 +112,8 @@ var Droppables = { if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; + this.last_active.onDrop(element, this.last_active.element, event); + return true; } }, @@ -121,25 +121,25 @@ var Droppables = { if(this.last_active) this.deactivate(this.last_active); } -} +}; var Draggables = { drags: [], observers: [], - + register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); - + Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, - + unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { @@ -148,24 +148,24 @@ var Draggables = { Event.stopObserving(document, "keypress", this.eventKeypress); } }, - + activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, - + deactivate: function() { this.activeDraggable = null; }, - + updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -173,36 +173,36 @@ var Draggables = { // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; - + this.activeDraggable.updateDrag(event, pointer); }, - + endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, - + keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, - + addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, - + removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, - + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { @@ -210,7 +210,7 @@ var Draggables = { }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, - + _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( @@ -218,7 +218,7 @@ var Draggables = { ).length; }); } -} +}; /*--------------------------------------------------------------------------*/ @@ -234,12 +234,12 @@ var Draggable = Class.create({ }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false + afterFinish: function(){ + Draggable._dragging[element] = false } - }); + }); }, zindex: 1000, revert: false, @@ -250,57 +250,57 @@ var Draggable = Class.create({ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; - + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); - + var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); - + if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); - + if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; - + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } - Element.makePositioned(this.element); // fix IE + Element.makePositioned(this.element); // fix IE this.options = options; - this.dragging = false; + this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); - + Draggables.register(this); }, - + destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, - + currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, - + initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { + if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( @@ -309,34 +309,34 @@ var Draggable = Class.create({ tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; - + var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = Position.cumulativeOffset(this.element); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - + Draggables.activate(this); Event.stop(event); } }, - + startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); - + if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } - + if(this.options.ghosting) { this._clone = this.element.cloneNode(true); - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this.element._originallyAbsolute) + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } - + if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); @@ -347,28 +347,28 @@ var Draggable = Class.create({ this.originalScrollTop = this.options.scroll.scrollTop; } } - + Draggables.notify('onStart', this, event); - + if(this.options.starteffect) this.options.starteffect(this.element); }, - + updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); - + if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } - + Draggables.notify('onDrag', this, event); - + this.draw(pointer); if(this.options.change) this.options.change(this); - + if(this.options.scroll) { this.stopScrolling(); - + var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } @@ -386,16 +386,16 @@ var Draggable = Class.create({ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } - + // fix AppleWebKit rendering if(Prototype.Browser.WebKit) window.scrollBy(0,0); - + Event.stop(event); }, - + finishDrag: function(event, success) { this.dragging = false; - + if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -403,24 +403,24 @@ var Draggable = Class.create({ } if(this.options.ghosting) { - if (!this.element._originallyAbsolute) + if (!this._originallyAbsolute) Position.relativize(this.element); - delete this.element._originallyAbsolute; + delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); - + var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') @@ -433,67 +433,67 @@ var Draggable = Class.create({ if(this.options.zindex) this.element.style.zIndex = this.originalZ; - if(this.options.endeffect) + if(this.options.endeffect) this.options.endeffect(this.element); - + Draggables.deactivate(this); Droppables.reset(); }, - + keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, - + endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, - + draw: function(point) { var pos = Position.cumulativeOffset(this.element); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } - + var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; - + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) }.bind(this)); - + if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)) + return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} - + var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; - + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, - + stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); @@ -501,14 +501,14 @@ var Draggable = Class.create({ Draggables._lastScrollPointer = null; } }, - + startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, - + scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; @@ -524,7 +524,7 @@ var Draggable = Class.create({ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } - + Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); @@ -538,10 +538,10 @@ var Draggable = Class.create({ Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } - + if(this.options.change) this.options.change(this); }, - + _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { @@ -560,7 +560,7 @@ var Draggable = Class.create({ H = documentElement.clientHeight; } else { W = body.offsetWidth; - H = body.offsetHeight + H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; @@ -577,11 +577,11 @@ var SortableObserver = Class.create({ this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, - + onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - + onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) @@ -591,11 +591,11 @@ var SortableObserver = Class.create({ var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - + sortables: { }, - + _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { + while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } @@ -606,22 +606,23 @@ var Sortable = { if(!element) return; return Sortable.sortables[element.id]; }, - + destroy: function(element){ - var s = Sortable.options(element); - + element = $(element); + var s = Sortable.sortables[element.id]; + if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); - + delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); - var options = Object.extend({ + var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, @@ -635,17 +636,17 @@ var Sortable = { delay: 0, hoverclass: null, ghosting: false, - quiet: false, + quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be + + // these take arrays of elements or ids and can be // used for better initialization performance elements: false, handles: false, - + onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); @@ -682,24 +683,24 @@ var Sortable = { if(options.zindex) options_for_draggable.zindex = options.zindex; - // build options for the droppables + // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover - } - + }; + var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass - } + }; // fix for gecko engine - Element.cleanWhitespace(element); + Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; @@ -712,14 +713,14 @@ var Sortable = { (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); + (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; - options.droppables.push(e); + options.droppables.push(e); }); - + if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); @@ -741,7 +742,7 @@ var Sortable = { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, - + findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); @@ -758,7 +759,7 @@ var Sortable = { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } @@ -769,26 +770,26 @@ var Sortable = { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, - + onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); - + if(!Element.isParent(dropon, element)) { var index; - + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; - + if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - + for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); @@ -801,9 +802,9 @@ var Sortable = { } } } - + dropon.insertBefore(element, child); - + Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } @@ -816,34 +817,34 @@ var Sortable = { mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; + if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { - Sortable._marker = + Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } + } var offsets = Position.cumulativeOffset(dropon); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - + if(position=='after') - if(sortable.overlap == 'horizontal') + if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - + Sortable._marker.show(); }, - + _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; - + for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; - + var child = { id: encodeURIComponent(match ? match[1] : null), element: element, @@ -851,16 +852,16 @@ var Sortable = { children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) - } - + }; + /* Get the element containing the children and recurse over it */ if (child.container) - this._tree(child.container, options, child) - + this._tree(child.container, options, child); + parent.children.push (child); } - return parent; + return parent; }, tree: function(element) { @@ -873,15 +874,15 @@ var Sortable = { name: element.id, format: sortableOptions.format }, arguments[1] || { }); - + var root = { id: null, parent: null, children: [], container: element, position: 0 - } - + }; + return Sortable._tree(element, options, root); }, @@ -897,7 +898,7 @@ var Sortable = { sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); - + return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); @@ -906,14 +907,14 @@ var Sortable = { setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); - + var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); - + new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { @@ -922,16 +923,16 @@ var Sortable = { } }); }, - + serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - + if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + + return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { @@ -940,16 +941,16 @@ var Sortable = { }).join('&'); } } -} +}; // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); -} +}; -Element.findChildren = function(element, only, recursive, tagName) { +Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); @@ -965,8 +966,8 @@ Element.findChildren = function(element, only, recursive, tagName) { }); return (elements.length>0 ? elements.flatten() : []); -} +}; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -} +}; \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js index f030b5dbe9..5a639d2dea 100644 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -3,46 +3,46 @@ // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ +// For details, see the script.aculo.us web site: http://script.aculo.us/ -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { var color = '#'; - if (this.slice(0,4) == 'rgb(') { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if (this.slice(0,1) == '#') { - if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if (this.length==7) color = this.toLowerCase(); - } - } - return (color.length==7 ? color : (arguments[0] || this)); + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ -Element.collectTextNodes = function(element) { +Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : + return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; -Element.collectTextNodesIgnoreClass = function(element, className) { +Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { - element = $(element); - element.setStyle({fontSize: (percent/100) + 'em'}); + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; @@ -70,28 +70,23 @@ var Effect = { Transitions: { linear: Prototype.K, sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; + return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, - pulse: function(pos, pulses) { - pulses = pulses || 5; - return ( - ((pos % (1/pulses)) * pulses).round() == 0 ? - ((pos * pulses * 2) - (pos * pulses * 2).floor()) : - 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) - ); + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, - spring: function(pos) { - return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; @@ -112,14 +107,14 @@ var Effect = { tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; - + element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( - character == ' ' ? String.fromCharCode(160) : character), + character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); @@ -128,13 +123,13 @@ var Effect = { }, multiple: function(element, effect) { var elements; - if (((typeof element == 'object') || - Object.isFunction(element)) && + if (((typeof element == 'object') || + Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; - + var options = Object.extend({ speed: 0.1, delay: 0.0 @@ -156,7 +151,7 @@ var Effect = { var options = Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } }, arguments[2] || { }); - Effect[element.visible() ? + Effect[element.visible() ? Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); } }; @@ -168,20 +163,20 @@ Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; - this.interval = null; + this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); - - var position = Object.isString(effect.options.queue) ? + + var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; - + switch(position) { case 'front': - // move unstarted effects after this effect + // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; @@ -195,13 +190,13 @@ Effect.ScopedQueue = Class.create(Enumerable, { timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } - + effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); - + if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, @@ -214,7 +209,7 @@ Effect.ScopedQueue = Class.create(Enumerable, { }, loop: function() { var timePos = new Date().getTime(); - for(var i=0, len=this.effects.length;i0) { @@ -430,9 +437,9 @@ Effect.Scale = Class.create(Effect.Base, { this.fontSizeType = fontSizeType; } }.bind(this)); - + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; - + this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; @@ -507,17 +514,16 @@ Effect.Highlight = Class.create(Effect.Base, { Effect.ScrollTo = function(element) { var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(), - max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, - elementOffsets[1] > max ? max : elementOffsets[1], + elementOffsets[1], options, - function(p){ scrollTo(scrollOffsets.left, p.round()) } + function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; @@ -529,9 +535,9 @@ Effect.Fade = function(element) { var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, - afterFinishInternal: function(effect) { + afterFinishInternal: function(effect) { if (effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); + effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); @@ -547,15 +553,15 @@ Effect.Appear = function(element) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { - effect.element.setOpacity(effect.options.from).show(); + effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); - var oldStyle = { - opacity: element.getInlineOpacity(), + var oldStyle = { + opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, @@ -563,12 +569,12 @@ Effect.Puff = function(element) { height: element.style.height }; return new Effect.Parallel( - [ new Effect.Scale(element, 200, - { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], - Object.extend({ duration: 1.0, + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element) + Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } @@ -580,12 +586,12 @@ Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, + Object.extend({ scaleContent: false, + scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); - } + } }, arguments[1] || { }) ); }; @@ -593,15 +599,15 @@ Effect.BlindUp = function(element) { Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } @@ -616,16 +622,16 @@ Effect.SwitchOff = function(element) { from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { - new Effect.Scale(effect.element, 1, { + new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { + beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } - }) + }); } }, arguments[1] || { })); }; @@ -637,16 +643,16 @@ Effect.DropOut = function(element) { left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( - [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { - effect.effects[0].element.makePositioned(); + effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); - } + } }, arguments[1] || { })); }; @@ -674,7 +680,7 @@ Effect.Shake = function(element) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); - }}) }}) }}) }}) }}) }}); + }}); }}); }}); }}); }}); }}); }; Effect.SlideDown = function(element) { @@ -682,9 +688,9 @@ Effect.SlideDown = function(element) { // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, @@ -692,11 +698,11 @@ Effect.SlideDown = function(element) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().setStyle({height: '0px'}).show(); + effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); @@ -710,8 +716,8 @@ Effect.SlideUp = function(element) { var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, - Object.extend({ scaleContent: false, - scaleX: false, + Object.extend({ scaleContent: false, + scaleX: false, scaleMode: 'box', scaleFrom: 100, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, @@ -721,7 +727,7 @@ Effect.SlideUp = function(element) { effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); - }, + }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); @@ -734,15 +740,15 @@ Effect.SlideUp = function(element) { ); }; -// Bug in opera makes the TD containing this element expand for a instance after finish +// Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { - return new Effect.Scale(element, window.opera ? 1 : 0, { + return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { - effect.element.makeClipping(); - }, + effect.element.makeClipping(); + }, afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); + effect.element.hide().undoClipping(); } }); }; @@ -762,13 +768,13 @@ Effect.Grow = function(element) { width: element.style.width, opacity: element.getInlineOpacity() }; - var dims = element.getDimensions(); + var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; - + switch (options.direction) { case 'top-left': - initialMoveX = initialMoveY = moveX = moveY = 0; + initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; @@ -793,11 +799,11 @@ Effect.Grow = function(element) { moveY = -dims.height / 2; break; } - + return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, - duration: 0.01, + duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, @@ -806,17 +812,17 @@ Effect.Grow = function(element) { [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { - scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { - effect.effects[0].element.setStyle({height: '0px'}).show(); + effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { - effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) - ) + ); } }); }; @@ -838,7 +844,7 @@ Effect.Shrink = function(element) { var dims = element.getDimensions(); var moveX, moveY; - + switch (options.direction) { case 'top-left': moveX = moveY = 0; @@ -855,19 +861,19 @@ Effect.Shrink = function(element) { moveX = dims.width; moveY = dims.height; break; - case 'center': + case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } - + return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) - ], Object.extend({ + ], Object.extend({ beforeStartInternal: function(effect) { - effect.effects[0].element.makePositioned().makeClipping(); + effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } @@ -877,12 +883,14 @@ Effect.Shrink = function(element) { Effect.Pulsate = function(element) { element = $(element); - var options = arguments[1] || { }; - var oldOpacity = element.getInlineOpacity(); - var transition = options.transition || Effect.Transitions.sinoidal; - var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; - reverser.bind(transition); - return new Effect.Opacity(element, + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); @@ -896,12 +904,12 @@ Effect.Fold = function(element) { width: element.style.width, height: element.style.height }; element.makeClipping(); - return new Effect.Scale(element, 5, Object.extend({ + return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, + new Effect.Scale(element, 1, { + scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); @@ -916,7 +924,7 @@ Effect.Morph = Class.create(Effect.Base, { var options = Object.extend({ style: { } }, arguments[1] || { }); - + if (!Object.isString(options.style)) this.style = $H(options.style); else { if (options.style.include(':')) @@ -934,18 +942,18 @@ Effect.Morph = Class.create(Effect.Base, { effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); - } + }; } } this.start(options); }, - + setup: function(){ function parseColor(color){ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ - return parseInt( color.slice(i*2+1,i*2+3), 16 ) + return parseInt( color.slice(i*2+1,i*2+3), 16 ); }); } this.transforms = this.style.map(function(pair){ @@ -965,9 +973,9 @@ Effect.Morph = Class.create(Effect.Base, { } var originalValue = this.element.getStyle(property); - return { - style: property.camelize(), - originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }; @@ -978,13 +986,13 @@ Effect.Morph = Class.create(Effect.Base, { transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) - ) + ); }); }, update: function(position) { var style = { }, transform, i = this.transforms.length; while(i--) - style[(transform = this.transforms[i]).style] = + style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ (Math.round(transform.originalValue[0]+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + @@ -993,7 +1001,7 @@ Effect.Morph = Class.create(Effect.Base, { (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : (transform.originalValue + - (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } @@ -1030,7 +1038,7 @@ Effect.Transform = Class.create({ }); Element.CSS_PROPERTIES = $w( - 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + @@ -1039,7 +1047,7 @@ Element.CSS_PROPERTIES = $w( 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); - + Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.__parseStyleElement = document.createElement('div'); @@ -1051,11 +1059,11 @@ String.prototype.parseStyle = function(){ String.__parseStyleElement.innerHTML = '
    '; style = String.__parseStyleElement.childNodes[0].style; } - + Element.CSS_PROPERTIES.each(function(property){ - if (style[property]) styleRules.set(property, style[property]); + if (style[property]) styleRules.set(property, style[property]); }); - + if (Prototype.Browser.IE && this.include('opacity')) styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); @@ -1074,14 +1082,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; - styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { - hash.set(property, css[property]); - return hash; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; }); - if (!styles.opacity) styles.set('opacity', element.getOpacity()); + if (!styles.opacity) styles.opacity = element.getOpacity(); return styles; }; -}; +} Effect.Methods = { morph: function(element, style) { @@ -1090,7 +1098,7 @@ Effect.Methods = { return element; }, visualEffect: function(element, effect, options) { - element = $(element) + element = $(element); var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; @@ -1104,17 +1112,17 @@ Effect.Methods = { $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 'pulsate shake puff squish switchOff dropOut').each( - function(effect) { + function(effect) { Effect.Methods[effect] = function(element, options){ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; - } + }; } ); -$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( function(f) { Effect.Methods[f] = Element[f]; } ); -Element.addMethods(Effect.Methods); +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index 2c70b8a7e8..dfe8ab4e13 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.6.0.2 +/* Prototype JavaScript framework, version 1.6.0.3 * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. @@ -7,23 +7,26 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.2', + Version: '1.6.0.3', Browser: { - IE: !!(window.attachEvent && !window.opera), - Opera: !!window.opera, + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - document.createElement('div').__proto__ && - document.createElement('div').__proto__ !== - document.createElement('form').__proto__ + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', @@ -83,12 +86,13 @@ Class.Methods = { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { - var method = value, value = Object.extend((function(m) { + var method = value; + value = (function(m) { return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method), { - valueOf: function() { return method }, - toString: function() { return method.toString() } - }); + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); } this.prototype[property] = value; } @@ -167,7 +171,7 @@ Object.extend(Object, { }, isElement: function(object) { - return object && object.nodeType == 1; + return !!(object && object.nodeType == 1); }, isArray: function(object) { @@ -198,7 +202,8 @@ Object.extend(Object, { Object.extend(Function.prototype, { argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; }, @@ -232,6 +237,11 @@ Object.extend(Function.prototype, { }, timeout); }, + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + wrap: function(wrapper) { var __method = this; return function() { @@ -248,8 +258,6 @@ Object.extend(Function.prototype, { } }); -Function.prototype.defer = Function.prototype.delay.curry(0.01); - Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + @@ -530,7 +538,7 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto return this.replace(/&/g,'&').replace(//g,'>'); }, unescapeHTML: function() { - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); @@ -547,7 +555,7 @@ Object.extend(String.prototype.escapeHTML, { text: document.createTextNode('') }); -with (String.prototype.escapeHTML) div.appendChild(text); +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); var Template = Class.create({ initialize: function(template, pattern) { @@ -589,10 +597,9 @@ var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; - iterator = iterator.bind(context); try { this._each(function(value) { - iterator(value, index++); + iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; @@ -601,47 +608,46 @@ var Enumerable = { }, eachSlice: function(number, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator(value, index); + result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator(value, index)) + if (result = !!iterator.call(context, value, index)) throw $break; }); return result; }, collect: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, detect: function(iterator, context) { - iterator = iterator.bind(context); var result; this.each(function(value, index) { - if (iterator(value, index)) { + if (iterator.call(context, value, index)) { result = value; throw $break; } @@ -650,17 +656,16 @@ var Enumerable = { }, findAll: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (iterator(value, index)) + if (iterator.call(context, value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) @@ -668,7 +673,7 @@ var Enumerable = { this.each(function(value, index) { if (filter.match(value)) - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; }, @@ -696,9 +701,8 @@ var Enumerable = { }, inject: function(memo, iterator, context) { - iterator = iterator.bind(context); this.each(function(value, index) { - memo = iterator(memo, value, index); + memo = iterator.call(context, memo, value, index); }); return memo; }, @@ -711,10 +715,10 @@ var Enumerable = { }, max: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); @@ -722,10 +726,10 @@ var Enumerable = { }, min: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); @@ -733,10 +737,10 @@ var Enumerable = { }, partition: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator(value, index) ? + (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -751,19 +755,20 @@ var Enumerable = { }, reject: function(iterator, context) { - iterator = iterator.bind(context); var results = []; this.each(function(value, index) { - if (!iterator(value, index)) + if (!iterator.call(context, value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { - iterator = iterator.bind(context); return this.map(function(value, index) { - return {value: value, criteria: iterator(value, index)}; + return { + value: value, + criteria: iterator.call(context, value, index) + }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; @@ -815,8 +820,12 @@ function $A(iterable) { if (Prototype.Browser.WebKit) { $A = function(iterable) { if (!iterable) return []; - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && - iterable.toArray) return iterable.toArray(); + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; @@ -963,8 +972,8 @@ Object.extend(Number.prototype, { return this + 1; }, - times: function(iterator) { - $R(0, this, true).each(iterator); + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); return this; }, @@ -1011,7 +1020,9 @@ var Hash = Class.create(Enumerable, (function() { }, get: function(key) { - return this._object[key]; + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; }, unset: function(key) { @@ -1051,14 +1062,14 @@ var Hash = Class.create(Enumerable, (function() { }, toQueryString: function() { - return this.map(function(pair) { + return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) - return values.map(toQueryPair.curry(key)).join('&'); - } - return toQueryPair(key, values); + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; }).join('&'); }, @@ -1558,6 +1569,7 @@ if (!Node.ELEMENT_NODE) { return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; }).call(window); Element.cache = { }; @@ -1574,12 +1586,14 @@ Element.Methods = { }, hide: function(element) { - $(element).style.display = 'none'; + element = $(element); + element.style.display = 'none'; return element; }, show: function(element) { - $(element).style.display = ''; + element = $(element); + element.style.display = ''; return element; }, @@ -1733,7 +1747,7 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? element.descendants()[expression] : - element.select(expression)[index || 0]; + Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { @@ -1863,24 +1877,16 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); - var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; - if (element.sourceIndex && !Prototype.Browser.Opera) { - var e = element.sourceIndex, a = ancestor.sourceIndex, - nextAncestor = ancestor.nextSibling; - if (!nextAncestor) { - do { ancestor = ancestor.parentNode; } - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); - } - if (nextAncestor && nextAncestor.sourceIndex) - return (e > a && e < nextAncestor.sourceIndex); - } + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) - if (element == originalAncestor) return true; + if (element == ancestor) return true; + return false; }, @@ -1895,7 +1901,7 @@ Element.Methods = { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; - if (!value) { + if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } @@ -1934,7 +1940,7 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = $(element).getStyle('display'); + var display = element.getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; @@ -1963,7 +1969,7 @@ Element.Methods = { element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined - if (window.opera) { + if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } @@ -2018,7 +2024,7 @@ Element.Methods = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName == 'BODY') break; + if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } @@ -2028,7 +2034,7 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return; + if (element.getStyle('position') == 'absolute') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); @@ -2052,7 +2058,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return; + if (element.getStyle('position') == 'relative') return element; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; @@ -2103,7 +2109,7 @@ Element.Methods = { element = forElement; do { - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } @@ -2218,6 +2224,9 @@ else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); @@ -2231,6 +2240,8 @@ else if (Prototype.Browser.IE) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } var position = element.getStyle('position'); if (position !== 'static') return proceed(element); // Trigger hasLayout on the offset parent so that IE6 reports @@ -2246,6 +2257,14 @@ else if (Prototype.Browser.IE) { ); }); + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2337,7 +2356,7 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc').each(function(attr) { + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); @@ -2390,7 +2409,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName == 'IMG' && element.width) { + if(element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2521,7 +2540,7 @@ Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); - return node && node.specified; + return !!(node && node.specified); } }; @@ -2530,9 +2549,9 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { + document.createElement('div')['__proto__']) { window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div').__proto__; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } @@ -2547,7 +2566,7 @@ Element.extend = (function() { element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName, property, value; + tagName = element.tagName.toUpperCase(), property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); @@ -2643,7 +2662,7 @@ Element.addMethods = function(methods) { if (window[klass]) return window[klass]; window[klass] = { }; - window[klass].prototype = document.createElement(tagName).__proto__; + window[klass].prototype = document.createElement(tagName)['__proto__']; return window[klass]; } @@ -2669,12 +2688,18 @@ Element.addMethods = function(methods) { document.viewport = { getDimensions: function() { - var dimensions = { }; - var B = Prototype.Browser; + var dimensions = { }, B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } }); return dimensions; }, @@ -2693,14 +2718,24 @@ document.viewport = { window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); - this.compileMatcher(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + }, shouldUseXPath: function() { @@ -2715,16 +2750,29 @@ var Selector = Class.create({ // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + if ((/(\[[\w-]*?:|:checked)/).test(e)) return false; return true; }, - compileMatcher: function() { - if (this.shouldUseXPath()) - return this.compileXPathMatcher(); + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + compileMatcher: function() { var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; @@ -2742,7 +2790,7 @@ var Selector = Class.create({ p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); + new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } @@ -2781,8 +2829,27 @@ var Selector = Class.create({ findElements: function(root) { root = root || document; - if (this.xpath) return document._getElementsByXPath(this.xpath, root); - return this.matcher(root); + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } }, match: function(element) { @@ -2873,10 +2940,10 @@ Object.extend(Selector, { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'empty': "[count(*) = 0 and (count(text()) = 0)]", 'checked': "[@checked]", - 'disabled': "[@disabled]", - 'enabled': "[not(@disabled)]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; @@ -2968,7 +3035,7 @@ Object.extend(Selector, { className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[([\w]+)\]/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3081,7 +3148,7 @@ Object.extend(Selector, { nextElementSibling: function(node) { while (node = node.nextSibling) - if (node.nodeType == 1) return node; + if (node.nodeType == 1) return node; return null; }, @@ -3270,7 +3337,7 @@ Object.extend(Selector, { 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + if (node.tagName == '!' || node.firstChild) continue; results.push(node); } return results; @@ -3288,7 +3355,8 @@ Object.extend(Selector, { 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled) results.push(node); + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); return results; }, @@ -3308,11 +3376,14 @@ Object.extend(Selector, { operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv.startsWith(v); }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } }, split: function(expression) { @@ -3386,7 +3457,7 @@ var Form = { var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); - if (value != null && (element.type != 'submit' || (!submitted && + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values @@ -3547,7 +3618,6 @@ Form.Element.Methods = { disable: function(element) { element = $(element); - element.blur(); element.disabled = true; return element; }, @@ -3587,22 +3657,22 @@ Form.Element.Serializers = { else element.value = value; }, - select: function(element, index) { - if (Object.isUndefined(index)) + select: function(element, value) { + if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { - var opt, value, single = !Object.isArray(index); + var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; - value = this.optionValue(opt); + currentValue = this.optionValue(opt); if (single) { - if (value == index) { + if (currentValue == value) { opt.selected = true; return; } } - else opt.selected = index.include(value); + else opt.selected = value.include(currentValue); } } }, @@ -3773,8 +3843,23 @@ Event.Methods = (function() { isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { - var node = Event.extend(event).target; - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); }, findElement: function(event, expression) { @@ -3785,11 +3870,15 @@ Event.Methods = (function() { }, pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; return { x: event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)), + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), y: event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)) + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) }; }, @@ -3834,7 +3923,7 @@ Event.extend = (function() { }; } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; Object.extend(Event.prototype, methods); return Prototype.K; } @@ -3899,10 +3988,20 @@ Object.extend(Event, (function() { cache[id][eventName] = null; } + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + return { observe: function(element, eventName, handler) { element = $(element); -- cgit v1.2.3 From b579184819f483ac2dffae605806ea2ecd6c1519 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Tue, 18 Nov 2008 20:08:20 +0100 Subject: Remove mention of long-dead define_javascript_functions --- actionpack/lib/action_view/helpers/javascript_helper.rb | 3 --- 1 file changed, 3 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 32089442b7..8f64acf102 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -31,9 +31,6 @@ module ActionView # to use all basic AJAX functionality. For the Scriptaculous-based # JavaScript helpers, like visual effects, autocompletion, drag and drop # and so on, you should use the method described above. - # * Use <%= define_javascript_functions %>: this will copy all the - # JavaScript support functions within a single script block. Not - # recommended. # # For documentation on +javascript_include_tag+ see # ActionView::Helpers::AssetTagHelper. -- cgit v1.2.3 From 19e9a1f47db89fc94b1c1f0e8bd533c9e925f4d1 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Tue, 18 Nov 2008 20:09:59 +0100 Subject: Remove duplicate distribution of prototype and scriptaculous. This was previously needed by define_javascript_functions which has been removed for a while. --- .../action_view/helpers/javascripts/controls.js | 963 ----- .../action_view/helpers/javascripts/dragdrop.js | 973 ----- .../lib/action_view/helpers/javascripts/effects.js | 1128 ----- .../action_view/helpers/javascripts/prototype.js | 4320 -------------------- 4 files changed, 7384 deletions(-) delete mode 100644 actionpack/lib/action_view/helpers/javascripts/controls.js delete mode 100644 actionpack/lib/action_view/helpers/javascripts/dragdrop.js delete mode 100644 actionpack/lib/action_view/helpers/javascripts/effects.js delete mode 100644 actionpack/lib/action_view/helpers/javascripts/prototype.js (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js deleted file mode 100644 index ca29aefdd1..0000000000 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ /dev/null @@ -1,963 +0,0 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) -// Contributors: -// Richard Livsey -// Rahul Bhargava -// Rob Wills -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -// Autocompleter.Base handles all the autocompletion functionality -// that's independent of the data source for autocompletion. This -// includes drawing the autocompletion menu, observing keyboard -// and mouse events, and similar. -// -// Specific autocompleters need to provide, at the very least, -// a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method -// should get the text for which to provide autocompletion by -// invoking this.getToken(), NOT by directly accessing -// this.element.value. This is to allow incremental tokenized -// autocompletion. Specific auto-completion logic (AJAX, etc) -// belongs in getUpdatedChoices. -// -// Tokenized incremental autocompletion is enabled automatically -// when an autocompleter is instantiated with the 'tokens' option -// in the options parameter, e.g.: -// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); -// will incrementally autocomplete with a comma as the token. -// Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it -// allows smart autocompletion after linebreaks. - -if(typeof Effect == 'undefined') - throw("controls.js requires including script.aculo.us' effects.js library"); - -var Autocompleter = { }; -Autocompleter.Base = Class.create({ - baseInitialize: function(element, update, options) { - element = $(element); - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; - this.entryCount = 0; - this.oldElementValue = this.element.value; - - if(this.setOptions) - this.setOptions(options); - else - this.options = options || { }; - - this.options.paramName = this.options.paramName || this.element.name; - this.options.tokens = this.options.tokens || []; - this.options.frequency = this.options.frequency || 0.4; - this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; - Position.clone(element, update, { - setHeight: false, - offsetTop: element.offsetHeight - }); - } - Effect.Appear(update,{duration:0.15}); - }; - this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(typeof(this.options.tokens) == 'string') - this.options.tokens = new Array(this.options.tokens); - // Force carriage returns as token delimiters anyway - if (!this.options.tokens.include('\n')) - this.options.tokens.push('\n'); - - this.observer = null; - - this.element.setAttribute('autocomplete','off'); - - Element.hide(this.update); - - Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); - Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); - }, - - show: function() { - if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && - (Prototype.Browser.IE) && - (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, - ''); - this.iefix = $(this.update.id+'_iefix'); - } - if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); - }, - - fixIEOverlapping: function() { - Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); - this.iefix.style.zIndex = 1; - this.update.style.zIndex = 2; - Element.show(this.iefix); - }, - - hide: function() { - this.stopIndicator(); - if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); - if(this.iefix) Element.hide(this.iefix); - }, - - startIndicator: function() { - if(this.options.indicator) Element.show(this.options.indicator); - }, - - stopIndicator: function() { - if(this.options.indicator) Element.hide(this.options.indicator); - }, - - onKeyPress: function(event) { - if(this.active) - switch(event.keyCode) { - case Event.KEY_TAB: - case Event.KEY_RETURN: - this.selectEntry(); - Event.stop(event); - case Event.KEY_ESC: - this.hide(); - this.active = false; - Event.stop(event); - return; - case Event.KEY_LEFT: - case Event.KEY_RIGHT: - return; - case Event.KEY_UP: - this.markPrevious(); - this.render(); - Event.stop(event); - return; - case Event.KEY_DOWN: - this.markNext(); - this.render(); - Event.stop(event); - return; - } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || - (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; - - this.changed = true; - this.hasFocus = true; - - if(this.observer) clearTimeout(this.observer); - this.observer = - setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); - }, - - activate: function() { - this.changed = false; - this.hasFocus = true; - this.getUpdatedChoices(); - }, - - onHover: function(event) { - var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) - { - this.index = element.autocompleteIndex; - this.render(); - } - Event.stop(event); - }, - - onClick: function(event) { - var element = Event.findElement(event, 'LI'); - this.index = element.autocompleteIndex; - this.selectEntry(); - this.hide(); - }, - - onBlur: function(event) { - // needed to make click events working - setTimeout(this.hide.bind(this), 250); - this.hasFocus = false; - this.active = false; - }, - - render: function() { - if(this.entryCount > 0) { - for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : - Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { - this.show(); - this.active = true; - } - } else { - this.active = false; - this.hide(); - } - }, - - markPrevious: function() { - if(this.index > 0) this.index--; - else this.index = this.entryCount-1; - this.getEntry(this.index).scrollIntoView(true); - }, - - markNext: function() { - if(this.index < this.entryCount-1) this.index++; - else this.index = 0; - this.getEntry(this.index).scrollIntoView(false); - }, - - getEntry: function(index) { - return this.update.firstChild.childNodes[index]; - }, - - getCurrentEntry: function() { - return this.getEntry(this.index); - }, - - selectEntry: function() { - this.active = false; - this.updateElement(this.getCurrentEntry()); - }, - - updateElement: function(selectedElement) { - if (this.options.updateElement) { - this.options.updateElement(selectedElement); - return; - } - var value = ''; - if (this.options.select) { - var nodes = $(selectedElement).select('.' + this.options.select) || []; - if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); - } else - value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - - var bounds = this.getTokenBounds(); - if (bounds[0] != -1) { - var newValue = this.element.value.substr(0, bounds[0]); - var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); - if (whitespace) - newValue += whitespace[0]; - this.element.value = newValue + value + this.element.value.substr(bounds[1]); - } else { - this.element.value = value; - } - this.oldElementValue = this.element.value; - this.element.focus(); - - if (this.options.afterUpdateElement) - this.options.afterUpdateElement(this.element, selectedElement); - }, - - updateChoices: function(choices) { - if(!this.changed && this.hasFocus) { - this.update.innerHTML = choices; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.down()); - - if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = - this.update.down().childNodes.length; - for (var i = 0; i < this.entryCount; i++) { - var entry = this.getEntry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entryCount = 0; - } - - this.stopIndicator(); - this.index = 0; - - if(this.entryCount==1 && this.options.autoSelect) { - this.selectEntry(); - this.hide(); - } else { - this.render(); - } - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onObserverEvent: function() { - this.changed = false; - this.tokenBounds = null; - if(this.getToken().length>=this.options.minChars) { - this.getUpdatedChoices(); - } else { - this.active = false; - this.hide(); - } - this.oldElementValue = this.element.value; - }, - - getToken: function() { - var bounds = this.getTokenBounds(); - return this.element.value.substring(bounds[0], bounds[1]).strip(); - }, - - getTokenBounds: function() { - if (null != this.tokenBounds) return this.tokenBounds; - var value = this.element.value; - if (value.strip().empty()) return [-1, 0]; - var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); - var offset = (diff == this.oldElementValue.length ? 1 : 0); - var prevTokenPos = -1, nextTokenPos = value.length; - var tp; - for (var index = 0, l = this.options.tokens.length; index < l; ++index) { - tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); - if (tp > prevTokenPos) prevTokenPos = tp; - tp = value.indexOf(this.options.tokens[index], diff + offset); - if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; - } - return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); - } -}); - -Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { - var boundary = Math.min(newS.length, oldS.length); - for (var index = 0; index < boundary; ++index) - if (newS[index] != oldS[index]) - return index; - return boundary; -}; - -Ajax.Autocompleter = Class.create(Autocompleter.Base, { - initialize: function(element, update, url, options) { - this.baseInitialize(element, update, options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this); - this.options.defaultParams = this.options.parameters || null; - this.url = url; - }, - - getUpdatedChoices: function() { - this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + - encodeURIComponent(this.getToken()); - - this.options.parameters = this.options.callback ? - this.options.callback(this.element, entry) : entry; - - if(this.options.defaultParams) - this.options.parameters += '&' + this.options.defaultParams; - - new Ajax.Request(this.url, this.options); - }, - - onComplete: function(request) { - this.updateChoices(request.responseText); - } -}); - -// The local array autocompleter. Used when you'd prefer to -// inject an array of autocompletion options into the page, rather -// than sending out Ajax queries, which can be quite slow sometimes. -// -// The constructor takes four parameters. The first two are, as usual, -// the id of the monitored textbox, and id of the autocompletion menu. -// The third is the array you want to autocomplete from, and the fourth -// is the options block. -// -// Extra local autocompletion options: -// - choices - How many autocompletion choices to offer -// -// - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the -// autocomplete array. Defaults to true, which will -// match text at the beginning of any *word* in the -// strings in the autocomplete array. If you want to -// search anywhere in the string, additionally set -// the option fullSearch to true (default: off). -// -// - fullSsearch - Search anywhere in autocomplete array strings. -// -// - partialChars - How many characters to enter before triggering -// a partial match (unlike minChars, which defines -// how many characters are required to do any match -// at all). Defaults to 2. -// -// - ignoreCase - Whether to ignore case when autocompleting. -// Defaults to true. -// -// It's possible to pass in a custom function as the 'selector' -// option, if you prefer to write your own autocompletion logic. -// In that case, the other options above will not apply unless -// you support them. - -Autocompleter.Local = Class.create(Autocompleter.Base, { - initialize: function(element, update, array, options) { - this.baseInitialize(element, update, options); - this.options.array = array; - }, - - getUpdatedChoices: function() { - this.updateChoices(this.options.selector(this)); - }, - - setOptions: function(options) { - this.options = Object.extend({ - choices: 10, - partialSearch: true, - partialChars: 2, - ignoreCase: true, - fullSearch: false, - selector: function(instance) { - var ret = []; // Beginning matches - var partial = []; // Inside matches - var entry = instance.getToken(); - var count = 0; - - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { - - var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : - elem.indexOf(entry); - - while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + - elem.substr(entry.length) + "
  • "); - break; - } else if (entry.length >= instance.options.partialChars && - instance.options.partialSearch && foundPos != -1) { - if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { - partial.push("
  • " + elem.substr(0, foundPos) + "" + - elem.substr(foundPos, entry.length) + "" + elem.substr( - foundPos + entry.length) + "
  • "); - break; - } - } - - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : - elem.indexOf(entry, foundPos + 1); - - } - } - if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); - return "
      " + ret.join('') + "
    "; - } - }, options || { }); - } -}); - -// AJAX in-place editor and collection editor -// Full rewrite by Christophe Porteneuve (April 2007). - -// Use this if you notice weird scrolling problems on some browsers, -// the DOM might be a bit confused when this gets called so do this -// waits 1 ms (with setTimeout) until it does the activation -Field.scrollFreeActivate = function(field) { - setTimeout(function() { - Field.activate(field); - }, 1); -}; - -Ajax.InPlaceEditor = Class.create({ - initialize: function(element, url, options) { - this.url = url; - this.element = element = $(element); - this.prepareOptions(); - this._controls = { }; - arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! - Object.extend(this.options, options || { }); - if (!this.options.formId && this.element.id) { - this.options.formId = this.element.id + '-inplaceeditor'; - if ($(this.options.formId)) - this.options.formId = ''; - } - if (this.options.externalControl) - this.options.externalControl = $(this.options.externalControl); - if (!this.options.externalControl) - this.options.externalControlOnly = false; - this._originalBackground = this.element.getStyle('background-color') || 'transparent'; - this.element.title = this.options.clickToEditText; - this._boundCancelHandler = this.handleFormCancellation.bind(this); - this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); - this._boundFailureHandler = this.handleAJAXFailure.bind(this); - this._boundSubmitHandler = this.handleFormSubmission.bind(this); - this._boundWrapperHandler = this.wrapUp.bind(this); - this.registerListeners(); - }, - checkForEscapeOrReturn: function(e) { - if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; - if (Event.KEY_ESC == e.keyCode) - this.handleFormCancellation(e); - else if (Event.KEY_RETURN == e.keyCode) - this.handleFormSubmission(e); - }, - createControl: function(mode, handler, extraClasses) { - var control = this.options[mode + 'Control']; - var text = this.options[mode + 'Text']; - if ('button' == control) { - var btn = document.createElement('input'); - btn.type = 'submit'; - btn.value = text; - btn.className = 'editor_' + mode + '_button'; - if ('cancel' == mode) - btn.onclick = this._boundCancelHandler; - this._form.appendChild(btn); - this._controls[mode] = btn; - } else if ('link' == control) { - var link = document.createElement('a'); - link.href = '#'; - link.appendChild(document.createTextNode(text)); - link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; - link.className = 'editor_' + mode + '_link'; - if (extraClasses) - link.className += ' ' + extraClasses; - this._form.appendChild(link); - this._controls[mode] = link; - } - }, - createEditField: function() { - var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); - var fld; - if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { - fld = document.createElement('input'); - fld.type = 'text'; - var size = this.options.size || this.options.cols || 0; - if (0 < size) fld.size = size; - } else { - fld = document.createElement('textarea'); - fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); - fld.cols = this.options.cols || 40; - } - fld.name = this.options.paramName; - fld.value = text; // No HTML breaks conversion anymore - fld.className = 'editor_field'; - if (this.options.submitOnBlur) - fld.onblur = this._boundSubmitHandler; - this._controls.editor = fld; - if (this.options.loadTextURL) - this.loadExternalText(); - this._form.appendChild(this._controls.editor); - }, - createForm: function() { - var ipe = this; - function addText(mode, condition) { - var text = ipe.options['text' + mode + 'Controls']; - if (!text || condition === false) return; - ipe._form.appendChild(document.createTextNode(text)); - }; - this._form = $(document.createElement('form')); - this._form.id = this.options.formId; - this._form.addClassName(this.options.formClassName); - this._form.onsubmit = this._boundSubmitHandler; - this.createEditField(); - if ('textarea' == this._controls.editor.tagName.toLowerCase()) - this._form.appendChild(document.createElement('br')); - if (this.options.onFormCustomization) - this.options.onFormCustomization(this, this._form); - addText('Before', this.options.okControl || this.options.cancelControl); - this.createControl('ok', this._boundSubmitHandler); - addText('Between', this.options.okControl && this.options.cancelControl); - this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); - addText('After', this.options.okControl || this.options.cancelControl); - }, - destroy: function() { - if (this._oldInnerHTML) - this.element.innerHTML = this._oldInnerHTML; - this.leaveEditMode(); - this.unregisterListeners(); - }, - enterEditMode: function(e) { - if (this._saving || this._editing) return; - this._editing = true; - this.triggerCallback('onEnterEditMode'); - if (this.options.externalControl) - this.options.externalControl.hide(); - this.element.hide(); - this.createForm(); - this.element.parentNode.insertBefore(this._form, this.element); - if (!this.options.loadTextURL) - this.postProcessEditField(); - if (e) Event.stop(e); - }, - enterHover: function(e) { - if (this.options.hoverClassName) - this.element.addClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onEnterHover'); - }, - getText: function() { - return this.element.innerHTML.unescapeHTML(); - }, - handleAJAXFailure: function(transport) { - this.triggerCallback('onFailure', transport); - if (this._oldInnerHTML) { - this.element.innerHTML = this._oldInnerHTML; - this._oldInnerHTML = null; - } - }, - handleFormCancellation: function(e) { - this.wrapUp(); - if (e) Event.stop(e); - }, - handleFormSubmission: function(e) { - var form = this._form; - var value = $F(this._controls.editor); - this.prepareSubmission(); - var params = this.options.callback(form, value) || ''; - if (Object.isString(params)) - params = params.toQueryParams(); - params.editorId = this.element.id; - if (this.options.htmlResponse) { - var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Updater({ success: this.element }, this.url, options); - } else { - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.url, options); - } - if (e) Event.stop(e); - }, - leaveEditMode: function() { - this.element.removeClassName(this.options.savingClassName); - this.removeForm(); - this.leaveHover(); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - if (this.options.externalControl) - this.options.externalControl.show(); - this._saving = false; - this._editing = false; - this._oldInnerHTML = null; - this.triggerCallback('onLeaveEditMode'); - }, - leaveHover: function(e) { - if (this.options.hoverClassName) - this.element.removeClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onLeaveHover'); - }, - loadExternalText: function() { - this._form.addClassName(this.options.loadingClassName); - this._controls.editor.disabled = true; - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._form.removeClassName(this.options.loadingClassName); - var text = transport.responseText; - if (this.options.stripLoadedTextTags) - text = text.stripTags(); - this._controls.editor.value = text; - this._controls.editor.disabled = false; - this.postProcessEditField(); - }.bind(this), - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - postProcessEditField: function() { - var fpc = this.options.fieldPostCreation; - if (fpc) - $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); - }, - prepareOptions: function() { - this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); - Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); - [this._extraDefaultOptions].flatten().compact().each(function(defs) { - Object.extend(this.options, defs); - }.bind(this)); - }, - prepareSubmission: function() { - this._saving = true; - this.removeForm(); - this.leaveHover(); - this.showSaving(); - }, - registerListeners: function() { - this._listeners = { }; - var listener; - $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { - listener = this[pair.value].bind(this); - this._listeners[pair.key] = listener; - if (!this.options.externalControlOnly) - this.element.observe(pair.key, listener); - if (this.options.externalControl) - this.options.externalControl.observe(pair.key, listener); - }.bind(this)); - }, - removeForm: function() { - if (!this._form) return; - this._form.remove(); - this._form = null; - this._controls = { }; - }, - showSaving: function() { - this._oldInnerHTML = this.element.innerHTML; - this.element.innerHTML = this.options.savingText; - this.element.addClassName(this.options.savingClassName); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - }, - triggerCallback: function(cbName, arg) { - if ('function' == typeof this.options[cbName]) { - this.options[cbName](this, arg); - } - }, - unregisterListeners: function() { - $H(this._listeners).each(function(pair) { - if (!this.options.externalControlOnly) - this.element.stopObserving(pair.key, pair.value); - if (this.options.externalControl) - this.options.externalControl.stopObserving(pair.key, pair.value); - }.bind(this)); - }, - wrapUp: function(transport) { - this.leaveEditMode(); - // Can't use triggerCallback due to backward compatibility: requires - // binding + direct element - this._boundComplete(transport, this.element); - } -}); - -Object.extend(Ajax.InPlaceEditor.prototype, { - dispose: Ajax.InPlaceEditor.prototype.destroy -}); - -Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { - initialize: function($super, element, url, options) { - this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; - $super(element, url, options); - }, - - createEditField: function() { - var list = document.createElement('select'); - list.name = this.options.paramName; - list.size = 1; - this._controls.editor = list; - this._collection = this.options.collection || []; - if (this.options.loadCollectionURL) - this.loadCollection(); - else - this.checkForExternalText(); - this._form.appendChild(this._controls.editor); - }, - - loadCollection: function() { - this._form.addClassName(this.options.loadingClassName); - this.showLoadingText(this.options.loadingCollectionText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - var js = transport.responseText.strip(); - if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw('Server returned an invalid collection representation.'); - this._collection = eval(js); - this.checkForExternalText(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadCollectionURL, options); - }, - - showLoadingText: function(text) { - this._controls.editor.disabled = true; - var tempOption = this._controls.editor.firstChild; - if (!tempOption) { - tempOption = document.createElement('option'); - tempOption.value = ''; - this._controls.editor.appendChild(tempOption); - tempOption.selected = true; - } - tempOption.update((text || '').stripScripts().stripTags()); - }, - - checkForExternalText: function() { - this._text = this.getText(); - if (this.options.loadTextURL) - this.loadExternalText(); - else - this.buildOptionList(); - }, - - loadExternalText: function() { - this.showLoadingText(this.options.loadingText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._text = transport.responseText.strip(); - this.buildOptionList(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - - buildOptionList: function() { - this._form.removeClassName(this.options.loadingClassName); - this._collection = this._collection.map(function(entry) { - return 2 === entry.length ? entry : [entry, entry].flatten(); - }); - var marker = ('value' in this.options) ? this.options.value : this._text; - var textFound = this._collection.any(function(entry) { - return entry[0] == marker; - }.bind(this)); - this._controls.editor.update(''); - var option; - this._collection.each(function(entry, index) { - option = document.createElement('option'); - option.value = entry[0]; - option.selected = textFound ? entry[0] == marker : 0 == index; - option.appendChild(document.createTextNode(entry[1])); - this._controls.editor.appendChild(option); - }.bind(this)); - this._controls.editor.disabled = false; - Field.scrollFreeActivate(this._controls.editor); - } -}); - -//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** -//**** This only exists for a while, in order to let **** -//**** users adapt to the new API. Read up on the new **** -//**** API and convert your code to it ASAP! **** - -Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { - if (!options) return; - function fallback(name, expr) { - if (name in options || expr === undefined) return; - options[name] = expr; - }; - fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : - options.cancelLink == options.cancelButton == false ? false : undefined))); - fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : - options.okLink == options.okButton == false ? false : undefined))); - fallback('highlightColor', options.highlightcolor); - fallback('highlightEndColor', options.highlightendcolor); -}; - -Object.extend(Ajax.InPlaceEditor, { - DefaultOptions: { - ajaxOptions: { }, - autoRows: 3, // Use when multi-line w/ rows == 1 - cancelControl: 'link', // 'link'|'button'|false - cancelText: 'cancel', - clickToEditText: 'Click to edit', - externalControl: null, // id|elt - externalControlOnly: false, - fieldPostCreation: 'activate', // 'activate'|'focus'|false - formClassName: 'inplaceeditor-form', - formId: null, // id|elt - highlightColor: '#ffff99', - highlightEndColor: '#ffffff', - hoverClassName: '', - htmlResponse: true, - loadingClassName: 'inplaceeditor-loading', - loadingText: 'Loading...', - okControl: 'button', // 'link'|'button'|false - okText: 'ok', - paramName: 'value', - rows: 1, // If 1 and multi-line, uses autoRows - savingClassName: 'inplaceeditor-saving', - savingText: 'Saving...', - size: 0, - stripLoadedTextTags: false, - submitOnBlur: false, - textAfterControls: '', - textBeforeControls: '', - textBetweenControls: '' - }, - DefaultCallbacks: { - callback: function(form) { - return Form.serialize(form); - }, - onComplete: function(transport, element) { - // For backward compatibility, this one is bound to the IPE, and passes - // the element directly. It was too often customized, so we don't break it. - new Effect.Highlight(element, { - startcolor: this.options.highlightColor, keepBackgroundImage: true }); - }, - onEnterEditMode: null, - onEnterHover: function(ipe) { - ipe.element.style.backgroundColor = ipe.options.highlightColor; - if (ipe._effect) - ipe._effect.cancel(); - }, - onFailure: function(transport, ipe) { - alert('Error communication with the server: ' + transport.responseText.stripTags()); - }, - onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. - onLeaveEditMode: null, - onLeaveHover: function(ipe) { - ipe._effect = new Effect.Highlight(ipe.element, { - startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, - restorecolor: ipe._originalBackground, keepBackgroundImage: true - }); - } - }, - Listeners: { - click: 'enterEditMode', - keydown: 'checkForEscapeOrReturn', - mouseover: 'enterHover', - mouseout: 'leaveHover' - } -}); - -Ajax.InPlaceCollectionEditor.DefaultOptions = { - loadingCollectionText: 'Loading options...' -}; - -// Delayed observer, like Form.Element.Observer, -// but waits for delay after last key input -// Ideal for live-search fields - -Form.Element.DelayedObserver = Class.create({ - initialize: function(element, delay, callback) { - this.delay = delay || 0.5; - this.element = $(element); - this.callback = callback; - this.timer = null; - this.lastValue = $F(this.element); - Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); - }, - delayedListener: function(event) { - if(this.lastValue == $F(this.element)) return; - if(this.timer) clearTimeout(this.timer); - this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); - this.lastValue = $F(this.element); - }, - onTimerEvent: function() { - this.timer = null; - this.callback(this.element, $F(this.element)); - } -}); \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js deleted file mode 100644 index 07229f986f..0000000000 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ /dev/null @@ -1,973 +0,0 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -if(Object.isUndefined(Effect)) - throw("dragdrop.js requires including script.aculo.us' effects.js library"); - -var Droppables = { - drops: [], - - remove: function(element) { - this.drops = this.drops.reject(function(d) { return d.element==$(element) }); - }, - - add: function(element) { - element = $(element); - var options = Object.extend({ - greedy: true, - hoverclass: null, - tree: false - }, arguments[1] || { }); - - // cache containers - if(options.containment) { - options._containers = []; - var containment = options.containment; - if(Object.isArray(containment)) { - containment.each( function(c) { options._containers.push($(c)) }); - } else { - options._containers.push($(containment)); - } - } - - if(options.accept) options.accept = [options.accept].flatten(); - - Element.makePositioned(element); // fix IE - options.element = element; - - this.drops.push(options); - }, - - findDeepestChild: function(drops) { - deepest = drops[0]; - - for (i = 1; i < drops.length; ++i) - if (Element.isParent(drops[i].element, deepest.element)) - deepest = drops[i]; - - return deepest; - }, - - isContained: function(element, drop) { - var containmentNode; - if(drop.tree) { - containmentNode = element.treeNode; - } else { - containmentNode = element.parentNode; - } - return drop._containers.detect(function(c) { return containmentNode == c }); - }, - - isAffected: function(point, element, drop) { - return ( - (drop.element!=element) && - ((!drop._containers) || - this.isContained(element, drop)) && - ((!drop.accept) || - (Element.classNames(element).detect( - function(v) { return drop.accept.include(v) } ) )) && - Position.within(drop.element, point[0], point[1]) ); - }, - - deactivate: function(drop) { - if(drop.hoverclass) - Element.removeClassName(drop.element, drop.hoverclass); - this.last_active = null; - }, - - activate: function(drop) { - if(drop.hoverclass) - Element.addClassName(drop.element, drop.hoverclass); - this.last_active = drop; - }, - - show: function(point, element) { - if(!this.drops.length) return; - var drop, affected = []; - - this.drops.each( function(drop) { - if(Droppables.isAffected(point, element, drop)) - affected.push(drop); - }); - - if(affected.length>0) - drop = Droppables.findDeepestChild(affected); - - if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); - if (drop) { - Position.within(drop.element, point[0], point[1]); - if(drop.onHover) - drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - - if (drop != this.last_active) Droppables.activate(drop); - } - }, - - fire: function(event, element) { - if(!this.last_active) return; - Position.prepare(); - - if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) - if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; - } - }, - - reset: function() { - if(this.last_active) - this.deactivate(this.last_active); - } -}; - -var Draggables = { - drags: [], - observers: [], - - register: function(draggable) { - if(this.drags.length == 0) { - this.eventMouseUp = this.endDrag.bindAsEventListener(this); - this.eventMouseMove = this.updateDrag.bindAsEventListener(this); - this.eventKeypress = this.keyPress.bindAsEventListener(this); - - Event.observe(document, "mouseup", this.eventMouseUp); - Event.observe(document, "mousemove", this.eventMouseMove); - Event.observe(document, "keypress", this.eventKeypress); - } - this.drags.push(draggable); - }, - - unregister: function(draggable) { - this.drags = this.drags.reject(function(d) { return d==draggable }); - if(this.drags.length == 0) { - Event.stopObserving(document, "mouseup", this.eventMouseUp); - Event.stopObserving(document, "mousemove", this.eventMouseMove); - Event.stopObserving(document, "keypress", this.eventKeypress); - } - }, - - activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); - } else { - window.focus(); // allows keypress events if window isn't currently focused, fails for Safari - this.activeDraggable = draggable; - } - }, - - deactivate: function() { - this.activeDraggable = null; - }, - - updateDrag: function(event) { - if(!this.activeDraggable) return; - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - // Mozilla-based browsers fire successive mousemove events with - // the same coordinates, prevent needless redrawing (moz bug?) - if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; - this._lastPointer = pointer; - - this.activeDraggable.updateDrag(event, pointer); - }, - - endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; - } - if(!this.activeDraggable) return; - this._lastPointer = null; - this.activeDraggable.endDrag(event); - this.activeDraggable = null; - }, - - keyPress: function(event) { - if(this.activeDraggable) - this.activeDraggable.keyPress(event); - }, - - addObserver: function(observer) { - this.observers.push(observer); - this._cacheObserverCallbacks(); - }, - - removeObserver: function(element) { // element instead of observer fixes mem leaks - this.observers = this.observers.reject( function(o) { return o.element==element }); - this._cacheObserverCallbacks(); - }, - - notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' - if(this[eventName+'Count'] > 0) - this.observers.each( function(o) { - if(o[eventName]) o[eventName](eventName, draggable, event); - }); - if(draggable.options[eventName]) draggable.options[eventName](draggable, event); - }, - - _cacheObserverCallbacks: function() { - ['onStart','onEnd','onDrag'].each( function(eventName) { - Draggables[eventName+'Count'] = Draggables.observers.select( - function(o) { return o[eventName]; } - ).length; - }); - } -}; - -/*--------------------------------------------------------------------------*/ - -var Draggable = Class.create({ - initialize: function(element) { - var defaults = { - handle: false, - reverteffect: function(element, top_offset, left_offset) { - var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; - new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, - queue: {scope:'_draggable', position:'end'} - }); - }, - endeffect: function(element) { - var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, - queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false - } - }); - }, - zindex: 1000, - revert: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } - delay: 0 - }; - - if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) - Object.extend(defaults, { - starteffect: function(element) { - element._opacity = Element.getOpacity(element); - Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); - } - }); - - var options = Object.extend(defaults, arguments[1] || { }); - - this.element = $(element); - - if(options.handle && Object.isString(options.handle)) - this.handle = this.element.down('.'+options.handle, 0); - - if(!this.handle) this.handle = $(options.handle); - if(!this.handle) this.handle = this.element; - - if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { - options.scroll = $(options.scroll); - this._isScrollChild = Element.childOf(this.element, options.scroll); - } - - Element.makePositioned(this.element); // fix IE - - this.options = options; - this.dragging = false; - - this.eventMouseDown = this.initDrag.bindAsEventListener(this); - Event.observe(this.handle, "mousedown", this.eventMouseDown); - - Draggables.register(this); - }, - - destroy: function() { - Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); - Draggables.unregister(this); - }, - - currentDelta: function() { - return([ - parseInt(Element.getStyle(this.element,'left') || '0'), - parseInt(Element.getStyle(this.element,'top') || '0')]); - }, - - initDrag: function(event) { - if(!Object.isUndefined(Draggable._dragging[this.element]) && - Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { - // abort on form elements, fixes a Firefox issue - var src = Event.element(event); - if((tag_name = src.tagName.toUpperCase()) && ( - tag_name=='INPUT' || - tag_name=='SELECT' || - tag_name=='OPTION' || - tag_name=='BUTTON' || - tag_name=='TEXTAREA')) return; - - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var pos = Position.cumulativeOffset(this.element); - this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - - Draggables.activate(this); - Event.stop(event); - } - }, - - startDrag: function(event) { - this.dragging = true; - if(!this.delta) - this.delta = this.currentDelta(); - - if(this.options.zindex) { - this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); - this.element.style.zIndex = this.options.zindex; - } - - if(this.options.ghosting) { - this._clone = this.element.cloneNode(true); - this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this._originallyAbsolute) - Position.absolutize(this.element); - this.element.parentNode.insertBefore(this._clone, this.element); - } - - if(this.options.scroll) { - if (this.options.scroll == window) { - var where = this._getWindowScroll(this.options.scroll); - this.originalScrollLeft = where.left; - this.originalScrollTop = where.top; - } else { - this.originalScrollLeft = this.options.scroll.scrollLeft; - this.originalScrollTop = this.options.scroll.scrollTop; - } - } - - Draggables.notify('onStart', this, event); - - if(this.options.starteffect) this.options.starteffect(this.element); - }, - - updateDrag: function(event, pointer) { - if(!this.dragging) this.startDrag(event); - - if(!this.options.quiet){ - Position.prepare(); - Droppables.show(pointer, this.element); - } - - Draggables.notify('onDrag', this, event); - - this.draw(pointer); - if(this.options.change) this.options.change(this); - - if(this.options.scroll) { - this.stopScrolling(); - - var p; - if (this.options.scroll == window) { - with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } - } else { - p = Position.page(this.options.scroll); - p[0] += this.options.scroll.scrollLeft + Position.deltaX; - p[1] += this.options.scroll.scrollTop + Position.deltaY; - p.push(p[0]+this.options.scroll.offsetWidth); - p.push(p[1]+this.options.scroll.offsetHeight); - } - var speed = [0,0]; - if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); - if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); - if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); - if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); - this.startScrolling(speed); - } - - // fix AppleWebKit rendering - if(Prototype.Browser.WebKit) window.scrollBy(0,0); - - Event.stop(event); - }, - - finishDrag: function(event, success) { - this.dragging = false; - - if(this.options.quiet){ - Position.prepare(); - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - Droppables.show(pointer, this.element); - } - - if(this.options.ghosting) { - if (!this._originallyAbsolute) - Position.relativize(this.element); - delete this._originallyAbsolute; - Element.remove(this._clone); - this._clone = null; - } - - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; - } - if(dropped && this.options.onDropped) this.options.onDropped(this.element); - Draggables.notify('onEnd', this, event); - - var revert = this.options.revert; - if(revert && Object.isFunction(revert)) revert = revert(this.element); - - var d = this.currentDelta(); - if(revert && this.options.reverteffect) { - if (dropped == 0 || revert != 'failure') - this.options.reverteffect(this.element, - d[1]-this.delta[1], d[0]-this.delta[0]); - } else { - this.delta = d; - } - - if(this.options.zindex) - this.element.style.zIndex = this.originalZ; - - if(this.options.endeffect) - this.options.endeffect(this.element); - - Draggables.deactivate(this); - Droppables.reset(); - }, - - keyPress: function(event) { - if(event.keyCode!=Event.KEY_ESC) return; - this.finishDrag(event, false); - Event.stop(event); - }, - - endDrag: function(event) { - if(!this.dragging) return; - this.stopScrolling(); - this.finishDrag(event, true); - Event.stop(event); - }, - - draw: function(point) { - var pos = Position.cumulativeOffset(this.element); - if(this.options.ghosting) { - var r = Position.realOffset(this.element); - pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; - } - - var d = this.currentDelta(); - pos[0] -= d[0]; pos[1] -= d[1]; - - if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { - pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; - pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; - } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) - }.bind(this)); - - if(this.options.snap) { - if(Object.isFunction(this.options.snap)) { - p = this.options.snap(p[0],p[1],this); - } else { - if(Object.isArray(this.options.snap)) { - p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); - } else { - p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)); - } - }} - - var style = this.element.style; - if((!this.options.constraint) || (this.options.constraint=='horizontal')) - style.left = p[0] + "px"; - if((!this.options.constraint) || (this.options.constraint=='vertical')) - style.top = p[1] + "px"; - - if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering - }, - - stopScrolling: function() { - if(this.scrollInterval) { - clearInterval(this.scrollInterval); - this.scrollInterval = null; - Draggables._lastScrollPointer = null; - } - }, - - startScrolling: function(speed) { - if(!(speed[0] || speed[1])) return; - this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; - this.lastScrolled = new Date(); - this.scrollInterval = setInterval(this.scroll.bind(this), 10); - }, - - scroll: function() { - var current = new Date(); - var delta = current - this.lastScrolled; - this.lastScrolled = current; - if(this.options.scroll == window) { - with (this._getWindowScroll(this.options.scroll)) { - if (this.scrollSpeed[0] || this.scrollSpeed[1]) { - var d = delta / 1000; - this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); - } - } - } else { - this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; - this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; - } - - Position.prepare(); - Droppables.show(Draggables._lastPointer, this.element); - Draggables.notify('onDrag', this); - if (this._isScrollChild) { - Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); - Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; - Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; - if (Draggables._lastScrollPointer[0] < 0) - Draggables._lastScrollPointer[0] = 0; - if (Draggables._lastScrollPointer[1] < 0) - Draggables._lastScrollPointer[1] = 0; - this.draw(Draggables._lastScrollPointer); - } - - if(this.options.change) this.options.change(this); - }, - - _getWindowScroll: function(w) { - var T, L, W, H; - with (w.document) { - if (w.document.documentElement && documentElement.scrollTop) { - T = documentElement.scrollTop; - L = documentElement.scrollLeft; - } else if (w.document.body) { - T = body.scrollTop; - L = body.scrollLeft; - } - if (w.innerWidth) { - W = w.innerWidth; - H = w.innerHeight; - } else if (w.document.documentElement && documentElement.clientWidth) { - W = documentElement.clientWidth; - H = documentElement.clientHeight; - } else { - W = body.offsetWidth; - H = body.offsetHeight; - } - } - return { top: T, left: L, width: W, height: H }; - } -}); - -Draggable._dragging = { }; - -/*--------------------------------------------------------------------------*/ - -var SortableObserver = Class.create({ - initialize: function(element, observer) { - this.element = $(element); - this.observer = observer; - this.lastValue = Sortable.serialize(this.element); - }, - - onStart: function() { - this.lastValue = Sortable.serialize(this.element); - }, - - onEnd: function() { - Sortable.unmark(); - if(this.lastValue != Sortable.serialize(this.element)) - this.observer(this.element) - } -}); - -var Sortable = { - SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - - sortables: { }, - - _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { - if(element.id && Sortable.sortables[element.id]) return element; - element = element.parentNode; - } - }, - - options: function(element) { - element = Sortable._findRootElement($(element)); - if(!element) return; - return Sortable.sortables[element.id]; - }, - - destroy: function(element){ - element = $(element); - var s = Sortable.sortables[element.id]; - - if(s) { - Draggables.removeObserver(s.element); - s.droppables.each(function(d){ Droppables.remove(d) }); - s.draggables.invoke('destroy'); - - delete Sortable.sortables[s.element.id]; - } - }, - - create: function(element) { - element = $(element); - var options = Object.extend({ - element: element, - tag: 'li', // assumes li children, override with tag: 'tagname' - dropOnEmpty: false, - tree: false, - treeTag: 'ul', - overlap: 'vertical', // one of 'vertical', 'horizontal' - constraint: 'vertical', // one of 'vertical', 'horizontal', false - containment: element, // also takes array of elements (or id's); or false - handle: false, // or a CSS class - only: false, - delay: 0, - hoverclass: null, - ghosting: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be - // used for better initialization performance - elements: false, - handles: false, - - onChange: Prototype.emptyFunction, - onUpdate: Prototype.emptyFunction - }, arguments[1] || { }); - - // clear any old sortable with same element - this.destroy(element); - - // build options for the draggables - var options_for_draggable = { - revert: true, - quiet: options.quiet, - scroll: options.scroll, - scrollSpeed: options.scrollSpeed, - scrollSensitivity: options.scrollSensitivity, - delay: options.delay, - ghosting: options.ghosting, - constraint: options.constraint, - handle: options.handle }; - - if(options.starteffect) - options_for_draggable.starteffect = options.starteffect; - - if(options.reverteffect) - options_for_draggable.reverteffect = options.reverteffect; - else - if(options.ghosting) options_for_draggable.reverteffect = function(element) { - element.style.top = 0; - element.style.left = 0; - }; - - if(options.endeffect) - options_for_draggable.endeffect = options.endeffect; - - if(options.zindex) - options_for_draggable.zindex = options.zindex; - - // build options for the droppables - var options_for_droppable = { - overlap: options.overlap, - containment: options.containment, - tree: options.tree, - hoverclass: options.hoverclass, - onHover: Sortable.onHover - }; - - var options_for_tree = { - onHover: Sortable.onEmptyHover, - overlap: options.overlap, - containment: options.containment, - hoverclass: options.hoverclass - }; - - // fix for gecko engine - Element.cleanWhitespace(element); - - options.draggables = []; - options.droppables = []; - - // drop on empty handling - if(options.dropOnEmpty || options.tree) { - Droppables.add(element, options_for_tree); - options.droppables.push(element); - } - - (options.elements || this.findElements(element, options) || []).each( function(e,i) { - var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); - options.draggables.push( - new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); - Droppables.add(e, options_for_droppable); - if(options.tree) e.treeNode = element; - options.droppables.push(e); - }); - - if(options.tree) { - (Sortable.findTreeElements(element, options) || []).each( function(e) { - Droppables.add(e, options_for_tree); - e.treeNode = element; - options.droppables.push(e); - }); - } - - // keep reference - this.sortables[element.id] = options; - - // for onupdate - Draggables.addObserver(new SortableObserver(element, options.onUpdate)); - - }, - - // return all suitable-for-sortable elements in a guaranteed order - findElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.tag); - }, - - findTreeElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.treeTag); - }, - - onHover: function(element, dropon, overlap) { - if(Element.isParent(dropon, element)) return; - - if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { - return; - } else if(overlap>0.5) { - Sortable.mark(dropon, 'before'); - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } else { - Sortable.mark(dropon, 'after'); - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } - }, - - onEmptyHover: function(element, dropon, overlap) { - var oldParentNode = element.parentNode; - var droponOptions = Sortable.options(dropon); - - if(!Element.isParent(dropon, element)) { - var index; - - var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); - var child = null; - - if(children) { - var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - - for (index = 0; index < children.length; index += 1) { - if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { - offset -= Element.offsetSize (children[index], droponOptions.overlap); - } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { - child = index + 1 < children.length ? children[index + 1] : null; - break; - } else { - child = children[index]; - break; - } - } - } - - dropon.insertBefore(element, child); - - Sortable.options(oldParentNode).onChange(element); - droponOptions.onChange(element); - } - }, - - unmark: function() { - if(Sortable._marker) Sortable._marker.hide(); - }, - - mark: function(dropon, position) { - // mark on ghosting only - var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; - - if(!Sortable._marker) { - Sortable._marker = - ($('dropmarker') || Element.extend(document.createElement('DIV'))). - hide().addClassName('dropmarker').setStyle({position:'absolute'}); - document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } - var offsets = Position.cumulativeOffset(dropon); - Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - - if(position=='after') - if(sortable.overlap == 'horizontal') - Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); - else - Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - - Sortable._marker.show(); - }, - - _tree: function(element, options, parent) { - var children = Sortable.findElements(element, options) || []; - - for (var i = 0; i < children.length; ++i) { - var match = children[i].id.match(options.format); - - if (!match) continue; - - var child = { - id: encodeURIComponent(match ? match[1] : null), - element: element, - parent: parent, - children: [], - position: parent.children.length, - container: $(children[i]).down(options.treeTag) - }; - - /* Get the element containing the children and recurse over it */ - if (child.container) - this._tree(child.container, options, child); - - parent.children.push (child); - } - - return parent; - }, - - tree: function(element) { - element = $(element); - var sortableOptions = this.options(element); - var options = Object.extend({ - tag: sortableOptions.tag, - treeTag: sortableOptions.treeTag, - only: sortableOptions.only, - name: element.id, - format: sortableOptions.format - }, arguments[1] || { }); - - var root = { - id: null, - parent: null, - children: [], - container: element, - position: 0 - }; - - return Sortable._tree(element, options, root); - }, - - /* Construct a [i] index for a particular node */ - _constructIndex: function(node) { - var index = ''; - do { - if (node.id) index = '[' + node.position + ']' + index; - } while ((node = node.parent) != null); - return index; - }, - - sequence: function(element) { - element = $(element); - var options = Object.extend(this.options(element), arguments[1] || { }); - - return $(this.findElements(element, options) || []).map( function(item) { - return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; - }); - }, - - setSequence: function(element, new_sequence) { - element = $(element); - var options = Object.extend(this.options(element), arguments[2] || { }); - - var nodeMap = { }; - this.findElements(element, options).each( function(n) { - if (n.id.match(options.format)) - nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; - n.parentNode.removeChild(n); - }); - - new_sequence.each(function(ident) { - var n = nodeMap[ident]; - if (n) { - n[1].appendChild(n[0]); - delete nodeMap[ident]; - } - }); - }, - - serialize: function(element) { - element = $(element); - var options = Object.extend(Sortable.options(element), arguments[1] || { }); - var name = encodeURIComponent( - (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - - if (options.tree) { - return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + - encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); - }).flatten().join('&'); - } else { - return Sortable.sequence(element, arguments[1]).map( function(item) { - return name + "[]=" + encodeURIComponent(item); - }).join('&'); - } - } -}; - -// Returns true if child is contained within element -Element.isParent = function(child, element) { - if (!child.parentNode || child == element) return false; - if (child.parentNode == element) return true; - return Element.isParent(child.parentNode, element); -}; - -Element.findChildren = function(element, only, recursive, tagName) { - if(!element.hasChildNodes()) return null; - tagName = tagName.toUpperCase(); - if(only) only = [only].flatten(); - var elements = []; - $A(element.childNodes).each( function(e) { - if(e.tagName && e.tagName.toUpperCase()==tagName && - (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) - elements.push(e); - if(recursive) { - var grandchildren = Element.findChildren(e, only, recursive, tagName); - if(grandchildren) elements.push(grandchildren); - } - }); - - return (elements.length>0 ? elements.flatten() : []); -}; - -Element.offsetSize = function (element, type) { - return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -}; \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js deleted file mode 100644 index 5a639d2dea..0000000000 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ /dev/null @@ -1,1128 +0,0 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// Contributors: -// Justin Palmer (http://encytemedia.com/) -// Mark Pilgrim (http://diveintomark.org/) -// Martin Bialasinki -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { - var color = '#'; - if (this.slice(0,4) == 'rgb(') { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if (this.slice(0,1) == '#') { - if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if (this.length==7) color = this.toLowerCase(); - } - } - return (color.length==7 ? color : (arguments[0] || this)); -}; - -/*--------------------------------------------------------------------------*/ - -Element.collectTextNodes = function(element) { - return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); - }).flatten().join(''); -}; - -Element.collectTextNodesIgnoreClass = function(element, className) { - return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? - Element.collectTextNodesIgnoreClass(node, className) : '')); - }).flatten().join(''); -}; - -Element.setContentZoom = function(element, percent) { - element = $(element); - element.setStyle({fontSize: (percent/100) + 'em'}); - if (Prototype.Browser.WebKit) window.scrollBy(0,0); - return element; -}; - -Element.getInlineOpacity = function(element){ - return $(element).style.opacity || ''; -}; - -Element.forceRerendering = function(element) { - try { - element = $(element); - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch(e) { } -}; - -/*--------------------------------------------------------------------------*/ - -var Effect = { - _elementDoesNotExistError: { - name: 'ElementDoesNotExistError', - message: 'The specified DOM element does not exist, but is required for this effect to operate' - }, - Transitions: { - linear: Prototype.K, - sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + .5; - }, - reverse: function(pos) { - return 1-pos; - }, - flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; - return pos > 1 ? 1 : pos; - }, - wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; - }, - pulse: function(pos, pulses) { - return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; - }, - spring: function(pos) { - return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); - }, - none: function(pos) { - return 0; - }, - full: function(pos) { - return 1; - } - }, - DefaultOptions: { - duration: 1.0, // seconds - fps: 100, // 100= assume 66fps max. - sync: false, // true for combining - from: 0.0, - to: 1.0, - delay: 0.0, - queue: 'parallel' - }, - tagifyText: function(element) { - var tagifyStyle = 'position:relative'; - if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; - - element = $(element); - $A(element.childNodes).each( function(child) { - if (child.nodeType==3) { - child.nodeValue.toArray().each( function(character) { - element.insertBefore( - new Element('span', {style: tagifyStyle}).update( - character == ' ' ? String.fromCharCode(160) : character), - child); - }); - Element.remove(child); - } - }); - }, - multiple: function(element, effect) { - var elements; - if (((typeof element == 'object') || - Object.isFunction(element)) && - (element.length)) - elements = element; - else - elements = $(element).childNodes; - - var options = Object.extend({ - speed: 0.1, - delay: 0.0 - }, arguments[2] || { }); - var masterDelay = options.delay; - - $A(elements).each( function(element, index) { - new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); - }); - }, - PAIRS: { - 'slide': ['SlideDown','SlideUp'], - 'blind': ['BlindDown','BlindUp'], - 'appear': ['Appear','Fade'] - }, - toggle: function(element, effect) { - element = $(element); - effect = (effect || 'appear').toLowerCase(); - var options = Object.extend({ - queue: { position:'end', scope:(element.id || 'global'), limit: 1 } - }, arguments[2] || { }); - Effect[element.visible() ? - Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); - } -}; - -Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; - -/* ------------- core effects ------------- */ - -Effect.ScopedQueue = Class.create(Enumerable, { - initialize: function() { - this.effects = []; - this.interval = null; - }, - _each: function(iterator) { - this.effects._each(iterator); - }, - add: function(effect) { - var timestamp = new Date().getTime(); - - var position = Object.isString(effect.options.queue) ? - effect.options.queue : effect.options.queue.position; - - switch(position) { - case 'front': - // move unstarted effects after this effect - this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { - e.startOn += effect.finishOn; - e.finishOn += effect.finishOn; - }); - break; - case 'with-last': - timestamp = this.effects.pluck('startOn').max() || timestamp; - break; - case 'end': - // start effect after last queued effect has finished - timestamp = this.effects.pluck('finishOn').max() || timestamp; - break; - } - - effect.startOn += timestamp; - effect.finishOn += timestamp; - - if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) - this.effects.push(effect); - - if (!this.interval) - this.interval = setInterval(this.loop.bind(this), 15); - }, - remove: function(effect) { - this.effects = this.effects.reject(function(e) { return e==effect }); - if (this.effects.length == 0) { - clearInterval(this.interval); - this.interval = null; - } - }, - loop: function() { - var timePos = new Date().getTime(); - for(var i=0, len=this.effects.length;i= this.startOn) { - if (timePos >= this.finishOn) { - this.render(1.0); - this.cancel(); - this.event('beforeFinish'); - if (this.finish) this.finish(); - this.event('afterFinish'); - return; - } - var pos = (timePos - this.startOn) / this.totalTime, - frame = (pos * this.totalFrames).round(); - if (frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - } - }, - cancel: function() { - if (!this.options.sync) - Effect.Queues.get(Object.isString(this.options.queue) ? - 'global' : this.options.queue.scope).remove(this); - this.state = 'finished'; - }, - event: function(eventName) { - if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); - if (this.options[eventName]) this.options[eventName](this); - }, - inspect: function() { - var data = $H(); - for(property in this) - if (!Object.isFunction(this[property])) data.set(property, this[property]); - return '#'; - } -}); - -Effect.Parallel = Class.create(Effect.Base, { - initialize: function(effects) { - this.effects = effects || []; - this.start(arguments[1]); - }, - update: function(position) { - this.effects.invoke('render', position); - }, - finish: function(position) { - this.effects.each( function(effect) { - effect.render(1.0); - effect.cancel(); - effect.event('beforeFinish'); - if (effect.finish) effect.finish(position); - effect.event('afterFinish'); - }); - } -}); - -Effect.Tween = Class.create(Effect.Base, { - initialize: function(object, from, to) { - object = Object.isString(object) ? $(object) : object; - var args = $A(arguments), method = args.last(), - options = args.length == 5 ? args[3] : null; - this.method = Object.isFunction(method) ? method.bind(object) : - Object.isFunction(object[method]) ? object[method].bind(object) : - function(value) { object[method] = value }; - this.start(Object.extend({ from: from, to: to }, options || { })); - }, - update: function(position) { - this.method(position); - } -}); - -Effect.Event = Class.create(Effect.Base, { - initialize: function() { - this.start(Object.extend({ duration: 0 }, arguments[0] || { })); - }, - update: Prototype.emptyFunction -}); - -Effect.Opacity = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - // make this work on IE on elements without 'layout' - if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) - this.element.setStyle({zoom: 1}); - var options = Object.extend({ - from: this.element.getOpacity() || 0.0, - to: 1.0 - }, arguments[1] || { }); - this.start(options); - }, - update: function(position) { - this.element.setOpacity(position); - } -}); - -Effect.Move = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - x: 0, - y: 0, - mode: 'relative' - }, arguments[1] || { }); - this.start(options); - }, - setup: function() { - this.element.makePositioned(); - this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); - this.originalTop = parseFloat(this.element.getStyle('top') || '0'); - if (this.options.mode == 'absolute') { - this.options.x = this.options.x - this.originalLeft; - this.options.y = this.options.y - this.originalTop; - } - }, - update: function(position) { - this.element.setStyle({ - left: (this.options.x * position + this.originalLeft).round() + 'px', - top: (this.options.y * position + this.originalTop).round() + 'px' - }); - } -}); - -// for backwards compatibility -Effect.MoveBy = function(element, toTop, toLeft) { - return new Effect.Move(element, - Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); -}; - -Effect.Scale = Class.create(Effect.Base, { - initialize: function(element, percent) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - scaleX: true, - scaleY: true, - scaleContent: true, - scaleFromCenter: false, - scaleMode: 'box', // 'box' or 'contents' or { } with provided values - scaleFrom: 100.0, - scaleTo: percent - }, arguments[2] || { }); - this.start(options); - }, - setup: function() { - this.restoreAfterFinish = this.options.restoreAfterFinish || false; - this.elementPositioning = this.element.getStyle('position'); - - this.originalStyle = { }; - ['top','left','width','height','fontSize'].each( function(k) { - this.originalStyle[k] = this.element.style[k]; - }.bind(this)); - - this.originalTop = this.element.offsetTop; - this.originalLeft = this.element.offsetLeft; - - var fontSize = this.element.getStyle('font-size') || '100%'; - ['em','px','%','pt'].each( function(fontSizeType) { - if (fontSize.indexOf(fontSizeType)>0) { - this.fontSize = parseFloat(fontSize); - this.fontSizeType = fontSizeType; - } - }.bind(this)); - - this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; - - this.dims = null; - if (this.options.scaleMode=='box') - this.dims = [this.element.offsetHeight, this.element.offsetWidth]; - if (/^content/.test(this.options.scaleMode)) - this.dims = [this.element.scrollHeight, this.element.scrollWidth]; - if (!this.dims) - this.dims = [this.options.scaleMode.originalHeight, - this.options.scaleMode.originalWidth]; - }, - update: function(position) { - var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if (this.options.scaleContent && this.fontSize) - this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); - this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); - }, - finish: function(position) { - if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); - }, - setDimensions: function(height, width) { - var d = { }; - if (this.options.scaleX) d.width = width.round() + 'px'; - if (this.options.scaleY) d.height = height.round() + 'px'; - if (this.options.scaleFromCenter) { - var topd = (height - this.dims[0])/2; - var leftd = (width - this.dims[1])/2; - if (this.elementPositioning == 'absolute') { - if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; - if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; - } else { - if (this.options.scaleY) d.top = -topd + 'px'; - if (this.options.scaleX) d.left = -leftd + 'px'; - } - } - this.element.setStyle(d); - } -}); - -Effect.Highlight = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); - this.start(options); - }, - setup: function() { - // Prevent executing on elements not in the layout flow - if (this.element.getStyle('display')=='none') { this.cancel(); return; } - // Disable background image during the effect - this.oldStyle = { }; - if (!this.options.keepBackgroundImage) { - this.oldStyle.backgroundImage = this.element.getStyle('background-image'); - this.element.setStyle({backgroundImage: 'none'}); - } - if (!this.options.endcolor) - this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); - if (!this.options.restorecolor) - this.options.restorecolor = this.element.getStyle('background-color'); - // init color calculations - this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); - this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); - }, - update: function(position) { - this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ - return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); - }, - finish: function() { - this.element.setStyle(Object.extend(this.oldStyle, { - backgroundColor: this.options.restorecolor - })); - } -}); - -Effect.ScrollTo = function(element) { - var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(); - - if (options.offset) elementOffsets[1] += options.offset; - - return new Effect.Tween(null, - scrollOffsets.top, - elementOffsets[1], - options, - function(p){ scrollTo(scrollOffsets.left, p.round()); } - ); -}; - -/* ------------- combination effects ------------- */ - -Effect.Fade = function(element) { - element = $(element); - var oldOpacity = element.getInlineOpacity(); - var options = Object.extend({ - from: element.getOpacity() || 1.0, - to: 0.0, - afterFinishInternal: function(effect) { - if (effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); - } - }, arguments[1] || { }); - return new Effect.Opacity(element,options); -}; - -Effect.Appear = function(element) { - element = $(element); - var options = Object.extend({ - from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), - to: 1.0, - // force Safari to render floated elements properly - afterFinishInternal: function(effect) { - effect.element.forceRerendering(); - }, - beforeSetup: function(effect) { - effect.element.setOpacity(effect.options.from).show(); - }}, arguments[1] || { }); - return new Effect.Opacity(element,options); -}; - -Effect.Puff = function(element) { - element = $(element); - var oldStyle = { - opacity: element.getInlineOpacity(), - position: element.getStyle('position'), - top: element.style.top, - left: element.style.left, - width: element.style.width, - height: element.style.height - }; - return new Effect.Parallel( - [ new Effect.Scale(element, 200, - { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], - Object.extend({ duration: 1.0, - beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().setStyle(oldStyle); } - }, arguments[1] || { }) - ); -}; - -Effect.BlindUp = function(element) { - element = $(element); - element.makeClipping(); - return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, - restoreAfterFinish: true, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); - } - }, arguments[1] || { }) - ); -}; - -Effect.BlindDown = function(element) { - element = $(element); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, - scaleFrom: 0, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, - afterFinishInternal: function(effect) { - effect.element.undoClipping(); - } - }, arguments[1] || { })); -}; - -Effect.SwitchOff = function(element) { - element = $(element); - var oldOpacity = element.getInlineOpacity(); - return new Effect.Appear(element, Object.extend({ - duration: 0.4, - from: 0, - transition: Effect.Transitions.flicker, - afterFinishInternal: function(effect) { - new Effect.Scale(effect.element, 1, { - duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { - effect.element.makePositioned().makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); - } - }); - } - }, arguments[1] || { })); -}; - -Effect.DropOut = function(element) { - element = $(element); - var oldStyle = { - top: element.getStyle('top'), - left: element.getStyle('left'), - opacity: element.getInlineOpacity() }; - return new Effect.Parallel( - [ new Effect.Move(element, {x: 0, y: 100, sync: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 }) ], - Object.extend( - { duration: 0.5, - beforeSetup: function(effect) { - effect.effects[0].element.makePositioned(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); - } - }, arguments[1] || { })); -}; - -Effect.Shake = function(element) { - element = $(element); - var options = Object.extend({ - distance: 20, - duration: 0.5 - }, arguments[1] || {}); - var distance = parseFloat(options.distance); - var split = parseFloat(options.duration) / 10.0; - var oldStyle = { - top: element.getStyle('top'), - left: element.getStyle('left') }; - return new Effect.Move(element, - { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { - effect.element.undoPositioned().setStyle(oldStyle); - }}); }}); }}); }}); }}); }}); -}; - -Effect.SlideDown = function(element) { - element = $(element).cleanWhitespace(); - // SlideDown need to have the content of the element wrapped in a container element with fixed height! - var oldInnerBottom = element.down().getStyle('bottom'); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, - scaleFrom: window.opera ? 0 : 1, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makePositioned(); - effect.element.down().makePositioned(); - if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, - afterUpdateInternal: function(effect) { - effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); - }, - afterFinishInternal: function(effect) { - effect.element.undoClipping().undoPositioned(); - effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } - }, arguments[1] || { }) - ); -}; - -Effect.SlideUp = function(element) { - element = $(element).cleanWhitespace(); - var oldInnerBottom = element.down().getStyle('bottom'); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, window.opera ? 0 : 1, - Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'box', - scaleFrom: 100, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makePositioned(); - effect.element.down().makePositioned(); - if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().show(); - }, - afterUpdateInternal: function(effect) { - effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().undoPositioned(); - effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); - } - }, arguments[1] || { }) - ); -}; - -// Bug in opera makes the TD containing this element expand for a instance after finish -Effect.Squish = function(element) { - return new Effect.Scale(element, window.opera ? 1 : 0, { - restoreAfterFinish: true, - beforeSetup: function(effect) { - effect.element.makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); - } - }); -}; - -Effect.Grow = function(element) { - element = $(element); - var options = Object.extend({ - direction: 'center', - moveTransition: Effect.Transitions.sinoidal, - scaleTransition: Effect.Transitions.sinoidal, - opacityTransition: Effect.Transitions.full - }, arguments[1] || { }); - var oldStyle = { - top: element.style.top, - left: element.style.left, - height: element.style.height, - width: element.style.width, - opacity: element.getInlineOpacity() }; - - var dims = element.getDimensions(); - var initialMoveX, initialMoveY; - var moveX, moveY; - - switch (options.direction) { - case 'top-left': - initialMoveX = initialMoveY = moveX = moveY = 0; - break; - case 'top-right': - initialMoveX = dims.width; - initialMoveY = moveY = 0; - moveX = -dims.width; - break; - case 'bottom-left': - initialMoveX = moveX = 0; - initialMoveY = dims.height; - moveY = -dims.height; - break; - case 'bottom-right': - initialMoveX = dims.width; - initialMoveY = dims.height; - moveX = -dims.width; - moveY = -dims.height; - break; - case 'center': - initialMoveX = dims.width / 2; - initialMoveY = dims.height / 2; - moveX = -dims.width / 2; - moveY = -dims.height / 2; - break; - } - - return new Effect.Move(element, { - x: initialMoveX, - y: initialMoveY, - duration: 0.01, - beforeSetup: function(effect) { - effect.element.hide().makeClipping().makePositioned(); - }, - afterFinishInternal: function(effect) { - new Effect.Parallel( - [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), - new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), - new Effect.Scale(effect.element, 100, { - scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, - sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) - ], Object.extend({ - beforeSetup: function(effect) { - effect.effects[0].element.setStyle({height: '0px'}).show(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); - } - }, options) - ); - } - }); -}; - -Effect.Shrink = function(element) { - element = $(element); - var options = Object.extend({ - direction: 'center', - moveTransition: Effect.Transitions.sinoidal, - scaleTransition: Effect.Transitions.sinoidal, - opacityTransition: Effect.Transitions.none - }, arguments[1] || { }); - var oldStyle = { - top: element.style.top, - left: element.style.left, - height: element.style.height, - width: element.style.width, - opacity: element.getInlineOpacity() }; - - var dims = element.getDimensions(); - var moveX, moveY; - - switch (options.direction) { - case 'top-left': - moveX = moveY = 0; - break; - case 'top-right': - moveX = dims.width; - moveY = 0; - break; - case 'bottom-left': - moveX = 0; - moveY = dims.height; - break; - case 'bottom-right': - moveX = dims.width; - moveY = dims.height; - break; - case 'center': - moveX = dims.width / 2; - moveY = dims.height / 2; - break; - } - - return new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), - new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), - new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) - ], Object.extend({ - beforeStartInternal: function(effect) { - effect.effects[0].element.makePositioned().makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } - }, options) - ); -}; - -Effect.Pulsate = function(element) { - element = $(element); - var options = arguments[1] || { }, - oldOpacity = element.getInlineOpacity(), - transition = options.transition || Effect.Transitions.linear, - reverser = function(pos){ - return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); - }; - - return new Effect.Opacity(element, - Object.extend(Object.extend({ duration: 2.0, from: 0, - afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } - }, options), {transition: reverser})); -}; - -Effect.Fold = function(element) { - element = $(element); - var oldStyle = { - top: element.style.top, - left: element.style.left, - width: element.style.width, - height: element.style.height }; - element.makeClipping(); - return new Effect.Scale(element, 5, Object.extend({ - scaleContent: false, - scaleX: false, - afterFinishInternal: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, - scaleY: false, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().setStyle(oldStyle); - } }); - }}, arguments[1] || { })); -}; - -Effect.Morph = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - style: { } - }, arguments[1] || { }); - - if (!Object.isString(options.style)) this.style = $H(options.style); - else { - if (options.style.include(':')) - this.style = options.style.parseStyle(); - else { - this.element.addClassName(options.style); - this.style = $H(this.element.getStyles()); - this.element.removeClassName(options.style); - var css = this.element.getStyles(); - this.style = this.style.reject(function(style) { - return style.value == css[style.key]; - }); - options.afterFinishInternal = function(effect) { - effect.element.addClassName(effect.options.style); - effect.transforms.each(function(transform) { - effect.element.style[transform.style] = ''; - }); - }; - } - } - this.start(options); - }, - - setup: function(){ - function parseColor(color){ - if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; - color = color.parseColor(); - return $R(0,2).map(function(i){ - return parseInt( color.slice(i*2+1,i*2+3), 16 ); - }); - } - this.transforms = this.style.map(function(pair){ - var property = pair[0], value = pair[1], unit = null; - - if (value.parseColor('#zzzzzz') != '#zzzzzz') { - value = value.parseColor(); - unit = 'color'; - } else if (property == 'opacity') { - value = parseFloat(value); - if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) - this.element.setStyle({zoom: 1}); - } else if (Element.CSS_LENGTH.test(value)) { - var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); - value = parseFloat(components[1]); - unit = (components.length == 3) ? components[2] : null; - } - - var originalValue = this.element.getStyle(property); - return { - style: property.camelize(), - originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), - targetValue: unit=='color' ? parseColor(value) : value, - unit: unit - }; - }.bind(this)).reject(function(transform){ - return ( - (transform.originalValue == transform.targetValue) || - ( - transform.unit != 'color' && - (isNaN(transform.originalValue) || isNaN(transform.targetValue)) - ) - ); - }); - }, - update: function(position) { - var style = { }, transform, i = this.transforms.length; - while(i--) - style[(transform = this.transforms[i]).style] = - transform.unit=='color' ? '#'+ - (Math.round(transform.originalValue[0]+ - (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + - (Math.round(transform.originalValue[1]+ - (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + - (Math.round(transform.originalValue[2]+ - (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : - (transform.originalValue + - (transform.targetValue - transform.originalValue) * position).toFixed(3) + - (transform.unit === null ? '' : transform.unit); - this.element.setStyle(style, true); - } -}); - -Effect.Transform = Class.create({ - initialize: function(tracks){ - this.tracks = []; - this.options = arguments[1] || { }; - this.addTracks(tracks); - }, - addTracks: function(tracks){ - tracks.each(function(track){ - track = $H(track); - var data = track.values().first(); - this.tracks.push($H({ - ids: track.keys().first(), - effect: Effect.Morph, - options: { style: data } - })); - }.bind(this)); - return this; - }, - play: function(){ - return new Effect.Parallel( - this.tracks.map(function(track){ - var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); - var elements = [$(ids) || $$(ids)].flatten(); - return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); - }).flatten(), - this.options - ); - } -}); - -Element.CSS_PROPERTIES = $w( - 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + - 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + - 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + - 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + - 'fontSize fontWeight height left letterSpacing lineHeight ' + - 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ - 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + - 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + - 'right textIndent top width wordSpacing zIndex'); - -Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; - -String.__parseStyleElement = document.createElement('div'); -String.prototype.parseStyle = function(){ - var style, styleRules = $H(); - if (Prototype.Browser.WebKit) - style = new Element('div',{style:this}).style; - else { - String.__parseStyleElement.innerHTML = '
    '; - style = String.__parseStyleElement.childNodes[0].style; - } - - Element.CSS_PROPERTIES.each(function(property){ - if (style[property]) styleRules.set(property, style[property]); - }); - - if (Prototype.Browser.IE && this.include('opacity')) - styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); - - return styleRules; -}; - -if (document.defaultView && document.defaultView.getComputedStyle) { - Element.getStyles = function(element) { - var css = document.defaultView.getComputedStyle($(element), null); - return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { - styles[property] = css[property]; - return styles; - }); - }; -} else { - Element.getStyles = function(element) { - element = $(element); - var css = element.currentStyle, styles; - styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { - results[property] = css[property]; - return results; - }); - if (!styles.opacity) styles.opacity = element.getOpacity(); - return styles; - }; -} - -Effect.Methods = { - morph: function(element, style) { - element = $(element); - new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); - return element; - }, - visualEffect: function(element, effect, options) { - element = $(element); - var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); - new Effect[klass](element, options); - return element; - }, - highlight: function(element, options) { - element = $(element); - new Effect.Highlight(element, options); - return element; - } -}; - -$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ - 'pulsate shake puff squish switchOff dropOut').each( - function(effect) { - Effect.Methods[effect] = function(element, options){ - element = $(element); - Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); - return element; - }; - } -); - -$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( - function(f) { Effect.Methods[f] = Element[f]; } -); - -Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js deleted file mode 100644 index dfe8ab4e13..0000000000 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ /dev/null @@ -1,4320 +0,0 @@ -/* Prototype JavaScript framework, version 1.6.0.3 - * (c) 2005-2008 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -var Prototype = { - Version: '1.6.0.3', - - Browser: { - IE: !!(window.attachEvent && - navigator.userAgent.indexOf('Opera') === -1), - Opera: navigator.userAgent.indexOf('Opera') > -1, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && - navigator.userAgent.indexOf('KHTML') === -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, - - BrowserFeatures: { - XPath: !!document.evaluate, - SelectorsAPI: !!document.querySelector, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div')['__proto__'] && - document.createElement('div')['__proto__'] !== - document.createElement('form')['__proto__'] - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - - -/* Based on Alex Arnell's inheritance implementation. */ -var Class = { - create: function() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - var subclass = function() { }; - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0; i < properties.length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - - return klass; - } -}; - -Class.Methods = { - addMethods: function(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); - - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } -}; - -var Abstract = { }; - -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; - -Object.extend(Object, { - inspect: function(object) { - try { - if (Object.isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - }, - - toJSON: function(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); - } - - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; - - var results = []; - for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) - results.push(property.toJSON() + ': ' + value); - } - - return '{' + results.join(', ') + '}'; - }, - - toQueryString: function(object) { - return $H(object).toQueryString(); - }, - - toHTML: function(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, - - keys: function(object) { - var keys = []; - for (var property in object) - keys.push(property); - return keys; - }, - - values: function(object) { - var values = []; - for (var property in object) - values.push(object[property]); - return values; - }, - - clone: function(object) { - return Object.extend({ }, object); - }, - - isElement: function(object) { - return !!(object && object.nodeType == 1); - }, - - isArray: function(object) { - return object != null && typeof object == "object" && - 'splice' in object && 'join' in object; - }, - - isHash: function(object) { - return object instanceof Hash; - }, - - isFunction: function(object) { - return typeof object == "function"; - }, - - isString: function(object) { - return typeof object == "string"; - }, - - isNumber: function(object) { - return typeof object == "number"; - }, - - isUndefined: function(object) { - return typeof object == "undefined"; - } -}); - -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - }, - - bind: function() { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); - return function() { - return __method.apply(object, args.concat($A(arguments))); - } - }, - - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); - return function(event) { - return __method.apply(object, [event || window.event].concat(args)); - } - }, - - curry: function() { - if (!arguments.length) return this; - var __method = this, args = $A(arguments); - return function() { - return __method.apply(this, args.concat($A(arguments))); - } - }, - - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - }, - - defer: function() { - var args = [0.01].concat($A(arguments)); - return this.delay.apply(this, args); - }, - - wrap: function(wrapper) { - var __method = this; - return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); - } - }, - - methodize: function() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); - }; - } -}); - -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; - -/*--------------------------------------------------------------------------*/ - -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - } finally { - this.currentlyExecuting = false; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { - var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - }, - - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - }, - - scan: function(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - }, - - truncate: function(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - }, - - strip: function() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, - - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, - - stripScripts: function() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, - - extractScripts: function() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - }, - - evalScripts: function() { - return this.extractScripts().map(function(script) { return eval(script) }); - }, - - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, - - unescapeHTML: function() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; - }, - - toQueryParams: function(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - }, - - toArray: function() { - return this.split(''); - }, - - succ: function() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, - - times: function(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; - }, - - capitalize: function() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, - - underscore: function() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, - - dasherize: function() { - return this.gsub(/_/,'-'); - }, - - inspect: function(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { - return this.inspect(true); - }, - - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, - - isJSON: function() { - var str = this; - if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, - - evalJSON: function(sanitize) { - var json = this.unfilterJSON(); - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, - - include: function(pattern) { - return this.indexOf(pattern) > -1; - }, - - startsWith: function(pattern) { - return this.indexOf(pattern) === 0; - }, - - endsWith: function(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - }, - - empty: function() { - return this == ''; - }, - - blank: function() { - return /^\s*$/.test(this); - }, - - interpolate: function(object, pattern) { - return new Template(this, pattern).evaluate(object); - } -}); - -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); - } -}); - -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); - -String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = { - each: function(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - }, - - eachSlice: function(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - }, - - all: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - }, - - any: function(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - }, - - collect: function(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - detect: function(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - }, - - findAll: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - grep: function(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(filter); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - }, - - include: function(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - }, - - inGroupsOf: function(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - }, - - inject: function(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - }, - - invoke: function(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - }, - - max: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - }, - - min: function(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - }, - - partition: function(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - }, - - pluck: function(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - }, - - reject: function(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - }, - - sortBy: function(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - }, - - toArray: function() { - return this.map(); - }, - - zip: function() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - }, - - size: function() { - return this.toArray().length; - }, - - inspect: function() { - return '#'; - } -}; - -Object.extend(Enumerable, { - map: Enumerable.collect, - find: Enumerable.detect, - select: Enumerable.findAll, - filter: Enumerable.findAll, - member: Enumerable.include, - entries: Enumerable.toArray, - every: Enumerable.all, - some: Enumerable.any -}); -function $A(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - -if (Prototype.Browser.WebKit) { - $A = function(iterable) { - if (!iterable) return []; - // In Safari, only use the `toArray` method if it's not a NodeList. - // A NodeList is a function, has an function `item` property, and a numeric - // `length` property. Adapted from Google Doctype. - if (!(typeof iterable === 'function' && typeof iterable.length === - 'number' && typeof iterable.item === 'function') && iterable.toArray) - return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - }; -} - -Array.from = $A; - -Object.extend(Array.prototype, Enumerable); - -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; - -Object.extend(Array.prototype, { - _each: function(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); - }, - - clear: function() { - this.length = 0; - return this; - }, - - first: function() { - return this[0]; - }, - - last: function() { - return this[this.length - 1]; - }, - - compact: function() { - return this.select(function(value) { - return value != null; - }); - }, - - flatten: function() { - return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); - }); - }, - - without: function() { - var values = $A(arguments); - return this.select(function(value) { - return !values.include(value); - }); - }, - - reverse: function(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - }, - - reduce: function() { - return this.length > 1 ? this : this[0]; - }, - - uniq: function(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - }, - - intersect: function(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - }, - - clone: function() { - return [].concat(this); - }, - - size: function() { - return this.length; - }, - - inspect: function() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - }, - - toJSON: function() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } -}); - -// use native browser JS 1.6 implementation if available -if (Object.isFunction(Array.prototype.forEach)) - Array.prototype._each = Array.prototype.forEach; - -if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; -}; - -if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; -}; - -Array.prototype.toArray = Array.prototype.clone; - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); - for (var i = 0, length = arguments.length; i < length; i++) { - if (Object.isArray(arguments[i])) { - for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) - array.push(arguments[i][j]); - } else { - array.push(arguments[i]); - } - } - return array; - }; -} -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); - }, - - succ: function() { - return this + 1; - }, - - times: function(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - }, - - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, - - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); - -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, - - _each: function(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, - - set: function(key, value) { - return this._object[key] = value; - }, - - get: function(key) { - // simulating poorly supported hasOwnProperty - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - }, - - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, - - toObject: function() { - return Object.clone(this._object); - }, - - keys: function() { - return this.pluck('key'); - }, - - values: function() { - return this.pluck('value'); - }, - - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, - - merge: function(object) { - return this.clone().update(object); - }, - - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, - - toQueryString: function() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - }, - - inspect: function() { - return '#'; - }, - - toJSON: function() { - return Object.toJSON(this.toObject()); - }, - - clone: function() { - return new Hash(this); - } - } -})()); - -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; -Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - }, - - _each: function(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - }, - - include: function(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } -}); - -var $R = function(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -}; - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); - -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); - -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.clone(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - // simulate other verbs over post - params['_method'] = this.method; - this.method = 'post'; - } - - this.parameters = params; - - if (params = Object.toQueryString(params)) { - // when GET, append parameters to URL - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; - } - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - // user-defined headers - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300); - }, - - getStatus: function() { - try { - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - // avoid memory leak in MSIE: clean up - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if(readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!window.Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); - }; - Object.extend(this.Element, element || { }); - if (element) this.Element.prototype = element.prototype; -}).call(window); - -Element.cache = { }; - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property) { - element = $(element); - var elements = []; - while (element = element[property]) - if (element.nodeType == 1) - elements.push(Element.extend(element)); - return elements; - }, - - ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); - }, - - descendants: function(element) { - return $(element).select("*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; - }, - - previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); - }, - - nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); - }, - - match: function(element, selector) { - if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); - return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? element.descendants()[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); - }, - - next: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); - }, - - select: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element, args); - }, - - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element.parentNode, args).without(element); - }, - - identify: function(element) { - element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; - if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return $(element).getDimensions().height; - }, - - getWidth: function(element) { - return $(element).getDimensions().width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!element.hasClassName(className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); - }, - - // removes whitespace-only text node children - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = element.cumulativeOffset(); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - getDimensions: function(element) { - element = $(element); - var display = element.getStyle('display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - // Opera returns the offset relative to the positioning context, when an - // element is position relative but top and left have not been defined - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - absolutize: function(element) { - element = $(element); - if (element.getStyle('position') == 'absolute') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - var offsets = element.positionedOffset(); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - relativize: function(element) { - element = $(element); - if (element.getStyle('position') == 'relative') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - // Safari fix - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - // find page position of source - source = $(source); - var p = source.viewportOffset(); - - // find coordinate system to use - element = $(element); - var delta = [0, 0]; - var parent = null; - // delta [0,0] will do fine with position: fixed elements, - // position:absolute needs offsetParent deltas - if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); - } - - // correct by body offsets (fixes Safari) - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - // set position - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Element.Methods.identify.counter = 1; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; - case 'height': case 'width': - // returns '0px' for hidden elements; we want it to return null - if (!Element.visible(element)) return null; - - // returns the border-box dimensions rather than the content-box - // dimensions, so we subtract padding and borders from the value - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - // IE doesn't report offsets correctly for static elements, so we change them - // to "relative" to get the values, then change them back. - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - // IE throws an error if element is not in document - try { element.offsetParent } - catch(e) { return $(document.body) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - // Trigger hasLayout on the offset parent so that IE6 reports - // accurate offsetTop and offsetLeft values for position: fixed. - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - return attribute ? attribute.toString().slice(23, -2) : null; - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - }; - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr, - src: v._getAttr, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; - - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; -} - -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -if ('outerHTML' in document.createElement('div')) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['', '
    ', 1], - TBODY: ['', '
    ', 2], - TR: ['', '
    ', 3], - TD: ['
    ', '
    ', 4], - SELECT: ['', 1] - } -}; - -(function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD - }); -}).call(Element._insertionTranslations); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div')['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div')['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; -} - -Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) - return Prototype.K; - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || element._extendedByPrototype || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; - - // extend methods for specific tags - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - // extend methods for all tags (Safari doesn't need this) - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - window[klass] = { }; - window[klass].prototype = document.createElement(tagName)['__proto__']; - return window[klass]; - } - - if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - -document.viewport = { - getDimensions: function() { - var dimensions = { }, B = Prototype.Browser; - $w('width height').each(function(d) { - var D = d.capitalize(); - if (B.WebKit && !document.evaluate) { - // Safari <3.0 needs self.innerWidth/Height - dimensions[d] = self['inner' + D]; - } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { - // Opera <9.5 needs document.body.clientWidth/Height - dimensions[d] = document.body['client' + D] - } else { - dimensions[d] = document.documentElement['client' + D]; - } - }); - return dimensions; - }, - - getWidth: function() { - return this.getDimensions().width; - }, - - getHeight: function() { - return this.getDimensions().height; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - - }, - - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; - - var e = this.expression; - - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; - - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; - - return true; - }, - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - // Make sure the browser treats the selector as valid. Test on an - // isolated element to minimize cost of this check. - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; - } - - return true; - }, - - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; - - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } - - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.matcher.push("return h.unique(n);\n}"); - eval(this.matcher.join('\n')); - Selector._cache[this.expression] = this.matcher; - }, - - compileXPathMatcher: function() { - var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; - - if (Selector._cache[e]) { - this.xpath = Selector._cache[e]; return; - } - - this.matcher = ['.//*']; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - if (m = e.match(ps[i])) { - this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : - new Template(x[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } - } - - this.xpath = this.matcher.join(''); - Selector._cache[this.expression] = this.xpath; - }, - - findElements: function(root) { - root = root || document; - var e = this.expression, results; - - switch (this.mode) { - case 'selectorsAPI': - // querySelectorAll queries document-wide, then filters to descendants - // of the context element. That's not what we want. - // Add an explicit context to the selector if necessary. - if (root !== document) { - var oldId = root.id, id = $(root).identify(); - e = "#" + id + " " + e; - } - - results = $A(root.querySelectorAll(e)).map(Element.extend); - root.id = oldId; - - return results; - case 'xpath': - return document._getElementsByXPath(this.xpath, root); - default: - return this.matcher(root); - } - }, - - match: function(element) { - this.tokens = []; - - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; - - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - // use the Selector.assertions methods unless the selector - // is too complex. - if (as[i]) { - this.tokens.push([i, Object.clone(m)]); - e = e.replace(m[0], ''); - } else { - // reluctantly do a document-wide search - // and look for a match in the array - return this.findElements(document).include(element); - } - } - } - } - - var match = true, name, matches; - for (var i = 0, token; token = this.tokens[i]; i++) { - name = token[0], matches = token[1]; - if (!Selector.assertions[name](element, matches)) { - match = false; break; - } - } - - return match; - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } -}); - -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in p) { - if (m = e.match(p[i])) { - v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); - exclusion.push("(" + v.substring(1, v.length - 1) + ")"); - e = e.replace(m[0], ''); - break; - } - } - } - return "[not(" + exclusion.join(" and ") + ")]"; - }, - 'nth-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); - }, - 'nth-last-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); - }, - 'nth-of-type': function(m) { - return Selector.xpath.pseudos.nth("position() ", m); - }, - 'nth-last-of-type': function(m) { - return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); - }, - 'first-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); - }, - 'last-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); - }, - 'only-of-type': function(m) { - var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); - }, - nth: function(fragment, m) { - var mm, formula = m[6], predicate; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - if (mm = formula.match(/^(\d+)$/)) // digit only - return '[' + fragment + "= " + mm[1] + ']'; - if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; - predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + - "((#{fragment} - #{b}) div #{a} >= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } - } - } - }, - - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: { - // combinators must be listed first - // (and descendant needs to be last combinator) - laterSibling: /^\s*~\s*/, - child: /^\s*>\s*/, - adjacent: /^\s*\+\s*/, - descendant: /^\s/, - - // selectors follow - tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, - id: /^#([\w\-\*]+)(\b|$)/, - className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: -/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ - }, - - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); - }, - - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); - }, - - id: function(element, matches) { - return element.id === matches[1]; - }, - - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); - }, - - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, - - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, - - // marks an array of nodes for counting - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, - - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, - - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, - - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._countedByPrototype) { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, - - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, - - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, - - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, - - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, - - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, - - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, - - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, - - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, - - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; - }, - - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, - - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, - - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, - - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, - - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, - - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); - } - return results; - }, - - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, - - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, - - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, - - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '$=': function(nv, v) { return nv.endsWith(v); }, - '*=': function(nv, v) { return nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, - - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - return expressions; - }, - - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, - - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, - - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; - } -}); - -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }, - - // IE improperly serializes _countedByPrototype in (inner|outer)HTML. - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } - }); -} - -function $$() { - return Selector.findChildElements(document, $A(arguments)); -} -var Form = { - reset: function(form) { - $(form).reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; - - var data = elements.inject({ }, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - // a key is already present; construct an array of values - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; - } - } - return result; - }); - - return options.hash ? data : Object.toQueryString(data); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - form.findFirstElement().activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = { - input: function(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return Form.Element.Serializers.inputSelector(element, value); - default: - return Form.Element.Serializers.textarea(element, value); - } - }, - - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; - else element.checked = !!value; - }, - - textarea: function(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - }, - - select: function(element, value) { - if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - }, - - selectOne: function(element) { - var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, - - selectMany: function(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); - } - return values; - }, - - optionValue: function(opt) { - // extend element because hasAttribute may not be native - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; - } -}; - -/*--------------------------------------------------------------------------*/ - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -if (!window.Event) var Event = { }; - -Object.extend(Event, { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: { }, - - relatedTarget: function(event) { - var element; - switch(event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); - } -}); - -Event.Methods = (function() { - var isButton; - - if (Prototype.Browser.IE) { - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - isButton = function(event, code) { - return event.button == buttonMap[code]; - }; - - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; - default: return false; - } - }; - - } else { - isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; - } - - return { - isLeftClick: function(event) { return isButton(event, 0) }, - isMiddleClick: function(event) { return isButton(event, 1) }, - isRightClick: function(event) { return isButton(event, 2) }, - - element: function(event) { - event = Event.extend(event); - - var node = event.target, - type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - // Firefox screws up the "click" event when moving between radio buttons - // via arrow keys. It also screws up the "load" and "error" events on images, - // reporting the document as the target instead of the original image. - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; - return Element.extend(node); - }, - - findElement: function(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - }, - - pointer: function(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0, scrollTop: 0 }; - return { - x: event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)), - y: event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)) - }; - }, - - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, - - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; - } - }; -})(); - -Event.extend = (function() { - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (Prototype.Browser.IE) { - Object.extend(methods, { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return "[object Event]" } - }); - - return function(event) { - if (!event) return false; - if (event._extendedByPrototype) return event; - - event._extendedByPrototype = Prototype.emptyFunction; - var pointer = Event.pointer(event); - Object.extend(event, { - target: event.srcElement, - relatedTarget: Event.relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - return Object.extend(event, methods); - }; - - } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; - Object.extend(Event.prototype, methods); - return Prototype.K; - } -})(); - -Object.extend(Event, (function() { - var cache = Event.cache; - - function getEventID(element) { - if (element._prototypeEventID) return element._prototypeEventID[0]; - arguments.callee.id = arguments.callee.id || 1; - return element._prototypeEventID = [++arguments.callee.id]; - } - - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } - - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } - - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } - - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; - - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) - return false; - - Event.extend(event); - handler.call(element, event); - }; - - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } - - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } - - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); - } - - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; - } - - - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); - } - - // Safari has a dummy event handler on page unload so that it won't - // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" - // object when page is returned to via the back button using its bfcache. - if (Prototype.Browser.WebKit) { - window.addEventListener('unload', Prototype.emptyFunction, false); - } - - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); - - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; - - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); - } - - return element; - }, - - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); - - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; - - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; - } - - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; - - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } - - destroyWrapper(id, eventName, handler); - - return element; - }, - - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - event = document.createEventObject(); - event.eventType = "ondataavailable"; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); - } - - return Event.extend(event); - } - }; -})()); - -Object.extend(Event, Event.Methods); - -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); - -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize(), - loaded: false -}); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards and John Resig. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); - document.loaded = true; - } - - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); - - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); - } - - } else { - document.write(" + + + +
    +

    <%=h exception.class %> at <%=h path %>

    +

    <%=h exception.message %>

    + + + + + + +
    Ruby<%=h frames.first.filename %>: in <%=h frames.first.function %>, line <%=h frames.first.lineno %>
    Web<%=h req.request_method %> <%=h(req.host + path)%>
    + +

    Jump to:

    + +
    + +
    +

    Traceback (innermost first)

    +
      +<% frames.each { |frame| %> +
    • + <%=h frame.filename %>: in <%=h frame.function %> + + <% if frame.context_line %> +
      + <% if frame.pre_context %> +
        + <% frame.pre_context.each { |line| %> +
      1. <%=h line %>
      2. + <% } %> +
      + <% end %> + +
        +
      1. <%=h frame.context_line %>...
      + + <% if frame.post_context %> +
        + <% frame.post_context.each { |line| %> +
      1. <%=h line %>
      2. + <% } %> +
      + <% end %> +
      + <% end %> +
    • +<% } %> +
    +
    + +
    +

    Request information

    + +

    GET

    + <% unless req.GET.empty? %> + + + + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No GET data.

    + <% end %> + +

    POST

    + <% unless req.POST.empty? %> + + + + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No POST data.

    + <% end %> + + + + <% unless req.cookies.empty? %> + + + + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    + <% else %> +

    No cookie data.

    + <% end %> + +

    Rack ENV

    + + + + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
    VariableValue
    <%=h key %>
    <%=h val %>
    + +
    + +
    +

    + You're seeing this error because you use Rack::ShowException. +

    +
    + + + +HTML + + # :startdoc: + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb new file mode 100644 index 0000000000..ca81f7d83f --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb @@ -0,0 +1,105 @@ +require 'erb' +require 'rack/request' +require 'rack/utils' + +module Rack + # Rack::ShowStatus catches all empty responses the app it wraps and + # replaces them with a site explaining the error. + # + # Additional details can be put into rack.showstatus.detail + # and will be shown as HTML. If such details exist, the error page + # is always rendered, even if the reply was not empty. + + class ShowStatus + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + status, headers, body = @app.call(env) + + # client or server error, or explicit message + if status.to_i >= 400 && + (body.empty? rescue false) || env["rack.showstatus.detail"] + req = Rack::Request.new(env) + message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s + detail = env["rack.showstatus.detail"] || message + body = @template.result(binding) + size = body.respond_to?(:bytesize) ? body.bytesize : body.size + [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] + else + [status, headers, body] + end + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + +# adapted from Django +# Copyright (c) 2005, the Lawrence Journal-World +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +TEMPLATE = <<'HTML' + + + + + <%=h message %> at <%=h req.script_name + req.path_info %> + + + + +
    +

    <%=h message %> (<%= status.to_i %>)

    + + + + + + + + + +
    Request Method:<%=h req.request_method %>
    Request URL:<%=h req.url %>
    +
    +
    +

    <%= detail %>

    +
    + +
    +

    + You're seeing this error because you use Rack::ShowStatus. +

    +
    + + +HTML + + # :startdoc: + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb new file mode 100644 index 0000000000..168e8f83b2 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb @@ -0,0 +1,38 @@ +module Rack + + # The Rack::Static middleware intercepts requests for static files + # (javascript files, images, stylesheets, etc) based on the url prefixes + # passed in the options, and serves them using a Rack::File object. This + # allows a Rack stack to serve both static and dynamic content. + # + # Examples: + # use Rack::Static, :urls => ["/media"] + # will serve all requests beginning with /media from the "media" folder + # located in the current directory (ie media/*). + # + # use Rack::Static, :urls => ["/css", "/images"], :root => "public" + # will serve all requests beginning with /css or /images from the folder + # "public" in the current directory (ie public/css/* and public/images/*) + + class Static + + def initialize(app, options={}) + @app = app + @urls = options[:urls] || ["/favicon.ico"] + root = options[:root] || Dir.pwd + @file_server = Rack::File.new(root) + end + + def call(env) + path = env["PATH_INFO"] + can_serve = @urls.any? { |url| path.index(url) == 0 } + + if can_serve + @file_server.call(env) + else + @app.call(env) + end + end + + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb new file mode 100644 index 0000000000..b00f2d7a19 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb @@ -0,0 +1,48 @@ +module Rack + # Rack::URLMap takes a hash mapping urls or paths to apps, and + # dispatches accordingly. Support for HTTP/1.1 host names exists if + # the URLs start with http:// or https://. + # + # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part + # relevant for dispatch is in the SCRIPT_NAME, and the rest in the + # PATH_INFO. This should be taken care of when you need to + # reconstruct the URL in order to create links. + # + # URLMap dispatches in such a way that the longest paths are tried + # first, since they are most specific. + + class URLMap + def initialize(map) + @mapping = map.map { |location, app| + if location =~ %r{\Ahttps?://(.*?)(/.*)} + host, location = $1, $2 + else + host = nil + end + + unless location[0] == ?/ + raise ArgumentError, "paths need to start with /" + end + location = location.chomp('/') + + [host, location, app] + }.sort_by { |(h, l, a)| -l.size } # Longest path first + end + + def call(env) + path = env["PATH_INFO"].to_s.squeeze("/") + hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') + @mapping.each { |host, location, app| + next unless (hHost == host || sName == host \ + || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) + next unless location == path[0, location.size] + next unless path[location.size] == nil || path[location.size] == ?/ + env["SCRIPT_NAME"] += location + env["PATH_INFO"] = path[location.size..-1] + return app.call(env) + } + [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]] + end + end +end + diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb new file mode 100644 index 0000000000..25254bbdf2 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb @@ -0,0 +1,318 @@ +require 'tempfile' + +module Rack + # Rack::Utils contains a grab-bag of useful methods for writing web + # applications adopted from all kinds of Ruby libraries. + + module Utils + # Performs URI escaping so that you can construct proper + # query strings faster. Use this rather than the cgi.rb + # version since it's faster. (Stolen from Camping). + def escape(s) + s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { + '%'+$1.unpack('H2'*$1.size).join('%').upcase + }.tr(' ', '+') + end + module_function :escape + + # Unescapes a URI escaped string. (Stolen from Camping). + def unescape(s) + s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ + [$1.delete('%')].pack('H*') + } + end + module_function :unescape + + # Stolen from Mongrel, with some small modifications: + # Parses a query string by breaking it up at the '&' + # and ';' characters. You can also use this to parse + # cookies by changing the characters used in the second + # parameter (which defaults to '&;'). + + def parse_query(qs, d = '&;') + params = {} + + (qs || '').split(/[#{d}] */n).each do |p| + k, v = unescape(p).split('=', 2) + + if cur = params[k] + if cur.class == Array + params[k] << v + else + params[k] = [cur, v] + end + else + params[k] = v + end + end + + return params + end + module_function :parse_query + + def build_query(params) + params.map { |k, v| + if v.class == Array + build_query(v.map { |x| [k, x] }) + else + escape(k) + "=" + escape(v) + end + }.join("&") + end + module_function :build_query + + # Escape ampersands, brackets and quotes to their HTML/XML entities. + def escape_html(string) + string.to_s.gsub("&", "&"). + gsub("<", "<"). + gsub(">", ">"). + gsub("'", "'"). + gsub('"', """) + end + module_function :escape_html + + def select_best_encoding(available_encodings, accept_encoding) + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + + expanded_accept_encoding = + accept_encoding.map { |m, q| + if m == "*" + (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] } + else + [[m, q]] + end + }.inject([]) { |mem, list| + mem + list + } + + encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m } + + unless encoding_candidates.include?("identity") + encoding_candidates.push("identity") + end + + expanded_accept_encoding.find_all { |m, q| + q == 0.0 + }.each { |m, _| + encoding_candidates.delete(m) + } + + return (encoding_candidates & available_encodings)[0] + end + module_function :select_best_encoding + + # The recommended manner in which to implement a contexting application + # is to define a method #context in which a new Context is instantiated. + # + # As a Context is a glorified block, it is highly recommended that you + # define the contextual block within the application's operational scope. + # This would typically the application as you're place into Rack's stack. + # + # class MyObject + # ... + # def context app + # Rack::Utils::Context.new app do |env| + # do_stuff + # response = app.call(env) + # do_more_stuff + # end + # end + # ... + # end + # + # mobj = MyObject.new + # app = mobj.context other_app + # Rack::Handler::Mongrel.new app + class Context < Proc + alias_method :old_inspect, :inspect + attr_reader :for, :app + def initialize app_f, app_r + raise 'running context not provided' unless app_f + raise 'running context does not respond to #context' unless app_f.respond_to? :context + raise 'application context not provided' unless app_r + raise 'application context does not respond to #call' unless app_r.respond_to? :call + @for = app_f + @app = app_r + end + def inspect + "#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}" + end + def context app_r + raise 'new application context not provided' unless app_r + raise 'new application context does not respond to #call' unless app_r.respond_to? :call + @for.context app_r + end + def pretty_print pp + pp.text old_inspect + pp.nest 1 do + pp.breakable + pp.text '=for> ' + pp.pp @for + pp.breakable + pp.text '=app> ' + pp.pp @app + end + end + end + + # A case-normalizing Hash, adjusting on [] and []=. + class HeaderHash < Hash + def initialize(hash={}) + hash.each { |k, v| self[k] = v } + end + + def to_hash + {}.replace(self) + end + + def [](k) + super capitalize(k) + end + + def []=(k, v) + super capitalize(k), v + end + + def capitalize(k) + k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase } + end + end + + # Every standard HTTP code mapped to the appropriate message. + # Stolen from Mongrel. + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported' + } + + # A multipart form data parser, adapted from IOWA. + # + # Usually, Rack::Request#POST takes care of calling this. + + module Multipart + EOL = "\r\n" + + def self.parse_multipart(env) + unless env['CONTENT_TYPE'] =~ + %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n + nil + else + boundary = "--#{$1}" + + params = {} + buf = "" + content_length = env['CONTENT_LENGTH'].to_i + input = env['rack.input'] + + boundary_size = boundary.size + EOL.size + bufsize = 16384 + + content_length -= boundary_size + + status = input.read(boundary_size) + raise EOFError, "bad content body" unless status == boundary + EOL + + rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/ + + loop { + head = nil + body = '' + filename = content_type = name = nil + + until head && buf =~ rx + if !head && i = buf.index("\r\n\r\n") + head = buf.slice!(0, i+2) # First \r\n + buf.slice!(0, 2) # Second \r\n + + filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1] + content_type = head[/Content-Type: (.*)\r\n/ni, 1] + name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1] + + if filename + body = Tempfile.new("RackMultipart") + body.binmode if body.respond_to?(:binmode) + end + + next + end + + # Save the read body part. + if head && (boundary_size+4 < buf.size) + body << buf.slice!(0, buf.size - (boundary_size+4)) + end + + c = input.read(bufsize < content_length ? bufsize : content_length) + raise EOFError, "bad content body" if c.nil? || c.empty? + buf << c + content_length -= c.size + end + + # Save the rest. + if i = buf.index(rx) + body << buf.slice!(0, i) + buf.slice!(0, boundary_size+2) + + content_length = -1 if $1 == "--" + end + + if filename + body.rewind + data = {:filename => filename, :type => content_type, + :name => name, :tempfile => body, :head => head} + else + data = body + end + + if name + if name =~ /\[\]\z/ + params[name] ||= [] + params[name] << data + else + params[name] = data + end + end + + break if buf.empty? || content_length == -1 + } + + params + end + end + end + end +end diff --git a/actionpack/lib/action_controller/vendor/rack.rb b/actionpack/lib/action_controller/vendor/rack.rb new file mode 100644 index 0000000000..2ab7575b05 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack.rb @@ -0,0 +1,8 @@ +require 'rubygems' + +begin + gem 'rack', '~> 0.4.0' + require 'rack' +rescue Gem::LoadError + require "#{File.dirname(__FILE__)}/rack-0.4.0/rack" +end -- cgit v1.2.3 From 51383c57a2f556d1ea44c93deb68e7b70f8c6e35 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 22 Nov 2008 18:19:42 -0800 Subject: MiniTest compat: don't check for test/unit's assertion in particular --- actionpack/test/controller/resources_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index d5e8a3ae4a..541e8270c9 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1240,7 +1240,7 @@ class ResourcesTest < ActionController::TestCase end def assert_not_recognizes(expected_options, path) - assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Test::Unit::AssertionFailedError do + assert_raise ActionController::RoutingError, ActionController::MethodNotAllowed, Assertion do assert_recognizes(expected_options, path) end end -- cgit v1.2.3 From 4d2ccbb364b62275bad142d9a0b66ee7fd0c424a Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 22 Nov 2008 22:40:32 -0800 Subject: Use a relative require for bundled rack lib --- actionpack/lib/action_controller/vendor/rack.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/vendor/rack.rb b/actionpack/lib/action_controller/vendor/rack.rb index 2ab7575b05..2438059f76 100644 --- a/actionpack/lib/action_controller/vendor/rack.rb +++ b/actionpack/lib/action_controller/vendor/rack.rb @@ -4,5 +4,6 @@ begin gem 'rack', '~> 0.4.0' require 'rack' rescue Gem::LoadError - require "#{File.dirname(__FILE__)}/rack-0.4.0/rack" + $LOAD_PATH << "#{File.dirname(__FILE__)}/rack-0.4.0" + require 'rack' end -- cgit v1.2.3 From 5ea9f2cac6e136e27d7fef2662412dda25391860 Mon Sep 17 00:00:00 2001 From: Sam Pohlenz Date: Thu, 20 Nov 2008 14:05:20 -0800 Subject: Allow helpers directory to be overridden via ActionController::Base.helpers_dir (Sam Pohlenz) [#1424 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_controller/helpers.rb | 14 ++++++++------ actionpack/test/abstract_unit.rb | 1 + actionpack/test/controller/helper_test.rb | 16 +++++++++++++++- actionpack/test/fixtures/alternate_helpers/foo_helper.rb | 3 +++ 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 actionpack/test/fixtures/alternate_helpers/foo_helper.rb (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 40655c6577..59a4bbea59 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz] + * Remove deprecated ActionController::Base#assign_default_content_type_and_charset * Changed the default of ActionView#render to assume partials instead of files when not given an options hash [DHH]. Examples: diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index 9cffa07b26..6064931eb8 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -1,13 +1,15 @@ # FIXME: helper { ... } is broken on Ruby 1.9 module ActionController #:nodoc: module Helpers #:nodoc: - HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") - def self.included(base) # Initialize the base module to aggregate its helpers. base.class_inheritable_accessor :master_helper_module base.master_helper_module = Module.new + # Set the default directory for helpers + base.class_inheritable_accessor :helpers_dir + base.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") + # Extend base with class methods to declare helpers. base.extend(ClassMethods) @@ -88,8 +90,8 @@ module ActionController #:nodoc: # When the argument is a module it will be included directly in the template class. # helper FooHelper # => includes FooHelper # - # When the argument is the symbol :all, the controller will include all helpers from - # app/helpers/**/*.rb under RAILS_ROOT. + # When the argument is the symbol :all, the controller will include all helpers beneath + # ActionController::Base.helpers_dir (defaults to app/helpers/**/*.rb under RAILS_ROOT). # helper :all # # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available @@ -213,8 +215,8 @@ module ActionController #:nodoc: # Extract helper names from files in app/helpers/**/*.rb def all_application_helpers - extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/ - Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } + extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ + Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 9623afa89d..76812b94df 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,6 +1,7 @@ $:.unshift(File.dirname(__FILE__) + '/../lib') $:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') +$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') require 'rubygems' require 'yaml' diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 83e3b085e7..5f36461b89 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -ActionController::Helpers::HELPERS_DIR.replace File.dirname(__FILE__) + '/../fixtures/helpers' +ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers' class TestController < ActionController::Base attr_accessor :delegate_attr @@ -130,6 +130,20 @@ class HelperTest < Test::Unit::TestCase assert methods.include?('foobar') end + def test_all_helpers_with_alternate_helper_dir + @controller_class.helpers_dir = File.dirname(__FILE__) + '/../fixtures/alternate_helpers' + + # Reload helpers + @controller_class.master_helper_module = Module.new + @controller_class.helper :all + + # helpers/abc_helper.rb should not be included + assert !master_helper_methods.include?('bare_a') + + # alternate_helpers/foo_helper.rb + assert master_helper_methods.include?('baz') + end + def test_helper_proxy methods = ApplicationController.helpers.methods.map(&:to_s) diff --git a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb new file mode 100644 index 0000000000..a956fce6fa --- /dev/null +++ b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb @@ -0,0 +1,3 @@ +module FooHelper + def baz() end +end -- cgit v1.2.3 From 9fdb15e60f4d4e37916e5354c50d559773bbe014 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 20 Nov 2008 23:06:19 +0100 Subject: Change the forgery token implementation to just be a simple random string. This deprecates the use of :secret and :digest which were only needed when we were hashing session ids. --- .../request_forgery_protection.rb | 46 ++--------- actionpack/test/controller/render_test.rb | 3 +- .../controller/request_forgery_protection_test.rb | 93 ++-------------------- 3 files changed, 15 insertions(+), 127 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 3e0e94a06b..f3e6288c26 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -5,8 +5,6 @@ module ActionController #:nodoc: module RequestForgeryProtection def self.included(base) base.class_eval do - class_inheritable_accessor :request_forgery_protection_options - self.request_forgery_protection_options = {} helper_method :form_authenticity_token helper_method :protect_against_forgery? end @@ -14,7 +12,7 @@ module ActionController #:nodoc: end # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a - # forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all + # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only # HTML/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 anyway. @@ -57,12 +55,8 @@ module ActionController #:nodoc: # Example: # # class FooController < ApplicationController - # # uses the cookie session store (then you don't need a separate :secret) # protect_from_forgery :except => :index # - # # uses one of the other session stores that uses a session_id value. - # protect_from_forgery :secret => 'my-little-pony', :except => :index - # # # you can disable csrf protection on controller-by-controller basis: # skip_before_filter :verify_authenticity_token # end @@ -70,13 +64,12 @@ module ActionController #:nodoc: # Valid Options: # # * :only/:except - Passed to the before_filter call. Set which actions are verified. - # * :secret - Custom salt used to generate the form_authenticity_token. - # Leave this off if you are using the cookie session store. - # * :digest - Message digest used for hashing. Defaults to 'SHA1'. def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) - request_forgery_protection_options.update(options) + if options[:secret] || options[:digest] + ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller) + end end end @@ -90,7 +83,7 @@ module ActionController #:nodoc: # # * is the format restricted? By default, only HTML and AJAX requests are checked. # * is it a GET request? Gets should be safe and idempotent - # * Does the form_authenticity_token match the given _token value from the params? + # * Does the form_authenticity_token match the given token value from the params? def verified_request? !protect_against_forgery? || request.method == :get || @@ -105,34 +98,9 @@ module ActionController #:nodoc: # Sets the token value for the current session. Pass a :secret option # in +protect_from_forgery+ to add a custom salt to the hash. def form_authenticity_token - @form_authenticity_token ||= if !session.respond_to?(:session_id) - raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session." - elsif request_forgery_protection_options[:secret] - authenticity_token_from_session_id - elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest) - authenticity_token_from_cookie_session - else - raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)." - end - end - - # Generates a unique digest using the session_id and the CSRF secret. - def authenticity_token_from_session_id - key = if request_forgery_protection_options[:secret].respond_to?(:call) - request_forgery_protection_options[:secret].call(@session) - else - request_forgery_protection_options[:secret] - end - digest = request_forgery_protection_options[:digest] ||= 'SHA1' - OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s) - end - - # No secret was given, so assume this is a cookie session store. - def authenticity_token_from_cookie_session - session[:csrf_id] ||= CGI::Session.generate_unique_id - session.dbman.generate_digest(session[:csrf_id]) + session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) end - + def protect_against_forgery? allow_forgery_protection && request_forgery_protection_token end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index d5320596d5..972e425e35 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -883,12 +883,13 @@ class RenderTest < ActionController::TestCase end def test_enum_rjs_test + ActiveSupport::SecureRandom.stubs(:base64).returns("asdf") get :enum_rjs_test body = %{ $$(".product").each(function(value, index) { new Effect.Highlight(element,{}); new Effect.Highlight(value,{}); - Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}}); + Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value) + '&authenticity_token=' + encodeURIComponent('asdf')})}}); new Draggable(value, {}); }); }.gsub(/^ /, '').strip diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 00c6bc0ab0..ef0bf5fd08 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -5,13 +5,6 @@ ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' end -# simulates cookie session store -class FakeSessionDbMan - def self.generate_digest(data) - Digest::SHA1.hexdigest("secure") - end -end - # common controller actions module RequestForgeryProtectionActions def index @@ -35,30 +28,11 @@ end # sample controllers class RequestForgeryProtectionController < ActionController::Base - include RequestForgeryProtectionActions - protect_from_forgery :only => :index, :secret => 'abc' -end - -class RequestForgeryProtectionWithoutSecretController < ActionController::Base - include RequestForgeryProtectionActions - protect_from_forgery -end - -# no token is given, assume the cookie store is used -class CsrfCookieMonsterController < ActionController::Base include RequestForgeryProtectionActions protect_from_forgery :only => :index end -# sessions are turned off -class SessionOffController < ActionController::Base - protect_from_forgery :secret => 'foobar' - session :off - def rescue_action(e) raise e end - include RequestForgeryProtectionActions -end - -class FreeCookieController < CsrfCookieMonsterController +class FreeCookieController < RequestForgeryProtectionController self.allow_forgery_protection = false def index @@ -237,45 +211,9 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase @request = ActionController::TestRequest.new @request.format = :html @response = ActionController::TestResponse.new - class << @request.session - def session_id() '123' end - end - @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') - ActionController::Base.request_forgery_protection_token = :authenticity_token - end -end + @token = "cf50faa3fe97702ca1ae" -class RequestForgeryProtectionWithoutSecretControllerTest < ActionController::TestCase - def setup - @controller = RequestForgeryProtectionWithoutSecretController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - class << @request.session - def session_id() '123' end - end - @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') - ActionController::Base.request_forgery_protection_token = :authenticity_token - end - - # def test_should_raise_error_without_secret - # assert_raises ActionController::InvalidAuthenticityToken do - # get :index - # end - # end -end - -class CsrfCookieMonsterControllerTest < ActionController::TestCase - include RequestForgeryProtectionTests - def setup - @controller = CsrfCookieMonsterController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - class << @request.session - attr_accessor :dbman - end - # simulate a cookie session store - @request.session.dbman = FakeSessionDbMan - @token = Digest::SHA1.hexdigest("secure") + ActiveSupport::SecureRandom.stubs(:base64).returns(@token) ActionController::Base.request_forgery_protection_token = :authenticity_token end end @@ -285,7 +223,9 @@ class FreeCookieControllerTest < ActionController::TestCase @controller = FreeCookieController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') + @token = "cf50faa3fe97702ca1ae" + + ActiveSupport::SecureRandom.stubs(:base64).returns(@token) end def test_should_not_render_form_with_token_tag @@ -304,24 +244,3 @@ class FreeCookieControllerTest < ActionController::TestCase end end end - -class SessionOffControllerTest < ActionController::TestCase - def setup - @controller = SessionOffController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - @token = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('SHA1'), 'abc', '123') - end - - # TODO: Rewrite this test. - # This test was passing but for the wrong reason. - # Sessions aren't really being turned off, so an exception was raised - # because sessions weren't on - not because the token didn't match. - # - # def test_should_raise_correct_exception - # @request.session = {} # session(:off) doesn't appear to work with controller tests - # assert_raises(ActionController::InvalidAuthenticityToken) do - # post :index, :authenticity_token => @token, :format => :html - # end - # end -end -- cgit v1.2.3 From 04d2d043ca9c59ab93522f1d8c0810cf47f05b23 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Sun, 23 Nov 2008 16:42:15 +0100 Subject: Move the cookie store to use the MessageVerifier class. This removes support for ancient cookie-store generated cookies which were double escaped. --- actionpack/CHANGELOG | 2 ++ .../lib/action_controller/session/cookie_store.rb | 32 ++++++++++------------ .../test/controller/session/cookie_store_test.rb | 13 ++------- 3 files changed, 18 insertions(+), 29 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 59a4bbea59..9581442208 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Koz] + * Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz] * Remove deprecated ActionController::Base#assign_default_content_type_and_charset diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index f2fb200950..ea0ea4f841 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -1,6 +1,5 @@ require 'cgi' require 'cgi/session' -require 'openssl' # to generate the HMAC message digest # This cookie-based session store is the Rails default. Sessions typically # contain at most a user_id and flash message; both fit within the 4K cookie @@ -121,32 +120,20 @@ class CGI::Session::CookieStore write_cookie('value' => nil, 'expires' => 1.year.ago) end - # Generate the HMAC keyed message digest. Uses SHA1 by default. - def generate_digest(data) - key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret - OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data) - end - private # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) - data = ActiveSupport::Base64.encode64s(Marshal.dump(session)) - "#{data}--#{generate_digest(data)}" + verifier.generate(session) end # Unmarshal cookie data to a hash and verify its integrity. def unmarshal(cookie) if cookie - data, digest = cookie.split('--') - - # Do two checks to transparently support old double-escaped data. - unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data)) - delete - raise TamperedWithCookie - end - - Marshal.load(ActiveSupport::Base64.decode64(data)) + verifier.verify(cookie) end + rescue ActiveSupport::MessageVerifier::InvalidSignature + delete + raise TamperedWithCookie end # Read the session data cookie. @@ -164,4 +151,13 @@ class CGI::Session::CookieStore def clear_old_cookie_value @session.cgi.cookies[@cookie_options['name']].clear end + + def verifier + if @secret.respond_to?(:call) + key = @secret.call + else + key = @secret + end + ActiveSupport::MessageVerifier.new(key, @digest) + end end diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 30422314a1..52c1f7559c 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -45,8 +45,8 @@ class CookieStoreTest < Test::Unit::TestCase { :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}], :a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }], :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}], - :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }], - :double_escaped => [CGI.escape('BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf'), { 'user_id' => 123, 'flash' => {} }] } + :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }] + } end @@ -105,15 +105,6 @@ class CookieStoreTest < Test::Unit::TestCase end end - def test_restores_double_encoded_cookies - set_cookie! cookie_value(:double_escaped) - new_session do |session| - session.dbman.restore - assert_equal session["user_id"], 123 - assert_equal session["flash"], {} - end - end - def test_close_doesnt_write_cookie_if_data_is_blank new_session do |session| assert_no_cookies session -- cgit v1.2.3 From 2c01f2b4e9d4a95bb2baca8ae57209eb10aa78b2 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 23 Nov 2008 13:42:07 -0600 Subject: use autoload instead of explicit requires for ActionView --- actionpack/lib/action_view.rb | 36 +++++++++++--------- actionpack/lib/action_view/base.rb | 2 +- actionpack/lib/action_view/erb/util.rb | 38 +++++++++++++++++++++ actionpack/lib/action_view/helpers.rb | 28 +++++++++++++--- actionpack/lib/action_view/template_handler.rb | 5 --- actionpack/lib/action_view/template_handlers.rb | 13 +++++--- .../lib/action_view/template_handlers/erb.rb | 39 ---------------------- 7 files changed, 91 insertions(+), 70 deletions(-) create mode 100644 actionpack/lib/action_view/erb/util.rb (limited to 'actionpack') diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 7b1d3f8e7c..0c76204060 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -31,23 +31,29 @@ rescue LoadError end end -require 'action_view/template_handlers' -require 'action_view/renderable' -require 'action_view/renderable_partial' +module ActionView + def self.load_all! + [Base, InlineTemplate, TemplateError] + end -require 'action_view/template' -require 'action_view/inline_template' -require 'action_view/paths' + autoload :Base, 'action_view/base' + autoload :Helpers, 'action_view/helpers' + autoload :InlineTemplate, 'action_view/inline_template' + autoload :Partials, 'action_view/partials' + autoload :PathSet, 'action_view/paths' + autoload :Renderable, 'action_view/renderable' + autoload :RenderablePartial, 'action_view/renderable_partial' + autoload :Template, 'action_view/template' + autoload :TemplateError, 'action_view/template_error' + autoload :TemplateHandler, 'action_view/template_handler' + autoload :TemplateHandlers, 'action_view/template_handlers' + autoload :Helpers, 'action_view/helpers' +end -require 'action_view/base' -require 'action_view/partials' -require 'action_view/template_error' +class ERB + autoload :Util, 'action_view/erb/util' +end I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" -require 'action_view/helpers' - -ActionView::Base.class_eval do - include ActionView::Partials - include ActionView::Helpers -end +ActionView.load_all! diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 0d3752d875..7697848713 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -157,7 +157,7 @@ module ActionView #:nodoc: # # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base - include ERB::Util + include Helpers, Partials, ::ERB::Util extend ActiveSupport::Memoizable attr_accessor :base_path, :assigns, :template_extension diff --git a/actionpack/lib/action_view/erb/util.rb b/actionpack/lib/action_view/erb/util.rb new file mode 100644 index 0000000000..3c77c5ce76 --- /dev/null +++ b/actionpack/lib/action_view/erb/util.rb @@ -0,0 +1,38 @@ +require 'erb' + +class ERB + module Util + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } + JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } + + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # In your ERb templates, use this method to escape any unsafe content. For example: + # <%=h @person.name %> + # + # ==== Example: + # puts html_escape("is a > 0 & a < 10?") + # # => is a > 0 & a < 10? + def html_escape(s) + s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] } + end + + # A utility method for escaping HTML entities in JSON strings. + # This method is also aliased as j. + # + # In your ERb templates, use this method to escape any HTML entities: + # <%=j @person.to_json %> + # + # ==== Example: + # puts json_escape("is a > 0 & a < 10?") + # # => is a \u003E 0 \u0026 a \u003C 10? + def json_escape(s) + s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + end + + alias j json_escape + module_function :j + module_function :json_escape + end +end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index ff97df204c..de8682560f 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -1,10 +1,28 @@ -Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file| - next unless file =~ /^([a-z][a-z_]*_helper).rb$/ - require "action_view/helpers/#{$1}" -end - module ActionView #:nodoc: module Helpers #:nodoc: + autoload :ActiveRecordHelper, 'action_view/helpers/active_record_helper' + autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper' + autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper' + autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper' + autoload :CacheHelper, 'action_view/helpers/cache_helper' + autoload :CaptureHelper, 'action_view/helpers/capture_helper' + autoload :DateHelper, 'action_view/helpers/date_helper' + autoload :DebugHelper, 'action_view/helpers/debug_helper' + autoload :FormHelper, 'action_view/helpers/form_helper' + autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper' + autoload :FormTagHelper, 'action_view/helpers/form_tag_helper' + autoload :JavascriptHelper, 'action_view/helpers/javascript_helper' + autoload :NumberHelper, 'action_view/helpers/number_helper' + autoload :PrototypeHelper, 'action_view/helpers/prototype_helper' + autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper' + autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper' + autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper' + autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper' + autoload :TagHelper, 'action_view/helpers/tag_helper' + autoload :TextHelper, 'action_view/helpers/text_helper' + autoload :TranslationHelper, 'action_view/helpers/translation_helper' + autoload :UrlHelper, 'action_view/helpers/url_helper' + def self.included(base) base.extend(ClassMethods) end diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index d7e7c9b199..5efe9459b5 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,11 +1,6 @@ # Legacy TemplateHandler stub module ActionView - module TemplateHandlers - module Compilable - end - end - class TemplateHandler def self.call(template) new.compile(template) diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb index 6c8aa4c2a7..1052c4e75c 100644 --- a/actionpack/lib/action_view/template_handlers.rb +++ b/actionpack/lib/action_view/template_handlers.rb @@ -1,10 +1,13 @@ -require 'action_view/template_handler' -require 'action_view/template_handlers/builder' -require 'action_view/template_handlers/erb' -require 'action_view/template_handlers/rjs' - module ActionView #:nodoc: module TemplateHandlers #:nodoc: + autoload :ERB, 'action_view/template_handlers/erb' + autoload :RJS, 'action_view/template_handlers/rjs' + autoload :Builder, 'action_view/template_handlers/builder' + + # Legacy Compilable stub + module Compilable + end + def self.extended(base) base.register_default_template_handler :erb, TemplateHandlers::ERB base.register_template_handler :rjs, TemplateHandlers::RJS diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb index 3def949f1e..f8d3da15be 100644 --- a/actionpack/lib/action_view/template_handlers/erb.rb +++ b/actionpack/lib/action_view/template_handlers/erb.rb @@ -1,42 +1,3 @@ -require 'erb' - -class ERB - module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } - JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } - - # A utility method for escaping HTML tag characters. - # This method is also aliased as h. - # - # In your ERb templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> - # - # ==== Example: - # puts html_escape("is a > 0 & a < 10?") - # # => is a > 0 & a < 10? - def html_escape(s) - s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] } - end - - # A utility method for escaping HTML entities in JSON strings. - # This method is also aliased as j. - # - # In your ERb templates, use this method to escape any HTML entities: - # <%=j @person.to_json %> - # - # ==== Example: - # puts json_escape("is a > 0 & a < 10?") - # # => is a \u003E 0 \u0026 a \u003C 10? - def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } - end - - alias j json_escape - module_function :j - module_function :json_escape - end -end - module ActionView module TemplateHandlers class ERB < TemplateHandler -- cgit v1.2.3 From bc4d05b244c78f03ade51d8b95f16dd48ceb63d3 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 23 Nov 2008 13:57:01 -0600 Subject: A back support for legacy TemplateHandler#render API --- actionpack/lib/action_view/template_handler.rb | 29 +++++++++++++++++++++++-- actionpack/lib/action_view/template_handlers.rb | 4 ---- actionpack/test/template/render_test.rb | 11 ++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index 5efe9459b5..672da0ed2b 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,9 +1,34 @@ # Legacy TemplateHandler stub - module ActionView + module TemplateHandlers #:nodoc: + module Compilable + def self.included(base) + 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 TemplateHandler def self.call(template) - new.compile(template) + "#{name}.new(self).render(template, local_assigns)" + end + + def initialize(view = nil) + @view = view + end + + def render(template, local_assigns) + raise "Need to implement #{self.class.name}#render(template, local_assigns)" end end end diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb index 1052c4e75c..d06ddd5fb5 100644 --- a/actionpack/lib/action_view/template_handlers.rb +++ b/actionpack/lib/action_view/template_handlers.rb @@ -4,10 +4,6 @@ module ActionView #:nodoc: autoload :RJS, 'action_view/template_handlers/rjs' autoload :Builder, 'action_view/template_handlers/builder' - # Legacy Compilable stub - module Compilable - end - def self.extended(base) base.register_default_template_handler :erb, TemplateHandlers::ERB base.register_template_handler :rjs, TemplateHandlers::RJS diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 0323c33b95..b316d5c23e 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -175,6 +175,17 @@ class ViewRenderTest < Test::Unit::TestCase assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end + class LegacyHandler < ActionView::TemplateHandler + def render(template, local_assigns) + "source: #{template.source}; locals: #{local_assigns.inspect}" + end + end + + def test_render_legacy_handler_with_custom_type + ActionView::Template.register_template_handler :foo, LegacyHandler + assert_equal 'source: Hello, <%= name %>!; locals: {:name=>"Josh"}', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) + end + def test_render_with_layout assert_equal %(\nHello world!\n), @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield") -- cgit v1.2.3 From 2db8571edf105c7c68e06eea385b032951042ef8 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 13:10:27 -0800 Subject: Don't hide deeper LoadErrors --- actionpack/test/controller/assert_select_test.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index a79278159d..ed8c4427c9 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -9,9 +9,10 @@ require 'controller/fake_controllers' unless defined?(ActionMailer) begin - $:.unshift(File.dirname(__FILE__) + "/../../../actionmailer/lib") + $:.unshift("#{File.dirname(__FILE__)}/../../../actionmailer/lib") require 'action_mailer' - rescue LoadError + rescue LoadError => e + raise unless e.message =~ /action_mailer/ require 'rubygems' gem 'actionmailer' end -- cgit v1.2.3 From d36158794b19ee8ea49d74061218b37d4301f0f9 Mon Sep 17 00:00:00 2001 From: Yaroslav Markin Date: Sun, 23 Nov 2008 17:30:59 +0300 Subject: Add i18n for number_to_human_size() helper storage units. Translation key is number.human.storage_units. [#1448 state:committed] Signed-off-by: Jeremy Kemper --- actionpack/lib/action_view/helpers/number_helper.rb | 7 +++---- actionpack/lib/action_view/locale/en.yml | 1 + actionpack/test/template/number_helper_i18n_test.rb | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 77f19b36a6..3e734ccaab 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -220,8 +220,6 @@ module ActionView end end - STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze - # Formats the bytes in +size+ into a more understandable representation # (e.g., giving it 1500 yields 1.5 KB). This method is useful for # reporting file sizes to users. This method returns nil if @@ -257,6 +255,7 @@ module ActionView defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {} defaults = defaults.merge(human) + storage_units = I18n.translate(:'number.human.storage_units', :locale => options[:locale], :raise => true) unless args.empty? ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' + @@ -268,12 +267,12 @@ module ActionView separator ||= (options[:separator] || defaults[:separator]) delimiter ||= (options[:delimiter] || defaults[:delimiter]) - max_exp = STORAGE_UNITS.size - 1 + max_exp = storage_units.size - 1 number = Float(number) exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024 exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit number /= 1024 ** exponent - unit = STORAGE_UNITS[exponent] + unit = storage_units[exponent] begin escaped_separator = Regexp.escape(separator) diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 002226fd9c..9542b035aa 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -44,6 +44,7 @@ # separator: delimiter: "" precision: 1 + storage_units: [Bytes, KB, MB, GB, TB] # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() datetime: diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb index 67c61a5f2e..2528bead36 100644 --- a/actionpack/test/template/number_helper_i18n_test.rb +++ b/actionpack/test/template/number_helper_i18n_test.rb @@ -10,6 +10,7 @@ class NumberHelperI18nTests < Test::Unit::TestCase @number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' } @currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 } @human_defaults = { :precision => 1 } + @human_storage_units_defaults = %w(Bytes KB MB GB TB) @percentage_defaults = { :delimiter => '' } @precision_defaults = { :delimiter => '' } @@ -47,6 +48,8 @@ class NumberHelperI18nTests < Test::Unit::TestCase I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) I18n.expects(:translate).with(:'number.human.format', :locale => 'en', :raise => true).returns(@human_defaults) + I18n.expects(:translate).with(:'number.human.storage_units', :locale => 'en', + :raise => true).returns(@human_storage_units_defaults) # can't be called with 1 because this directly returns without calling I18n.translate number_to_human_size(1025, :locale => 'en') end -- cgit v1.2.3 From d75a2345015046e08f8191748f0e246e1b9f9703 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 23 Nov 2008 15:15:05 -0600 Subject: simplify console with helpers --- actionpack/lib/action_view/helpers.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index de8682560f..693ab7c2e0 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -42,6 +42,7 @@ module ActionView #:nodoc: include FormHelper include FormOptionsHelper include FormTagHelper + include JavaScriptHelper include NumberHelper include PrototypeHelper include RecordIdentificationHelper -- cgit v1.2.3 From 6de1060eb555b5053f7d95269ceb23fce04e0523 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 13:22:46 -0800 Subject: Changelog for #1448. Mention updating old translations with storage_units key. --- actionpack/CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 9581442208..1d6e5455da 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -27,7 +27,9 @@ * Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [DHH] -*2.2.1 [RC2] (November 14th, 2008)* +*2.2.2 (November 21st, 2008)* + +* I18n: translate number_to_human_size. Add storage_units: [Bytes, KB, MB, GB, TB] to your translations. #1448 [Yaroslav Markin] * Restore backwards compatible functionality for setting relative_url_root. Include deprecation -- cgit v1.2.3 From 31ce92f7b5784bc5b6a441e88cd734c7b8b1c58f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 23 Nov 2008 16:35:13 -0600 Subject: Use autoload instead of explicit requires for ActionController --- actionpack/lib/action_controller.rb | 96 +++++++++++++--------- actionpack/lib/action_controller/base.rb | 14 ++-- actionpack/lib/action_controller/caching.rb | 18 ++-- actionpack/lib/action_controller/cgi_process.rb | 1 - actionpack/lib/action_controller/integration.rb | 4 - .../lib/action_controller/performance_test.rb | 1 - actionpack/lib/action_controller/rack_process.rb | 1 - .../lib/action_controller/request_profiler.rb | 1 - actionpack/lib/action_controller/resources.rb | 4 - actionpack/lib/action_controller/routing.rb | 1 - .../lib/action_controller/routing/route_set.rb | 2 + .../lib/action_controller/session_management.rb | 7 -- actionpack/lib/action_view/test_case.rb | 2 - .../test/activerecord/active_record_store_test.rb | 1 - actionpack/test/controller/cgi_test.rb | 1 - actionpack/test/controller/dispatcher_test.rb | 2 - actionpack/test/controller/integration_test.rb | 2 - .../test/controller/integration_upload_test.rb | 2 - actionpack/test/controller/rack_test.rb | 1 - actionpack/test/controller/request_test.rb | 1 - actionpack/test/controller/routing_test.rb | 1 - .../test/controller/session/cookie_store_test.rb | 3 - .../controller/session/mem_cache_store_test.rb | 3 - 23 files changed, 73 insertions(+), 96 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ff8ec0d2fc..5f87429aca 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -32,47 +32,63 @@ rescue LoadError end $:.unshift "#{File.dirname(__FILE__)}/action_controller/vendor/html-scanner" -require 'action_controller/vendor/rack' -require 'action_controller/base' -require 'action_controller/request' -require 'action_controller/rescue' -require 'action_controller/benchmarking' -require 'action_controller/flash' -require 'action_controller/filters' -require 'action_controller/layout' -require 'action_controller/mime_responds' -require 'action_controller/helpers' -require 'action_controller/cookies' -require 'action_controller/cgi_process' -require 'action_controller/caching' -require 'action_controller/verification' -require 'action_controller/streaming' -require 'action_controller/session_management' -require 'action_controller/http_authentication' -require 'action_controller/rack_process' -require 'action_controller/record_identifier' -require 'action_controller/request_forgery_protection' -require 'action_controller/headers' -require 'action_controller/translation' +module ActionController + # TODO: Review explicit to see if they will automatically be handled by + # the initilizer if they are really needed. + def self.load_all! + [Base, CgiRequest, CgiResponse, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter] + end -require 'action_view' + autoload :AbstractRequest, 'action_controller/request' + autoload :AbstractResponse, 'action_controller/response' + autoload :Base, 'action_controller/base' + autoload :Benchmarking, 'action_controller/benchmarking' + autoload :Caching, 'action_controller/caching' + autoload :CgiRequest, 'action_controller/cgi_process' + autoload :CgiResponse, 'action_controller/cgi_process' + autoload :Cookies, 'action_controller/cookies' + autoload :Dispatcher, 'action_controller/dispatcher' + autoload :Filters, 'action_controller/filters' + autoload :Flash, 'action_controller/flash' + autoload :Helpers, 'action_controller/helpers' + autoload :HttpAuthentication, 'action_controller/http_authentication' + autoload :IntegrationTest, 'action_controller/integration' + autoload :Layout, 'action_controller/layout' + autoload :MimeResponds, 'action_controller/mime_responds' + autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' + autoload :RackRequest, 'action_controller/rack_process' + autoload :RackResponse, 'action_controller/rack_process' + autoload :RecordIdentifier, 'action_controller/record_identifier' + autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' + autoload :Rescue, 'action_controller/rescue' + autoload :Resources, 'action_controller/resources' + autoload :Routing, 'action_controller/routing' + autoload :SessionManagement, 'action_controller/session_management' + autoload :StatusCodes, 'action_controller/status_codes' + autoload :Streaming, 'action_controller/streaming' + autoload :TestCase, 'action_controller/test_case' + autoload :TestProcess, 'action_controller/test_process' + autoload :Translation, 'action_controller/translation' + autoload :UrlRewriter, 'action_controller/url_rewriter' + autoload :UrlWriter, 'action_controller/url_rewriter' + autoload :Verification, 'action_controller/verification' -ActionController::Base.class_eval do - include ActionController::Flash - include ActionController::Filters - include ActionController::Layout - include ActionController::Benchmarking - include ActionController::Rescue - include ActionController::MimeResponds - include ActionController::Helpers - include ActionController::Cookies - include ActionController::Caching - include ActionController::Verification - include ActionController::Streaming - include ActionController::SessionManagement - include ActionController::HttpAuthentication::Basic::ControllerMethods - include ActionController::RecordIdentifier - include ActionController::RequestForgeryProtection - include ActionController::Translation + module Http + autoload :Headers, 'action_controller/headers' + end end + +class CGI + class Session + autoload :ActiveRecordStore, 'action_controller/session/active_record_store' + autoload :CookieStore, 'action_controller/session/cookie_store' + autoload :DRbStore, 'action_controller/session/drb_store' + autoload :MemCacheStore, 'action_controller/session/mem_cache_store' + end +end + +autoload :Mime, 'action_controller/mime_type' +autoload :Rack, 'action_controller/vendor/rack' + +ActionController.load_all! diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index ad6562024a..a42c1c38b9 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,10 +1,3 @@ -require 'action_controller/mime_type' -require 'action_controller/request' -require 'action_controller/response' -require 'action_controller/routing' -require 'action_controller/resources' -require 'action_controller/url_rewriter' -require 'action_controller/status_codes' require 'action_view' require 'drb' require 'set' @@ -1332,4 +1325,11 @@ module ActionController #:nodoc: close_session end end + + Base.class_eval do + include Flash, Filters, Layout, Benchmarking, Rescue, MimeResponds, Helpers + include Cookies, Caching, Verification, Streaming + include SessionManagement, HttpAuthentication::Basic::ControllerMethods + include RecordIdentifier, RequestForgeryProtection, Translation + end end diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index c4063dfb4b..b4d251eb3c 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -2,13 +2,6 @@ require 'fileutils' require 'uri' require 'set' -require 'action_controller/caching/pages' -require 'action_controller/caching/actions' -require 'action_controller/caching/sql_cache' -require 'action_controller/caching/sweeping' -require 'action_controller/caching/fragments' - - module ActionController #:nodoc: # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. @@ -31,6 +24,12 @@ module ActionController #:nodoc: # ActionController::Base.cache_store = :mem_cache_store, "localhost" # ActionController::Base.cache_store = MyOwnStore.new("parameter") module Caching + autoload :Actions, 'action_controller/caching/actions' + autoload :Fragments, 'action_controller/caching/fragments' + autoload :Pages, 'action_controller/caching/pages' + autoload :SqlCache, 'action_controller/caching/sql_cache' + autoload :Sweeping, 'action_controller/caching/sweeping' + def self.included(base) #:nodoc: base.class_eval do @@cache_store = nil @@ -63,10 +62,9 @@ module ActionController #:nodoc: end end - - private + private def cache_configured? self.class.cache_configured? end end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb index fabacd9b83..45b51a7488 100644 --- a/actionpack/lib/action_controller/cgi_process.rb +++ b/actionpack/lib/action_controller/cgi_process.rb @@ -1,5 +1,4 @@ require 'action_controller/cgi_ext' -require 'action_controller/session/cookie_store' module ActionController #:nodoc: class Base diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index b3771c1ebc..333fb742e4 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -1,7 +1,3 @@ -require 'action_controller/test_case' -require 'action_controller/dispatcher' -require 'action_controller/test_process' - require 'stringio' require 'uri' diff --git a/actionpack/lib/action_controller/performance_test.rb b/actionpack/lib/action_controller/performance_test.rb index 85543fffae..d88180087d 100644 --- a/actionpack/lib/action_controller/performance_test.rb +++ b/actionpack/lib/action_controller/performance_test.rb @@ -1,4 +1,3 @@ -require 'action_controller/integration' require 'active_support/testing/performance' require 'active_support/testing/default' diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index e8ea3704a8..58d7b53e31 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -1,5 +1,4 @@ require 'action_controller/cgi_ext' -require 'action_controller/session/cookie_store' module ActionController #:nodoc: class RackRequest < AbstractRequest #:nodoc: diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb index a6471d0c08..70bb77e7ac 100644 --- a/actionpack/lib/action_controller/request_profiler.rb +++ b/actionpack/lib/action_controller/request_profiler.rb @@ -1,5 +1,4 @@ require 'optparse' -require 'action_controller/integration' module ActionController class RequestProfiler diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 7700b9d4d0..b5ea764911 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -669,7 +669,3 @@ module ActionController end end end - -class ActionController::Routing::RouteSet::Mapper - include ActionController::Resources -end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index 8d51e823a6..efd474097e 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -1,6 +1,5 @@ require 'cgi' require 'uri' -require 'action_controller/polymorphic_routes' require 'action_controller/routing/optimisations' require 'action_controller/routing/routing_ext' require 'action_controller/routing/route' diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 1b8b52ad10..3bb25dbba9 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -7,6 +7,8 @@ module ActionController # Mapper instances have relatively few instance methods, in order to avoid # clashes with named routes. class Mapper #:doc: + include ActionController::Resources + def initialize(set) #:nodoc: @set = set end diff --git a/actionpack/lib/action_controller/session_management.rb b/actionpack/lib/action_controller/session_management.rb index fd3d94ed97..60a9aec39c 100644 --- a/actionpack/lib/action_controller/session_management.rb +++ b/actionpack/lib/action_controller/session_management.rb @@ -1,10 +1,3 @@ -require 'action_controller/session/cookie_store' -require 'action_controller/session/drb_store' -require 'action_controller/session/mem_cache_store' -if Object.const_defined?(:ActiveRecord) - require 'action_controller/session/active_record_store' -end - module ActionController #:nodoc: module SessionManagement #:nodoc: def self.included(base) diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index cdea1def92..4ab4ed233f 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,5 +1,3 @@ -require 'action_controller/test_case' - module ActionView class TestCase < ActiveSupport::TestCase include ActionController::TestCase::Assertions diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index fd7da89aa7..677d434f9c 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -1,7 +1,6 @@ # These tests exercise CGI::Session::ActiveRecordStore, so you're going to # need AR in a sibling directory to AP and have SQLite installed. require 'active_record_unit' -require 'action_controller/session/active_record_store' module CommonActiveRecordStoreTests def test_basics diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb index 87fbf1a4cd..ac1c8abc59 100644 --- a/actionpack/test/controller/cgi_test.rb +++ b/actionpack/test/controller/cgi_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'action_controller/cgi_process' class BaseCgiTest < Test::Unit::TestCase def setup diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index 3ee78a6156..61bfb2b6e9 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -2,8 +2,6 @@ require 'abstract_unit' uses_mocha 'dispatcher tests' do -require 'action_controller/dispatcher' - class DispatcherTest < Test::Unit::TestCase Dispatcher = ActionController::Dispatcher diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 7e4c3e171a..b39d35930d 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1,6 +1,4 @@ require 'abstract_unit' -require 'action_controller/integration' -require 'action_controller/routing' uses_mocha 'integration' do diff --git a/actionpack/test/controller/integration_upload_test.rb b/actionpack/test/controller/integration_upload_test.rb index 4af9b7e697..b1dd6a6341 100644 --- a/actionpack/test/controller/integration_upload_test.rb +++ b/actionpack/test/controller/integration_upload_test.rb @@ -1,6 +1,4 @@ require 'abstract_unit' -require 'action_controller/integration' -require 'action_controller/routing' unless defined? ApplicationController class ApplicationController < ActionController::Base diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb index d5e56b9584..7e8b0f9bf2 100644 --- a/actionpack/test/controller/rack_test.rb +++ b/actionpack/test/controller/rack_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'action_controller/rack_process' class BaseRackTest < Test::Unit::TestCase def setup diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index 7e264289e6..316a203e97 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'action_controller/integration' class RequestTest < ActiveSupport::TestCase def setup diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index b8a143cfd9..d62c7a1743 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'controller/fake_controllers' -require 'action_controller/routing' class MilestonesController < ActionController::Base def index() head :ok end diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 52c1f7559c..b5f14acc1f 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -1,7 +1,4 @@ require 'abstract_unit' -require 'action_controller/cgi_process' -require 'action_controller/cgi_ext' - require 'stringio' diff --git a/actionpack/test/controller/session/mem_cache_store_test.rb b/actionpack/test/controller/session/mem_cache_store_test.rb index a7d48431f8..9ab927a01f 100644 --- a/actionpack/test/controller/session/mem_cache_store_test.rb +++ b/actionpack/test/controller/session/mem_cache_store_test.rb @@ -1,7 +1,4 @@ require 'abstract_unit' -require 'action_controller/cgi_process' -require 'action_controller/cgi_ext' - class CGI::Session def cache -- cgit v1.2.3 From e931012287df0bca83cae04d95c2e0835ae08758 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 14:48:36 -0800 Subject: Require Mocha >= 0.9.3 which includes a MiniTest adapter --- actionpack/test/abstract_unit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 76812b94df..bee598e1ef 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -8,7 +8,7 @@ require 'yaml' require 'stringio' require 'test/unit' -gem 'mocha', '>= 0.9.0' +gem 'mocha', '>= 0.9.3' require 'mocha' begin -- cgit v1.2.3 From 95072a6846a1898bddfd983c6f951ac0de5c511d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 16:12:41 -0800 Subject: Fix dangling tzinfo dependency --- actionpack/test/template/form_options_helper_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index a33eb85b66..86a0bb6a79 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'tzinfo' TZInfo::Timezone.cattr_reader :loaded_zones @@ -682,4 +683,4 @@ uses_mocha "FormOptionsHelperTest" do end end -end \ No newline at end of file +end -- cgit v1.2.3 From fb4bb93d439f32421c8836261dce0c7de1addf82 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 18:29:38 -0800 Subject: Drop unneeded drb require --- actionpack/lib/action_controller/base.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index a42c1c38b9..041ff62a74 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,5 +1,4 @@ require 'action_view' -require 'drb' require 'set' module ActionController #:nodoc: -- cgit v1.2.3 From 2dd0ec48a5068a095e362fad2a77d63b86fdfd95 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 23 Nov 2008 19:12:00 -0800 Subject: Autoload HTML::Document and sanitizers --- .../lib/action_controller/assertions/response_assertions.rb | 3 --- .../lib/action_controller/assertions/selector_assertions.rb | 3 +-- actionpack/lib/action_controller/assertions/tag_assertions.rb | 5 +---- actionpack/lib/action_controller/vendor/html-scanner.rb | 9 +++++++++ actionpack/lib/action_view/helpers/sanitize_helper.rb | 11 +---------- actionpack/lib/action_view/helpers/text_helper.rb | 10 ---------- 6 files changed, 12 insertions(+), 29 deletions(-) create mode 100644 actionpack/lib/action_controller/vendor/html-scanner.rb (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb index e2e8bbdc71..7ab24389b8 100644 --- a/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -1,6 +1,3 @@ -require 'rexml/document' -require 'html/document' - module ActionController module Assertions # A small suite of assertions that test responses from Rails applications. diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index bcbb570e4b..40ac572fdb 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -3,8 +3,7 @@ # Under MIT and/or CC By license. #++ -require 'rexml/document' -require 'html/document' +require 'action_controller/vendor/html-scanner' module ActionController module Assertions diff --git a/actionpack/lib/action_controller/assertions/tag_assertions.rb b/actionpack/lib/action_controller/assertions/tag_assertions.rb index 90ba3668fb..80249e0e83 100644 --- a/actionpack/lib/action_controller/assertions/tag_assertions.rb +++ b/actionpack/lib/action_controller/assertions/tag_assertions.rb @@ -1,6 +1,3 @@ -require 'rexml/document' -require 'html/document' - module ActionController module Assertions # Pair of assertions to testing elements in the HTML output of the response. @@ -127,4 +124,4 @@ module ActionController end end end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb new file mode 100644 index 0000000000..5c6db99858 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/html-scanner.rb @@ -0,0 +1,9 @@ +$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner" + +module HTML + autoload :Document, 'html/document' + autoload :Sanitizer, 'html/sanitizer' + autoload :FullSanitizer, 'html/sanitizer' + autoload :LinkSanitizer, 'html/sanitizer' + autoload :WhiteListSanitizer, 'html/sanitizer' +end diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 435ba936e1..200c1d6085 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,14 +1,5 @@ require 'action_view/helpers/tag_helper' - -begin - require 'html/document' -rescue LoadError - html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner" - if File.directory?(html_scanner_path) - $:.unshift html_scanner_path - require 'html/document' - end -end +require 'action_controller/vendor/html-scanner' module ActionView module Helpers #:nodoc: diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 510c1a6a76..506138a735 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -1,15 +1,5 @@ require 'action_view/helpers/tag_helper' -begin - require 'html/document' -rescue LoadError - html_scanner_path = "#{File.dirname(__FILE__)}/../../action_controller/vendor/html-scanner" - if File.directory?(html_scanner_path) - $:.unshift html_scanner_path - require 'html/document' - end -end - module ActionView module Helpers #:nodoc: # The TextHelper module provides a set of methods for filtering, formatting -- cgit v1.2.3 From 5ffd1e0c02e605158efc08f3cbb6aebb79978553 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 09:58:52 -0600 Subject: Ensure integration test is load in script/console [#1452 state:resolved] --- actionpack/lib/action_controller.rb | 10 ++++++++++ actionpack/lib/action_controller/test_case.rb | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 5f87429aca..678938af86 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -53,6 +53,7 @@ module ActionController autoload :Flash, 'action_controller/flash' autoload :Helpers, 'action_controller/helpers' autoload :HttpAuthentication, 'action_controller/http_authentication' + autoload :Integration, 'action_controller/integration' autoload :IntegrationTest, 'action_controller/integration' autoload :Layout, 'action_controller/layout' autoload :MimeResponds, 'action_controller/mime_responds' @@ -74,6 +75,15 @@ module ActionController autoload :UrlWriter, 'action_controller/url_rewriter' autoload :Verification, 'action_controller/verification' + module Assertions + autoload :DomAssertions, 'action_controller/assertions/dom_assertions' + autoload :ModelAssertions, 'action_controller/assertions/model_assertions' + autoload :ResponseAssertions, 'action_controller/assertions/response_assertions' + autoload :RoutingAssertions, 'action_controller/assertions/routing_assertions' + autoload :SelectorAssertions, 'action_controller/assertions/selector_assertions' + autoload :TagAssertions, 'action_controller/assertions/tag_assertions' + end + module Http autoload :Headers, 'action_controller/headers' end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b925230118..79a8e1364d 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -107,7 +107,6 @@ module ActionController class TestCase < ActiveSupport::TestCase module Assertions %w(response selector tag dom routing model).each do |kind| - require "action_controller/assertions/#{kind}_assertions" include ActionController::Assertions.const_get("#{kind.camelize}Assertions") end -- cgit v1.2.3 From f0f07c6427c7c80783ee59705f82dcdd1cd8fdb1 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 10:05:15 -0600 Subject: prefer autoloading Mime::Type --- actionpack/lib/action_view/template.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 12808581a3..ca82454c0b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,5 +1,3 @@ -require 'action_controller/mime_type' - module ActionView #:nodoc: class Template extend TemplateHandlers -- cgit v1.2.3 From 426a86ab1e4fc2488215a9adab4511a59646a413 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 10:20:41 -0600 Subject: prefer autoloaded html scanner --- actionpack/lib/action_controller.rb | 4 ++-- actionpack/lib/action_controller/assertions/selector_assertions.rb | 2 -- actionpack/lib/action_view/helpers/sanitize_helper.rb | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 678938af86..8b4819bf65 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -31,8 +31,6 @@ rescue LoadError end end -$:.unshift "#{File.dirname(__FILE__)}/action_controller/vendor/html-scanner" - module ActionController # TODO: Review explicit to see if they will automatically be handled by # the initilizer if they are really needed. @@ -99,6 +97,8 @@ class CGI end autoload :Mime, 'action_controller/mime_type' + +autoload :HTML, 'action_controller/vendor/html-scanner' autoload :Rack, 'action_controller/vendor/rack' ActionController.load_all! diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index 40ac572fdb..e03fed7abb 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -3,8 +3,6 @@ # Under MIT and/or CC By license. #++ -require 'action_controller/vendor/html-scanner' - module ActionController module Assertions unless const_defined?(:NO_STRIP) diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 200c1d6085..d89b955317 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,4 @@ require 'action_view/helpers/tag_helper' -require 'action_controller/vendor/html-scanner' module ActionView module Helpers #:nodoc: -- cgit v1.2.3 From 703fecb4fc81c3a975a53c9c4534f40193bd1ab9 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 11:37:57 -0600 Subject: Add LAZY env flag for testing autoload/lazy load feature --- actionpack/lib/action_controller.rb | 2 +- actionpack/lib/action_view.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 8b4819bf65..08e6f4efa8 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -101,4 +101,4 @@ autoload :Mime, 'action_controller/mime_type' autoload :HTML, 'action_controller/vendor/html-scanner' autoload :Rack, 'action_controller/vendor/rack' -ActionController.load_all! +ActionController.load_all! unless ENV['LAZY'] diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 0c76204060..436bce4a69 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -56,4 +56,4 @@ end I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" -ActionView.load_all! +ActionView.load_all! unless ENV['LAZY'] -- cgit v1.2.3 From a76351093c014029a8c9ec7c9c341dde11c57f4d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 12:14:28 -0600 Subject: helpers require dependencies --- actionpack/lib/action_controller/helpers.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index 6064931eb8..402750c57d 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -1,3 +1,5 @@ +require 'active_support/dependencies' + # FIXME: helper { ... } is broken on Ruby 1.9 module ActionController #:nodoc: module Helpers #:nodoc: -- cgit v1.2.3 From 8aeed003f562fe9199614d013163c89b5182cd33 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 24 Nov 2008 12:14:45 -0600 Subject: prototype and scripty helpers require json --- actionpack/lib/action_view/helpers/prototype_helper.rb | 1 + actionpack/lib/action_view/helpers/scriptaculous_helper.rb | 1 + 2 files changed, 2 insertions(+) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index a3eccc741d..7fab3102e7 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -1,4 +1,5 @@ require 'set' +require 'active_support/json' module ActionView module Helpers diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb index 1d01dafd0e..e16935ea87 100644 --- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb +++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb @@ -1,4 +1,5 @@ require 'action_view/helpers/javascript_helper' +require 'active_support/json' module ActionView module Helpers -- cgit v1.2.3 From eac16d0ee1f9a46e686503196e3920e2113ccc0a Mon Sep 17 00:00:00 2001 From: Geoff Garside Date: Tue, 18 Nov 2008 15:14:55 +0000 Subject: Reorder the way in which map.resource routes are added to the set. This prevents the singular named route from hitting :create instead of :show. Signed-off-by: Michael Koziarski --- actionpack/lib/action_controller/resources.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index b5ea764911..c170528af1 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -535,9 +535,9 @@ module ActionController with_options :controller => resource.controller do |map| map_collection_actions(map, resource) - map_default_singleton_actions(map, resource) map_new_actions(map, resource) map_member_actions(map, resource) + map_default_singleton_actions(map, resource) map_associations(resource, options) -- cgit v1.2.3 From 61becfe2b99f1d026c300fbe16ac853b2147b3cf Mon Sep 17 00:00:00 2001 From: Geoff Garside Date: Mon, 24 Nov 2008 12:23:27 +0000 Subject: Test default singleton resource route to ensure it uses GET. This is important if using map.root :resource instead of map.root :resources for some reason. Signed-off-by: Michael Koziarski --- actionpack/test/controller/resources_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'actionpack') diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 541e8270c9..79b28c0773 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -997,6 +997,16 @@ class ResourcesTest < ActionController::TestCase end end + def test_default_singleton_restful_route_uses_get + with_routing do |set| + set.draw do |map| + map.resource :product + end + + assert_equal :get, set.named_routes.routes[:product].conditions[:method] + end + end + protected def with_restful_routing(*args) with_routing do |set| -- cgit v1.2.3 From 565fad350ebef6d42c567fb9ba57477f76dad1a9 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 24 Nov 2008 12:05:17 -0800 Subject: Ruby 1.9 compat: explicitly require delegate for cookie's DelegateClass --- actionpack/lib/action_controller/cgi_ext/cookie.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/cgi_ext/cookie.rb b/actionpack/lib/action_controller/cgi_ext/cookie.rb index 009ddd1c64..9cd19bb12d 100644 --- a/actionpack/lib/action_controller/cgi_ext/cookie.rb +++ b/actionpack/lib/action_controller/cgi_ext/cookie.rb @@ -1,3 +1,5 @@ +require 'delegate' + CGI.module_eval { remove_const "Cookie" } # TODO: document how this differs from stdlib CGI::Cookie -- cgit v1.2.3 From 104f3a57768602289299b3be0fab5b1ed21d7653 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 24 Nov 2008 18:43:04 -0800 Subject: Add config.preload_frameworks to load all frameworks at startup. Default to false so Rails autoloads itself as it's used. --- actionpack/lib/action_controller.rb | 2 -- actionpack/lib/action_view.rb | 2 -- 2 files changed, 4 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 08e6f4efa8..62d75a47ec 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -100,5 +100,3 @@ autoload :Mime, 'action_controller/mime_type' autoload :HTML, 'action_controller/vendor/html-scanner' autoload :Rack, 'action_controller/vendor/rack' - -ActionController.load_all! unless ENV['LAZY'] diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 436bce4a69..210a5f1a93 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -55,5 +55,3 @@ class ERB end I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" - -ActionView.load_all! unless ENV['LAZY'] -- cgit v1.2.3 From f8558798d404f3373517a85fe9e3e8d519c8f3d9 Mon Sep 17 00:00:00 2001 From: Craig Davey Date: Tue, 25 Nov 2008 10:05:59 -0600 Subject: Ensure all HTML:: constants are available to autoload [#1462 state:resolved] Signed-off-by: Joshua Peek --- actionpack/lib/action_controller/vendor/html-scanner.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb index 5c6db99858..f622d195ee 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner.rb @@ -1,9 +1,16 @@ $LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner" module HTML + autoload :CDATA, 'html/node' autoload :Document, 'html/document' - autoload :Sanitizer, 'html/sanitizer' autoload :FullSanitizer, 'html/sanitizer' autoload :LinkSanitizer, 'html/sanitizer' + autoload :Node, 'html/node' + autoload :Sanitizer, 'html/sanitizer' + autoload :Selector, 'html/selector' + autoload :Tag, 'html/node' + autoload :Text, 'html/node' + autoload :Tokenizer, 'html/tokenizer' + autoload :Version, 'html/version' autoload :WhiteListSanitizer, 'html/sanitizer' end -- cgit v1.2.3 From 759183c822240ee0a550f1f5a556ffc314b68099 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 25 Nov 2008 10:38:20 -0600 Subject: Ensure ActionView will be available to ActionMailer if ActionController is not loaded --- actionpack/lib/action_controller.rb | 2 ++ actionpack/lib/action_controller/base.rb | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 62d75a47ec..a7a7d932c4 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -100,3 +100,5 @@ autoload :Mime, 'action_controller/mime_type' autoload :HTML, 'action_controller/vendor/html-scanner' autoload :Rack, 'action_controller/vendor/rack' + +require 'action_view' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 041ff62a74..7e38f95076 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,4 +1,3 @@ -require 'action_view' require 'set' module ActionController #:nodoc: -- cgit v1.2.3 From d4754677a34d34d4a0955a04f2cc6571bdc5e82d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 25 Nov 2008 12:32:14 -0600 Subject: Deprecate assert_valid --- actionpack/lib/action_controller/assertions/model_assertions.rb | 1 + actionpack/test/controller/action_pack_assertions_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/assertions/model_assertions.rb b/actionpack/lib/action_controller/assertions/model_assertions.rb index d25214bb66..3a7b39b106 100644 --- a/actionpack/lib/action_controller/assertions/model_assertions.rb +++ b/actionpack/lib/action_controller/assertions/model_assertions.rb @@ -11,6 +11,7 @@ module ActionController # assert_valid(model) # def assert_valid(record) + ::ActiveSupport::Deprecation.warn("assert_valid is deprecated. Use assert record.valid? instead", caller) clean_backtrace do assert record.valid?, record.errors.full_messages.join("\n") end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 6cad0e2827..ea56048f37 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -461,14 +461,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_valid get :get_valid_record - assert_valid assigns('record') + assert_deprecated { assert_valid assigns('record') } end def test_assert_valid_failing get :get_invalid_record begin - assert_valid assigns('record') + assert_deprecated { assert_valid assigns('record') } assert false rescue ActiveSupport::TestCase::Assertion => e end -- cgit v1.2.3 From 3dd3ffde06931d47e3052260efba26b1cc5bd7c9 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 25 Nov 2008 13:20:12 -0600 Subject: Depend on rack 0.4.0 instead of vendoring it --- actionpack/Rakefile | 1 + actionpack/lib/action_controller.rb | 4 +- .../action_controller/vendor/rack-0.4.0/rack.rb | 81 ---- .../vendor/rack-0.4.0/rack/adapter/camping.rb | 22 -- .../rack-0.4.0/rack/auth/abstract/handler.rb | 28 -- .../rack-0.4.0/rack/auth/abstract/request.rb | 37 -- .../vendor/rack-0.4.0/rack/auth/basic.rb | 58 --- .../vendor/rack-0.4.0/rack/auth/digest/md5.rb | 124 ------ .../vendor/rack-0.4.0/rack/auth/digest/nonce.rb | 51 --- .../vendor/rack-0.4.0/rack/auth/digest/params.rb | 55 --- .../vendor/rack-0.4.0/rack/auth/digest/request.rb | 40 -- .../vendor/rack-0.4.0/rack/auth/openid.rb | 437 --------------------- .../vendor/rack-0.4.0/rack/builder.rb | 56 --- .../vendor/rack-0.4.0/rack/cascade.rb | 36 -- .../vendor/rack-0.4.0/rack/commonlogger.rb | 61 --- .../vendor/rack-0.4.0/rack/deflater.rb | 63 --- .../vendor/rack-0.4.0/rack/directory.rb | 158 -------- .../vendor/rack-0.4.0/rack/file.rb | 116 ------ .../vendor/rack-0.4.0/rack/handler.rb | 44 --- .../vendor/rack-0.4.0/rack/handler/cgi.rb | 57 --- .../rack-0.4.0/rack/handler/evented_mongrel.rb | 8 - .../vendor/rack-0.4.0/rack/handler/fastcgi.rb | 84 ---- .../vendor/rack-0.4.0/rack/handler/lsws.rb | 52 --- .../vendor/rack-0.4.0/rack/handler/mongrel.rb | 78 ---- .../vendor/rack-0.4.0/rack/handler/scgi.rb | 57 --- .../vendor/rack-0.4.0/rack/handler/webrick.rb | 57 --- .../vendor/rack-0.4.0/rack/lint.rb | 401 ------------------- .../vendor/rack-0.4.0/rack/lobster.rb | 65 --- .../vendor/rack-0.4.0/rack/mock.rb | 160 -------- .../vendor/rack-0.4.0/rack/recursive.rb | 57 --- .../vendor/rack-0.4.0/rack/reloader.rb | 64 --- .../vendor/rack-0.4.0/rack/request.rb | 209 ---------- .../vendor/rack-0.4.0/rack/response.rb | 166 -------- .../vendor/rack-0.4.0/rack/session/abstract/id.rb | 140 ------- .../vendor/rack-0.4.0/rack/session/cookie.rb | 71 ---- .../vendor/rack-0.4.0/rack/session/memcache.rb | 97 ----- .../vendor/rack-0.4.0/rack/session/pool.rb | 73 ---- .../vendor/rack-0.4.0/rack/showexceptions.rb | 344 ---------------- .../vendor/rack-0.4.0/rack/showstatus.rb | 105 ----- .../vendor/rack-0.4.0/rack/static.rb | 38 -- .../vendor/rack-0.4.0/rack/urlmap.rb | 48 --- .../vendor/rack-0.4.0/rack/utils.rb | 318 --------------- actionpack/lib/action_controller/vendor/rack.rb | 9 - 43 files changed, 4 insertions(+), 4226 deletions(-) delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/adapter/camping.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/handler.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/request.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/basic.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/md5.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/nonce.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/params.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/request.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/openid.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/builder.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/cascade.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/commonlogger.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/deflater.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/directory.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/file.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/cgi.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/evented_mongrel.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/fastcgi.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/lsws.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/mongrel.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/scgi.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/webrick.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lint.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lobster.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/mock.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/recursive.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/reloader.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/request.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/response.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showexceptions.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb delete mode 100644 actionpack/lib/action_controller/vendor/rack.rb (limited to 'actionpack') diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 74c04c7740..1a1b908122 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -81,6 +81,7 @@ spec = Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD) + s.add_dependency('rack', '= 0.4.0') s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index a7a7d932c4..da5f1e81e6 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -31,6 +31,9 @@ rescue LoadError end end +gem 'rack', '~> 0.4.0' +require 'rack' + module ActionController # TODO: Review explicit to see if they will automatically be handled by # the initilizer if they are really needed. @@ -99,6 +102,5 @@ end autoload :Mime, 'action_controller/mime_type' autoload :HTML, 'action_controller/vendor/html-scanner' -autoload :Rack, 'action_controller/vendor/rack' require 'action_view' diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack.rb deleted file mode 100644 index 62a1b35880..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack.rb +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (C) 2007, 2008 Christian Neukirchen -# -# Rack is freely distributable under the terms of an MIT-style license. -# See COPYING or http://www.opensource.org/licenses/mit-license.php. - -$: << File.expand_path(File.dirname(__FILE__)) - - -# The Rack main module, serving as a namespace for all core Rack -# modules and classes. -# -# All modules meant for use in your application are autoloaded here, -# so it should be enough just to require rack.rb in your code. - -module Rack - # The Rack protocol version number implemented. - VERSION = [0,1] - - # Return the Rack protocol version as a dotted string. - def self.version - VERSION.join(".") - end - - # Return the Rack release as a dotted string. - def self.release - "0.4" - end - - autoload :Builder, "rack/builder" - autoload :Cascade, "rack/cascade" - autoload :CommonLogger, "rack/commonlogger" - autoload :File, "rack/file" - autoload :Deflater, "rack/deflater" - autoload :Directory, "rack/directory" - autoload :ForwardRequest, "rack/recursive" - autoload :Handler, "rack/handler" - autoload :Lint, "rack/lint" - autoload :Recursive, "rack/recursive" - autoload :Reloader, "rack/reloader" - autoload :ShowExceptions, "rack/showexceptions" - autoload :ShowStatus, "rack/showstatus" - autoload :Static, "rack/static" - autoload :URLMap, "rack/urlmap" - autoload :Utils, "rack/utils" - - autoload :MockRequest, "rack/mock" - autoload :MockResponse, "rack/mock" - - autoload :Request, "rack/request" - autoload :Response, "rack/response" - - module Auth - autoload :Basic, "rack/auth/basic" - autoload :AbstractRequest, "rack/auth/abstract/request" - autoload :AbstractHandler, "rack/auth/abstract/handler" - autoload :OpenID, "rack/auth/openid" - module Digest - autoload :MD5, "rack/auth/digest/md5" - autoload :Nonce, "rack/auth/digest/nonce" - autoload :Params, "rack/auth/digest/params" - autoload :Request, "rack/auth/digest/request" - end - end - - module Session - autoload :Cookie, "rack/session/cookie" - autoload :Pool, "rack/session/pool" - autoload :Memcache, "rack/session/memcache" - end - - # *Adapters* connect Rack with third party web frameworks. - # - # Rack includes an adapter for Camping, see README for other - # frameworks supporting Rack in their code bases. - # - # Refer to the submodules for framework-specific calling details. - - module Adapter - autoload :Camping, "rack/adapter/camping" - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/adapter/camping.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/adapter/camping.rb deleted file mode 100644 index b910a13267..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/adapter/camping.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Rack - module Adapter - class Camping - def initialize(app) - @app = app - end - - def call(env) - env["PATH_INFO"] ||= "" - env["SCRIPT_NAME"] ||= "" - controller = @app.run(env['rack.input'], env) - h = controller.headers - h.each_pair do |k,v| - if v.kind_of? URI - h[k] = v.to_s - end - end - [controller.status, controller.headers, controller.body] - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/handler.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/handler.rb deleted file mode 100644 index b213eac6f4..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/handler.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Rack - module Auth - # Rack::Auth::AbstractHandler implements common authentication functionality. - # - # +realm+ should be set for all handlers. - - class AbstractHandler - - attr_accessor :realm - - def initialize(app, &authenticator) - @app, @authenticator = app, authenticator - end - - - private - - def unauthorized(www_authenticate = challenge) - return [ 401, { 'WWW-Authenticate' => www_authenticate.to_s }, [] ] - end - - def bad_request - [ 400, {}, [] ] - end - - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/request.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/request.rb deleted file mode 100644 index 1d9ccec685..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/abstract/request.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Rack - module Auth - class AbstractRequest - - def initialize(env) - @env = env - end - - def provided? - !authorization_key.nil? - end - - def parts - @parts ||= @env[authorization_key].split(' ', 2) - end - - def scheme - @scheme ||= parts.first.downcase.to_sym - end - - def params - @params ||= parts.last - end - - - private - - AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] - - def authorization_key - @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } - end - - end - - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/basic.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/basic.rb deleted file mode 100644 index 9557224648..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/basic.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'rack/auth/abstract/handler' -require 'rack/auth/abstract/request' - -module Rack - module Auth - # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. - # - # Initialize with the Rack application that you want protecting, - # and a block that checks if a username and password pair are valid. - # - # See also: example/protectedlobster.rb - - class Basic < AbstractHandler - - def call(env) - auth = Basic::Request.new(env) - - return unauthorized unless auth.provided? - - return bad_request unless auth.basic? - - if valid?(auth) - env['REMOTE_USER'] = auth.username - - return @app.call(env) - end - - unauthorized - end - - - private - - def challenge - 'Basic realm="%s"' % realm - end - - def valid?(auth) - @authenticator.call(*auth.credentials) - end - - class Request < Auth::AbstractRequest - def basic? - :basic == scheme - end - - def credentials - @credentials ||= params.unpack("m*").first.split(/:/, 2) - end - - def username - credentials.first - end - end - - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/md5.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/md5.rb deleted file mode 100644 index 6d2bd29c2e..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/md5.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'rack/auth/abstract/handler' -require 'rack/auth/digest/request' -require 'rack/auth/digest/params' -require 'rack/auth/digest/nonce' -require 'digest/md5' - -module Rack - module Auth - module Digest - # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of - # HTTP Digest Authentication, as per RFC 2617. - # - # Initialize with the [Rack] application that you want protecting, - # and a block that looks up a plaintext password for a given username. - # - # +opaque+ needs to be set to a constant base64/hexadecimal string. - # - class MD5 < AbstractHandler - - attr_accessor :opaque - - attr_writer :passwords_hashed - - def initialize(app) - super - @passwords_hashed = nil - end - - def passwords_hashed? - !!@passwords_hashed - end - - def call(env) - auth = Request.new(env) - - unless auth.provided? - return unauthorized - end - - if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) - return bad_request - end - - if valid?(auth) - if auth.nonce.stale? - return unauthorized(challenge(:stale => true)) - else - env['REMOTE_USER'] = auth.username - - return @app.call(env) - end - end - - unauthorized - end - - - private - - QOP = 'auth'.freeze - - def params(hash = {}) - Params.new do |params| - params['realm'] = realm - params['nonce'] = Nonce.new.to_s - params['opaque'] = H(opaque) - params['qop'] = QOP - - hash.each { |k, v| params[k] = v } - end - end - - def challenge(hash = {}) - "Digest #{params(hash)}" - end - - def valid?(auth) - valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) - end - - def valid_qop?(auth) - QOP == auth.qop - end - - def valid_opaque?(auth) - H(opaque) == auth.opaque - end - - def valid_nonce?(auth) - auth.nonce.valid? - end - - def valid_digest?(auth) - digest(auth, @authenticator.call(auth.username)) == auth.response - end - - def md5(data) - ::Digest::MD5.hexdigest(data) - end - - alias :H :md5 - - def KD(secret, data) - H([secret, data] * ':') - end - - def A1(auth, password) - [ auth.username, auth.realm, password ] * ':' - end - - def A2(auth) - [ auth.method, auth.uri ] * ':' - end - - def digest(auth, password) - password_hash = passwords_hashed? ? password : H(A1(auth, password)) - - KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':') - end - - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/nonce.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/nonce.rb deleted file mode 100644 index dbe109f29a..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/nonce.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'digest/md5' - -module Rack - module Auth - module Digest - # Rack::Auth::Digest::Nonce is the default nonce generator for the - # Rack::Auth::Digest::MD5 authentication handler. - # - # +private_key+ needs to set to a constant string. - # - # +time_limit+ can be optionally set to an integer (number of seconds), - # to limit the validity of the generated nonces. - - class Nonce - - class << self - attr_accessor :private_key, :time_limit - end - - def self.parse(string) - new(*string.unpack("m*").first.split(' ', 2)) - end - - def initialize(timestamp = Time.now, given_digest = nil) - @timestamp, @given_digest = timestamp.to_i, given_digest - end - - def to_s - [([ @timestamp, digest ] * ' ')].pack("m*").strip - end - - def digest - ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':') - end - - def valid? - digest == @given_digest - end - - def stale? - !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit - end - - def fresh? - !stale? - end - - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/params.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/params.rb deleted file mode 100644 index 730e2efdc8..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/params.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Rack - module Auth - module Digest - class Params < Hash - - def self.parse(str) - split_header_value(str).inject(new) do |header, param| - k, v = param.split('=', 2) - header[k] = dequote(v) - header - end - end - - def self.dequote(str) # From WEBrick::HTTPUtils - ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup - ret.gsub!(/\\(.)/, "\\1") - ret - end - - def self.split_header_value(str) - str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] } - end - - def initialize - super - - yield self if block_given? - end - - def [](k) - super k.to_s - end - - def []=(k, v) - super k.to_s, v.to_s - end - - UNQUOTED = ['qop', 'nc', 'stale'] - - def to_s - inject([]) do |parts, (k, v)| - parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v)) - parts - end.join(', ') - end - - def quote(str) # From WEBrick::HTTPUtils - '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' - end - - end - end - end -end - diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/request.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/request.rb deleted file mode 100644 index a0227543fb..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/digest/request.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'rack/auth/abstract/request' -require 'rack/auth/digest/params' -require 'rack/auth/digest/nonce' - -module Rack - module Auth - module Digest - class Request < Auth::AbstractRequest - - def method - @env['REQUEST_METHOD'] - end - - def digest? - :digest == scheme - end - - def correct_uri? - @env['PATH_INFO'] == uri - end - - def nonce - @nonce ||= Nonce.parse(params['nonce']) - end - - def params - @params ||= Params.parse(parts.last) - end - - def method_missing(sym) - if params.has_key? key = sym.to_s - return params[key] - end - super - end - - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/openid.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/openid.rb deleted file mode 100644 index 2bd064ea4a..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/auth/openid.rb +++ /dev/null @@ -1,437 +0,0 @@ -# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net - -gem 'ruby-openid', '~> 2' if defined? Gem -require 'rack/auth/abstract/handler' #rack -require 'uri' #std -require 'pp' #std -require 'openid' #gem -require 'openid/extension' #gem -require 'openid/store/memory' #gem - -module Rack - module Auth - # Rack::Auth::OpenID provides a simple method for permitting - # openid based logins. It requires the ruby-openid library from - # janrain to operate, as well as a rack method of session management. - # - # The ruby-openid home page is at http://openidenabled.com/ruby-openid/. - # - # The OpenID specifications can be found at - # http://openid.net/specs/openid-authentication-1_1.html - # and - # http://openid.net/specs/openid-authentication-2_0.html. Documentation - # for published OpenID extensions and related topics can be found at - # http://openid.net/developers/specs/. - # - # It is recommended to read through the OpenID spec, as well as - # ruby-openid's documentation, to understand what exactly goes on. However - # a setup as simple as the presented examples is enough to provide - # functionality. - # - # This library strongly intends to utilize the OpenID 2.0 features of the - # ruby-openid library, while maintaining OpenID 1.0 compatiblity. - # - # All responses from this rack application will be 303 redirects unless an - # error occurs, with the exception of an authentication request requiring - # an HTML form submission. - # - # NOTE: Extensions are not currently supported by this implimentation of - # the OpenID rack application due to the complexity of the current - # ruby-openid extension handling. - # - # NOTE: Due to the amount of data that this library stores in the - # session, Rack::Session::Cookie may fault. - class OpenID < AbstractHandler - class NoSession < RuntimeError; end - # Required for ruby-openid - OIDStore = ::OpenID::Store::Memory.new - HTML = '%s%s' - - # A Hash of options is taken as it's single initializing - # argument. For example: - # - # simple_oid = OpenID.new('http://mysite.com/') - # - # return_oid = OpenID.new('http://mysite.com/', { - # :return_to => 'http://mysite.com/openid' - # }) - # - # page_oid = OpenID.new('http://mysite.com/', - # :login_good => 'http://mysite.com/auth_good' - # ) - # - # complex_oid = OpenID.new('http://mysite.com/', - # :return_to => 'http://mysite.com/openid', - # :login_good => 'http://mysite.com/user/preferences', - # :auth_fail => [500, {'Content-Type'=>'text/plain'}, - # 'Unable to negotiate with foreign server.'], - # :immediate => true, - # :extensions => { - # ::OpenID::SReg => [['email'],['nickname']] - # } - # ) - # - # = Arguments - # - # The first argument is the realm, identifying the site they are trusting - # with their identity. This is required. - # - # NOTE: In OpenID 1.x, the realm or trust_root is optional and the - # return_to url is required. As this library strives tward ruby-openid - # 2.0, and OpenID 2.0 compatibiliy, the realm is required and return_to - # is optional. However, this implimentation is still backwards compatible - # with OpenID 1.0 servers. - # - # The optional second argument is a hash of options. - # - # == Options - # - # :return_to defines the url to return to after the client - # authenticates with the openid service provider. This url should point - # to where Rack::Auth::OpenID is mounted. If :return_to is not - # provided, :return_to will be the current url including all query - # parameters. - # - # :session_key defines the key to the session hash in the env. - # It defaults to 'rack.session'. - # - # :openid_param defines at what key in the request parameters to - # find the identifier to resolve. As per the 2.0 spec, the default is - # 'openid_identifier'. - # - # :immediate as true will make immediate type of requests the - # default. See OpenID specification documentation. - # - # === URL options - # - # :login_good is the url to go to after the authentication - # process has completed. - # - # :login_fail is the url to go to after the authentication - # process has failed. - # - # :login_quit is the url to go to after the authentication - # process - # has been cancelled. - # - # === Response options - # - # :no_session should be a rack response to be returned if no or - # an incompatible session is found. - # - # :auth_fail should be a rack response to be returned if an - # OpenID::DiscoveryFailure occurs. This is typically due to being unable - # to access the identity url or identity server. - # - # :error should be a rack response to return if any other - # generic error would occur and options[:catch_errors] is true. - # - # === Extensions - # - # :extensions should be a hash of openid extension - # implementations. The key should be the extension main module, the value - # should be an array of arguments for extension::Request.new - # - # The hash is iterated over and passed to #add_extension for processing. - # Please see #add_extension for further documentation. - def initialize(realm, options={}) - @realm = realm - realm = URI(realm) - if realm.path.empty? - raise ArgumentError, "Invalid realm path: '#{realm.path}'" - elsif not realm.absolute? - raise ArgumentError, "Realm '#{@realm}' not absolute" - end - - [:return_to, :login_good, :login_fail, :login_quit].each do |key| - if options.key? key and luri = URI(options[key]) - if !luri.absolute? - raise ArgumentError, ":#{key} is not an absolute uri: '#{luri}'" - end - end - end - - if options[:return_to] and ruri = URI(options[:return_to]) - if ruri.path.empty? - raise ArgumentError, "Invalid return_to path: '#{ruri.path}'" - elsif realm.path != ruri.path[0, realm.path.size] - raise ArgumentError, 'return_to not within realm.' \ - end - end - - # TODO: extension support - if extensions = options.delete(:extensions) - extensions.each do |ext, args| - add_extension ext, *args - end - end - - @options = { - :session_key => 'rack.session', - :openid_param => 'openid_identifier', - #:return_to, :login_good, :login_fail, :login_quit - #:no_session, :auth_fail, :error - :store => OIDStore, - :immediate => false, - :anonymous => false, - :catch_errors => false - }.merge(options) - @extensions = {} - end - - attr_reader :options, :extensions - - # It sets up and uses session data at :openid within the - # session. It sets up the ::OpenID::Consumer using the store specified by - # options[:store]. - # - # If the parameter specified by options[:openid_param] is - # present, processing is passed to #check and the result is returned. - # - # If the parameter 'openid.mode' is set, implying a followup from the - # openid server, processing is passed to #finish and the result is - # returned. - # - # If neither of these conditions are met, a 400 error is returned. - # - # If an error is thrown and options[:catch_errors] is false, the - # exception will be reraised. Otherwise a 500 error is returned. - def call(env) - env['rack.auth.openid'] = self - session = env[@options[:session_key]] - unless session and session.is_a? Hash - raise(NoSession, 'No compatible session') - end - # let us work in our own namespace... - session = (session[:openid] ||= {}) - unless session and session.is_a? Hash - raise(NoSession, 'Incompatible session') - end - - request = Rack::Request.new env - consumer = ::OpenID::Consumer.new session, @options[:store] - - if request.params['openid.mode'] - finish consumer, session, request - elsif request.params[@options[:openid_param]] - check consumer, session, request - else - env['rack.errors'].puts "No valid params provided." - bad_request - end - rescue NoSession - env['rack.errors'].puts($!.message, *$@) - - @options. ### Missing or incompatible session - fetch :no_session, [ 500, - {'Content-Type'=>'text/plain'}, - $!.message ] - rescue - env['rack.errors'].puts($!.message, *$@) - - if not @options[:catch_error] - raise($!) - end - @options. - fetch :error, [ 500, - {'Content-Type'=>'text/plain'}, - 'OpenID has encountered an error.' ] - end - - # As the first part of OpenID consumer action, #check retrieves the data - # required for completion. - # - # * session[:openid][:openid_param] is set to the submitted - # identifier to be authenticated. - # * session[:openid][:site_return] is set as the request's - # HTTP_REFERER, unless already set. - # * env['rack.auth.openid.request'] is the openid checkid - # request instance. - def check(consumer, session, req) - session[:openid_param] = req.params[@options[:openid_param]] - oid = consumer.begin(session[:openid_param], @options[:anonymous]) - pp oid if $DEBUG - req.env['rack.auth.openid.request'] = oid - - session[:site_return] ||= req.env['HTTP_REFERER'] - - # SETUP_NEEDED check! - # see OpenID::Consumer::CheckIDRequest docs - query_args = [@realm, *@options.values_at(:return_to, :immediate)] - query_args[1] ||= req.url - query_args[2] = false if session.key? :setup_needed - pp query_args if $DEBUG - - ## Extension support - extensions.each do |ext,args| - oid.add_extension ext::Request.new(*args) - end - - if oid.send_redirect?(*query_args) - redirect = oid.redirect_url(*query_args) - if $DEBUG - pp redirect - pp Rack::Utils.parse_query(URI(redirect).query) - end - [ 303, {'Location'=>redirect}, [] ] - else - # check on 'action' option. - formbody = oid.form_markup(*query_args) - if $DEBUG - pp formbody - end - body = HTML % ['Confirm...', formbody] - [ 200, {'Content-Type'=>'text/html'}, body.to_a ] - end - rescue ::OpenID::DiscoveryFailure => e - # thrown from inside OpenID::Consumer#begin by yadis stuff - req.env['rack.errors'].puts($!.message, *$@) - - @options. ### Foreign server failed - fetch :auth_fail, [ 503, - {'Content-Type'=>'text/plain'}, - 'Foreign server failure.' ] - end - - # This is the final portion of authentication. Unless any errors outside - # of specification occur, a 303 redirect will be returned with Location - # determined by the OpenID response type. If none of the response type - # :login_* urls are set, the redirect will be set to - # session[:openid][:site_return]. If - # session[:openid][:site_return] is unset, the realm will be - # used. - # - # Any messages from OpenID's response are appended to the 303 response - # body. - # - # Data gathered from extensions are stored in session[:openid] with the - # extension's namespace uri as the key. - # - # * env['rack.auth.openid.response'] is the openid response. - # - # The four valid possible outcomes are: - # * failure: options[:login_fail] or - # session[:site_return] or the realm - # * session[:openid] is cleared and any messages are send to - # rack.errors - # * session[:openid]['authenticated'] is false - # * success: options[:login_good] or - # session[:site_return] or the realm - # * session[:openid] is cleared - # * session[:openid]['authenticated'] is true - # * session[:openid]['identity'] is the actual identifier - # * session[:openid]['identifier'] is the pretty identifier - # * cancel: options[:login_good] or - # session[:site_return] or the realm - # * session[:openid] is cleared - # * session[:openid]['authenticated'] is false - # * setup_needed: resubmits the authentication request. A flag is set for - # non-immediate handling. - # * session[:openid][:setup_needed] is set to true, - # which will prevent immediate style openid authentication. - def finish(consumer, session, req) - oid = consumer.complete(req.params, req.url) - pp oid if $DEBUG - req.env['rack.auth.openid.response'] = oid - - goto = session.fetch :site_return, @realm - body = [] - - case oid.status - when ::OpenID::Consumer::FAILURE - session.clear - session['authenticated'] = false - req.env['rack.errors'].puts oid.message - - goto = @options[:login_fail] if @option.key? :login_fail - body << "Authentication unsuccessful.\n" - when ::OpenID::Consumer::SUCCESS - session.clear - - ## Extension support - extensions.each do |ext, args| - session[ext::NS_URI] = ext::Response. - from_success_response(oid). - get_extension_args - end - - session['authenticated'] = true - # Value for unique identification and such - session['identity'] = oid.identity_url - # Value for display and UI labels - session['identifier'] = oid.display_identifier - - goto = @options[:login_good] if @options.key? :login_good - body << "Authentication successful.\n" - when ::OpenID::Consumer::CANCEL - session.clear - session['authenticated'] = false - - goto = @options[:login_fail] if @option.key? :login_fail - body << "Authentication cancelled.\n" - when ::OpenID::Consumer::SETUP_NEEDED - session[:setup_needed] = true - unless o_id = session[:openid_param] - raise('Required values missing.') - end - - goto = req.script_name+ - '?'+@options[:openid_param]+ - '='+o_id - body << "Reauthentication required.\n" - end - body << oid.message if oid.message - [ 303, {'Location'=>goto}, body] - end - - # The first argument should be the main extension module. - # The extension module should contain the constants: - # * class Request, with OpenID::Extension as an ancestor - # * class Response, with OpenID::Extension as an ancestor - # * string NS_URI, which defines the namespace of the extension, should - # be an absolute http uri - # - # All trailing arguments will be passed to extension::Request.new in - # #check. - # The openid response will be passed to - # extension::Response#from_success_response, #get_extension_args will be - # called on the result to attain the gathered data. - # - # This method returns the key at which the response data will be found in - # the session, which is the namespace uri by default. - def add_extension ext, *args - if not ext.is_a? Module - raise TypeError, "#{ext.inspect} is not a module" - elsif not (m = %w'Request Response NS_URI' - ext.constants).empty? - raise ArgumentError, "#{ext.inspect} missing #{m*', '}" - end - - consts = [ext::Request, ext::Response] - - if not consts.all?{|c| c.is_a? Class } - raise TypeError, "#{ext.inspect}'s Request or Response is not a class" - elsif not consts.all?{|c| ::OpenID::Extension > c } - raise ArgumentError, "#{ext.inspect}'s Request or Response not a decendant of OpenID::Extension" - end - - if not ext::NS_URI.is_a? String - raise TypeError, "#{ext.inspect}'s NS_URI is not a string" - elsif not uri = URI(ext::NS_URI) - raise ArgumentError, "#{ext.inspect}'s NS_URI is not a valid uri" - elsif not uri.scheme =~ /^https?$/ - raise ArgumentError, "#{ext.inspect}'s NS_URI is not an http uri" - elsif not uri.absolute? - raise ArgumentError, "#{ext.inspect}'s NS_URI is not and absolute uri" - end - @extensions[ext] = args - return ext::NS_URI - end - - # A conveniance method that returns the namespace of all current - # extensions used by this instance. - def extension_namespaces - @extensions.keys.map{|e|e::NS_URI} - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/builder.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/builder.rb deleted file mode 100644 index e708b52aee..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/builder.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Rack - # Rack::Builder implements a small DSL to iteratively construct Rack - # applications. - # - # Example: - # - # app = Rack::Builder.new { - # use Rack::CommonLogger - # use Rack::ShowExceptions - # map "/lobster" do - # use Rack::Lint - # run Rack::Lobster.new - # end - # } - # - # +use+ adds a middleware to the stack, +run+ dispatches to an application. - # You can use +map+ to construct a Rack::URLMap in a convenient way. - - class Builder - def initialize(&block) - @ins = [] - instance_eval(&block) if block_given? - end - - def use(middleware, *args, &block) - @ins << if block_given? - lambda { |app| middleware.new(app, *args, &block) } - else - lambda { |app| middleware.new(app, *args) } - end - end - - def run(app) - @ins << app #lambda { |nothing| app } - end - - def map(path, &block) - if @ins.last.kind_of? Hash - @ins.last[path] = Rack::Builder.new(&block).to_app - else - @ins << {} - map(path, &block) - end - end - - def to_app - @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last - inner_app = @ins.last - @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) } - end - - def call(env) - to_app.call(env) - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/cascade.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/cascade.rb deleted file mode 100644 index a038aa1105..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/cascade.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Rack - # Rack::Cascade tries an request on several apps, and returns the - # first response that is not 404 (or in a list of configurable - # status codes). - - class Cascade - attr_reader :apps - - def initialize(apps, catch=404) - @apps = apps - @catch = [*catch] - end - - def call(env) - status = headers = body = nil - raise ArgumentError, "empty cascade" if @apps.empty? - @apps.each { |app| - begin - status, headers, body = app.call(env) - break unless @catch.include?(status.to_i) - end - } - [status, headers, body] - end - - def add app - @apps << app - end - - def include? app - @apps.include? app - end - - alias_method :<<, :add - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/commonlogger.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/commonlogger.rb deleted file mode 100644 index 5e68ac626d..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/commonlogger.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Rack - # Rack::CommonLogger forwards every request to an +app+ given, and - # logs a line in the Apache common log format to the +logger+, or - # rack.errors by default. - - class CommonLogger - def initialize(app, logger=nil) - @app = app - @logger = logger - end - - def call(env) - dup._call(env) - end - - def _call(env) - @env = env - @logger ||= self - @time = Time.now - @status, @header, @body = @app.call(env) - [@status, @header, self] - end - - def close - @body.close if @body.respond_to? :close - end - - # By default, log to rack.errors. - def <<(str) - @env["rack.errors"].write(str) - @env["rack.errors"].flush - end - - def each - length = 0 - @body.each { |part| - length += part.size - yield part - } - - @now = Time.now - - # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common - # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 - - # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % - @logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} % - [ - @env['HTTP_X_FORWARDED_FOR'] || @env["REMOTE_ADDR"] || "-", - @env["REMOTE_USER"] || "-", - @now.strftime("%d/%b/%Y %H:%M:%S"), - @env["REQUEST_METHOD"], - @env["PATH_INFO"], - @env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"], - @env["HTTP_VERSION"], - @status.to_s[0..3], - (length.zero? ? "-" : length.to_s), - @now - @time - ] - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/deflater.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/deflater.rb deleted file mode 100644 index 1341eecec2..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/deflater.rb +++ /dev/null @@ -1,63 +0,0 @@ -require "zlib" -require "stringio" - -module Rack - -class Deflater - def initialize(app) - @app = app - end - - def call(env) - status, headers, body = @app.call(env) - - request = Request.new(env) - - encoding = Utils.select_best_encoding(%w(gzip deflate identity), request.accept_encoding) - - case encoding - when "gzip" - mtime = headers["Last-Modified"] || Time.now - [status, headers.merge("Content-Encoding" => "gzip"), self.class.gzip(body, mtime)] - when "deflate" - [status, headers.merge("Content-Encoding" => "deflate"), self.class.deflate(body)] - when "identity" - [status, headers, body] - when nil - message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." - [406, {"Content-Type" => "text/plain"}, message] - end - end - - def self.gzip(body, mtime) - io = StringIO.new - gzip = Zlib::GzipWriter.new(io) - gzip.mtime = mtime - - # TODO: Add streaming - body.each { |part| gzip << part } - - gzip.close - return io.string - end - - DEFLATE_ARGS = [ - Zlib::DEFAULT_COMPRESSION, - # drop the zlib header which causes both Safari and IE to choke - -Zlib::MAX_WBITS, - Zlib::DEF_MEM_LEVEL, - Zlib::DEFAULT_STRATEGY - ] - - # Loosely based on Mongrel's Deflate handler - def self.deflate(body) - deflater = Zlib::Deflate.new(*DEFLATE_ARGS) - - # TODO: Add streaming - body.each { |part| deflater << part } - - return deflater.finish - end -end - -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/directory.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/directory.rb deleted file mode 100644 index 31e0db8449..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/directory.rb +++ /dev/null @@ -1,158 +0,0 @@ -require 'time' - -module Rack - # Rack::Directory serves entries below the +root+ given, according to the - # path info of the Rack request. If a directory is found, the file's contents - # will be presented in an html based index. If a file is found, the env will - # be passed to the specified +app+. - # - # If +app+ is not specified, a Rack::File of the same +root+ will be used. - - class Directory - DIR_FILE = "%s%s%s%s" - DIR_PAGE = <<-PAGE - - %s - - -

    %s

    -
    - - - - - - - -%s -
    NameSizeTypeLast Modified
    -
    - - PAGE - - attr_reader :files - attr_accessor :root, :path - - def initialize(root, app=nil) - @root = root - @app = app - unless defined? @app - @app = Rack::File.new(@root) - end - end - - def call(env) - dup._call(env) - end - - F = ::File - - def _call(env) - if env["PATH_INFO"].include? ".." - body = "Forbidden\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size - return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]] - end - - @path = F.join(@root, Utils.unescape(env['PATH_INFO'])) - - if F.exist?(@path) and F.readable?(@path) - if F.file?(@path) - return @app.call(env) - elsif F.directory?(@path) - @files = [['../','Parent Directory','','','']] - sName, pInfo = env.values_at('SCRIPT_NAME', 'PATH_INFO') - Dir.entries(@path).sort.each do |file| - next if file[0] == ?. - fl = F.join(@path, file) - sz = F.size(fl) - url = F.join(sName, pInfo, file) - type = F.directory?(fl) ? 'directory' : - MIME_TYPES.fetch(F.extname(file)[1..-1],'unknown') - size = (type!='directory' ? (sz<10240 ? "#{sz}B" : "#{sz/1024}KB") : '-') - mtime = F.mtime(fl).httpdate - @files << [ url, file, size, type, mtime ] - end - return [ 200, {'Content-Type'=>'text/html'}, self ] - end - end - - body = "Entity not found: #{env["PATH_INFO"]}\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size - return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]] - end - - def each - show_path = @path.sub(/^#{@root}/,'') - files = @files.map{|f| DIR_FILE % f }*"\n" - page = DIR_PAGE % [ show_path, show_path , files ] - page.each_line{|l| yield l } - end - - def each_entry - @files.each{|e| yield e } - end - - # From WEBrick. - MIME_TYPES = { - "ai" => "application/postscript", - "asc" => "text/plain", - "avi" => "video/x-msvideo", - "bin" => "application/octet-stream", - "bmp" => "image/bmp", - "class" => "application/octet-stream", - "cer" => "application/pkix-cert", - "crl" => "application/pkix-crl", - "crt" => "application/x-x509-ca-cert", - #"crl" => "application/x-pkcs7-crl", - "css" => "text/css", - "dms" => "application/octet-stream", - "doc" => "application/msword", - "dvi" => "application/x-dvi", - "eps" => "application/postscript", - "etx" => "text/x-setext", - "exe" => "application/octet-stream", - "gif" => "image/gif", - "htm" => "text/html", - "html" => "text/html", - "jpe" => "image/jpeg", - "jpeg" => "image/jpeg", - "jpg" => "image/jpeg", - "js" => "text/javascript", - "lha" => "application/octet-stream", - "lzh" => "application/octet-stream", - "mov" => "video/quicktime", - "mpe" => "video/mpeg", - "mpeg" => "video/mpeg", - "mpg" => "video/mpeg", - "pbm" => "image/x-portable-bitmap", - "pdf" => "application/pdf", - "pgm" => "image/x-portable-graymap", - "png" => "image/png", - "pnm" => "image/x-portable-anymap", - "ppm" => "image/x-portable-pixmap", - "ppt" => "application/vnd.ms-powerpoint", - "ps" => "application/postscript", - "qt" => "video/quicktime", - "ras" => "image/x-cmu-raster", - "rb" => "text/plain", - "rd" => "text/plain", - "rtf" => "application/rtf", - "sgm" => "text/sgml", - "sgml" => "text/sgml", - "tif" => "image/tiff", - "tiff" => "image/tiff", - "txt" => "text/plain", - "xbm" => "image/x-xbitmap", - "xls" => "application/vnd.ms-excel", - "xml" => "text/xml", - "xpm" => "image/x-xpixmap", - "xwd" => "image/x-xwindowdump", - "zip" => "application/zip", - } - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/file.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/file.rb deleted file mode 100644 index afb213836c..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/file.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'time' - -module Rack - # Rack::File serves files below the +root+ given, according to the - # path info of the Rack request. - # - # Handlers can detect if bodies are a Rack::File, and use mechanisms - # like sendfile on the +path+. - - class File - attr_accessor :root - attr_accessor :path - - def initialize(root) - @root = root - end - - def call(env) - dup._call(env) - end - - F = ::File - - def _call(env) - if env["PATH_INFO"].include? ".." - body = "Forbidden\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size - return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]] - end - - @path = F.join(@root, Utils.unescape(env["PATH_INFO"])) - ext = F.extname(@path)[1..-1] - - if F.file?(@path) && F.readable?(@path) - [200, { - "Last-Modified" => F.mtime(@path).httpdate, - "Content-Type" => MIME_TYPES[ext] || "text/plain", - "Content-Length" => F.size(@path).to_s - }, self] - else - body = "File not found: #{env["PATH_INFO"]}\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size - [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]] - end - end - - def each - F.open(@path, "rb") { |file| - while part = file.read(8192) - yield part - end - } - end - - # :stopdoc: - # From WEBrick with some additions. - MIME_TYPES = { - "ai" => "application/postscript", - "asc" => "text/plain", - "avi" => "video/x-msvideo", - "bin" => "application/octet-stream", - "bmp" => "image/bmp", - "class" => "application/octet-stream", - "cer" => "application/pkix-cert", - "crl" => "application/pkix-crl", - "crt" => "application/x-x509-ca-cert", - #"crl" => "application/x-pkcs7-crl", - "css" => "text/css", - "dms" => "application/octet-stream", - "doc" => "application/msword", - "dvi" => "application/x-dvi", - "eps" => "application/postscript", - "etx" => "text/x-setext", - "exe" => "application/octet-stream", - "gif" => "image/gif", - "htm" => "text/html", - "html" => "text/html", - "jpe" => "image/jpeg", - "jpeg" => "image/jpeg", - "jpg" => "image/jpeg", - "js" => "text/javascript", - "lha" => "application/octet-stream", - "lzh" => "application/octet-stream", - "mov" => "video/quicktime", - "mp3" => "audio/mpeg", - "mpe" => "video/mpeg", - "mpeg" => "video/mpeg", - "mpg" => "video/mpeg", - "pbm" => "image/x-portable-bitmap", - "pdf" => "application/pdf", - "pgm" => "image/x-portable-graymap", - "png" => "image/png", - "pnm" => "image/x-portable-anymap", - "ppm" => "image/x-portable-pixmap", - "ppt" => "application/vnd.ms-powerpoint", - "ps" => "application/postscript", - "qt" => "video/quicktime", - "ras" => "image/x-cmu-raster", - "rb" => "text/plain", - "rd" => "text/plain", - "rtf" => "application/rtf", - "sgm" => "text/sgml", - "sgml" => "text/sgml", - "tif" => "image/tiff", - "tiff" => "image/tiff", - "txt" => "text/plain", - "xbm" => "image/x-xbitmap", - "xls" => "application/vnd.ms-excel", - "xml" => "text/xml", - "xpm" => "image/x-xpixmap", - "xwd" => "image/x-xwindowdump", - "zip" => "application/zip", - } - # :startdoc: - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler.rb deleted file mode 100644 index debd15d66e..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Rack - # *Handlers* connect web servers with Rack. - # - # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI - # and LiteSpeed. - # - # Handlers usually are activated by calling MyHandler.run(myapp). - # A second optional hash can be passed to include server-specific - # configuration. - module Handler - def self.get(server) - return unless server - - if klass = @handlers[server] - obj = Object - klass.split("::").each { |x| obj = obj.const_get(x) } - obj - else - Rack::Handler.const_get(server.capitalize) - end - end - - def self.register(server, klass) - @handlers ||= {} - @handlers[server] = klass - end - - autoload :CGI, "rack/handler/cgi" - autoload :FastCGI, "rack/handler/fastcgi" - autoload :Mongrel, "rack/handler/mongrel" - autoload :EventedMongrel, "rack/handler/evented_mongrel" - autoload :WEBrick, "rack/handler/webrick" - autoload :LSWS, "rack/handler/lsws" - autoload :SCGI, "rack/handler/scgi" - - register 'cgi', 'Rack::Handler::CGI' - register 'fastcgi', 'Rack::Handler::FastCGI' - register 'mongrel', 'Rack::Handler::Mongrel' - register 'emongrel', 'Rack::Handler::EventedMongrel' - register 'webrick', 'Rack::Handler::WEBrick' - register 'lsws', 'Rack::Handler::LSWS' - register 'scgi', 'Rack::Handler::SCGI' - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/cgi.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/cgi.rb deleted file mode 100644 index 1922402c8c..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/cgi.rb +++ /dev/null @@ -1,57 +0,0 @@ -module Rack - module Handler - class CGI - def self.run(app, options=nil) - serve app - end - - def self.serve(app) - env = ENV.to_hash - env.delete "HTTP_CONTENT_LENGTH" - - env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" - - env.update({"rack.version" => [0,1], - "rack.input" => STDIN, - "rack.errors" => STDERR, - - "rack.multithread" => false, - "rack.multiprocess" => true, - "rack.run_once" => true, - - "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" - }) - - env["QUERY_STRING"] ||= "" - env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] - env["REQUEST_PATH"] ||= "/" - - status, headers, body = app.call(env) - begin - send_headers status, headers - send_body body - ensure - body.close if body.respond_to? :close - end - end - - def self.send_headers(status, headers) - STDOUT.print "Status: #{status}\r\n" - headers.each { |k, vs| - vs.each { |v| - STDOUT.print "#{k}: #{v}\r\n" - } - } - STDOUT.print "\r\n" - STDOUT.flush - end - - def self.send_body(body) - body.each { |part| - STDOUT.print part - STDOUT.flush - } - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/evented_mongrel.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/evented_mongrel.rb deleted file mode 100644 index 1eb5eb909b..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/evented_mongrel.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'swiftcore/evented_mongrel' - -module Rack - module Handler - class EventedMongrel < Mongrel - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/fastcgi.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/fastcgi.rb deleted file mode 100644 index 5f8801d516..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/fastcgi.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'fcgi' -require 'socket' - -module Rack - module Handler - class FastCGI - def self.run(app, options={}) - file = options[:File] and STDIN.reopen(UNIXServer.new(file)) - port = options[:Port] and STDIN.reopen(TCPServer.new(port)) - FCGI.each { |request| - serve request, app - } - end - - module ProperStream # :nodoc: - def each # This is missing by default. - while line = gets - yield line - end - end - - def read(*args) - if args.empty? - super || "" # Empty string on EOF. - else - super - end - end - end - - def self.serve(request, app) - env = request.env - env.delete "HTTP_CONTENT_LENGTH" - - request.in.extend ProperStream - - env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" - - env.update({"rack.version" => [0,1], - "rack.input" => request.in, - "rack.errors" => request.err, - - "rack.multithread" => false, - "rack.multiprocess" => true, - "rack.run_once" => false, - - "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" - }) - - env["QUERY_STRING"] ||= "" - env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] - env["REQUEST_PATH"] ||= "/" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" - - status, headers, body = app.call(env) - begin - send_headers request.out, status, headers - send_body request.out, body - ensure - body.close if body.respond_to? :close - request.finish - end - end - - def self.send_headers(out, status, headers) - out.print "Status: #{status}\r\n" - headers.each { |k, vs| - vs.each { |v| - out.print "#{k}: #{v}\r\n" - } - } - out.print "\r\n" - out.flush - end - - def self.send_body(out, body) - body.each { |part| - out.print part - out.flush - } - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/lsws.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/lsws.rb deleted file mode 100644 index 48b82b5861..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/lsws.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'lsapi' -#require 'cgi' -module Rack - module Handler - class LSWS - def self.run(app, options=nil) - while LSAPI.accept != nil - serve app - end - end - def self.serve(app) - env = ENV.to_hash - env.delete "HTTP_CONTENT_LENGTH" - env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" - env.update({"rack.version" => [0,1], - "rack.input" => STDIN, - "rack.errors" => STDERR, - "rack.multithread" => false, - "rack.multiprocess" => true, - "rack.run_once" => false, - "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" - }) - env["QUERY_STRING"] ||= "" - env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] - env["REQUEST_PATH"] ||= "/" - status, headers, body = app.call(env) - begin - send_headers status, headers - send_body body - ensure - body.close if body.respond_to? :close - end - end - def self.send_headers(status, headers) - print "Status: #{status}\r\n" - headers.each { |k, vs| - vs.each { |v| - print "#{k}: #{v}\r\n" - } - } - print "\r\n" - STDOUT.flush - end - def self.send_body(body) - body.each { |part| - print part - STDOUT.flush - } - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/mongrel.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/mongrel.rb deleted file mode 100644 index 8bdd6b7c44..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/mongrel.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'mongrel' -require 'stringio' - -module Rack - module Handler - class Mongrel < ::Mongrel::HttpHandler - def self.run(app, options={}) - server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0', - options[:Port] || 8080) - # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. - # Use is similar to #run, replacing the app argument with a hash of - # { path=>app, ... } or an instance of Rack::URLMap. - if options[:map] - if app.is_a? Hash - app.each do |path, appl| - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - elsif app.is_a? URLMap - app.instance_variable_get(:@mapping).each do |(host, path, appl)| - next if !host.nil? && !options[:Host].nil? && options[:Host] != host - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - else - raise ArgumentError, "first argument should be a Hash or URLMap" - end - else - server.register('/', Rack::Handler::Mongrel.new(app)) - end - yield server if block_given? - server.run.join - end - - def initialize(app) - @app = app - end - - def process(request, response) - env = {}.replace(request.params) - env.delete "HTTP_CONTENT_TYPE" - env.delete "HTTP_CONTENT_LENGTH" - - env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" - - env.update({"rack.version" => [0,1], - "rack.input" => request.body || StringIO.new(""), - "rack.errors" => STDERR, - - "rack.multithread" => true, - "rack.multiprocess" => false, # ??? - "rack.run_once" => false, - - "rack.url_scheme" => "http", - }) - env["QUERY_STRING"] ||= "" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" - - status, headers, body = @app.call(env) - - begin - response.status = status.to_i - headers.each { |k, vs| - vs.each { |v| - response.header[k] = v - } - } - body.each { |part| - response.body << part - } - response.finished - ensure - body.close if body.respond_to? :close - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/scgi.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/scgi.rb deleted file mode 100644 index 0e143395b9..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/scgi.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'scgi' -require 'stringio' - -module Rack - module Handler - class SCGI < ::SCGI::Processor - attr_accessor :app - - def self.run(app, options=nil) - new(options.merge(:app=>app, - :host=>options[:Host], - :port=>options[:Port], - :socket=>options[:Socket])).listen - end - - def initialize(settings = {}) - @app = settings[:app] - @log = Object.new - def @log.info(*args); end - def @log.error(*args); end - super(settings) - end - - def process_request(request, input_body, socket) - env = {}.replace(request) - env.delete "HTTP_CONTENT_TYPE" - env.delete "HTTP_CONTENT_LENGTH" - env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2) - env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] - env["PATH_INFO"] = env["REQUEST_PATH"] - env["QUERY_STRING"] ||= "" - env["SCRIPT_NAME"] = "" - env.update({"rack.version" => [0,1], - "rack.input" => StringIO.new(input_body), - "rack.errors" => STDERR, - - "rack.multithread" => true, - "rack.multiprocess" => true, - "rack.run_once" => false, - - "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" - }) - status, headers, body = app.call(env) - begin - socket.write("Status: #{status}\r\n") - headers.each do |k, vs| - vs.each {|v| socket.write("#{k}: #{v}\r\n")} - end - socket.write("\r\n") - body.each {|s| socket.write(s)} - ensure - body.close if body.respond_to? :close - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/webrick.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/webrick.rb deleted file mode 100644 index c3222fdcc5..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/handler/webrick.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'webrick' -require 'stringio' - -module Rack - module Handler - class WEBrick < WEBrick::HTTPServlet::AbstractServlet - def self.run(app, options={}) - server = ::WEBrick::HTTPServer.new(options) - server.mount "/", Rack::Handler::WEBrick, app - trap(:INT) { server.shutdown } - yield server if block_given? - server.start - end - - def initialize(server, app) - super server - @app = app - end - - def service(req, res) - env = req.meta_vars - env.delete_if { |k, v| v.nil? } - - env.update({"rack.version" => [0,1], - "rack.input" => StringIO.new(req.body.to_s), - "rack.errors" => STDERR, - - "rack.multithread" => true, - "rack.multiprocess" => false, - "rack.run_once" => false, - - "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" - }) - - env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] - env["QUERY_STRING"] ||= "" - env["REQUEST_PATH"] ||= "/" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" - - status, headers, body = @app.call(env) - begin - res.status = status.to_i - headers.each { |k, vs| - vs.each { |v| - res[k] = v - } - } - body.each { |part| - res.body << part - } - ensure - body.close if body.respond_to? :close - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lint.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lint.rb deleted file mode 100644 index 7bc433b863..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lint.rb +++ /dev/null @@ -1,401 +0,0 @@ -module Rack - # Rack::Lint validates your application and the requests and - # responses according to the Rack spec. - - class Lint - def initialize(app) - @app = app - end - - # :stopdoc: - - class LintError < RuntimeError; end - module Assertion - def assert(message, &block) - unless block.call - raise LintError, message - end - end - end - include Assertion - - ## This specification aims to formalize the Rack protocol. You - ## can (and should) use Rack::Lint to enforce it. - ## - ## When you develop middleware, be sure to add a Lint before and - ## after to catch all mistakes. - - ## = Rack applications - - ## A Rack application is an Ruby object (not a class) that - ## responds to +call+. - def call(env=nil) - dup._call(env) - end - - def _call(env) - ## It takes exactly one argument, the *environment* - assert("No env given") { env } - check_env env - - env['rack.input'] = InputWrapper.new(env['rack.input']) - env['rack.errors'] = ErrorWrapper.new(env['rack.errors']) - - ## and returns an Array of exactly three values: - status, headers, @body = @app.call(env) - ## The *status*, - check_status status - ## the *headers*, - check_headers headers - ## and the *body*. - check_content_type status, headers - [status, headers, self] - end - - ## == The Environment - def check_env(env) - ## The environment must be an true instance of Hash (no - ## subclassing allowed) that includes CGI-like headers. - ## The application is free to modify the environment. - assert("env #{env.inspect} is not a Hash, but #{env.class}") { - env.instance_of? Hash - } - - ## - ## The environment is required to include these variables - ## (adopted from PEP333), except when they'd be empty, but see - ## below. - - ## REQUEST_METHOD:: The HTTP request method, such as - ## "GET" or "POST". This cannot ever - ## be an empty string, and so is - ## always required. - - ## SCRIPT_NAME:: The initial portion of the request - ## URL's "path" that corresponds to the - ## application object, so that the - ## application knows its virtual - ## "location". This may be an empty - ## string, if the application corresponds - ## to the "root" of the server. - - ## PATH_INFO:: The remainder of the request URL's - ## "path", designating the virtual - ## "location" of the request's target - ## within the application. This may be an - ## empty string, if the request URL targets - ## the application root and does not have a - ## trailing slash. - - ## QUERY_STRING:: The portion of the request URL that - ## follows the ?, if any. May be - ## empty, but is always required! - - ## SERVER_NAME, SERVER_PORT:: When combined with SCRIPT_NAME and PATH_INFO, these variables can be used to complete the URL. Note, however, that HTTP_HOST, if present, should be used in preference to SERVER_NAME for reconstructing the request URL. SERVER_NAME and SERVER_PORT can never be empty strings, and so are always required. - - ## HTTP_ Variables:: Variables corresponding to the - ## client-supplied HTTP request - ## headers (i.e., variables whose - ## names begin with HTTP_). The - ## presence or absence of these - ## variables should correspond with - ## the presence or absence of the - ## appropriate HTTP header in the - ## request. - - ## In addition to this, the Rack environment must include these - ## Rack-specific variables: - - ## rack.version:: The Array [0,1], representing this version of Rack. - ## rack.url_scheme:: +http+ or +https+, depending on the request URL. - ## rack.input:: See below, the input stream. - ## rack.errors:: See below, the error stream. - ## rack.multithread:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise. - ## rack.multiprocess:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise. - ## rack.run_once:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar). - - ## The server or the application can store their own data in the - ## environment, too. The keys must contain at least one dot, - ## and should be prefixed uniquely. The prefix rack. - ## is reserved for use with the Rack core distribution and must - ## not be used otherwise. - ## - - %w[REQUEST_METHOD SERVER_NAME SERVER_PORT - QUERY_STRING - rack.version rack.input rack.errors - rack.multithread rack.multiprocess rack.run_once].each { |header| - assert("env missing required key #{header}") { env.include? header } - } - - ## The environment must not contain the keys - ## HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH - ## (use the versions without HTTP_). - %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header| - assert("env contains #{header}, must use #{header[5,-1]}") { - not env.include? header - } - } - - ## The CGI keys (named without a period) must have String values. - env.each { |key, value| - next if key.include? "." # Skip extensions - assert("env variable #{key} has non-string value #{value.inspect}") { - value.instance_of? String - } - } - - ## - ## There are the following restrictions: - - ## * rack.version must be an array of Integers. - assert("rack.version must be an Array, was #{env["rack.version"].class}") { - env["rack.version"].instance_of? Array - } - ## * rack.url_scheme must either be +http+ or +https+. - assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") { - %w[http https].include? env["rack.url_scheme"] - } - - ## * There must be a valid input stream in rack.input. - check_input env["rack.input"] - ## * There must be a valid error stream in rack.errors. - check_error env["rack.errors"] - - ## * The REQUEST_METHOD must be one of +GET+, +POST+, +PUT+, - ## +DELETE+, +HEAD+, +OPTIONS+, +TRACE+. - assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") { - %w[GET POST PUT DELETE - HEAD OPTIONS TRACE].include?(env["REQUEST_METHOD"]) - } - - ## * The SCRIPT_NAME, if non-empty, must start with / - assert("SCRIPT_NAME must start with /") { - !env.include?("SCRIPT_NAME") || - env["SCRIPT_NAME"] == "" || - env["SCRIPT_NAME"] =~ /\A\// - } - ## * The PATH_INFO, if non-empty, must start with / - assert("PATH_INFO must start with /") { - !env.include?("PATH_INFO") || - env["PATH_INFO"] == "" || - env["PATH_INFO"] =~ /\A\// - } - ## * The CONTENT_LENGTH, if given, must consist of digits only. - assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") { - !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/ - } - - ## * One of SCRIPT_NAME or PATH_INFO must be - ## set. PATH_INFO should be / if - ## SCRIPT_NAME is empty. - assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") { - env["SCRIPT_NAME"] || env["PATH_INFO"] - } - ## SCRIPT_NAME never should be /, but instead be empty. - assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") { - env["SCRIPT_NAME"] != "/" - } - end - - ## === The Input Stream - def check_input(input) - ## The input stream must respond to +gets+, +each+ and +read+. - [:gets, :each, :read].each { |method| - assert("rack.input #{input} does not respond to ##{method}") { - input.respond_to? method - } - } - end - - class InputWrapper - include Assertion - - def initialize(input) - @input = input - end - - ## * +gets+ must be called without arguments and return a string, - ## or +nil+ on EOF. - def gets(*args) - assert("rack.input#gets called with arguments") { args.size == 0 } - v = @input.gets - assert("rack.input#gets didn't return a String") { - v.nil? or v.instance_of? String - } - v - end - - ## * +read+ must be called without or with one integer argument - ## and return a string, or +nil+ on EOF. - def read(*args) - assert("rack.input#read called with too many arguments") { - args.size <= 1 - } - if args.size == 1 - assert("rack.input#read called with non-integer argument") { - args.first.kind_of? Integer - } - end - v = @input.read(*args) - assert("rack.input#read didn't return a String") { - v.nil? or v.instance_of? String - } - v - end - - ## * +each+ must be called without arguments and only yield Strings. - def each(*args) - assert("rack.input#each called with arguments") { args.size == 0 } - @input.each { |line| - assert("rack.input#each didn't yield a String") { - line.instance_of? String - } - yield line - } - end - - ## * +close+ must never be called on the input stream. - def close(*args) - assert("rack.input#close must not be called") { false } - end - end - - ## === The Error Stream - def check_error(error) - ## The error stream must respond to +puts+, +write+ and +flush+. - [:puts, :write, :flush].each { |method| - assert("rack.error #{error} does not respond to ##{method}") { - error.respond_to? method - } - } - end - - class ErrorWrapper - include Assertion - - def initialize(error) - @error = error - end - - ## * +puts+ must be called with a single argument that responds to +to_s+. - def puts(str) - @error.puts str - end - - ## * +write+ must be called with a single argument that is a String. - def write(str) - assert("rack.errors#write not called with a String") { str.instance_of? String } - @error.write str - end - - ## * +flush+ must be called without arguments and must be called - ## in order to make the error appear for sure. - def flush - @error.flush - end - - ## * +close+ must never be called on the error stream. - def close(*args) - assert("rack.errors#close must not be called") { false } - end - end - - ## == The Response - - ## === The Status - def check_status(status) - ## The status, if parsed as integer (+to_i+), must be greater than or equal to 100. - assert("Status must be >=100 seen as integer") { status.to_i >= 100 } - end - - ## === The Headers - def check_headers(header) - ## The header must respond to each, and yield values of key and value. - assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") { - header.respond_to? :each - } - header.each { |key, value| - ## The header keys must be Strings. - assert("header key must be a string, was #{key.class}") { - key.instance_of? String - } - ## The header must not contain a +Status+ key, - assert("header must not contain Status") { key.downcase != "status" } - ## contain keys with : or newlines in their name, - assert("header names must not contain : or \\n") { key !~ /[:\n]/ } - ## contain keys names that end in - or _, - assert("header names must not end in - or _") { key !~ /[-_]\z/ } - ## but only contain keys that consist of - ## letters, digits, _ or - and start with a letter. - assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ } - ## - ## The values of the header must respond to #each. - assert("header values must respond to #each, but the value of " + - "'#{key}' doesn't (is #{value.class})") { value.respond_to? :each } - value.each { |item| - ## The values passed on #each must be Strings - assert("header values must consist of Strings, but '#{key}' also contains a #{item.class}") { - item.instance_of?(String) - } - ## and not contain characters below 037. - assert("invalid header value #{key}: #{item.inspect}") { - item !~ /[\000-\037]/ - } - } - } - end - - ## === The Content-Type - def check_content_type(status, headers) - headers.each { |key, value| - ## There must be a Content-Type, except when the - ## +Status+ is 204 or 304, in which case there must be none - ## given. - if key.downcase == "content-type" - assert("Content-Type header found in #{status} response, not allowed"){ - not [204, 304].include? status.to_i - } - return - end - } - assert("No Content-Type header found") { - [204, 304].include? status.to_i - } - end - - ## === The Body - def each - @closed = false - ## The Body must respond to #each - @body.each { |part| - ## and must only yield String values. - assert("Body yielded non-string value #{part.inspect}") { - part.instance_of? String - } - yield part - } - ## - ## If the Body responds to #close, it will be called after iteration. - # XXX howto: assert("Body has not been closed") { @closed } - - ## - ## The Body commonly is an Array of Strings, the application - ## instance itself, or a File-like object. - end - - def close - @closed = true - @body.close if @body.respond_to?(:close) - end - - # :startdoc: - - end -end - -## == Thanks -## Some parts of this specification are adopted from PEP333: Python -## Web Server Gateway Interface -## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank -## everyone involved in that effort. diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lobster.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lobster.rb deleted file mode 100644 index 33f6b81b01..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/lobster.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'zlib' - -require 'rack/request' -require 'rack/response' - -module Rack - # Paste has a Pony, Rack has a Lobster! - class Lobster - LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2 - P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0 - t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ - I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0]) - - LambdaLobster = lambda { |env| - if env["QUERY_STRING"].include?("flip") - lobster = LobsterString.split("\n"). - map { |line| line.ljust(42).reverse }. - join("\n") - href = "?" - else - lobster = LobsterString - href = "?flip" - end - - [200, {"Content-Type" => "text/html"}, - ["Lobstericious!", - "
    ", lobster, "
    ", - "flip!"] - ] - } - - def call(env) - req = Request.new(env) - if req.GET["flip"] == "left" - lobster = LobsterString.split("\n"). - map { |line| line.ljust(42).reverse }. - join("\n") - href = "?flip=right" - elsif req.GET["flip"] == "crash" - raise "Lobster crashed" - else - lobster = LobsterString - href = "?flip=left" - end - - Response.new.finish do |res| - res.write "Lobstericious!" - res.write "
    "
    -        res.write lobster
    -        res.write "
    " - res.write "

    flip!

    " - res.write "

    crash!

    " - end - end - - end -end - -if $0 == __FILE__ - require 'rack' - require 'rack/showexceptions' - Rack::Handler::WEBrick.run \ - Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), - :Port => 9292 -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/mock.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/mock.rb deleted file mode 100644 index f43b9af3ef..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/mock.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'uri' -require 'stringio' -require 'rack/lint' -require 'rack/utils' -require 'rack/response' - -module Rack - # Rack::MockRequest helps testing your Rack application without - # actually using HTTP. - # - # After performing a request on a URL with get/post/put/delete, it - # returns a MockResponse with useful helper methods for effective - # testing. - # - # You can pass a hash with additional configuration to the - # get/post/put/delete. - # :input:: A String or IO-like to be used as rack.input. - # :fatal:: Raise a FatalWarning if the app writes to rack.errors. - # :lint:: If true, wrap the application in a Rack::Lint. - - class MockRequest - class FatalWarning < RuntimeError - end - - class FatalWarner - def puts(warning) - raise FatalWarning, warning - end - - def write(warning) - raise FatalWarning, warning - end - - def flush - end - - def string - "" - end - end - - DEFAULT_ENV = { - "rack.version" => [0,1], - "rack.input" => StringIO.new, - "rack.errors" => StringIO.new, - "rack.multithread" => true, - "rack.multiprocess" => true, - "rack.run_once" => false, - } - - def initialize(app) - @app = app - end - - def get(uri, opts={}) request("GET", uri, opts) end - def post(uri, opts={}) request("POST", uri, opts) end - def put(uri, opts={}) request("PUT", uri, opts) end - def delete(uri, opts={}) request("DELETE", uri, opts) end - - def request(method="GET", uri="", opts={}) - env = self.class.env_for(uri, opts.merge(:method => method)) - - if opts[:lint] - app = Rack::Lint.new(@app) - else - app = @app - end - - errors = env["rack.errors"] - MockResponse.new(*(app.call(env) + [errors])) - end - - # Return the Rack environment used for a request to +uri+. - def self.env_for(uri="", opts={}) - uri = URI(uri) - env = DEFAULT_ENV.dup - - env["REQUEST_METHOD"] = opts[:method] || "GET" - env["SERVER_NAME"] = uri.host || "example.org" - env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80" - env["QUERY_STRING"] = uri.query.to_s - env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path - env["rack.url_scheme"] = uri.scheme || "http" - - env["SCRIPT_NAME"] = opts[:script_name] || "" - - if opts[:fatal] - env["rack.errors"] = FatalWarner.new - else - env["rack.errors"] = StringIO.new - end - - opts[:input] ||= "" - if String === opts[:input] - env["rack.input"] = StringIO.new(opts[:input]) - else - env["rack.input"] = opts[:input] - end - - opts.each { |field, value| - env[field] = value if String === field - } - - env - end - end - - # Rack::MockResponse provides useful helpers for testing your apps. - # Usually, you don't create the MockResponse on your own, but use - # MockRequest. - - class MockResponse - def initialize(status, headers, body, errors=StringIO.new("")) - @status = status.to_i - - @original_headers = headers - @headers = Rack::Utils::HeaderHash.new - headers.each { |field, values| - values.each { |value| - @headers[field] = value - } - @headers[field] = "" if values.empty? - } - - @body = "" - body.each { |part| @body << part } - - @errors = errors.string - end - - # Status - attr_reader :status - - # Headers - attr_reader :headers, :original_headers - - def [](field) - headers[field] - end - - - # Body - attr_reader :body - - def =~(other) - @body =~ other - end - - def match(other) - @body.match other - end - - - # Errors - attr_accessor :errors - - - include Response::Helpers - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/recursive.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/recursive.rb deleted file mode 100644 index bf8b965925..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/recursive.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'uri' - -module Rack - # Rack::ForwardRequest gets caught by Rack::Recursive and redirects - # the current request to the app at +url+. - # - # raise ForwardRequest.new("/not-found") - # - - class ForwardRequest < Exception - attr_reader :url, :env - - def initialize(url, env={}) - @url = URI(url) - @env = env - - @env["PATH_INFO"] = @url.path - @env["QUERY_STRING"] = @url.query if @url.query - @env["HTTP_HOST"] = @url.host if @url.host - @env["HTTP_PORT"] = @url.port if @url.port - @env["rack.url_scheme"] = @url.scheme if @url.scheme - - super "forwarding to #{url}" - end - end - - # Rack::Recursive allows applications called down the chain to - # include data from other applications (by using - # rack['rack.recursive.include'][...] or raise a - # ForwardRequest to redirect internally. - - class Recursive - def initialize(app) - @app = app - end - - def call(env) - @script_name = env["SCRIPT_NAME"] - @app.call(env.merge('rack.recursive.include' => method(:include))) - rescue ForwardRequest => req - call(env.merge(req.env)) - end - - def include(env, path) - unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ || - path[@script_name.size].nil?) - raise ArgumentError, "can only include below #{@script_name}, not #{path}" - end - - env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name, - "REQUEST_METHOD" => "GET", - "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "", - "rack.input" => StringIO.new("")) - @app.call(env) - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/reloader.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/reloader.rb deleted file mode 100644 index 25ca2f9e85..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/reloader.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'thread' - -module Rack - # Rack::Reloader checks on every request, but at most every +secs+ - # seconds, if a file loaded changed, and reloads it, logging to - # rack.errors. - # - # It is recommended you use ShowExceptions to catch SyntaxErrors etc. - - class Reloader - def initialize(app, secs=10) - @app = app - @secs = secs # reload every @secs seconds max - @last = Time.now - end - - def call(env) - if Time.now > @last + @secs - Thread.exclusive { - reload!(env['rack.errors']) - @last = Time.now - } - end - - @app.call(env) - end - - def reload!(stderr=STDERR) - need_reload = $LOADED_FEATURES.find_all { |loaded| - begin - if loaded =~ /\A[.\/]/ # absolute filename or 1.9 - abs = loaded - else - abs = $LOAD_PATH.map { |path| ::File.join(path, loaded) }. - find { |file| ::File.exist? file } - end - - if abs - ::File.mtime(abs) > @last - @secs rescue false - else - false - end - end - } - - need_reload.each { |l| - $LOADED_FEATURES.delete l - } - - need_reload.each { |to_load| - begin - if require to_load - stderr.puts "#{self.class}: reloaded `#{to_load}'" - end - rescue LoadError, SyntaxError => e - raise e # Possibly ShowExceptions - end - } - - stderr.flush - need_reload - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/request.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/request.rb deleted file mode 100644 index 2a9bcc15b9..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/request.rb +++ /dev/null @@ -1,209 +0,0 @@ -require 'rack/utils' - -module Rack - # Rack::Request provides a convenient interface to a Rack - # environment. It is stateless, the environment +env+ passed to the - # constructor will be directly modified. - # - # req = Rack::Request.new(env) - # req.post? - # req.params["data"] - - class Request - # The environment of the request. - attr_reader :env - - def initialize(env) - @env = env - end - - def body; @env["rack.input"] end - def scheme; @env["rack.url_scheme"] end - def script_name; @env["SCRIPT_NAME"].to_s end - def path_info; @env["PATH_INFO"].to_s end - def port; @env["SERVER_PORT"].to_i end - def request_method; @env["REQUEST_METHOD"] end - def query_string; @env["QUERY_STRING"].to_s end - def content_length; @env['CONTENT_LENGTH'] end - def content_type; @env['CONTENT_TYPE'] end - - # The media type (type/subtype) portion of the CONTENT_TYPE header - # without any media type parameters. e.g., when CONTENT_TYPE is - # "text/plain;charset=utf-8", the media-type is "text/plain". - # - # For more information on the use of media types in HTTP, see: - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 - def media_type - content_type && content_type.split(/\s*[;,]\s*/, 2)[0].downcase - end - - # The media type parameters provided in CONTENT_TYPE as a Hash, or - # an empty Hash if no CONTENT_TYPE or media-type parameters were - # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", - # this method responds with the following Hash: - # { 'charset' => 'utf-8' } - def media_type_params - return {} if content_type.nil? - content_type.split(/\s*[;,]\s*/)[1..-1]. - collect { |s| s.split('=', 2) }. - inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash } - end - - # The character set of the request body if a "charset" media type - # parameter was given, or nil if no "charset" was specified. Note - # that, per RFC2616, text/* media types that specify no explicit - # charset are to be considered ISO-8859-1. - def content_charset - media_type_params['charset'] - end - - def host - # Remove port number. - (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '') - end - - def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end - def path_info=(s); @env["PATH_INFO"] = s.to_s end - - def get?; request_method == "GET" end - def post?; request_method == "POST" end - def put?; request_method == "PUT" end - def delete?; request_method == "DELETE" end - def head?; request_method == "HEAD" end - - # The set of form-data media-types. Requests that do not indicate - # one of the media types presents in this list will not be eligible - # for form-data / param parsing. - FORM_DATA_MEDIA_TYPES = [ - nil, - 'application/x-www-form-urlencoded', - 'multipart/form-data' - ] - - # Determine whether the request body contains form-data by checking - # the request media_type against registered form-data media-types: - # "application/x-www-form-urlencoded" and "multipart/form-data". The - # list of form-data media types can be modified through the - # +FORM_DATA_MEDIA_TYPES+ array. - def form_data? - FORM_DATA_MEDIA_TYPES.include?(media_type) - end - - # Returns the data recieved in the query string. - def GET - if @env["rack.request.query_string"] == query_string - @env["rack.request.query_hash"] - else - @env["rack.request.query_string"] = query_string - @env["rack.request.query_hash"] = - Utils.parse_query(query_string) - end - end - - # Returns the data recieved in the request body. - # - # This method support both application/x-www-form-urlencoded and - # multipart/form-data. - def POST - if @env["rack.request.form_input"].eql? @env["rack.input"] - @env["rack.request.form_hash"] - elsif form_data? - @env["rack.request.form_input"] = @env["rack.input"] - unless @env["rack.request.form_hash"] = - Utils::Multipart.parse_multipart(env) - @env["rack.request.form_vars"] = @env["rack.input"].read - @env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"]) - end - @env["rack.request.form_hash"] - else - {} - end - end - - # The union of GET and POST data. - def params - self.GET.update(self.POST) - rescue EOFError => e - self.GET - end - - # shortcut for request.params[key] - def [](key) - params[key.to_s] - end - - # shortcut for request.params[key] = value - def []=(key, value) - params[key.to_s] = value - end - - # like Hash#values_at - def values_at(*keys) - keys.map{|key| params[key] } - end - - # the referer of the client or '/' - def referer - @env['HTTP_REFERER'] || '/' - end - alias referrer referer - - - def cookies - return {} unless @env["HTTP_COOKIE"] - - if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] - @env["rack.request.cookie_hash"] - else - @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"] - # According to RFC 2109: - # If multiple cookies satisfy the criteria above, they are ordered in - # the Cookie header such that those with more specific Path attributes - # precede those with less specific. Ordering with respect to other - # attributes (e.g., Domain) is unspecified. - @env["rack.request.cookie_hash"] = - Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)| - h[k] = Array === v ? v.first : v - h - } - end - end - - def xhr? - @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" - end - - # Tries to return a remake of the original request URL as a string. - def url - url = scheme + "://" - url << host - - if scheme == "https" && port != 443 || - scheme == "http" && port != 80 - url << ":#{port}" - end - - url << fullpath - - url - end - - def fullpath - path = script_name + path_info - path << "?" << query_string unless query_string.empty? - path - end - - def accept_encoding - @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part| - m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick - - if m - [m[1], (m[2] || 1.0).to_f] - else - raise "Invalid value for Accept-Encoding: #{part.inspect}" - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/response.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/response.rb deleted file mode 100644 index 3ae095f257..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/response.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'rack/request' -require 'rack/utils' - -module Rack - # Rack::Response provides a convenient interface to create a Rack - # response. - # - # It allows setting of headers and cookies, and provides useful - # defaults (a OK response containing HTML). - # - # You can use Response#write to iteratively generate your response, - # but note that this is buffered by Rack::Response until you call - # +finish+. +finish+ however can take a block inside which calls to - # +write+ are syncronous with the Rack response. - # - # Your application's +call+ should end returning Response#finish. - - class Response - def initialize(body=[], status=200, header={}, &block) - @status = status - @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. - merge(header)) - - @writer = lambda { |x| @body << x } - @block = nil - - @body = [] - - if body.respond_to? :to_str - write body.to_str - elsif body.respond_to?(:each) - body.each { |part| - write part.to_s - } - else - raise TypeError, "stringable or iterable required" - end - - yield self if block_given? - end - - attr_reader :header - attr_accessor :status, :body - - def [](key) - header[key] - end - - def []=(key, value) - header[key] = value - end - - def set_cookie(key, value) - case value - when Hash - domain = "; domain=" + value[:domain] if value[:domain] - path = "; path=" + value[:path] if value[:path] - # According to RFC 2109, we need dashes here. - # N.B.: cgi.rb uses spaces... - expires = "; expires=" + value[:expires].clone.gmtime. - strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] - value = value[:value] - end - value = [value] unless Array === value - cookie = Utils.escape(key) + "=" + - value.map { |v| Utils.escape v }.join("&") + - "#{domain}#{path}#{expires}" - - case self["Set-Cookie"] - when Array - self["Set-Cookie"] << cookie - when String - self["Set-Cookie"] = [self["Set-Cookie"], cookie] - when nil - self["Set-Cookie"] = cookie - end - end - - def delete_cookie(key, value={}) - unless Array === self["Set-Cookie"] - self["Set-Cookie"] = [self["Set-Cookie"]].compact - end - - self["Set-Cookie"].reject! { |cookie| - cookie =~ /\A#{Utils.escape(key)}=/ - } - - set_cookie(key, - {:value => '', :path => nil, :domain => nil, - :expires => Time.at(0) }.merge(value)) - end - - - def finish(&block) - @block = block - - if [204, 304].include?(status.to_i) - header.delete "Content-Type" - [status.to_i, header.to_hash, []] - else - [status.to_i, header.to_hash, self] - end - end - alias to_a finish # For *response - - def each(&callback) - @body.each(&callback) - @writer = callback - @block.call(self) if @block - end - - def write(str) - @writer.call str.to_s - str - end - - def close - body.close if body.respond_to?(:close) - end - - def empty? - @block == nil && @body.empty? - end - - alias headers header - - module Helpers - def invalid?; @status < 100 || @status >= 600; end - - def informational?; @status >= 100 && @status < 200; end - def successful?; @status >= 200 && @status < 300; end - def redirection?; @status >= 300 && @status < 400; end - def client_error?; @status >= 400 && @status < 500; end - def server_error?; @status >= 500 && @status < 600; end - - def ok?; @status == 200; end - def forbidden?; @status == 403; end - def not_found?; @status == 404; end - - def redirect?; [301, 302, 303, 307].include? @status; end - def empty?; [201, 204, 304].include? @status; end - - # Headers - attr_reader :headers, :original_headers - - def include?(header) - !!headers[header] - end - - def content_type - headers["Content-Type"] - end - - def content_length - cl = headers["Content-Length"] - cl ? cl.to_i : cl - end - - def location - headers["Location"] - end - end - - include Helpers - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb deleted file mode 100644 index c220b2cb8d..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/abstract/id.rb +++ /dev/null @@ -1,140 +0,0 @@ -# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net -# bugrep: Andreas Zehnder - -require 'rack/utils' -require 'time' - -module Rack - module Session - module Abstract - # ID sets up a basic framework for implementing an id based sessioning - # service. Cookies sent to the client for maintaining sessions will only - # contain an id reference. Only #get_session and #set_session should - # need to be overwritten. - # - # All parameters are optional. - # * :key determines the name of the cookie, by default it is - # 'rack.session' - # * :domain and :path set the related cookie values, by default - # domain is nil, and the path is '/'. - # * :expire_after is the number of seconds in which the session - # cookie will expire. By default it is set not to provide any - # expiry time. - class ID - attr_reader :key - DEFAULT_OPTIONS = { - :key => 'rack.session', - :path => '/', - :domain => nil, - :expire_after => nil - } - - def initialize(app, options={}) - @default_options = self.class::DEFAULT_OPTIONS.merge(options) - @key = @default_options[:key] - @default_context = context app - end - - def call(env) - @default_context.call(env) - end - - def context(app) - Rack::Utils::Context.new self, app do |env| - load_session env - response = app.call(env) - commit_session env, response - response - end - end - - private - - # Extracts the session id from provided cookies and passes it and the - # environment to #get_session. It then sets the resulting session into - # 'rack.session', and places options and session metadata into - # 'rack.session.options'. - def load_session(env) - sid = (env['HTTP_COOKIE']||'')[/#{@key}=([^,;]+)/,1] - sid, session = get_session(env, sid) - unless session.is_a?(Hash) - puts 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG - raise TypeError, 'Session not a Hash' - end - - options = @default_options. - merge({ :id => sid, :by => self, :at => Time.now }) - - env['rack.session'] = session - env['rack.session.options'] = options - - return true - end - - # Acquires the session from the environment and the session id from - # the session options and passes them to #set_session. It then - # proceeds to set a cookie up in the response with the session's id. - def commit_session(env, response) - unless response.is_a?(Array) - puts 'Response: '+response.inspect if $DEBUG - raise ArgumentError, 'Response is not an array.' - end - - options = env['rack.session.options'] - unless options.is_a?(Hash) - puts 'Options: '+options.inspect if $DEBUG - raise TypeError, 'Options not a Hash' - end - - sid, time, z = options.values_at(:id, :at, :by) - unless self == z - warn "#{self} not managing this session." - return - end - - unless env['rack.session'].is_a?(Hash) - warn 'Session: '+sid.inspect+"\n"+session.inspect if $DEBUG - raise TypeError, 'Session not a Hash' - end - - unless set_session(env, sid) - warn "Session not saved." if $DEBUG - warn "#{env['rack.session'].inspect} has been lost."if $DEBUG - return false - end - - cookie = Utils.escape(@key)+'='+Utils.escape(sid) - cookie<< "; domain=#{options[:domain]}" if options[:domain] - cookie<< "; path=#{options[:path]}" if options[:path] - if options[:expire_after] - expiry = time + options[:expire_after] - cookie<< "; expires=#{expiry.httpdate}" - end - - case a = (h = response[1])['Set-Cookie'] - when Array then a << cookie - when String then h['Set-Cookie'] = [a, cookie] - when nil then h['Set-Cookie'] = cookie - end - - return true - end - - # Should return [session_id, session]. All thread safety and session - # retrival proceedures should occur here. - # If nil is provided as the session id, generation of a new valid id - # should occur within. - def get_session(env, sid) - raise '#get_session needs to be implemented.' - end - - # All thread safety and session storage proceedures should occur here. - # Should return true or false dependant on whether or not the session - # was saved or not. - def set_session(env, sid) - raise '#set_session needs to be implemented.' - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb deleted file mode 100644 index 154a87c3ea..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/cookie.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Rack - - module Session - - # Rack::Session::Cookie provides simple cookie based session management. - # The session is a Ruby Hash stored as base64 encoded marshalled data - # set to :key (default: rack.session). - # - # Example: - # - # use Rack::Session::Cookie, :key => 'rack.session', - # :domain => 'foo.com', - # :path => '/', - # :expire_after => 2592000 - # - # All parameters are optional. - - class Cookie - - def initialize(app, options={}) - @app = app - @key = options[:key] || "rack.session" - @default_options = {:domain => nil, - :path => "/", - :expire_after => nil}.merge(options) - end - - def call(env) - load_session(env) - status, headers, body = @app.call(env) - commit_session(env, status, headers, body) - end - - private - - def load_session(env) - request = Rack::Request.new(env) - session_data = request.cookies[@key] - - begin - session_data = session_data.unpack("m*").first - session_data = Marshal.load(session_data) - env["rack.session"] = session_data - rescue - env["rack.session"] = Hash.new - end - - env["rack.session.options"] = @default_options.dup - end - - def commit_session(env, status, headers, body) - session_data = Marshal.dump(env["rack.session"]) - session_data = [session_data].pack("m*") - - if session_data.size > (4096 - @key.size) - env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.") - [status, headers, body] - else - options = env["rack.session.options"] - cookie = Hash.new - cookie[:value] = session_data - cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? - response = Rack::Response.new(body, status, headers) - response.set_cookie(@key, cookie.merge(options)) - response.to_a - end - end - - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb deleted file mode 100644 index 7cda9d8697..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/memcache.rb +++ /dev/null @@ -1,97 +0,0 @@ -# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net - -require 'rack/session/abstract/id' -require 'memcache' - -module Rack - module Session - # Rack::Session::Memcache provides simple cookie based session management. - # Session data is stored in memcached. The corresponding session key is - # maintained in the cookie. - # You may treat Session::Memcache as you would Session::Pool with the - # following caveats. - # - # * Setting :expire_after to 0 would note to the Memcache server to hang - # onto the session data until it would drop it according to it's own - # specifications. However, the cookie sent to the client would expire - # immediately. - # - # Note that memcache does drop data before it may be listed to expire. For - # a full description of behaviour, please see memcache's documentation. - - class Memcache < Abstract::ID - attr_reader :mutex, :pool - DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge({ - :namespace => 'rack:session', - :memcache_server => 'localhost:11211' - }) - - def initialize(app, options={}) - super - @pool = MemCache.new @default_options[:memcache_server], @default_options - unless @pool.servers.any?{|s|s.alive?} - raise "#{self} unable to find server during initialization." - end - @mutex = Mutex.new - end - - private - - def get_session(env, sid) - session = sid && @pool.get(sid) - unless session and session.is_a?(Hash) - session = {} - lc = 0 - @mutex.synchronize do - begin - raise RuntimeError, 'Unique id finding looping excessively' if (lc+=1) > 1000 - sid = "%08x" % rand(0xffffffff) - ret = @pool.add(sid, session) - end until /^STORED/ =~ ret - end - end - class << session - @deleted = [] - def delete key - (@deleted||=[]) << key - super - end - end - [sid, session] - rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted - warn "#{self} is unable to find server." - warn $!.inspect - return [ nil, {} ] - end - - def set_session(env, sid) - session = env['rack.session'] - options = env['rack.session.options'] - expiry = options[:expire_after] || 0 - o, s = @mutex.synchronize do - old_session = @pool.get(sid) - unless old_session.is_a?(Hash) - warn 'Session not properly initialized.' if $DEBUG - old_session = {} - @pool.add sid, old_session, expiry - end - session.instance_eval do - @deleted.each{|k| old_session.delete(k) } if defined? @deleted - end - @pool.set sid, old_session.merge(session), expiry - [old_session, session] - end - s.each do |k,v| - next unless o.has_key?(k) and v != o[k] - warn "session value assignment collision at #{k.inspect}:"+ - "\n\t#{o[k].inspect}\n\t#{v.inspect}" - end if $DEBUG and env['rack.multithread'] - return true - rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted - warn "#{self} is unable to find server." - warn $!.inspect - return false - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb deleted file mode 100644 index 6e5e788210..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/session/pool.rb +++ /dev/null @@ -1,73 +0,0 @@ -# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net -# THANKS: -# apeiros, for session id generation, expiry setup, and threadiness -# sergio, threadiness and bugreps - -require 'rack/session/abstract/id' -require 'thread' - -module Rack - module Session - # Rack::Session::Pool provides simple cookie based session management. - # Session data is stored in a hash held by @pool. - # In the context of a multithreaded environment, sessions being - # committed to the pool is done in a merging manner. - # - # Example: - # myapp = MyRackApp.new - # sessioned = Rack::Session::Pool.new(myapp, - # :key => 'rack.session', - # :domain => 'foo.com', - # :path => '/', - # :expire_after => 2592000 - # ) - # Rack::Handler::WEBrick.run sessioned - - class Pool < Abstract::ID - attr_reader :mutex, :pool - DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.dup - - def initialize(app, options={}) - super - @pool = Hash.new - @mutex = Mutex.new - end - - private - - def get_session(env, sid) - session = @mutex.synchronize do - unless sess = @pool[sid] and ((expires = sess[:expire_at]).nil? or expires > Time.now) - @pool.delete_if{|k,v| expiry = v[:expire_at] and expiry < Time.now } - begin - sid = "%08x" % rand(0xffffffff) - end while @pool.has_key?(sid) - end - @pool[sid] ||= {} - end - [sid, session] - end - - def set_session(env, sid) - options = env['rack.session.options'] - expiry = options[:expire_after] && options[:at]+options[:expire_after] - @mutex.synchronize do - old_session = @pool[sid] - old_session[:expire_at] = expiry if expiry - session = old_session.merge(env['rack.session']) - @pool[sid] = session - session.each do |k,v| - next unless old_session.has_key?(k) and v != old_session[k] - warn "session value assignment collision at #{k}: #{old_session[k]} <- #{v}" - end if $DEBUG and env['rack.multithread'] - end - return true - rescue - warn "#{self} is unable to find server." - warn "#{env['rack.session'].inspect} has been lost." - warn $!.inspect - return false - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showexceptions.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showexceptions.rb deleted file mode 100644 index 1c85dec19a..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showexceptions.rb +++ /dev/null @@ -1,344 +0,0 @@ -require 'ostruct' -require 'erb' -require 'rack/request' - -module Rack - # Rack::ShowExceptions catches all exceptions raised from the app it - # wraps. It shows a useful backtrace with the sourcefile and - # clickable context, the whole Rack environment and the request - # data. - # - # Be careful when you use this on public-facing sites as it could - # reveal information helpful to attackers. - - class ShowExceptions - CONTEXT = 7 - - def initialize(app) - @app = app - @template = ERB.new(TEMPLATE) - end - - def call(env) - @app.call(env) - rescue StandardError, LoadError, SyntaxError => e - [500, {"Content-Type" => "text/html"}, pretty(env, e)] - end - - def pretty(env, exception) - req = Rack::Request.new(env) - path = (req.script_name + req.path_info).squeeze("/") - - frames = exception.backtrace.map { |line| - frame = OpenStruct.new - if line =~ /(.*?):(\d+)(:in `(.*)')?/ - frame.filename = $1 - frame.lineno = $2.to_i - frame.function = $4 - - begin - lineno = frame.lineno-1 - lines = ::File.readlines(frame.filename) - frame.pre_context_lineno = [lineno-CONTEXT, 0].max - frame.pre_context = lines[frame.pre_context_lineno...lineno] - frame.context_line = lines[lineno].chomp - frame.post_context_lineno = [lineno+CONTEXT, lines.size].min - frame.post_context = lines[lineno+1..frame.post_context_lineno] - rescue - end - - frame - else - nil - end - }.compact - - env["rack.errors"].puts "#{exception.class}: #{exception.message}" - env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l } - env["rack.errors"].flush - - [@template.result(binding)] - end - - def h(obj) # :nodoc: - case obj - when String - Utils.escape_html(obj) - else - Utils.escape_html(obj.inspect) - end - end - - # :stopdoc: - -# adapted from Django -# Copyright (c) 2005, the Lawrence Journal-World -# Used under the modified BSD license: -# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 -TEMPLATE = <<'HTML' - - - - - - <%=h exception.class %> at <%=h path %> - - - - - -
    -

    <%=h exception.class %> at <%=h path %>

    -

    <%=h exception.message %>

    - - - - - - -
    Ruby<%=h frames.first.filename %>: in <%=h frames.first.function %>, line <%=h frames.first.lineno %>
    Web<%=h req.request_method %> <%=h(req.host + path)%>
    - -

    Jump to:

    - -
    - -
    -

    Traceback (innermost first)

    -
      -<% frames.each { |frame| %> -
    • - <%=h frame.filename %>: in <%=h frame.function %> - - <% if frame.context_line %> -
      - <% if frame.pre_context %> -
        - <% frame.pre_context.each { |line| %> -
      1. <%=h line %>
      2. - <% } %> -
      - <% end %> - -
        -
      1. <%=h frame.context_line %>...
      - - <% if frame.post_context %> -
        - <% frame.post_context.each { |line| %> -
      1. <%=h line %>
      2. - <% } %> -
      - <% end %> -
      - <% end %> -
    • -<% } %> -
    -
    - -
    -

    Request information

    - -

    GET

    - <% unless req.GET.empty? %> - - - - - - - - - <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> - - - - - <% } %> - -
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    - <% else %> -

    No GET data.

    - <% end %> - -

    POST

    - <% unless req.POST.empty? %> - - - - - - - - - <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> - - - - - <% } %> - -
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    - <% else %> -

    No POST data.

    - <% end %> - - - - <% unless req.cookies.empty? %> - - - - - - - - - <% req.cookies.each { |key, val| %> - - - - - <% } %> - -
    VariableValue
    <%=h key %>
    <%=h val.inspect %>
    - <% else %> -

    No cookie data.

    - <% end %> - -

    Rack ENV

    - - - - - - - - - <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> - - - - - <% } %> - -
    VariableValue
    <%=h key %>
    <%=h val %>
    - -
    - -
    -

    - You're seeing this error because you use Rack::ShowException. -

    -
    - - - -HTML - - # :startdoc: - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb deleted file mode 100644 index ca81f7d83f..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/showstatus.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'erb' -require 'rack/request' -require 'rack/utils' - -module Rack - # Rack::ShowStatus catches all empty responses the app it wraps and - # replaces them with a site explaining the error. - # - # Additional details can be put into rack.showstatus.detail - # and will be shown as HTML. If such details exist, the error page - # is always rendered, even if the reply was not empty. - - class ShowStatus - def initialize(app) - @app = app - @template = ERB.new(TEMPLATE) - end - - def call(env) - status, headers, body = @app.call(env) - - # client or server error, or explicit message - if status.to_i >= 400 && - (body.empty? rescue false) || env["rack.showstatus.detail"] - req = Rack::Request.new(env) - message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s - detail = env["rack.showstatus.detail"] || message - body = @template.result(binding) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size - [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] - else - [status, headers, body] - end - end - - def h(obj) # :nodoc: - case obj - when String - Utils.escape_html(obj) - else - Utils.escape_html(obj.inspect) - end - end - - # :stopdoc: - -# adapted from Django -# Copyright (c) 2005, the Lawrence Journal-World -# Used under the modified BSD license: -# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 -TEMPLATE = <<'HTML' - - - - - <%=h message %> at <%=h req.script_name + req.path_info %> - - - - -
    -

    <%=h message %> (<%= status.to_i %>)

    - - - - - - - - - -
    Request Method:<%=h req.request_method %>
    Request URL:<%=h req.url %>
    -
    -
    -

    <%= detail %>

    -
    - -
    -

    - You're seeing this error because you use Rack::ShowStatus. -

    -
    - - -HTML - - # :startdoc: - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb deleted file mode 100644 index 168e8f83b2..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/static.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Rack - - # The Rack::Static middleware intercepts requests for static files - # (javascript files, images, stylesheets, etc) based on the url prefixes - # passed in the options, and serves them using a Rack::File object. This - # allows a Rack stack to serve both static and dynamic content. - # - # Examples: - # use Rack::Static, :urls => ["/media"] - # will serve all requests beginning with /media from the "media" folder - # located in the current directory (ie media/*). - # - # use Rack::Static, :urls => ["/css", "/images"], :root => "public" - # will serve all requests beginning with /css or /images from the folder - # "public" in the current directory (ie public/css/* and public/images/*) - - class Static - - def initialize(app, options={}) - @app = app - @urls = options[:urls] || ["/favicon.ico"] - root = options[:root] || Dir.pwd - @file_server = Rack::File.new(root) - end - - def call(env) - path = env["PATH_INFO"] - can_serve = @urls.any? { |url| path.index(url) == 0 } - - if can_serve - @file_server.call(env) - else - @app.call(env) - end - end - - end -end diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb deleted file mode 100644 index b00f2d7a19..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/urlmap.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Rack - # Rack::URLMap takes a hash mapping urls or paths to apps, and - # dispatches accordingly. Support for HTTP/1.1 host names exists if - # the URLs start with http:// or https://. - # - # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part - # relevant for dispatch is in the SCRIPT_NAME, and the rest in the - # PATH_INFO. This should be taken care of when you need to - # reconstruct the URL in order to create links. - # - # URLMap dispatches in such a way that the longest paths are tried - # first, since they are most specific. - - class URLMap - def initialize(map) - @mapping = map.map { |location, app| - if location =~ %r{\Ahttps?://(.*?)(/.*)} - host, location = $1, $2 - else - host = nil - end - - unless location[0] == ?/ - raise ArgumentError, "paths need to start with /" - end - location = location.chomp('/') - - [host, location, app] - }.sort_by { |(h, l, a)| -l.size } # Longest path first - end - - def call(env) - path = env["PATH_INFO"].to_s.squeeze("/") - hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') - @mapping.each { |host, location, app| - next unless (hHost == host || sName == host \ - || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) - next unless location == path[0, location.size] - next unless path[location.size] == nil || path[location.size] == ?/ - env["SCRIPT_NAME"] += location - env["PATH_INFO"] = path[location.size..-1] - return app.call(env) - } - [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]] - end - end -end - diff --git a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb b/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb deleted file mode 100644 index 25254bbdf2..0000000000 --- a/actionpack/lib/action_controller/vendor/rack-0.4.0/rack/utils.rb +++ /dev/null @@ -1,318 +0,0 @@ -require 'tempfile' - -module Rack - # Rack::Utils contains a grab-bag of useful methods for writing web - # applications adopted from all kinds of Ruby libraries. - - module Utils - # Performs URI escaping so that you can construct proper - # query strings faster. Use this rather than the cgi.rb - # version since it's faster. (Stolen from Camping). - def escape(s) - s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { - '%'+$1.unpack('H2'*$1.size).join('%').upcase - }.tr(' ', '+') - end - module_function :escape - - # Unescapes a URI escaped string. (Stolen from Camping). - def unescape(s) - s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ - [$1.delete('%')].pack('H*') - } - end - module_function :unescape - - # Stolen from Mongrel, with some small modifications: - # Parses a query string by breaking it up at the '&' - # and ';' characters. You can also use this to parse - # cookies by changing the characters used in the second - # parameter (which defaults to '&;'). - - def parse_query(qs, d = '&;') - params = {} - - (qs || '').split(/[#{d}] */n).each do |p| - k, v = unescape(p).split('=', 2) - - if cur = params[k] - if cur.class == Array - params[k] << v - else - params[k] = [cur, v] - end - else - params[k] = v - end - end - - return params - end - module_function :parse_query - - def build_query(params) - params.map { |k, v| - if v.class == Array - build_query(v.map { |x| [k, x] }) - else - escape(k) + "=" + escape(v) - end - }.join("&") - end - module_function :build_query - - # Escape ampersands, brackets and quotes to their HTML/XML entities. - def escape_html(string) - string.to_s.gsub("&", "&"). - gsub("<", "<"). - gsub(">", ">"). - gsub("'", "'"). - gsub('"', """) - end - module_function :escape_html - - def select_best_encoding(available_encodings, accept_encoding) - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - - expanded_accept_encoding = - accept_encoding.map { |m, q| - if m == "*" - (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] } - else - [[m, q]] - end - }.inject([]) { |mem, list| - mem + list - } - - encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m } - - unless encoding_candidates.include?("identity") - encoding_candidates.push("identity") - end - - expanded_accept_encoding.find_all { |m, q| - q == 0.0 - }.each { |m, _| - encoding_candidates.delete(m) - } - - return (encoding_candidates & available_encodings)[0] - end - module_function :select_best_encoding - - # The recommended manner in which to implement a contexting application - # is to define a method #context in which a new Context is instantiated. - # - # As a Context is a glorified block, it is highly recommended that you - # define the contextual block within the application's operational scope. - # This would typically the application as you're place into Rack's stack. - # - # class MyObject - # ... - # def context app - # Rack::Utils::Context.new app do |env| - # do_stuff - # response = app.call(env) - # do_more_stuff - # end - # end - # ... - # end - # - # mobj = MyObject.new - # app = mobj.context other_app - # Rack::Handler::Mongrel.new app - class Context < Proc - alias_method :old_inspect, :inspect - attr_reader :for, :app - def initialize app_f, app_r - raise 'running context not provided' unless app_f - raise 'running context does not respond to #context' unless app_f.respond_to? :context - raise 'application context not provided' unless app_r - raise 'application context does not respond to #call' unless app_r.respond_to? :call - @for = app_f - @app = app_r - end - def inspect - "#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}" - end - def context app_r - raise 'new application context not provided' unless app_r - raise 'new application context does not respond to #call' unless app_r.respond_to? :call - @for.context app_r - end - def pretty_print pp - pp.text old_inspect - pp.nest 1 do - pp.breakable - pp.text '=for> ' - pp.pp @for - pp.breakable - pp.text '=app> ' - pp.pp @app - end - end - end - - # A case-normalizing Hash, adjusting on [] and []=. - class HeaderHash < Hash - def initialize(hash={}) - hash.each { |k, v| self[k] = v } - end - - def to_hash - {}.replace(self) - end - - def [](k) - super capitalize(k) - end - - def []=(k, v) - super capitalize(k), v - end - - def capitalize(k) - k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase } - end - end - - # Every standard HTTP code mapped to the appropriate message. - # Stolen from Mongrel. - HTTP_STATUS_CODES = { - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Moved Temporarily', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Time-out', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Large', - 415 => 'Unsupported Media Type', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Time-out', - 505 => 'HTTP Version not supported' - } - - # A multipart form data parser, adapted from IOWA. - # - # Usually, Rack::Request#POST takes care of calling this. - - module Multipart - EOL = "\r\n" - - def self.parse_multipart(env) - unless env['CONTENT_TYPE'] =~ - %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n - nil - else - boundary = "--#{$1}" - - params = {} - buf = "" - content_length = env['CONTENT_LENGTH'].to_i - input = env['rack.input'] - - boundary_size = boundary.size + EOL.size - bufsize = 16384 - - content_length -= boundary_size - - status = input.read(boundary_size) - raise EOFError, "bad content body" unless status == boundary + EOL - - rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/ - - loop { - head = nil - body = '' - filename = content_type = name = nil - - until head && buf =~ rx - if !head && i = buf.index("\r\n\r\n") - head = buf.slice!(0, i+2) # First \r\n - buf.slice!(0, 2) # Second \r\n - - filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1] - content_type = head[/Content-Type: (.*)\r\n/ni, 1] - name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1] - - if filename - body = Tempfile.new("RackMultipart") - body.binmode if body.respond_to?(:binmode) - end - - next - end - - # Save the read body part. - if head && (boundary_size+4 < buf.size) - body << buf.slice!(0, buf.size - (boundary_size+4)) - end - - c = input.read(bufsize < content_length ? bufsize : content_length) - raise EOFError, "bad content body" if c.nil? || c.empty? - buf << c - content_length -= c.size - end - - # Save the rest. - if i = buf.index(rx) - body << buf.slice!(0, i) - buf.slice!(0, boundary_size+2) - - content_length = -1 if $1 == "--" - end - - if filename - body.rewind - data = {:filename => filename, :type => content_type, - :name => name, :tempfile => body, :head => head} - else - data = body - end - - if name - if name =~ /\[\]\z/ - params[name] ||= [] - params[name] << data - else - params[name] = data - end - end - - break if buf.empty? || content_length == -1 - } - - params - end - end - end - end -end diff --git a/actionpack/lib/action_controller/vendor/rack.rb b/actionpack/lib/action_controller/vendor/rack.rb deleted file mode 100644 index 2438059f76..0000000000 --- a/actionpack/lib/action_controller/vendor/rack.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rubygems' - -begin - gem 'rack', '~> 0.4.0' - require 'rack' -rescue Gem::LoadError - $LOAD_PATH << "#{File.dirname(__FILE__)}/rack-0.4.0" - require 'rack' -end -- cgit v1.2.3 From 4073a6d0a2f5926e10f06fe1702db7b1b7a20751 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 25 Nov 2008 19:49:49 -0800 Subject: Remove XmlSimple dependencies --- actionpack/test/controller/webservice_test.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 32f67ddd6c..6d2b3e4f23 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -101,14 +101,13 @@ class WebServiceTest < Test::Unit::TestCase end def test_post_xml_using_an_attributted_node_named_type - ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } + ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } process('POST', 'application/xml', 'Arial,123') assert_equal 'type, z', @controller.response.body assert @controller.params.has_key?(:type) - assert_equal 'string', @controller.params['type']['type'] - assert_equal 'Arial,12', @controller.params['type']['content'] - assert_equal '3', @controller.params['z'] + assert_equal 'Arial,12', @controller.params['type'], @controller.params.inspect + assert_equal '3', @controller.params['z'], @controller.params.inspect end def test_register_and_use_yaml @@ -128,7 +127,7 @@ class WebServiceTest < Test::Unit::TestCase end def test_register_and_use_xml_simple - ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } + ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } process('POST', 'application/xml', 'content...SimpleXml' ) assert_equal 'summary, title', @controller.response.body assert @controller.params.has_key?(:summary) -- cgit v1.2.3 From fef6c32afe2276dffa0347e25808a86e7a101af1 Mon Sep 17 00:00:00 2001 From: Aaron Batalion Date: Mon, 24 Nov 2008 02:24:19 -0500 Subject: Added optimal formatted routes to rails, deprecating the formatted_* methods, and reducing routes creation by 50% [#1359 state:committed] Signed-off-by: David Heinemeier Hansson --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_controller/resources.rb | 4 +-- .../lib/action_controller/routing/builder.rb | 2 ++ .../lib/action_controller/routing/optimisations.rb | 2 +- actionpack/lib/action_controller/routing/route.rb | 5 ++++ .../lib/action_controller/routing/route_set.rb | 10 ++++++- .../lib/action_controller/routing/segments.rb | 31 +++++++++++++++++++ actionpack/test/controller/resources_test.rb | 18 +++++------ actionpack/test/controller/url_rewriter_test.rb | 35 ++++++++++++++++++++++ 9 files changed, 95 insertions(+), 14 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 1d6e5455da..c469564eb5 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion] + * Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Koz] * Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz] diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index c170528af1..b6cfe2dd68 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -639,10 +639,8 @@ module ActionController formatted_route_path = "#{route_path}.:format" if route_name && @set.named_routes[route_name.to_sym].nil? - map.named_route(route_name, route_path, action_options) - map.named_route("formatted_#{route_name}", formatted_route_path, action_options) + map.named_route(route_name, formatted_route_path, action_options) else - map.connect(route_path, action_options) map.connect(formatted_route_path, action_options) end end diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index d4e501e780..44d759444a 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -34,6 +34,8 @@ module ActionController def segment_for(string) segment = case string + when /\A\.(:format)?\// + OptionalFormatSegment.new when /\A:(\w+)/ key = $1.to_sym key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb index 4522ebcb1a..714cf97861 100644 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ b/actionpack/lib/action_controller/routing/optimisations.rb @@ -65,7 +65,7 @@ module ActionController # rather than triggering the expensive logic in +url_for+. class PositionalArguments < Optimiser def guard_conditions - number_of_arguments = route.segment_keys.size + number_of_arguments = route.required_segment_keys.size # if they're using foo_url(:id=>2) it's one # argument, but we don't want to generate /foos/id2 if number_of_arguments == 1 diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb index a610ec7e84..e4dfdb1718 100644 --- a/actionpack/lib/action_controller/routing/route.rb +++ b/actionpack/lib/action_controller/routing/route.rb @@ -35,6 +35,11 @@ module ActionController segment.key if segment.respond_to? :key end.compact end + + def required_segment_keys + required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) } + required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact + end # Build a query string from the keys of the given hash. If +only_keys+ # is given (as an array), only the keys indicated will be used to build diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 3bb25dbba9..89cdf9d0f5 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -185,6 +185,14 @@ module ActionController end url_for(#{hash_access_method}(opts)) + + end + #Add an alias to support the now deprecated formatted_* URL. + def formatted_#{selector}(*args) + ActiveSupport::Deprecation.warn( + "formatted_#{selector}() has been deprecated. please pass format to the standard" + + "#{selector}() method instead.", caller) + #{selector}(*args) end protected :#{selector} end_eval @@ -361,7 +369,7 @@ module ActionController end # don't use the recalled keys when determining which routes to check - routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }] + routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] routes.each do |route| results = route.__send__(method, options, merged, expire_on) diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index f6b03edcca..5dda3d4d00 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -308,5 +308,36 @@ module ActionController end end end + + # The OptionalFormatSegment allows for any resource route to have an optional + # :format, which decreases the amount of routes created by 50%. + class OptionalFormatSegment < DynamicSegment + + def initialize(key = nil, options = {}) + super(:format, {:optional => true}.merge(options)) + end + + def interpolation_chunk + "." + super + end + + def regexp_chunk + '(\.[^/?\.]+)?' + end + + def to_s + '(.:format)?' + end + + #the value should not include the period (.) + def match_extraction(next_capture) + %[ + if (m = match[#{next_capture}]) + params[:#{key}] = URI.unescape(m.from(1)) + end + ] + end + end + end end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 79b28c0773..8dedeb23f6 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -187,7 +187,7 @@ class ResourcesTest < ActionController::TestCase assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| actions.keys.each do |action| - assert_named_route "/threads/1/messages/#{action}.xml", "formatted_#{action}_thread_messages_path", :action => action, :format => 'xml' + assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", :action => action, :format => 'xml' end end end @@ -316,7 +316,7 @@ class ResourcesTest < ActionController::TestCase end assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| - assert_named_route preview_path, :formatted_preview_new_thread_message_path, preview_options + assert_named_route preview_path, :preview_new_thread_message_path, preview_options end end end @@ -1130,14 +1130,14 @@ class ResourcesTest < ActionController::TestCase end assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options] - assert_named_route "#{full_path}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml') assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') - assert_named_route "#{shallow_path}/1.xml", "formatted_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') + assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options] - assert_named_route "#{full_path}/#{new_action}.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml') assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1') - assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "formatted_edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') + assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml') yield options[:options] if block_given? end @@ -1189,12 +1189,12 @@ class ResourcesTest < ActionController::TestCase name_prefix = options[:name_prefix] assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", options[:options] - assert_named_route "#{full_path}.xml", "formatted_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", options[:options] - assert_named_route "#{full_path}/new.xml", "formatted_new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", options[:options] - assert_named_route "#{full_path}/edit.xml", "formatted_edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml') end def assert_named_route(expected, route, options) diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 8bc343e2ea..bb714a0245 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -301,6 +301,41 @@ class UrlWriterTests < ActionController::TestCase assert_generates("/image", :controller=> :image) end + def test_named_routes_with_nil_keys + add_host! + ActionController::Routing::Routes.draw do |map| + map.main '', :controller => 'posts' + map.resources :posts + map.connect ':controller/:action/:id' + end + # We need to create a new class in order to install the new named route. + kls = Class.new { include ActionController::UrlWriter } + controller = kls.new + params = {:action => :index, :controller => :posts, :format => :xml} + assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) + params[:format] = nil + assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) + ensure + ActionController::Routing::Routes.load! + end + + def test_formatted_url_methods_are_deprecated + ActionController::Routing::Routes.draw do |map| + map.resources :posts + end + # We need to create a new class in order to install the new named route. + kls = Class.new { include ActionController::UrlWriter } + controller = kls.new + params = {:id => 1, :format => :xml} + assert_deprecated do + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) + end + assert_deprecated do + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) + end + ensure + ActionController::Routing::Routes.load! + end private def extract_params(url) url.split('?', 2).last.split('&') -- cgit v1.2.3 From a88094fd7a19a4b2d5c5b5044b10146e6c5c7245 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 26 Nov 2008 02:05:28 -0800 Subject: No need to have #generate and #generate_extras per instance --- actionpack/lib/action_controller/routing/route.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb index a610ec7e84..91c89fe16b 100644 --- a/actionpack/lib/action_controller/routing/route.rb +++ b/actionpack/lib/action_controller/routing/route.rb @@ -122,6 +122,16 @@ module ActionController super end + def generate(options, hash, expire_on = {}) + path, hash = generate_raw(options, hash, expire_on) + append_query_string(path, hash, extra_keys(options)) + end + + def generate_extras(options, hash, expire_on = {}) + path, hash = generate_raw(options, hash, expire_on) + [path, extra_keys(options)] + end + private def requirement_for(key) return requirements[key] if requirements.key? key @@ -150,11 +160,6 @@ module ActionController # the query string. (Never use keys from the recalled request when building the # query string.) - method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" raw_method end -- cgit v1.2.3 From 40b40c487040d9c721d486e8ec8cfbc53a8cd79a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 26 Nov 2008 15:57:36 +0100 Subject: Added support for multiple routes files and made draw not clear the map so they can be additive --- .../lib/action_controller/routing/route_set.rb | 54 +++++++++++++++++----- actionpack/test/controller/routing_test.rb | 29 ++++++++++-- 2 files changed, 67 insertions(+), 16 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 89cdf9d0f5..a9690d1807 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -200,9 +200,11 @@ module ActionController end end - attr_accessor :routes, :named_routes, :configuration_file + attr_accessor :routes, :named_routes, :configuration_files def initialize + self.configuration_files = [] + self.routes = [] self.named_routes = NamedRouteCollection.new @@ -216,7 +218,6 @@ module ActionController end def draw - clear! yield Mapper.new(self) install_helpers end @@ -240,8 +241,22 @@ module ActionController routes.empty? end + def add_configuration_file(path) + self.configuration_files << path + end + + # Deprecated accessor + def configuration_file=(path) + add_configuration_file(path) + end + + # Deprecated accessor + def configuration_file + configuration_files + end + def load! - Routing.use_controllers! nil # Clear the controller cache so we may discover new ones + Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones clear! load_routes! end @@ -250,24 +265,39 @@ module ActionController alias reload! load! def reload - if @routes_last_modified && configuration_file - mtime = File.stat(configuration_file).mtime - # if it hasn't been changed, then just return - return if mtime == @routes_last_modified - # if it has changed then record the new time and fall to the load! below - @routes_last_modified = mtime + if configuration_files.any? && @routes_last_modified + if routes_changed_at == @routes_last_modified + return # routes didn't change, don't reload + else + @routes_last_modified = routes_changed_at + end end + load! end def load_routes! - if configuration_file - load configuration_file - @routes_last_modified = File.stat(configuration_file).mtime + if configuration_files.any? + configuration_files.each { |config| load(config) } + @routes_last_modified = routes_changed_at else add_route ":controller/:action/:id" end end + + def routes_changed_at + routes_changed_at = nil + + configuration_files.each do |config| + config_changed_at = File.stat(config).mtime + + if routes_changed_at.nil? || config_changed_at > routes_changed_at + routes_changed_at = config_changed_at + end + end + + routes_changed_at + end def add_route(path, options = {}) route = builder.build(path, options) diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index d62c7a1743..d5b6bd6b2a 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -747,12 +747,16 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do ActionController::Base.optimise_named_routes = true @rs = ::ActionController::Routing::RouteSet.new - @rs.draw {|m| m.connect ':controller/:action/:id' } ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) end + + def teardown + @rs.clear! + end def test_default_setup + @rs.draw {|m| m.connect ':controller/:action/:id' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) @@ -769,6 +773,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def test_ignores_leading_slash + @rs.clear! @rs.draw {|m| m.connect '/:controller/:action/:id'} test_default_setup end @@ -1002,6 +1007,8 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def test_changing_controller + @rs.draw {|m| m.connect ':controller/:action/:id' } + assert_equal '/admin/stuff/show/10', rs.generate( {:controller => 'stuff', :action => 'show', :id => 10}, {:controller => 'admin/user', :action => 'index'} @@ -1155,10 +1162,12 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def test_action_expiry + @rs.draw {|m| m.connect ':controller/:action/:id' } assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) end def test_recognition_with_uppercase_controller_name + @rs.draw {|m| m.connect ':controller/:action/:id' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) @@ -2399,13 +2408,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def setup routes.instance_variable_set '@routes_last_modified', nil silence_warnings { Object.const_set :RAILS_ROOT, '.' } - ActionController::Routing::Routes.configuration_file = File.join(RAILS_ROOT, 'config', 'routes.rb') + routes.add_configuration_file(File.join(RAILS_ROOT, 'config', 'routes.rb')) @stat = stub_everything end def teardown - ActionController::Routing::Routes.configuration_file = nil + ActionController::Routing::Routes.configuration_files.clear Object.send :remove_const, :RAILS_ROOT end @@ -2448,12 +2457,24 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def test_load_with_configuration - routes.configuration_file = "foobarbaz" + routes.configuration_files.clear + routes.add_configuration_file("foobarbaz") File.expects(:stat).returns(@stat) routes.expects(:load).with("foobarbaz") routes.reload end + + def test_load_multiple_configurations + routes.add_configuration_file("engines.rb") + + File.expects(:stat).at_least_once.returns(@stat) + + routes.expects(:load).with('./config/routes.rb') + routes.expects(:load).with('engines.rb') + + routes.reload + end private def routes -- cgit v1.2.3 From 7d8f9ef0517c5e83b0e6042c3747e9cfe2b0a4ca Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 26 Nov 2008 20:26:55 +0100 Subject: Fix routing test and add changelog note about draw no longer clearing the route set --- actionpack/CHANGELOG | 2 ++ actionpack/test/controller/url_rewriter_test.rb | 1 + 2 files changed, 3 insertions(+) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index c469564eb5..06d73819fb 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [DHH] + * Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion] * Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Koz] diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index bb714a0245..e9d372544e 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -302,6 +302,7 @@ class UrlWriterTests < ActionController::TestCase end def test_named_routes_with_nil_keys + ActionController::Routing::Routes.clear! add_host! ActionController::Routing::Routes.draw do |map| map.main '', :controller => 'posts' -- cgit v1.2.3 From 9880baa90b330225f989a70c8859a29cec24f1ec Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 26 Nov 2008 17:18:50 -0800 Subject: Ensure Test::Unit::Assertions is available --- actionpack/lib/action_controller/integration.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 333fb742e4..65e3eed678 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -1,5 +1,6 @@ require 'stringio' require 'uri' +require 'active_support/test_case' module ActionController module Integration #:nodoc: -- cgit v1.2.3 From 4d910b033379727e5e7355590c50c72fc75e56db Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 26 Nov 2008 20:54:47 -0600 Subject: Super lazy load view paths in development mode (no indexing or caching at all). Switch layout finders to use view path api to take advantage of cache. --- actionpack/lib/action_controller/base.rb | 5 +- actionpack/lib/action_controller/dispatcher.rb | 1 - actionpack/lib/action_controller/layout.rb | 38 ++++-------- actionpack/lib/action_view/base.rb | 4 +- actionpack/lib/action_view/paths.rb | 67 ++++++++++++++++------ actionpack/lib/action_view/renderable.rb | 2 +- actionpack/test/abstract_unit.rb | 2 +- actionpack/test/controller/layout_test.rb | 14 ++--- .../test/template/compiled_templates_test.rb | 9 --- actionpack/test/template/render_test.rb | 17 +++++- 10 files changed, 88 insertions(+), 71 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 7e38f95076..dca66ff0a5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -867,8 +867,9 @@ module ActionController #:nodoc: end end - response.layout = layout = pick_layout(options) - logger.info("Rendering template within #{layout}") if logger && layout + layout = pick_layout(options) + response.layout = layout.path_without_format_and_extension if layout + logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout if content_type = options[:content_type] response.content_type = content_type.to_s diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 1c7a4b0545..6e4aba2280 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -137,7 +137,6 @@ module ActionController run_callbacks :prepare_dispatch Routing::Routes.reload - ActionController::Base.view_paths.reload! ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear end diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 3631ce86af..54108df06d 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -175,13 +175,12 @@ module ActionController #:nodoc: def default_layout(format) #:nodoc: layout = read_inheritable_attribute(:layout) return layout unless read_inheritable_attribute(:auto_layout) - @default_layout ||= {} - @default_layout[format] ||= default_layout_with_format(format, layout) - @default_layout[format] + find_layout(layout, format) end - def layout_list #:nodoc: - Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } + def find_layout(layout, *formats) #:nodoc: + return layout if layout.respond_to?(:render) + view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) end private @@ -189,7 +188,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? + child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) end end @@ -200,15 +199,6 @@ module ActionController #:nodoc: def normalize_conditions(conditions) conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} end - - def default_layout_with_format(format, layout) - list = layout_list - if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty? - (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil - else - layout - end - end end # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method @@ -217,20 +207,18 @@ module ActionController #:nodoc: # weblog/standard, but layout "standard" will return layouts/standard. def active_layout(passed_layout = nil) layout = passed_layout || self.class.default_layout(default_template_format) + active_layout = case layout - when String then layout when Symbol then __send__(layout) when Proc then layout.call(self) + else layout end - # Explicitly passed layout names with slashes are looked up relative to the template root, - # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative - # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. if active_layout - if active_layout.include?('/') && ! layout_directory?(active_layout) - active_layout + if layout = self.class.find_layout(active_layout, @template.template_format) + layout else - "layouts/#{active_layout}" + raise ActionView::MissingTemplate.new(self.class.view_paths, active_layout) end end end @@ -271,12 +259,6 @@ module ActionController #:nodoc: end end - def layout_directory?(layout_name) - @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false - rescue ActionView::MissingTemplate - false - end - def default_template_format response.template.template_format end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7697848713..da1f283deb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -322,9 +322,7 @@ module ActionView #:nodoc: end # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths["#{template_file_name}.#{template_format}"] - template - elsif template = self.view_paths[template_file_name] + if template = self.view_paths.find_template(template_file_name, template_format) template elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index d6bf2137af..f01ed9e6e0 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -40,18 +40,10 @@ module ActionView #:nodoc: end class Path #:nodoc: - def self.eager_load_templates! - @eager_load_templates = true - end - - def self.eager_load_templates? - @eager_load_templates || false - end - attr_reader :path, :paths delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path, load = true) + def initialize(path, load = false) raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze reload! if load @@ -65,9 +57,35 @@ module ActionView #:nodoc: to_str == path.to_str end + # Returns a ActionView::Template object for the given path string. The + # input path should be relative to the view path directory, + # +hello/index.html.erb+. This method also has a special exception to + # match partial file names without a handler extension. So + # +hello/index.html+ will match the first template it finds with a + # known template extension, +hello/index.html.erb+. Template extensions + # should not be confused with format extensions +html+, +js+, +xml+, + # etc. A format must be supplied to match a formated file. +hello/index+ + # will never match +hello/index.html.erb+. + # + # This method also has two different implementations, one that is "lazy" + # and makes file system calls every time and the other is cached, + # "eager" which looks up the template in an in memory index. The "lazy" + # version is designed for development where you want to automatically + # find new templates between requests. The "eager" version is designed + # for production mode and it is much faster but requires more time + # upfront to build the file index. def [](path) - raise "Unloaded view path! #{@path}" unless @loaded - @paths[path] + if loaded? + @paths[path] + else + Dir.glob("#{@path}/#{path}*").each do |file| + template = create_template(file) + if path == template.path_without_extension || path == template.path + return template + end + end + nil + end end def loaded? @@ -84,9 +102,7 @@ module ActionView #:nodoc: @paths = {} templates_in_path do |template| - # Eager load memoized methods and freeze cached template - template.freeze if self.class.eager_load_templates? - + template.freeze @paths[template.path] = template @paths[template.path_without_extension] ||= template end @@ -98,11 +114,13 @@ module ActionView #:nodoc: private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| - unless File.directory?(file) - yield Template.new(file.split("#{self}/").last, self) - end + yield create_template(file) unless File.directory?(file) end end + + def create_template(file) + Template.new(file.split("#{self}/").last, self) + end end def load @@ -121,5 +139,20 @@ module ActionView #:nodoc: end nil end + + def find_template(path, *formats) + if formats && formats.first == :all + formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) + end + formats.each do |format| + if template = self["#{path}.#{format}"] + return template + end + end + if template = self[path] + return template + end + nil + end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 5ff5569db6..c04399f2c9 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -96,7 +96,7 @@ module ActionView # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) + !(frozen? && Base::CompiledTemplates.method_defined?(symbol)) end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index bee598e1ef..24fdc03507 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -30,8 +30,8 @@ ActionController::Base.logger = nil ActionController::Routing::Routes.reload rescue nil FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') -ActionView::PathSet::Path.eager_load_templates! ActionController::Base.view_paths = FIXTURE_LOAD_PATH +ActionController::Base.view_paths.load def uses_mocha(test_name) yield diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 61c20f8299..18c01f755c 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -3,6 +3,10 @@ require 'abstract_unit' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths + +ActionView::Template::register_template_handler :mab, + lambda { |template| template.source.inspect } + ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ] class LayoutTest < ActionController::Base @@ -31,9 +35,6 @@ end class MultipleExtensions < LayoutTest end -ActionView::Template::register_template_handler :mab, - lambda { |template| template.source.inspect } - class LayoutAutoDiscoveryTest < ActionController::TestCase def setup @request.host = "www.nextangle.com" @@ -52,10 +53,9 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase end def test_third_party_template_library_auto_discovers_layout - ThirdPartyTemplateLibraryController.view_paths.reload! @controller = ThirdPartyTemplateLibraryController.new get :hello - assert_equal 'layouts/third_party_template_library', @controller.active_layout + assert_equal 'layouts/third_party_template_library.mab', @controller.active_layout.to_s assert_equal 'layouts/third_party_template_library', @response.layout assert_response :success assert_equal 'Mab', @response.body @@ -64,14 +64,14 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase def test_namespaced_controllers_auto_detect_layouts @controller = ControllerNameSpace::NestedController.new get :hello - assert_equal 'layouts/controller_name_space/nested', @controller.active_layout + assert_equal 'layouts/controller_name_space/nested', @controller.active_layout.to_s assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body end def test_namespaced_controllers_auto_detect_layouts @controller = MultipleExtensions.new get :hello - assert_equal 'layouts/multiple_extensions', @controller.active_layout + assert_equal 'layouts/multiple_extensions.html.erb', @controller.active_layout.to_s assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip end end diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index f7688b2072..0b3d039409 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -30,15 +30,6 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal "Hello world!", render(:file => "test/hello_world.erb") end - def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off - ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false) - assert_equal 0, @compiled_templates.instance_methods.size - assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") - ActionView::Template.any_instance.expects(:compile!).times(3) - 3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") } - assert_equal 1, @compiled_templates.instance_methods.size - end - private def render(*args) ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index b316d5c23e..28d38b0c76 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -4,7 +4,9 @@ require 'controller/fake_models' class ViewRenderTest < Test::Unit::TestCase def setup @assigns = { :secret => 'in the sauce' } - @view = ActionView::Base.new(ActionController::Base.view_paths, @assigns) + view_paths = ActionController::Base.view_paths + @view = ActionView::Base.new(view_paths, @assigns) + assert view_paths.first.loaded? end def test_render_file @@ -157,7 +159,7 @@ class ViewRenderTest < Test::Unit::TestCase end def test_render_fallbacks_to_erb_for_unknown_types - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo) + assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar) end CustomHandler = lambda do |template| @@ -196,3 +198,14 @@ class ViewRenderTest < Test::Unit::TestCase @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end end + +class LazyViewRenderTest < ViewRenderTest + # Test the same thing as above, but make sure the view path + # is not eager loaded + def setup + @assigns = { :secret => 'in the sauce' } + view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) + @view = ActionView::Base.new(view_paths, @assigns) + assert !view_paths.first.loaded? + end +end -- cgit v1.2.3 From 229f959d15e451890db60dbb73f8565079977814 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 27 Nov 2008 17:51:33 +0100 Subject: Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH] --- actionpack/CHANGELOG | 2 ++ .../lib/action_view/helpers/asset_tag_helper.rb | 14 ++++++-- actionpack/test/template/asset_tag_helper_test.rb | 40 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 06d73819fb..4d9dd2006f 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH] + * Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [DHH] * Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion] diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 4352d7819b..134907311a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -80,6 +80,12 @@ module ActionView # end # } # + # You can also implement a custom asset host object that responds to the call method and tasks one or two parameters just like the proc. + # + # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( + # "http://asset%d.example.com", "https://asset1.example.com" + # ) + # # === Using asset timestamps # # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the @@ -359,6 +365,7 @@ module ActionView # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching # is set to true (which is the case by default for the Rails production environment, but not for the development # environment). Examples: + # # ==== Examples # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false => @@ -629,11 +636,12 @@ module ActionView # 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 %d (the number is the source hash mod 4), - # or the value returned from invoking the proc if it's a proc. + # 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 = ActionController::Base.asset_host - if host.is_a?(Proc) - case host.arity + if host.is_a?(Proc) || host.respond_to?(:call) + case host.is_a?(Proc) ? host.arity : host.method(:call).arity when 2 host.call(source, request) else diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 2c0caef583..7597927f6d 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -359,6 +359,46 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) end + def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host + ENV['RAILS_ASSET_ID'] = '' + ActionController::Base.asset_host = Class.new do + def call(source, request) + if request.ssl? + "#{request.protocol}#{request.host_with_port}" + else + "#{request.protocol}assets#{source.length}.example.com" + end + end + end.new + + ActionController::Base.perform_caching = true + + assert_equal '/javascripts/vanilla.js'.length, 23 + assert_dom_equal( + %(), + javascript_include_tag(:all, :cache => 'vanilla') + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) + + class << @controller.request + def protocol() 'https://' end + def ssl?() true end + end + + assert_equal '/javascripts/secure.js'.length, 22 + assert_dom_equal( + %(), + javascript_include_tag(:all, :cache => 'secure') + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) + + ensure + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) + end + def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory ENV["RAILS_ASSET_ID"] = "" ActionController::Base.asset_host = 'http://a%d.example.com' -- cgit v1.2.3 From 5fa0457542b0ff541d0a80ff8c3561eec8e35959 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 27 Nov 2008 21:04:24 +0100 Subject: Revert "Super lazy load view paths in development mode (no indexing or caching at all). Switch layout finders to use view path api to take advantage of cache." as it killed dev mode reloading. This reverts commit 4d910b033379727e5e7355590c50c72fc75e56db. --- actionpack/lib/action_controller/base.rb | 5 +- actionpack/lib/action_controller/dispatcher.rb | 1 + actionpack/lib/action_controller/layout.rb | 38 ++++++++---- actionpack/lib/action_view/base.rb | 4 +- actionpack/lib/action_view/paths.rb | 67 ++++++---------------- actionpack/lib/action_view/renderable.rb | 2 +- actionpack/test/abstract_unit.rb | 2 +- actionpack/test/controller/layout_test.rb | 14 ++--- .../test/template/compiled_templates_test.rb | 9 +++ actionpack/test/template/render_test.rb | 17 +----- 10 files changed, 71 insertions(+), 88 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index dca66ff0a5..7e38f95076 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -867,9 +867,8 @@ module ActionController #:nodoc: end end - layout = pick_layout(options) - response.layout = layout.path_without_format_and_extension if layout - logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout + response.layout = layout = pick_layout(options) + logger.info("Rendering template within #{layout}") if logger && layout if content_type = options[:content_type] response.content_type = content_type.to_s diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 6e4aba2280..1c7a4b0545 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -137,6 +137,7 @@ module ActionController run_callbacks :prepare_dispatch Routing::Routes.reload + ActionController::Base.view_paths.reload! ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear end diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 54108df06d..3631ce86af 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -175,12 +175,13 @@ module ActionController #:nodoc: def default_layout(format) #:nodoc: layout = read_inheritable_attribute(:layout) return layout unless read_inheritable_attribute(:auto_layout) - find_layout(layout, format) + @default_layout ||= {} + @default_layout[format] ||= default_layout_with_format(format, layout) + @default_layout[format] end - def find_layout(layout, *formats) #:nodoc: - return layout if layout.respond_to?(:render) - view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) + def layout_list #:nodoc: + Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } end private @@ -188,7 +189,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) + child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? end end @@ -199,6 +200,15 @@ module ActionController #:nodoc: def normalize_conditions(conditions) conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} end + + def default_layout_with_format(format, layout) + list = layout_list + if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty? + (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil + else + layout + end + end end # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method @@ -207,18 +217,20 @@ module ActionController #:nodoc: # weblog/standard, but layout "standard" will return layouts/standard. def active_layout(passed_layout = nil) layout = passed_layout || self.class.default_layout(default_template_format) - active_layout = case layout + when String then layout when Symbol then __send__(layout) when Proc then layout.call(self) - else layout end + # Explicitly passed layout names with slashes are looked up relative to the template root, + # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative + # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. if active_layout - if layout = self.class.find_layout(active_layout, @template.template_format) - layout + if active_layout.include?('/') && ! layout_directory?(active_layout) + active_layout else - raise ActionView::MissingTemplate.new(self.class.view_paths, active_layout) + "layouts/#{active_layout}" end end end @@ -259,6 +271,12 @@ module ActionController #:nodoc: end end + def layout_directory?(layout_name) + @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false + rescue ActionView::MissingTemplate + false + end + def default_template_format response.template.template_format end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index da1f283deb..7697848713 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -322,7 +322,9 @@ module ActionView #:nodoc: end # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths.find_template(template_file_name, template_format) + if template = self.view_paths["#{template_file_name}.#{template_format}"] + template + elsif template = self.view_paths[template_file_name] template elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index f01ed9e6e0..d6bf2137af 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -40,10 +40,18 @@ module ActionView #:nodoc: end class Path #:nodoc: + def self.eager_load_templates! + @eager_load_templates = true + end + + def self.eager_load_templates? + @eager_load_templates || false + end + attr_reader :path, :paths delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path, load = false) + def initialize(path, load = true) raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze reload! if load @@ -57,35 +65,9 @@ module ActionView #:nodoc: to_str == path.to_str end - # Returns a ActionView::Template object for the given path string. The - # input path should be relative to the view path directory, - # +hello/index.html.erb+. This method also has a special exception to - # match partial file names without a handler extension. So - # +hello/index.html+ will match the first template it finds with a - # known template extension, +hello/index.html.erb+. Template extensions - # should not be confused with format extensions +html+, +js+, +xml+, - # etc. A format must be supplied to match a formated file. +hello/index+ - # will never match +hello/index.html.erb+. - # - # This method also has two different implementations, one that is "lazy" - # and makes file system calls every time and the other is cached, - # "eager" which looks up the template in an in memory index. The "lazy" - # version is designed for development where you want to automatically - # find new templates between requests. The "eager" version is designed - # for production mode and it is much faster but requires more time - # upfront to build the file index. def [](path) - if loaded? - @paths[path] - else - Dir.glob("#{@path}/#{path}*").each do |file| - template = create_template(file) - if path == template.path_without_extension || path == template.path - return template - end - end - nil - end + raise "Unloaded view path! #{@path}" unless @loaded + @paths[path] end def loaded? @@ -102,7 +84,9 @@ module ActionView #:nodoc: @paths = {} templates_in_path do |template| - template.freeze + # Eager load memoized methods and freeze cached template + template.freeze if self.class.eager_load_templates? + @paths[template.path] = template @paths[template.path_without_extension] ||= template end @@ -114,13 +98,11 @@ module ActionView #:nodoc: private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| - yield create_template(file) unless File.directory?(file) + unless File.directory?(file) + yield Template.new(file.split("#{self}/").last, self) + end end end - - def create_template(file) - Template.new(file.split("#{self}/").last, self) - end end def load @@ -139,20 +121,5 @@ module ActionView #:nodoc: end nil end - - def find_template(path, *formats) - if formats && formats.first == :all - formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) - end - formats.each do |format| - if template = self["#{path}.#{format}"] - return template - end - end - if template = self[path] - return template - end - nil - end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index c04399f2c9..5ff5569db6 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -96,7 +96,7 @@ module ActionView # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - !(frozen? && Base::CompiledTemplates.method_defined?(symbol)) + !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 24fdc03507..bee598e1ef 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -30,8 +30,8 @@ ActionController::Base.logger = nil ActionController::Routing::Routes.reload rescue nil FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') +ActionView::PathSet::Path.eager_load_templates! ActionController::Base.view_paths = FIXTURE_LOAD_PATH -ActionController::Base.view_paths.load def uses_mocha(test_name) yield diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 18c01f755c..61c20f8299 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -3,10 +3,6 @@ require 'abstract_unit' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths - -ActionView::Template::register_template_handler :mab, - lambda { |template| template.source.inspect } - ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ] class LayoutTest < ActionController::Base @@ -35,6 +31,9 @@ end class MultipleExtensions < LayoutTest end +ActionView::Template::register_template_handler :mab, + lambda { |template| template.source.inspect } + class LayoutAutoDiscoveryTest < ActionController::TestCase def setup @request.host = "www.nextangle.com" @@ -53,9 +52,10 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase end def test_third_party_template_library_auto_discovers_layout + ThirdPartyTemplateLibraryController.view_paths.reload! @controller = ThirdPartyTemplateLibraryController.new get :hello - assert_equal 'layouts/third_party_template_library.mab', @controller.active_layout.to_s + assert_equal 'layouts/third_party_template_library', @controller.active_layout assert_equal 'layouts/third_party_template_library', @response.layout assert_response :success assert_equal 'Mab', @response.body @@ -64,14 +64,14 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase def test_namespaced_controllers_auto_detect_layouts @controller = ControllerNameSpace::NestedController.new get :hello - assert_equal 'layouts/controller_name_space/nested', @controller.active_layout.to_s + assert_equal 'layouts/controller_name_space/nested', @controller.active_layout assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body end def test_namespaced_controllers_auto_detect_layouts @controller = MultipleExtensions.new get :hello - assert_equal 'layouts/multiple_extensions.html.erb', @controller.active_layout.to_s + assert_equal 'layouts/multiple_extensions', @controller.active_layout assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip end end diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index 0b3d039409..f7688b2072 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -30,6 +30,15 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal "Hello world!", render(:file => "test/hello_world.erb") end + def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off + ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false) + assert_equal 0, @compiled_templates.instance_methods.size + assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") + ActionView::Template.any_instance.expects(:compile!).times(3) + 3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") } + assert_equal 1, @compiled_templates.instance_methods.size + end + private def render(*args) ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 28d38b0c76..b316d5c23e 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -4,9 +4,7 @@ require 'controller/fake_models' class ViewRenderTest < Test::Unit::TestCase def setup @assigns = { :secret => 'in the sauce' } - view_paths = ActionController::Base.view_paths - @view = ActionView::Base.new(view_paths, @assigns) - assert view_paths.first.loaded? + @view = ActionView::Base.new(ActionController::Base.view_paths, @assigns) end def test_render_file @@ -159,7 +157,7 @@ class ViewRenderTest < Test::Unit::TestCase end def test_render_fallbacks_to_erb_for_unknown_types - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar) + assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo) end CustomHandler = lambda do |template| @@ -198,14 +196,3 @@ class ViewRenderTest < Test::Unit::TestCase @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end end - -class LazyViewRenderTest < ViewRenderTest - # Test the same thing as above, but make sure the view path - # is not eager loaded - def setup - @assigns = { :secret => 'in the sauce' } - view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) - @view = ActionView::Base.new(view_paths, @assigns) - assert !view_paths.first.loaded? - end -end -- cgit v1.2.3 From 9fc23745f1511d8d97433828d9ca87970994d138 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 28 Nov 2008 11:18:28 -0600 Subject: Reinstate "Super lazy load view paths in development mode (no indexing or caching at all). Switch layout finders to use view path api to take advantage of cache." as it killed dev mode reloading." --- actionpack/lib/action_controller/base.rb | 5 +- actionpack/lib/action_controller/dispatcher.rb | 1 - actionpack/lib/action_controller/layout.rb | 38 ++++-------- actionpack/lib/action_view/base.rb | 4 +- actionpack/lib/action_view/paths.rb | 67 ++++++++++++++++------ actionpack/lib/action_view/renderable.rb | 2 +- actionpack/test/abstract_unit.rb | 2 +- actionpack/test/controller/layout_test.rb | 14 ++--- .../test/template/compiled_templates_test.rb | 9 --- actionpack/test/template/render_test.rb | 17 +++++- 10 files changed, 88 insertions(+), 71 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 7e38f95076..dca66ff0a5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -867,8 +867,9 @@ module ActionController #:nodoc: end end - response.layout = layout = pick_layout(options) - logger.info("Rendering template within #{layout}") if logger && layout + layout = pick_layout(options) + response.layout = layout.path_without_format_and_extension if layout + logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout if content_type = options[:content_type] response.content_type = content_type.to_s diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 1c7a4b0545..6e4aba2280 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -137,7 +137,6 @@ module ActionController run_callbacks :prepare_dispatch Routing::Routes.reload - ActionController::Base.view_paths.reload! ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear end diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 3631ce86af..54108df06d 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -175,13 +175,12 @@ module ActionController #:nodoc: def default_layout(format) #:nodoc: layout = read_inheritable_attribute(:layout) return layout unless read_inheritable_attribute(:auto_layout) - @default_layout ||= {} - @default_layout[format] ||= default_layout_with_format(format, layout) - @default_layout[format] + find_layout(layout, format) end - def layout_list #:nodoc: - Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } + def find_layout(layout, *formats) #:nodoc: + return layout if layout.respond_to?(:render) + view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) end private @@ -189,7 +188,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? + child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) end end @@ -200,15 +199,6 @@ module ActionController #:nodoc: def normalize_conditions(conditions) conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} end - - def default_layout_with_format(format, layout) - list = layout_list - if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty? - (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil - else - layout - end - end end # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method @@ -217,20 +207,18 @@ module ActionController #:nodoc: # weblog/standard, but layout "standard" will return layouts/standard. def active_layout(passed_layout = nil) layout = passed_layout || self.class.default_layout(default_template_format) + active_layout = case layout - when String then layout when Symbol then __send__(layout) when Proc then layout.call(self) + else layout end - # Explicitly passed layout names with slashes are looked up relative to the template root, - # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative - # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. if active_layout - if active_layout.include?('/') && ! layout_directory?(active_layout) - active_layout + if layout = self.class.find_layout(active_layout, @template.template_format) + layout else - "layouts/#{active_layout}" + raise ActionView::MissingTemplate.new(self.class.view_paths, active_layout) end end end @@ -271,12 +259,6 @@ module ActionController #:nodoc: end end - def layout_directory?(layout_name) - @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false - rescue ActionView::MissingTemplate - false - end - def default_template_format response.template.template_format end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7697848713..da1f283deb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -322,9 +322,7 @@ module ActionView #:nodoc: end # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths["#{template_file_name}.#{template_format}"] - template - elsif template = self.view_paths[template_file_name] + if template = self.view_paths.find_template(template_file_name, template_format) template elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index d6bf2137af..f01ed9e6e0 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -40,18 +40,10 @@ module ActionView #:nodoc: end class Path #:nodoc: - def self.eager_load_templates! - @eager_load_templates = true - end - - def self.eager_load_templates? - @eager_load_templates || false - end - attr_reader :path, :paths delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path, load = true) + def initialize(path, load = false) raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze reload! if load @@ -65,9 +57,35 @@ module ActionView #:nodoc: to_str == path.to_str end + # Returns a ActionView::Template object for the given path string. The + # input path should be relative to the view path directory, + # +hello/index.html.erb+. This method also has a special exception to + # match partial file names without a handler extension. So + # +hello/index.html+ will match the first template it finds with a + # known template extension, +hello/index.html.erb+. Template extensions + # should not be confused with format extensions +html+, +js+, +xml+, + # etc. A format must be supplied to match a formated file. +hello/index+ + # will never match +hello/index.html.erb+. + # + # This method also has two different implementations, one that is "lazy" + # and makes file system calls every time and the other is cached, + # "eager" which looks up the template in an in memory index. The "lazy" + # version is designed for development where you want to automatically + # find new templates between requests. The "eager" version is designed + # for production mode and it is much faster but requires more time + # upfront to build the file index. def [](path) - raise "Unloaded view path! #{@path}" unless @loaded - @paths[path] + if loaded? + @paths[path] + else + Dir.glob("#{@path}/#{path}*").each do |file| + template = create_template(file) + if path == template.path_without_extension || path == template.path + return template + end + end + nil + end end def loaded? @@ -84,9 +102,7 @@ module ActionView #:nodoc: @paths = {} templates_in_path do |template| - # Eager load memoized methods and freeze cached template - template.freeze if self.class.eager_load_templates? - + template.freeze @paths[template.path] = template @paths[template.path_without_extension] ||= template end @@ -98,11 +114,13 @@ module ActionView #:nodoc: private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| - unless File.directory?(file) - yield Template.new(file.split("#{self}/").last, self) - end + yield create_template(file) unless File.directory?(file) end end + + def create_template(file) + Template.new(file.split("#{self}/").last, self) + end end def load @@ -121,5 +139,20 @@ module ActionView #:nodoc: end nil end + + def find_template(path, *formats) + if formats && formats.first == :all + formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) + end + formats.each do |format| + if template = self["#{path}.#{format}"] + return template + end + end + if template = self[path] + return template + end + nil + end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 5ff5569db6..c04399f2c9 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -96,7 +96,7 @@ module ActionView # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) + !(frozen? && Base::CompiledTemplates.method_defined?(symbol)) end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index bee598e1ef..24fdc03507 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -30,8 +30,8 @@ ActionController::Base.logger = nil ActionController::Routing::Routes.reload rescue nil FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') -ActionView::PathSet::Path.eager_load_templates! ActionController::Base.view_paths = FIXTURE_LOAD_PATH +ActionController::Base.view_paths.load def uses_mocha(test_name) yield diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 61c20f8299..18c01f755c 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -3,6 +3,10 @@ require 'abstract_unit' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths + +ActionView::Template::register_template_handler :mab, + lambda { |template| template.source.inspect } + ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ] class LayoutTest < ActionController::Base @@ -31,9 +35,6 @@ end class MultipleExtensions < LayoutTest end -ActionView::Template::register_template_handler :mab, - lambda { |template| template.source.inspect } - class LayoutAutoDiscoveryTest < ActionController::TestCase def setup @request.host = "www.nextangle.com" @@ -52,10 +53,9 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase end def test_third_party_template_library_auto_discovers_layout - ThirdPartyTemplateLibraryController.view_paths.reload! @controller = ThirdPartyTemplateLibraryController.new get :hello - assert_equal 'layouts/third_party_template_library', @controller.active_layout + assert_equal 'layouts/third_party_template_library.mab', @controller.active_layout.to_s assert_equal 'layouts/third_party_template_library', @response.layout assert_response :success assert_equal 'Mab', @response.body @@ -64,14 +64,14 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase def test_namespaced_controllers_auto_detect_layouts @controller = ControllerNameSpace::NestedController.new get :hello - assert_equal 'layouts/controller_name_space/nested', @controller.active_layout + assert_equal 'layouts/controller_name_space/nested', @controller.active_layout.to_s assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body end def test_namespaced_controllers_auto_detect_layouts @controller = MultipleExtensions.new get :hello - assert_equal 'layouts/multiple_extensions', @controller.active_layout + assert_equal 'layouts/multiple_extensions.html.erb', @controller.active_layout.to_s assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip end end diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index f7688b2072..0b3d039409 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -30,15 +30,6 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal "Hello world!", render(:file => "test/hello_world.erb") end - def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off - ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false) - assert_equal 0, @compiled_templates.instance_methods.size - assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") - ActionView::Template.any_instance.expects(:compile!).times(3) - 3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") } - assert_equal 1, @compiled_templates.instance_methods.size - end - private def render(*args) ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index b316d5c23e..28d38b0c76 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -4,7 +4,9 @@ require 'controller/fake_models' class ViewRenderTest < Test::Unit::TestCase def setup @assigns = { :secret => 'in the sauce' } - @view = ActionView::Base.new(ActionController::Base.view_paths, @assigns) + view_paths = ActionController::Base.view_paths + @view = ActionView::Base.new(view_paths, @assigns) + assert view_paths.first.loaded? end def test_render_file @@ -157,7 +159,7 @@ class ViewRenderTest < Test::Unit::TestCase end def test_render_fallbacks_to_erb_for_unknown_types - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo) + assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar) end CustomHandler = lambda do |template| @@ -196,3 +198,14 @@ class ViewRenderTest < Test::Unit::TestCase @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end end + +class LazyViewRenderTest < ViewRenderTest + # Test the same thing as above, but make sure the view path + # is not eager loaded + def setup + @assigns = { :secret => 'in the sauce' } + view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) + @view = ActionView::Base.new(view_paths, @assigns) + assert !view_paths.first.loaded? + end +end -- cgit v1.2.3 From 9fccf72725a72baeda508eccd6113b44329e0a7c Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 28 Nov 2008 14:31:54 -0600 Subject: fixed template recompile issue with previous commit and add some better tests so we can make sure it doesn't happen again --- actionpack/lib/action_view/paths.rb | 2 +- actionpack/lib/action_view/renderable.rb | 2 +- actionpack/lib/action_view/template.rb | 18 ++++++++ .../test/template/compiled_templates_test.rb | 50 +++++++++++++++++++++- actionpack/test/template/render_test.rb | 26 +++++++---- 5 files changed, 87 insertions(+), 11 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index f01ed9e6e0..ecb6103ade 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -102,7 +102,7 @@ module ActionView #:nodoc: @paths = {} templates_in_path do |template| - template.freeze + template.load! @paths[template.path] = template @paths[template.path_without_extension] ||= template end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index c04399f2c9..7258ad04bf 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -96,7 +96,7 @@ module ActionView # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - !(frozen? && Base::CompiledTemplates.method_defined?(symbol)) + !Base::CompiledTemplates.method_defined?(symbol) || !loaded? end end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index ca82454c0b..b486392ac4 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -57,6 +57,11 @@ module ActionView #:nodoc: end memoize :relative_path + def mtime + File.mtime(filename) + end + memoize :mtime + def source File.read(filename) end @@ -79,6 +84,19 @@ module ActionView #:nodoc: end end + def stale? + File.mtime(filename) > mtime + end + + def loaded? + @loaded + end + + def load! + @loaded = true + freeze + end + private def valid_extension?(extension) Template.template_handler_extensions.include?(extension) diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index 0b3d039409..a68b09bb45 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -30,9 +30,57 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal "Hello world!", render(:file => "test/hello_world.erb") end + def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached + ActionView::Template.any_instance.expects(:loaded?).times(3).returns(false) + assert_equal 0, @compiled_templates.instance_methods.size + assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") + ActionView::Template.any_instance.expects(:compile!).times(3) + 3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") } + assert_equal 1, @compiled_templates.instance_methods.size + end + + def test_template_changes_are_not_reflected_with_cached_templates + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + modify_template "test/hello_world.erb", "Goodbye world!" do + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + end + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + end + + def test_template_changes_are_reflected_with_uncached_templates + assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb") + modify_template "test/hello_world.erb", "Goodbye world!" do + assert_equal "Goodbye world!", render_without_cache(:file => "test/hello_world.erb") + end + assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb") + end + private def render(*args) - ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args) + render_with_cache(*args) + end + + def render_with_cache(*args) + view_paths = ActionController::Base.view_paths + assert view_paths.first.loaded? + ActionView::Base.new(view_paths, {}).render(*args) + end + + def render_without_cache(*args) + view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) + assert !view_paths.first.loaded? + ActionView::Base.new(view_paths, {}).render(*args) + end + + def modify_template(template, content) + filename = "#{FIXTURE_LOAD_PATH}/#{template}" + old_content = File.read(filename) + begin + File.open(filename, "wb+") { |f| f.write(content) } + yield + ensure + File.open(filename, "wb+") { |f| f.write(old_content) } + end end end end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 28d38b0c76..9e827abbca 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -1,12 +1,10 @@ require 'abstract_unit' require 'controller/fake_models' -class ViewRenderTest < Test::Unit::TestCase - def setup +module RenderTestCases + def setup_view(paths) @assigns = { :secret => 'in the sauce' } - view_paths = ActionController::Base.view_paths - @view = ActionView::Base.new(view_paths, @assigns) - assert view_paths.first.loaded? + @view = ActionView::Base.new(paths, @assigns) end def test_render_file @@ -199,13 +197,25 @@ class ViewRenderTest < Test::Unit::TestCase end end -class LazyViewRenderTest < ViewRenderTest +class CachedViewRenderTest < Test::Unit::TestCase + include RenderTestCases + + # Ensure view path cache is primed + def setup + view_paths = ActionController::Base.view_paths + assert view_paths.first.loaded? + setup_view(view_paths) + end +end + +class LazyViewRenderTest < Test::Unit::TestCase + include RenderTestCases + # Test the same thing as above, but make sure the view path # is not eager loaded def setup - @assigns = { :secret => 'in the sauce' } view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) - @view = ActionView::Base.new(view_paths, @assigns) assert !view_paths.first.loaded? + setup_view(view_paths) end end -- cgit v1.2.3 From 1182658e767d2db4a46faed35f0b1075c5dd9a88 Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Sat, 29 Nov 2008 16:03:44 -0600 Subject: Make sure #compute_public_path caching allows to return different results for different given sources [#1471 state:resolved] Signed-off-by: Joshua Peek --- actionpack/lib/action_view/helpers/asset_tag_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 134907311a..4ec7a383e5 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -592,7 +592,7 @@ module ActionView source else CacheGuard.synchronize do - Cache[@cache_key] ||= begin + Cache[@cache_key + [source]] ||= begin source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source) source = "/#{directory}/#{source}" unless source[0] == ?/ source = rewrite_asset_path(source) -- cgit v1.2.3 From 8521cebbad465ae0acba36bda3fd202a898d194a Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 29 Nov 2008 16:55:02 -0800 Subject: Turn on debugger autoeval --- actionpack/test/abstract_unit.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionpack') diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index bee598e1ef..cbf7351172 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -13,6 +13,7 @@ require 'mocha' begin require 'ruby-debug' + Debugger.settings[:autoeval] = true Debugger.start rescue LoadError # Debugging disabled. `gem install ruby-debug` to enable. -- cgit v1.2.3 From 635e2ccd3ea8da64f9ee046cc9d8580007cff679 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 29 Nov 2008 19:32:13 -0800 Subject: Extract named_helper module_eval so it's easier to override --- actionpack/lib/action_controller/routing/route_set.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index a9690d1807..13646aef61 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -138,9 +138,13 @@ module ActionController end end + def named_helper_module_eval(code, *args) + @module.module_eval(code, *args) + end + def define_hash_access(route, name, kind, options) selector = hash_access_name(name, kind) - @module.module_eval <<-end_eval # We use module_eval to avoid leaks + named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks def #{selector}(options = nil) options ? #{options.inspect}.merge(options) : #{options.inspect} end @@ -168,7 +172,7 @@ module ActionController # # foo_url(bar, baz, bang, :sort_by => 'baz') # - @module.module_eval <<-end_eval # We use module_eval to avoid leaks + named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks def #{selector}(*args) #{generate_optimisation_block(route, kind)} -- cgit v1.2.3 From 93456a2ed275575a03bd2d6234095395dee6a655 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 30 Nov 2008 17:03:38 -0800 Subject: Deprecated formatted_polymorphic_url --- actionpack/CHANGELOG | 2 ++ .../lib/action_controller/polymorphic_routes.rb | 41 +++++++++++----------- .../test/controller/polymorphic_routes_test.rb | 18 +++++----- 3 files changed, 32 insertions(+), 29 deletions(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 4d9dd2006f..1110c5cac6 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Deprecated formatted_polymorphic_url. [Jeremy Kemper] + * Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH] * Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [DHH] diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 28722c93ca..dce50c6c3b 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -36,12 +36,11 @@ module ActionController # # * edit_polymorphic_url, edit_polymorphic_path # * new_polymorphic_url, new_polymorphic_path - # * formatted_polymorphic_url, formatted_polymorphic_path # # Example usage: # # edit_polymorphic_path(@post) # => "/posts/1/edit" - # formatted_polymorphic_path([@post, :pdf]) # => "/posts/1.pdf" + # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf" module PolymorphicRoutes # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: @@ -55,7 +54,7 @@ module ActionController # ==== Options # # * :action - Specifies the action prefix for the named route: - # :new, :edit, or :formatted. Default is no prefix. + # :new or :edit. Default is no prefix. # * :routing_type - Allowed values are :path or :url. # Default is :url. # @@ -78,9 +77,8 @@ module ActionController end record = extract_record(record_or_hash_or_array) - format = extract_format(record_or_hash_or_array, options) namespace = extract_namespace(record_or_hash_or_array) - + args = case record_or_hash_or_array when Hash; [ record_or_hash_or_array ] when Array; record_or_hash_or_array.dup @@ -100,11 +98,10 @@ module ActionController end args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} - args << format if format - + named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options) - url_options = options.except(:action, :routing_type, :format) + url_options = options.except(:action, :routing_type) unless url_options.empty? args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options end @@ -119,7 +116,7 @@ module ActionController polymorphic_url(record_or_hash_or_array, options) end - %w(edit new formatted).each do |action| + %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ def #{action}_polymorphic_url(record_or_hash, options = {}) polymorphic_url(record_or_hash, options.merge(:action => "#{action}")) @@ -131,9 +128,21 @@ module ActionController EOT end + def formatted_polymorphic_url(record_or_hash, options = {}) + ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller) + options[:format] = record_or_hash.pop if Array === record_or_hash + polymorphic_url(record_or_hash, options) + end + + def formatted_polymorphic_path(record_or_hash, options = {}) + ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller) + options[:format] = record_or_hash.pop if record_or_hash === Array + polymorphic_url(record_or_hash, options.merge(:routing_type => :path)) + end + private def action_prefix(options) - options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : "" + options[:action] ? "#{options[:action]}_" : '' end def routing_type(options) @@ -171,17 +180,7 @@ module ActionController else record_or_hash_or_array end end - - def extract_format(record_or_hash_or_array, options) - if options[:action].to_s == "formatted" && record_or_hash_or_array.is_a?(Array) - record_or_hash_or_array.pop - elsif options[:format] - options[:format] - else - nil - end - end - + # Remove the first symbols from the array and return the url prefix # implied by those symbols. def extract_namespace(record_or_hash_or_array) diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 42dbea3b8f..09c7f74617 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -71,20 +71,22 @@ uses_mocha 'polymorphic URL helpers' do polymorphic_url(@article, :param1 => '10') end - def test_formatted_url_helper - expects(:formatted_article_url).with(@article, :pdf) - formatted_polymorphic_url([@article, :pdf]) + def test_formatted_url_helper_is_deprecated + expects(:articles_url).with(:format => :pdf) + assert_deprecated do + formatted_polymorphic_url([@article, :pdf]) + end end def test_format_option @article.save - expects(:formatted_article_url).with(@article, :pdf) + expects(:article_url).with(@article, :format => :pdf) polymorphic_url(@article, :format => :pdf) end def test_format_option_with_url_options @article.save - expects(:formatted_article_url).with(@article, :pdf, :param1 => '10') + expects(:article_url).with(@article, :format => :pdf, :param1 => '10') polymorphic_url(@article, :format => :pdf, :param1 => '10') end @@ -157,14 +159,14 @@ uses_mocha 'polymorphic URL helpers' do def test_nesting_with_array_containing_singleton_resource_and_format @tag = Tag.new @tag.save - expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf) - formatted_polymorphic_url([@article, :response, @tag, :pdf]) + expects(:article_response_tag_url).with(@article, @tag, :format => :pdf) + polymorphic_url([@article, :response, @tag], :format => :pdf) end def test_nesting_with_array_containing_singleton_resource_and_format_option @tag = Tag.new @tag.save - expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf) + expects(:article_response_tag_url).with(@article, @tag, :format => :pdf) polymorphic_url([@article, :response, @tag], :format => :pdf) end -- cgit v1.2.3 From 4fabc9b2f376c47d4381572167956063b3c8c418 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 30 Nov 2008 17:06:11 -0800 Subject: Simplify REMOTE_ADDR parsing --- actionpack/lib/action_controller/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index c079895683..78a57acf6f 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -209,7 +209,7 @@ module ActionController # delimited list in the case of multiple chained proxies; the last # address which is not trusted is the originating IP. def remote_ip - remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip) + remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/) unless remote_addr_list.blank? not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES} -- cgit v1.2.3 From eb5e6fe713756b36ee6df9b04f28cfb8bae60dbc Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sun, 30 Nov 2008 17:24:36 -0800 Subject: Simplify Request#path --- actionpack/lib/action_controller/request.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 78a57acf6f..baa955cb04 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -369,11 +369,9 @@ EOM # Returns the interpreted \path to requested resource after all the installation # directory of this application was taken into account. def path - path = (uri = request_uri) ? uri.split('?').first.to_s : '' - - # Cut off the path to the installation directory if given - path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '') - path || '' + path = request_uri.to_s[/\A[^\?]*/] + path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') + path end memoize :path -- cgit v1.2.3 From bda55f82c687920807f606a2b024f1882094ef1e Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Wed, 19 Nov 2008 12:55:27 -0600 Subject: allow options to be passed to email address auto generation Signed-off-by: Michael Koziarski [#1418 state:committed] --- actionpack/lib/action_view/helpers/text_helper.rb | 8 ++++---- actionpack/test/template/text_helper_test.rb | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 506138a735..1d9e4fe9b8 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -370,8 +370,8 @@ module ActionView options.reverse_merge!(:link => :all, :html => {}) case options[:link].to_sym - when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block) - when :email_addresses then auto_link_email_addresses(text, &block) + when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), options[:html], &block) + when :email_addresses then auto_link_email_addresses(text, options[:html], &block) when :urls then auto_link_urls(text, options[:html], &block) end end @@ -559,7 +559,7 @@ module ActionView # Turns all email addresses into clickable links. If a block is given, # each email is yielded and the result is used as the link text. - def auto_link_email_addresses(text) + def auto_link_email_addresses(text, html_options = {}) body = text.dup text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do text = $1 @@ -568,7 +568,7 @@ module ActionView text else display_text = (block_given?) ? yield(text) : text - %{#{display_text}} + mail_to text, display_text, html_options end end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 3e7a8f3e44..a6200fbdd7 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -262,6 +262,11 @@ class TextHelperTest < ActionView::TestCase email2_result = %{#{email2_raw}} assert_equal email2_result, auto_link(email2_raw) + email3_raw = '+david@loudthinking.com' + email3_result = %{#{email3_raw}} + assert_equal email3_result, auto_link(email3_raw, :all, :encode => :hex) + assert_equal email3_result, auto_link(email3_raw, :email_addresses, :encode => :hex) + link2_raw = 'www.rubyonrails.com' link2_result = generate_result(link2_raw, "http://#{link2_raw}") assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls) @@ -362,7 +367,7 @@ class TextHelperTest < ActionView::TestCase end def test_auto_link_with_options_hash - assert_dom_equal 'Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.', + assert_dom_equal 'Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.', auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.", :link => :all, :html => { :class => "menu", :target => "_blank" }) end -- cgit v1.2.3 From dab78e55cfcc111b898a1c2475c0c5c327db30f9 Mon Sep 17 00:00:00 2001 From: Tekin Suleyman Date: Tue, 25 Nov 2008 11:27:32 +0000 Subject: Ensure ActionMailer doesn't blow up when a two argument proc is set for the asset host Signed-off-by: Michael Koziarski [#1394 state:committed] --- actionpack/lib/action_view/helpers/asset_tag_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 4ec7a383e5..0633d5414e 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -574,7 +574,7 @@ module ActionView private def request - @controller.request + request? && @controller.request end def request? -- cgit v1.2.3 From 0a4a5f3129a137fc357e8444a08b135f0ad4fbe8 Mon Sep 17 00:00:00 2001 From: Darren Boyd Date: Sat, 22 Nov 2008 10:04:30 -0800 Subject: Making the IP Spoofing check in AbstractRequest#remote_ip configurable. Certain groups of web proxies do not set these values properly. Notably, proxies for cell phones, which often do not set the remote IP information correctly (not surprisingly, since the clients do not have an IP address). Allowing this to be configurable makes it possible for developers to choose to ignore this simple spoofing check, when a significant amount of their traffic would result in false positives anyway. Signed-off-by: Michael Koziarski [#1200 state:committed] --- actionpack/CHANGELOG | 2 ++ actionpack/lib/action_controller/base.rb | 4 ++++ actionpack/lib/action_controller/request.rb | 2 +- actionpack/test/controller/request_test.rb | 9 +++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 1110c5cac6..352c4253f4 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd] + * Deprecated formatted_polymorphic_url. [Jeremy Kemper] * Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH] diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index dca66ff0a5..c2f0c1c4f6 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -327,6 +327,10 @@ module ActionController #:nodoc: # sets it to :authenticity_token by default. cattr_accessor :request_forgery_protection_token + # Controls the IP Spoofing check when determining the remote IP. + @@ip_spoofing_check = true + cattr_accessor :ip_spoofing_check + # Indicates whether or not optimise the generated named # route helper methods cattr_accessor :optimise_named_routes diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index baa955cb04..087fffe87d 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -218,7 +218,7 @@ module ActionController remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') if @env.include? 'HTTP_CLIENT_IP' - if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) + if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) # We don't know which came from the proxy, and which from the user raise ActionControllerError.new(< Date: Mon, 1 Dec 2008 13:48:47 -0600 Subject: Add internal middleware stack to Dispatcher config.middleware.use Rack::Cache --- actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/dispatcher.rb | 8 +++++ .../lib/action_controller/middleware_stack.rb | 42 ++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 actionpack/lib/action_controller/middleware_stack.rb (limited to 'actionpack') diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index da5f1e81e6..2981f625a1 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -57,6 +57,7 @@ module ActionController autoload :Integration, 'action_controller/integration' autoload :IntegrationTest, 'action_controller/integration' autoload :Layout, 'action_controller/layout' + autoload :MiddlewareStack, 'action_controller/middleware_stack' autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :RackRequest, 'action_controller/rack_process' diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 6e4aba2280..4f400c4681 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -85,6 +85,9 @@ module ActionController end end + cattr_accessor :middleware + self.middleware = MiddlewareStack.new + cattr_accessor :error_file_path self.error_file_path = Rails.public_path if defined?(Rails.public_path) @@ -93,6 +96,7 @@ module ActionController def initialize(output = $stdout, request = nil, response = nil) @output, @request, @response = output, request, response + @app = @@middleware.build(lambda { |env| self._call(env) }) end def dispatch_unlocked @@ -127,6 +131,10 @@ module ActionController end def call(env) + @app.call(env) + end + + def _call(env) @request = RackRequest.new(env) @response = RackResponse.new(@request) dispatch diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb new file mode 100644 index 0000000000..1864bed23a --- /dev/null +++ b/actionpack/lib/action_controller/middleware_stack.rb @@ -0,0 +1,42 @@ +module ActionController + class MiddlewareStack < Array + class Middleware + attr_reader :klass, :args, :block + + def initialize(klass, *args, &block) + @klass = klass.is_a?(Class) ? klass : klass.to_s.constantize + @args = args + @block = block + end + + def ==(middleware) + case middleware + when Middleware + klass == middleware.klass + when Class + klass == middleware + else + klass == middleware.to_s.constantize + end + end + + def inspect + str = @klass.to_s + @args.each { |arg| str += ", #{arg.inspect}" } + str + end + + def build(app) + klass.new(app, *args, &block) + end + end + + def use(*args, &block) + push(Middleware.new(*args, &block)) + end + + def build(app) + reverse.inject(app) { |a, e| e.build(a) } + end + end +end -- cgit v1.2.3 From f54ae9a9976ec6b4ec228a535edd9b32cc60c43d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 3 Dec 2008 10:23:02 -0600 Subject: Fix failsafe response path. [#1504 state:committed] --- actionpack/lib/action_controller/dispatcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 6e4aba2280..e7345621cc 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -61,7 +61,7 @@ module ActionController private def failsafe_response_body(status) - error_path = "#{error_file_path}/#{status.to_s[0..3]}.html" + error_path = "#{error_file_path}/#{status.to_s[0..2]}.html" if File.exist?(error_path) File.read(error_path) -- cgit v1.2.3 From 3db59ce0dc91b73e106957dcf252bbae31679dfc Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 3 Dec 2008 10:23:43 -0600 Subject: Unnecessary CGI require --- actionpack/lib/action_view/helpers/tag_helper.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionpack') diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 1c8d2db183..2bdd960f4c 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -1,4 +1,3 @@ -require 'cgi' require 'erb' require 'set' -- cgit v1.2.3 From 2fc6c7dd05b9481ed81fe19c8fc0cc7915868404 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 3 Dec 2008 09:59:04 -0600 Subject: Validate template extensions [#1187 state:resolved] --- actionpack/lib/action_view/template.rb | 14 ++++++-------- actionpack/test/fixtures/test/dont_pick_me | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 actionpack/test/fixtures/test/dont_pick_me (limited to 'actionpack') diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index b486392ac4..52378e049e 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -115,16 +115,14 @@ module ActionView #:nodoc: # [base_path, name, format, extension] def split(file) if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) - if m[5] # Multipart formats + if valid_extension?(m[5]) # Multipart formats [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] - elsif m[4] # Single format + elsif valid_extension?(m[4]) # Single format [m[1], m[2], m[3], m[4]] - else - if valid_extension?(m[3]) # No format - [m[1], m[2], nil, m[3]] - else # No extension - [m[1], m[2], m[3], nil] - end + elsif valid_extension?(m[3]) # No format + [m[1], m[2], nil, m[3]] + else # No extension + [m[1], m[2], m[3], nil] end end end diff --git a/actionpack/test/fixtures/test/dont_pick_me b/actionpack/test/fixtures/test/dont_pick_me new file mode 100644 index 0000000000..0157c9e503 --- /dev/null +++ b/actionpack/test/fixtures/test/dont_pick_me @@ -0,0 +1 @@ +non-template file \ No newline at end of file -- cgit v1.2.3