diff options
author | rick <technoweenie@gmail.com> | 2008-08-26 11:53:33 -0700 |
---|---|---|
committer | rick <technoweenie@gmail.com> | 2008-08-26 11:53:33 -0700 |
commit | 0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2 (patch) | |
tree | 1a782151632dd80c8a18c3960536bdf8643debe3 /actionpack | |
parent | 0a6d75dedd79407376aae1f01302164dfd3e44b6 (diff) | |
parent | 229eedfda87a7706dbb5e3e51af8707b3adae375 (diff) | |
download | rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.tar.gz rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.tar.bz2 rails-0aef9d1a2651fa0acd2adcd2de308eeb0ec8cdd2.zip |
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'actionpack')
130 files changed, 6608 insertions, 4445 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 5b7bfe9c30..be490093ac 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,35 @@ *Edge* +* Allow polymorphic_url helper to take url options. #880 [Tarmo TĂ€nav] + +* Switched integration test runner to use Rack processor instead of CGI [Josh Peek] + +* Made AbstractRequest.if_modified_sense return nil if the header could not be parsed [Jamis Buck] + +* Added back ActionController::Base.allow_concurrency flag [Josh Peek] + +* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek] + +* Update Prototype to 1.6.0.2 #599 [Patrick Joyce] + +* Conditional GET utility methods. [Jeremy Kemper] + response.last_modified = @post.updated_at + response.etag = [:admin, @post, current_user] + + if request.fresh?(response) + head :not_modified + else + # render ... + end + +* All 2xx requests are considered successful [Josh Peek] + +* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH] + +* Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek] + +* Get buffer for fragment cache from template's @output_buffer [Josh Peek] + * Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek] * Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek] diff --git a/actionpack/README b/actionpack/README index 2746c3cc43..6090089bb9 100644 --- a/actionpack/README +++ b/actionpack/README @@ -31,7 +31,7 @@ http://www.rubyonrails.org. A short rundown of the major features: * Actions grouped in controller as methods instead of separate command objects - and can therefore share helper methods. + and can therefore share helper methods BlogController < ActionController::Base def show @@ -168,7 +168,7 @@ A short rundown of the major features: {Learn more}[link:classes/ActionController/Base.html] -* Javascript and Ajax integration. +* Javascript and Ajax integration link_to_function "Greeting", "alert('Hello world!')" link_to_remote "Delete this post", :update => "posts", @@ -177,7 +177,7 @@ A short rundown of the major features: {Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html] -* Pagination for navigating lists of results. +* Pagination for navigating lists of results # controller def list @@ -192,15 +192,9 @@ A short rundown of the major features: {Learn more}[link:classes/ActionController/Pagination.html] -* Easy testing of both controller and template result through TestRequest/Response - - class LoginControllerTest < Test::Unit::TestCase - def setup - @controller = LoginController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end +* Easy testing of both controller and rendered template through ActionController::TestCase + class LoginControllerTest < ActionController::TestCase def test_failing_authenticate process :authenticate, :user_name => "nop", :password => "" assert flash.has_key?(:alert) @@ -208,7 +202,7 @@ A short rundown of the major features: end end - {Learn more}[link:classes/ActionController/TestRequest.html] + {Learn more}[link:classes/ActionController/TestCase.html] * Automated benchmarking and integrated logging diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 3c4a339d50..e58071d4af 100755..100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -21,16 +21,13 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -$:.unshift(File.dirname(__FILE__)) unless - $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) - -unless defined?(ActiveSupport) - begin - $:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" +begin + require 'active_support' +rescue LoadError + activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" + if File.directory?(activesupport_path) + $:.unshift activesupport_path require 'active_support' - rescue LoadError - require 'rubygems' - gem 'activesupport' end end diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb index 765225ae24..e2e8bbdc71 100644 --- a/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -87,11 +87,11 @@ module ActionController # def assert_template(expected = nil, message=nil) clean_backtrace do - rendered = @response.rendered_template + rendered = @response.rendered_template.to_s msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered) assert_block(msg) do if expected.nil? - @response.rendered_template.nil? + @response.rendered_template.blank? else rendered.to_s.match(expected) end diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb index 491b72d586..312b4e228b 100644 --- a/actionpack/lib/action_controller/assertions/routing_assertions.rb +++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb @@ -2,7 +2,7 @@ module ActionController module Assertions # Suite of assertions to test routes generated by Rails and the handling of requests made to them. module RoutingAssertions - # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash) + # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash) # match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+. # # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes @@ -14,16 +14,16 @@ module ActionController # # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the - # extras argument, appending the query string on the path directly will not work. For example: + # extras argument, appending the query string on the path directly will not work. For example: # # # assert that a path of '/items/list/1?view=print' returns the correct options - # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }) + # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }) # - # The +message+ parameter allows you to pass in an error message that is displayed upon failure. + # The +message+ parameter allows you to pass in an error message that is displayed upon failure. # # ==== Examples # # Check the default route (i.e., the index action) - # assert_recognizes({:controller => 'items', :action => 'index'}, 'items') + # assert_recognizes({:controller => 'items', :action => 'index'}, 'items') # # # Test a specific action # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list') @@ -44,16 +44,16 @@ module ActionController request_method = nil end - clean_backtrace do - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? + clean_backtrace do + ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? request = recognized_request_for(path, request_method) - + expected_options = expected_options.clone extras.each_key { |key| expected_options.delete key } unless extras.nil? - + expected_options.stringify_keys! routing_diff = expected_options.diff(request.path_parameters) - msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>", + msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>", request.path_parameters, expected_options, expected_options.diff(request.path_parameters)) assert_block(msg) { request.path_parameters == expected_options } end @@ -64,7 +64,7 @@ module ActionController # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures. # # The +defaults+ parameter is unused. - # + # # ==== Examples # # Asserts that the default action is generated for a route with no action # assert_generates("/items", :controller => "items", :action => "index") @@ -73,34 +73,34 @@ module ActionController # assert_generates("/items/list", :controller => "items", :action => "list") # # # Tests the generation of a route with a parameter - # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" }) + # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" }) # # # Asserts that the generated route gives us our custom route # assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" } def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) - clean_backtrace do + clean_backtrace do expected_path = "/#{expected_path}" unless expected_path[0] == ?/ # Load routes.rb if it hasn't been loaded. - ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? - + ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? + generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults) found_extras = options.reject {|k, v| ! extra_keys.include? k} msg = build_message(message, "found extras <?>, not <?>", found_extras, extras) assert_block(msg) { found_extras == extras } - - msg = build_message(message, "The generated path <?> did not match <?>", generated_path, + + msg = build_message(message, "The generated path <?> did not match <?>", generated_path, expected_path) assert_block(msg) { expected_path == generated_path } end end - # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates + # Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates # <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+ # and +assert_generates+ into one step. # # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The - # +message+ parameter allows you to specify a custom error message to display upon failure. + # +message+ parameter allows you to specify a custom error message to display upon failure. # # ==== Examples # # Assert a basic route: a controller with the default action (index) @@ -119,12 +119,12 @@ module ActionController # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }) def assert_routing(path, options, defaults={}, extras={}, message=nil) assert_recognizes(options, path, extras, message) - - controller, default_controller = options[:controller], defaults[:controller] + + controller, default_controller = options[:controller], defaults[:controller] if controller && controller.include?(?/) && default_controller && default_controller.include?(?/) options[:controller] = "/#{controller}" end - + assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message) end diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index d3594e711c..9114894b1d 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -21,10 +21,8 @@ module ActionController # from the response HTML or elements selected by the enclosing assertion. # # In addition to HTML responses, you can make the following assertions: - # * +assert_select_rjs+ - Assertions on HTML content of RJS update and - # insertion operations. - # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, - # for example for dealing with feed item descriptions. + # * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations. + # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions. # * +assert_select_email+ - Assertions on the HTML body of an e-mail. # # Also see HTML::Selector to learn how to use selectors. @@ -409,6 +407,7 @@ module ActionController if rjs_type == :insert arg = args.shift + position = arg insertion = "insert_#{arg}".to_sym raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion] statement = "(#{RJS_STATEMENTS[insertion]})" @@ -420,6 +419,7 @@ module ActionController else statement = "#{RJS_STATEMENTS[:any]}" end + position ||= Regexp.new(RJS_INSERTIONS.join('|')) # Next argument we're looking for is the element identifier. If missing, we pick # any element. @@ -436,9 +436,14 @@ module ActionController Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) when :remove, :show, :hide, :toggle Regexp.new("#{statement}\\(\"#{id}\"\\)") - else - Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) - end + when :replace, :replace_html + Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)") + when :insert, :insert_html + Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);") + else + Regexp.union(Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)"), + Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);")) + end # Duplicate the body since the next step involves destroying it. matches = nil @@ -447,7 +452,7 @@ module ActionController matches = @response.body.match(pattern) else @response.body.gsub(pattern) do |match| - html = unescape_rjs($2) + html = unescape_rjs(match) matches ||= [] matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? } "" @@ -587,17 +592,16 @@ module ActionController :hide => /Element\.hide/, :toggle => /Element\.toggle/ } + RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") + RJS_PATTERN_HTML = /"((\\"|[^"])*)"/ RJS_INSERTIONS = [:top, :bottom, :before, :after] RJS_INSERTIONS.each do |insertion| - RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}")) + RJS_STATEMENTS["insert_#{insertion}".to_sym] = /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/ end - RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion| - Regexp.quote("new Insertion.#{insertion.to_s.camelize}") + /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/ end.join('|')) - RJS_PATTERN_HTML = /"((\\"|[^"])*)"/ - RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", - Regexp::MULTILINE) + RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index df94f78f18..91023cd774 100755..100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -283,6 +283,14 @@ module ActionController #:nodoc: @@debug_routes = true cattr_accessor :debug_routes + # Indicates whether to allow concurrent action processing. Your + # controller actions and any other code they call must also behave well + # when called from concurrent threads. Turned off by default. + @@allow_concurrency = false + cattr_accessor :allow_concurrency + + @@guard = Monitor.new + # Modern REST web services often need to submit complex data to the web application. # The <tt>@@param_parsers</tt> hash lets you register handlers which will process the HTTP body and add parameters to the # <tt>params</tt> hash. These handlers are invoked for POST and PUT requests. @@ -354,6 +362,15 @@ module ActionController #:nodoc: class_inheritable_accessor :allow_forgery_protection self.allow_forgery_protection = true + # If you are deploying to a subdirectory, you will need to set + # <tt>config.action_controller.relative_url_root</tt> + # This defaults to ENV['RAILS_RELATIVE_URL_ROOT'] + cattr_writer :relative_url_root + + def self.relative_url_root + @@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT'] + end + # Holds the request object that's primarily used to get environment variables through access like # <tt>request.env["REQUEST_URI"]</tt>. attr_internal :request @@ -411,11 +428,7 @@ module ActionController #:nodoc: # By default, all methods defined in ActionController::Base and included modules are hidden. # More methods can be hidden using <tt>hide_actions</tt>. def hidden_actions - unless read_inheritable_attribute(:hidden_actions) - write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s }) - end - - read_inheritable_attribute(:hidden_actions) + read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, []) end # Hide each of the given methods from being callable as actions. @@ -519,6 +532,8 @@ module ActionController #:nodoc: public # Extracts the action_name from the request parameters and performs that action. def process(request, response, method = :perform_action, *arguments) #:nodoc: + response.request = request + initialize_template_class(response) assign_shortcuts(request, response) initialize_current_url @@ -526,11 +541,13 @@ module ActionController #:nodoc: forget_variables_added_to_assigns log_processing - send(method, *arguments) - assign_default_content_type_and_charset + if @@allow_concurrency + send(method, *arguments) + else + @@guard.synchronize { send(method, *arguments) } + end - response.request = request response.prepare! unless component_request? response ensure @@ -763,9 +780,6 @@ module ActionController #:nodoc: # render :file => "/path/to/some/template.erb", :layout => true, :status => 404 # render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404 # - # # Renders a template relative to the template root and chooses the proper file extension - # render :file => "some/template", :use_full_path => true - # # === Rendering text # # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text @@ -896,21 +910,10 @@ module ActionController #:nodoc: response.content_type ||= Mime::JSON render_for_text(json, options[:status]) - elsif partial = options[:partial] - partial = default_template_name if partial == true + elsif options[:partial] + options[:partial] = default_template_name if options[:partial] == true add_variables_to_assigns - - if collection = options[:collection] - render_for_text( - @template.send!(:render_partial_collection, partial, collection, - options[:spacer_template], options[:locals], options[:as]), options[:status] - ) - else - render_for_text( - @template.send!(:render_partial, partial, - options[:object], options[:locals]), options[:status] - ) - end + render_for_text(@template.render(options), options[:status]) elsif options[:update] add_variables_to_assigns @@ -921,8 +924,7 @@ module ActionController #:nodoc: render_for_text(generator.to_s, options[:status]) elsif options[:nothing] - # Safari doesn't pass the headers of the return if the response is zero length - render_for_text(" ", options[:status]) + render_for_text(nil, options[:status]) else render_for_file(default_template_name, options[:status], true) @@ -968,6 +970,17 @@ module ActionController #:nodoc: render :nothing => true, :status => status end + # Sets the Last-Modified response header. Returns 304 Not Modified if the + # If-Modified-Since request header is <= last modified. + def last_modified!(utc_time) + head(:not_modified) if response.last_modified!(utc_time) + end + + # Sets the ETag response header. Returns 304 Not Modified if the + # If-None-Match request header matches. + def etag!(etag) + head(:not_modified) if response.etag!(etag) + end # Clears the rendered results, allowing for another render to be performed. def erase_render_results #:nodoc: @@ -1125,7 +1138,11 @@ module ActionController #:nodoc: response.body ||= '' response.body << text.to_s else - response.body = text.is_a?(Proc) ? text : text.to_s + response.body = case text + when Proc then text + when nil then " " # Safari doesn't pass the headers of the return if the response is zero length + else text.to_s + end end end @@ -1155,7 +1172,7 @@ module ActionController #:nodoc: def log_processing if logger && logger.info? - logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" + logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id) logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}" end @@ -1166,16 +1183,16 @@ module ActionController #:nodoc: end def perform_action - if self.class.action_methods.include?(action_name) + if action_methods.include?(action_name) send(action_name) default_render unless performed? elsif respond_to? :method_missing method_missing action_name default_render unless performed? - elsif template_exists? && template_public? + elsif template_exists? default_render else - raise UnknownAction, "No action responded to #{action_name}", caller + raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller end end @@ -1188,20 +1205,24 @@ module ActionController #:nodoc: end def assign_default_content_type_and_charset - response.content_type ||= Mime::HTML - response.charset ||= self.class.default_charset unless sending_file? - end - - def sending_file? - response.headers["Content-Transfer-Encoding"] == "binary" + response.assign_default_content_type_and_charset! end + deprecate :assign_default_content_type_and_charset => :'response.assign_default_content_type_and_charset!' def action_methods self.class.action_methods end def self.action_methods - @action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions + @action_methods ||= + # All public instance methods of this class, including ancestors + public_instance_methods(true).map { |m| m.to_s }.to_set - + # Except for public instance methods of Base and its ancestors + Base.public_instance_methods(true).map { |m| m.to_s } + + # Be sure to include shadowed public instance methods of this class + public_instance_methods(false).map { |m| m.to_s } - + # And always exclude explicitly hidden actions + hidden_actions end def add_variables_to_assigns @@ -1243,13 +1264,11 @@ module ActionController #:nodoc: @template.file_exists?(template_name) end - def template_public?(template_name = default_template_name) - @template.file_public?(template_name) - end - def template_exempt_from_layout?(template_name = default_template_name) template_name = @template.pick_template(template_name).to_s if @template @@exempt_from_layout.any? { |ext| template_name =~ ext } + rescue ActionView::MissingTemplate + false end def default_template_name(action_name = self.action_name) diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 57b31ec9d1..e9b434dd25 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -2,7 +2,7 @@ module ActionController #:nodoc: module Caching # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple - # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: + # parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like: # # <b>Hello <%= @name %></b> # <% cache do %> @@ -60,10 +60,8 @@ module ActionController #:nodoc: ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) end - def fragment_for(block, name = {}, options = nil) #:nodoc: + def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: if perform_caching - buffer = yield - if cache = read_fragment(name, options) buffer.concat(cache) else diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb index 8bc5e4c3a7..0ca27b30db 100644 --- a/actionpack/lib/action_controller/cgi_process.rb +++ b/actionpack/lib/action_controller/cgi_process.rb @@ -43,7 +43,7 @@ module ActionController #:nodoc: :session_path => "/", # available to all paths in app :session_key => "_session_id", :cookie_only => true - } unless const_defined?(:DEFAULT_SESSION_OPTIONS) + } def initialize(cgi, session_options = {}) @cgi = cgi @@ -61,53 +61,14 @@ module ActionController #:nodoc: end end - # The request body is an IO input stream. If the RAW_POST_DATA environment - # variable is already set, wrap it in a StringIO. - def body - if raw_post = env['RAW_POST_DATA'] - raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) - StringIO.new(raw_post) - else - @cgi.stdinput - end - end - - def query_parameters - @query_parameters ||= self.class.parse_query_parameters(query_string) - end - - def request_parameters - @request_parameters ||= parse_formatted_request_parameters + def body_stream #:nodoc: + @cgi.stdinput end def cookies @cgi.cookies.freeze end - def host_with_port_without_standard_port_handling - if forwarded = env["HTTP_X_FORWARDED_HOST"] - forwarded.split(/,\s?/).last - elsif http_host = env['HTTP_HOST'] - http_host - elsif server_name = env['SERVER_NAME'] - server_name - else - "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" - end - end - - def host - host_with_port_without_standard_port_handling.sub(/:\d+$/, '') - end - - def port - if host_with_port_without_standard_port_handling =~ /:(\d+)$/ - $1.to_i - else - standard_port - end - end - def session unless defined?(@session) if @session_options == false diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb index a4cddbcea2..0428f2a23d 100644 --- a/actionpack/lib/action_controller/cookies.rb +++ b/actionpack/lib/action_controller/cookies.rb @@ -22,6 +22,16 @@ module ActionController #:nodoc: # # cookies.delete :user_name # + # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: + # + # cookies[:key] = { + # :value => 'a yummy cookie', + # :expires => 1.year.from_now, + # :domain => 'domain.com' + # } + # + # cookies.delete(:key, :domain => 'domain.com') + # # The option symbols for setting cookies are: # # * <tt>:value</tt> - The cookie's value or list of values (as an array). diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 7df987d525..bdae5f9d86 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -2,8 +2,6 @@ module ActionController # Dispatches requests to the appropriate controller and takes care of # reloading the app after each request when Dependencies.load? is true. class Dispatcher - @@guard = Mutex.new - class << self def define_dispatcher_callbacks(cache_classes) unless cache_classes @@ -26,7 +24,7 @@ module ActionController to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers } end - after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush) + after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush) end # Backward-compatible class method takes CGI-specific args. Deprecated @@ -46,7 +44,7 @@ module ActionController def to_prepare(identifier = nil, &block) @prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier) - @prepare_dispatch_callbacks | callback + @prepare_dispatch_callbacks.replace_or_append!(callback) end # If the block raises, send status code as a last-ditch response. @@ -101,15 +99,13 @@ module ActionController end def dispatch - @@guard.synchronize do - begin - run_callbacks :before_dispatch - handle_request - rescue Exception => exception - failsafe_rescue exception - ensure - run_callbacks :after_dispatch, :enumerator => :reverse_each - end + begin + run_callbacks :before_dispatch + handle_request + rescue Exception => exception + failsafe_rescue exception + ensure + run_callbacks :after_dispatch, :enumerator => :reverse_each end end @@ -146,7 +142,7 @@ module ActionController end def flush_logger - RAILS_DEFAULT_LOGGER.flush + Base.logger.flush end protected diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb index 10dc0cc45b..1d7236f18a 100644 --- a/actionpack/lib/action_controller/filters.rb +++ b/actionpack/lib/action_controller/filters.rb @@ -109,16 +109,17 @@ module ActionController #:nodoc: update_options! options end + # override these to return true in appropriate subclass def before? - self.class == BeforeFilter + false end def after? - self.class == AfterFilter + false end def around? - self.class == AroundFilter + false end # Make sets of strings from :only/:except options @@ -170,6 +171,10 @@ module ActionController #:nodoc: :around end + def around? + true + end + def call(controller, &block) if should_run_callback?(controller) method = filter_responds_to_before_and_after? ? around_proc : self.method @@ -212,6 +217,10 @@ module ActionController #:nodoc: :before end + def before? + true + end + def call(controller, &block) super if controller.send!(:performed?) @@ -224,6 +233,10 @@ module ActionController #:nodoc: def type :after end + + def after? + true + end end # Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do diff --git a/actionpack/lib/action_controller/headers.rb b/actionpack/lib/action_controller/headers.rb index 7239438c49..139669c66f 100644 --- a/actionpack/lib/action_controller/headers.rb +++ b/actionpack/lib/action_controller/headers.rb @@ -1,31 +1,33 @@ +require 'active_support/memoizable' + module ActionController module Http class Headers < ::Hash - - def initialize(constructor = {}) - if constructor.is_a?(Hash) + extend ActiveSupport::Memoizable + + def initialize(*args) + if args.size == 1 && args[0].is_a?(Hash) super() - update(constructor) + update(args[0]) else - super(constructor) + super end end - + def [](header_name) if include?(header_name) - super + super else - super(normalize_header(header_name)) + super(env_name(header_name)) end end - - + private - # Takes an HTTP header name and returns it in the - # format - def normalize_header(header_name) + # Converts a HTTP header name to an environment variable name. + def env_name(header_name) "HTTP_#{header_name.upcase.gsub(/-/, '_')}" end + memoize :env_name end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 18c2df8b37..198a22e8dc 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -101,7 +101,7 @@ module ActionController @https = flag end - # Return +true+ if the session is mimicing a secure HTTPS request. + # Return +true+ if the session is mimicking a secure HTTPS request. # # if session.https? # ... @@ -165,11 +165,19 @@ module ActionController status/100 == 3 end - # Performs a GET request with the given parameters. The parameters may - # be +nil+, a Hash, or a string that is appropriately encoded - # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). - # The headers should be a hash. The keys will automatically be upcased, with the - # prefix 'HTTP_' added if needed. + # Performs a GET request with the given parameters. + # + # - +path+: The URI (as a String) on which you want to perform a GET request. + # - +parameters+: The HTTP parameters that you want to pass. This may be +nil+, + # a Hash, or a String that is appropriately encoded + # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). + # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will + # automatically be upcased, with the prefix 'HTTP_' added if needed. + # + # This method returns an AbstractResponse object, which one can use to inspect + # the details of the response. Furthermore, if this method was called from an + # ActionController::IntegrationTest object, then that object's <tt>@response</tt> + # instance variable will point to the same response object. # # You can also perform POST, PUT, DELETE, and HEAD requests with +post+, # +put+, +delete+, and +head+. @@ -220,21 +228,6 @@ module ActionController end private - class StubCGI < CGI #:nodoc: - attr_accessor :stdinput, :stdoutput, :env_table - - def initialize(env, stdinput = nil) - self.env_table = env - self.stdoutput = StringIO.new - - super - - stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding) - stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding) - @stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '') - end - end - # Tailors the session based on the given URI, setting the HTTPS value # and the hostname. def interpret_uri(path) @@ -282,9 +275,8 @@ module ActionController ActionController::Base.clear_last_instantiation! - cgi = StubCGI.new(env, data) - ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) - @result = cgi.stdoutput.string + env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '') + @status, @headers, result_body = ActionController::Dispatcher.new.call(env) @request_count += 1 @controller = ActionController::Base.last_instantiation @@ -298,32 +290,34 @@ module ActionController @html_document = nil - parse_result - return status - rescue MultiPartNeededException - boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1" - status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"})) - return status - end + # Inject status back in for backwords compatibility with CGI + @headers['Status'] = @status - # Parses the result of the response and extracts the various values, - # like cookies, status, headers, etc. - def parse_result - response_headers, result_body = @result.split(/\r\n\r\n/, 2) + @status, @status_message = @status.split(/ /) + @status = @status.to_i - @headers = Hash.new { |h,k| h[k] = [] } - response_headers.to_s.each_line do |line| - key, value = line.strip.split(/:\s*/, 2) - @headers[key.downcase] << value + cgi_headers = Hash.new { |h,k| h[k] = [] } + @headers.each do |key, value| + cgi_headers[key.downcase] << value end + cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first + @headers = cgi_headers - (@headers['set-cookie'] || [] ).each do |string| - name, value = string.match(/^([^=]*)=([^;]*);/)[1,2] + @response.headers['cookie'] ||= [] + (@headers['set-cookie'] || []).each do |cookie| + name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2] @cookies[name] = value + + # Fake CGI cookie header + # DEPRECATE: Use response.headers["Set-Cookie"] instead + @response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value) end - @status, @status_message = @headers["status"].first.to_s.split(/ /) - @status = @status.to_i + return status + rescue MultiPartNeededException + boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1" + status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"})) + return status end # Encode the cookies hash in a format suitable for passing to a @@ -336,13 +330,15 @@ module ActionController # Get a temporary URL writer object def generic_url_rewriter - cgi = StubCGI.new('REQUEST_METHOD' => "GET", - 'QUERY_STRING' => "", - "REQUEST_URI" => "/", - "HTTP_HOST" => host, - "SERVER_PORT" => https? ? "443" : "80", - "HTTPS" => https? ? "on" : "off") - ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {}) + env = { + 'REQUEST_METHOD' => "GET", + 'QUERY_STRING' => "", + "REQUEST_URI" => "/", + "HTTP_HOST" => host, + "SERVER_PORT" => https? ? "443" : "80", + "HTTPS" => https? ? "on" : "off" + } + ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {}) end def name_with_prefix(prefix, name) @@ -443,7 +439,7 @@ EOF end %w(get post put head delete cookies assigns - xml_http_request get_via_redirect post_via_redirect).each do |method| + xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless @integration_session # reset the html_document variable, but only for new get/post calls @@ -499,7 +495,7 @@ EOF # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless @integration_session - returning @integration_session.send!(sym, *args, &block) do + returning @integration_session.__send__(sym, *args, &block) do copy_session_variables! end end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 7c30bf0778..30564c7bb3 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -102,6 +102,12 @@ module ActionController 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) + unless url_options.empty? + args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options + end + send!(named_route, *args) end @@ -114,19 +120,19 @@ module ActionController %w(edit new formatted).each do |action| module_eval <<-EOT, __FILE__, __LINE__ - def #{action}_polymorphic_url(record_or_hash) - polymorphic_url(record_or_hash, :action => "#{action}") + def #{action}_polymorphic_url(record_or_hash, options = {}) + polymorphic_url(record_or_hash, options.merge(:action => "#{action}")) end - def #{action}_polymorphic_path(record_or_hash) - polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path) + def #{action}_polymorphic_path(record_or_hash, options = {}) + polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path)) end EOT end private def action_prefix(options) - options[:action] ? "#{options[:action]}_" : "" + options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : "" end def routing_type(options) diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index 01bc1ebb26..1ace16da07 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store' module ActionController #:nodoc: class RackRequest < AbstractRequest #:nodoc: - attr_accessor :env, :session_options + attr_accessor :session_options attr_reader :cgi class SessionFixationAttempt < StandardError #:nodoc: @@ -15,7 +15,7 @@ module ActionController #:nodoc: :session_path => "/", # available to all paths in app :session_key => "_session_id", :cookie_only => true - } unless const_defined?(:DEFAULT_SESSION_OPTIONS) + } def initialize(env, session_options = DEFAULT_SESSION_OPTIONS) @session_options = session_options @@ -24,39 +24,34 @@ module ActionController #:nodoc: super() end - %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED QUERY_STRING REMOTE_HOST + %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO + PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER SCRIPT_NAME SERVER_NAME SERVER_PROTOCOL HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST + HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| define_method(env.sub(/^HTTP_/n, '').downcase) do @env[env] end end - # The request body is an IO input stream. If the RAW_POST_DATA environment - # variable is already set, wrap it in a StringIO. - def body - if raw_post = env['RAW_POST_DATA'] - StringIO.new(raw_post) + def query_string + qs = super + if !qs.blank? + qs else - @env['rack.input'] + @env['QUERY_STRING'] end end - def key?(key) - @env.key?(key) - end - - def query_parameters - @query_parameters ||= self.class.parse_query_parameters(query_string) + def body_stream #:nodoc: + @env['rack.input'] end - def request_parameters - @request_parameters ||= parse_formatted_request_parameters + def key?(key) + @env.key?(key) end def cookies @@ -70,38 +65,6 @@ module ActionController #:nodoc: @env["rack.request.cookie_hash"] end - def host_with_port_without_standard_port_handling - if forwarded = @env["HTTP_X_FORWARDED_HOST"] - forwarded.split(/,\s?/).last - elsif http_host = @env['HTTP_HOST'] - http_host - elsif server_name = @env['SERVER_NAME'] - server_name - else - "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" - end - end - - def host - host_with_port_without_standard_port_handling.sub(/:\d+$/, '') - end - - def port - if host_with_port_without_standard_port_handling =~ /:(\d+)$/ - $1.to_i - else - standard_port - end - end - - def remote_addr - @env['REMOTE_ADDR'] - end - - def request_method - @env['REQUEST_METHOD'].downcase.to_sym - end - def server_port @env['SERVER_PORT'].to_i end @@ -189,23 +152,30 @@ end_msg end class RackResponse < AbstractResponse #:nodoc: - attr_accessor :status - def initialize(request) - @request = request + @cgi = request.cgi @writer = lambda { |x| @body << x } @block = nil super() end + # Retrieve status from instance variable if has already been delete + def status + @status || super + end + def out(output = $stdout, &block) + # Nasty hack because CGI sessions are closed after the normal + # prepare! statement + set_cookies! + @block = block - normalize_headers(@headers) - if [204, 304].include?(@status.to_i) - @headers.delete "Content-Type" - [status, @headers.to_hash, []] + @status = headers.delete("Status") + if [204, 304].include?(status.to_i) + headers.delete("Content-Type") + [status, headers.to_hash, []] else - [status, @headers.to_hash, self] + [status, headers.to_hash, self] end end alias to_a out @@ -237,43 +207,57 @@ end_msg @block == nil && @body.empty? end - private - def normalize_headers(options = "text/html") - if options.is_a?(String) - headers['Content-Type'] = options unless headers['Content-Type'] - else - headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length'] + def prepare! + super - headers['Content-Type'] = options.delete('type') || "text/html" - headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset'] + convert_language! + convert_expires! + set_status! + # set_cookies! + end - headers['Content-Language'] = options.delete('language') if options['language'] - headers['Expires'] = options.delete('expires') if options['expires'] + private + def convert_language! + headers["Content-Language"] = headers.delete("language") if headers["language"] + end - @status = options['Status'] || "200 OK" + def convert_expires! + headers["Expires"] = headers.delete("") if headers["expires"] + end - # Convert 'cookie' header to 'Set-Cookie' headers. - # Because Set-Cookie header can appear more the once in the response body, - # we store it in a line break seperated string that will be translated to - # multiple Set-Cookie header by the handler. - if cookie = options.delete('cookie') - cookies = [] + def convert_content_type! + super + headers['Content-Type'] = headers.delete('type') || "text/html" + headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] + end - case cookie - when Array then cookie.each { |c| cookies << c.to_s } - when Hash then cookie.each { |_, c| cookies << c.to_s } - else cookies << cookie.to_s - end + def set_content_length! + super + headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"] + end - @request.cgi.output_cookies.each { |c| cookies << c.to_s } if @request.cgi.output_cookies + def set_status! + self.status ||= "200 OK" + end - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact + def set_cookies! + # Convert 'cookie' header to 'Set-Cookie' headers. + # Because Set-Cookie header can appear more the once in the response body, + # we store it in a line break separated string that will be translated to + # multiple Set-Cookie header by the handler. + if cookie = headers.delete('cookie') + cookies = [] + + case cookie + when Array then cookie.each { |c| cookies << c.to_s } + when Hash then cookie.each { |_, c| cookies << c.to_s } + else cookies << cookie.to_s end - options.each { |k,v| headers[k] = v } - end + @cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies - "" + headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact + end end end diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 2d9f6c3e6f..364e6201cc 100755..100644 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -2,14 +2,21 @@ require 'tempfile' require 'stringio' require 'strscan' -module ActionController - # HTTP methods which are accepted by default. - ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options )) +require 'active_support/memoizable' +module ActionController # CgiRequest and TestRequest provide concrete implementations. class AbstractRequest - cattr_accessor :relative_url_root - remove_method :relative_url_root + extend ActiveSupport::Memoizable + + def self.relative_url_root=(*args) + ActiveSupport::Deprecation.warn( + "ActionController::AbstractRequest.relative_url_root= has been renamed." + + "You can now set it with config.action_controller.relative_url_root=", caller) + end + + HTTP_METHODS = %w(get head put post delete options) + HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } # The hash of environment variables for this request, # such as { 'RAILS_ENV' => 'production' }. @@ -18,15 +25,12 @@ module ActionController # The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>. # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. def request_method - @request_method ||= begin - method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase - if ACCEPTED_HTTP_METHODS.include?(method) - method.to_sym - else - raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}" - end - end + method = @env['REQUEST_METHOD'] + method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank? + + HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}") end + memoize :request_method # The HTTP request method as a lowercase symbol, such as <tt>:get</tt>. # Note, HEAD is returned as <tt>:get</tt> since the two are functionally @@ -61,36 +65,62 @@ module ActionController request_method == :head end - # Provides acccess to the request's HTTP headers, for example: + # Provides access to the request's HTTP headers, for example: # request.headers["Content-Type"] # => "text/plain" def headers - @headers ||= ActionController::Http::Headers.new(@env) + ActionController::Http::Headers.new(@env) end + memoize :headers def content_length - @content_length ||= env['CONTENT_LENGTH'].to_i + @env['CONTENT_LENGTH'].to_i end + memoize :content_length # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post format is extracted from the # X-Post-Data-Format HTTP header if present. def content_type - @content_type ||= Mime::Type.lookup(content_type_without_parameters) + Mime::Type.lookup(content_type_without_parameters) end + memoize :content_type # Returns the accepted MIME type for the request def accepts - @accepts ||= - begin - header = @env['HTTP_ACCEPT'].to_s.strip + header = @env['HTTP_ACCEPT'].to_s.strip - if header.empty? - [content_type, Mime::ALL].compact - else - Mime::Type.parse(header) - end - end + if header.empty? + [content_type, Mime::ALL].compact + else + Mime::Type.parse(header) + end + end + memoize :accepts + + def if_modified_since + if since = env['HTTP_IF_MODIFIED_SINCE'] + Time.rfc2822(since) rescue nil + end + end + memoize :if_modified_since + + def if_none_match + env['HTTP_IF_NONE_MATCH'] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if_none_match && if_none_match == etag + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. + def fresh?(response) + not_modified?(response.last_modified) || etag_matches?(response.etag) end # Returns the Mime type for the format used in the request. @@ -99,7 +129,7 @@ module ActionController # GET /posts/5.xhtml | request.format => Mime::HTML # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt> def format - @format ||= begin + @format ||= if parameters[:format] Mime::Type.lookup_by_extension(parameters[:format]) elsif ActionController::Base.use_accept_header @@ -109,16 +139,15 @@ module ActionController else Mime::Type.lookup_by_extension("html") end - end end - - + + # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension. # Example: # # class ApplicationController < ActionController::Base # before_filter :adjust_format_for_iphone - # + # # private # def adjust_format_for_iphone # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] @@ -197,42 +226,62 @@ EOM @env['REMOTE_ADDR'] end + memoize :remote_ip # Returns the lowercase name of the HTTP server software. def server_software (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil end + memoize :server_software # Returns the complete URL used for this request def url protocol + host_with_port + request_uri end + memoize :url # Return 'https://' if this is an SSL request and 'http://' otherwise. def protocol ssl? ? 'https://' : 'http://' end + memoize :protocol # Is this an SSL request? def ssl? @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' end + def raw_host_with_port + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + # Returns the host for this request, such as example.com. def host + raw_host_with_port.sub(/:\d+$/, '') end + memoize :host # Returns a host:port string for this request, such as example.com or # example.com:8080. def host_with_port - @host_with_port ||= host + port_string + "#{host}#{port_string}" end + memoize :host_with_port # Returns the port number of this request as an integer. def port - @port_as_int ||= @env['SERVER_PORT'].to_i + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end end + memoize :port # Returns the standard port number for this request's protocol def standard_port @@ -245,7 +294,7 @@ EOM # Returns a port suffix like ":8080" if the port number of this request # is not the default HTTP port 80 or HTTPS port 443. def port_string - (port == standard_port) ? '' : ":#{port}" + port == standard_port ? '' : ":#{port}" end # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify @@ -265,7 +314,7 @@ EOM parts[0..-(tld_length+2)] end - # Return the query string, accounting for server idiosyncracies. + # Return the query string, accounting for server idiosyncrasies. def query_string if uri = @env['REQUEST_URI'] uri.split('?', 2)[1] || '' @@ -273,8 +322,9 @@ EOM @env['QUERY_STRING'] || '' end end + memoize :query_string - # Return the request URI, accounting for server idiosyncracies. + # Return the request URI, accounting for server idiosyncrasies. # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. def request_uri if uri = @env['REQUEST_URI'] @@ -282,46 +332,33 @@ EOM (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri else # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. - script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = @env['PATH_INFO'] - uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil? - unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty? - uri << '?' << env_qs + uri = @env['PATH_INFO'].to_s + + if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) + uri = uri.sub(/#{script_filename}\//, '') end - if uri.nil? + env_qs = @env['QUERY_STRING'].to_s + uri += "?#{env_qs}" unless env_qs.empty? + + if uri.blank? @env.delete('REQUEST_URI') - uri else @env['REQUEST_URI'] = uri end end end + memoize :request_uri # 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/^#{relative_url_root}/, '') - path || '' - end - - # Returns the path minus the web server relative installation directory. - # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT. - # It can be automatically extracted for Apache setups. If the server is not - # Apache, this method returns an empty string. - def relative_url_root - @@relative_url_root ||= case - when @env["RAILS_RELATIVE_URL_ROOT"] - @env["RAILS_RELATIVE_URL_ROOT"] - when server_software == 'apache' - @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '') - else - '' - end + path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '') + path || '' end - + memoize :path # Read the request body. This is useful for web services that need to # work with raw requests directly. @@ -343,34 +380,56 @@ EOM @symbolized_path_parameters = @parameters = nil end - # The same as <tt>path_parameters</tt> with explicitly symbolized keys - def symbolized_path_parameters + # The same as <tt>path_parameters</tt> with explicitly symbolized keys + def symbolized_path_parameters @symbolized_path_parameters ||= path_parameters.symbolize_keys end # Returns a hash with the parameters used to form the path of the request. # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys. # - # Example: + # Example: # # {'action' => 'my_action', 'controller' => 'my_controller'} def path_parameters @path_parameters ||= {} end + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = env['RAW_POST_DATA'] + raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) + StringIO.new(raw_post) + else + body_stream + end + end - #-- - # Must be implemented in the concrete request - #++ + def remote_addr + @env['REMOTE_ADDR'] + end - # The request body is an IO input stream. - def body + def referrer + @env['HTTP_REFERER'] + end + alias referer referrer + + + def query_parameters + @query_parameters ||= self.class.parse_query_parameters(query_string) end - def query_parameters #:nodoc: + def request_parameters + @request_parameters ||= parse_formatted_request_parameters end - def request_parameters #:nodoc: + + #-- + # Must be implemented in the concrete request + #++ + + def body_stream #:nodoc: end def cookies #:nodoc: @@ -397,8 +456,9 @@ EOM # The raw content type string with its parameters stripped off. def content_type_without_parameters - @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters) + self.class.extract_content_type_without_parameters(content_type_with_parameters) end + memoize :content_type_without_parameters private def content_type_from_legacy_post_data_format_header diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 02c9d59d07..05a6d8bb79 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -17,7 +17,7 @@ module ActionController #:nodoc: # forged link from another site, is done by embedding a token based on 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 indempotent anyway. + # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway. # # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb index a6471d0c08..a6471d0c08 100755..100644 --- a/actionpack/lib/action_controller/request_profiler.rb +++ b/actionpack/lib/action_controller/request_profiler.rb diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index 163ed87fbb..a1a9d68a35 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -112,19 +112,23 @@ module ActionController #:nodoc: protected # Exception handler called when the performance of an action raises an exception. def rescue_action(exception) - log_error(exception) if logger - erase_results if performed? + if handler_for_rescue(exception) + rescue_action_with_handler(exception) + else + log_error(exception) if logger + erase_results if performed? - # Let the exception alter the response if it wants. - # For example, MethodNotAllowed sets the Allow header. - if exception.respond_to?(:handle_response!) - exception.handle_response!(response) - end + # Let the exception alter the response if it wants. + # For example, MethodNotAllowed sets the Allow header. + if exception.respond_to?(:handle_response!) + exception.handle_response!(response) + end - if consider_all_requests_local || local_request? - rescue_action_locally(exception) - else - rescue_action_in_public(exception) + if consider_all_requests_local || local_request? + rescue_action_locally(exception) + else + rescue_action_in_public(exception) + end end end @@ -178,7 +182,7 @@ module ActionController #:nodoc: @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub"))) @template.send!(:assign_variables_from_controller) - @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception), :use_full_path => false)) + @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception))) response.content_type = Mime::HTML render_for_file(rescues_path("layout"), response_code_for_rescue(exception)) @@ -200,7 +204,7 @@ module ActionController #:nodoc: def perform_action_with_rescue #:nodoc: perform_action_without_rescue rescue Exception => exception - rescue_action_with_handler(exception) || rescue_action(exception) + rescue_action(exception) end def rescues_path(template_name) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index af2fcaf3ad..5f579cdb11 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -14,10 +14,10 @@ module ActionController # # === The Different Methods and their Usage # - # +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request - # +POST+ Creation of resources - # +PUT+ Editing of attributes on a resource - # +DELETE+ Deletion of a resource + # [+GET+] Requests for a resource, no saving or editing of a resource should occur in a GET request + # [+POST+] Creation of resources + # [+PUT+] Editing of attributes on a resource + # [+DELETE+] Deletion of a resource # # === Examples # @@ -296,6 +296,10 @@ module ActionController # article_comments_url(:article_id => @article) # article_comment_url(:article_id => @article, :id => @comment) # + # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: + # + # articles_comments_url(@comment.article_id, @comment) + # # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. # Use this if you have named routes that may clash. # @@ -303,13 +307,13 @@ module ActionController # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' # # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource: - # + # # map.resources :articles do |article| # article.resources :comments, :name_prefix => nil - # end - # + # end + # # This will yield named resources like so: - # + # # comments_url(@article) # comment_url(@article, @comment) # @@ -477,8 +481,7 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| action_options = action_options_for(action, resource, method) - map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options) - map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options) + map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options) end end end @@ -491,18 +494,15 @@ module ActionController index_route_name << "_index" end - map.named_route(index_route_name, resource.path, index_action_options) - map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options) + map_named_routes(map, index_route_name, resource.path, index_action_options) create_action_options = action_options_for("create", resource) - map.connect(resource.path, create_action_options) - map.connect("#{resource.path}.:format", create_action_options) + map_unnamed_routes(map, resource.path, create_action_options) end def map_default_singleton_actions(map, resource) create_action_options = action_options_for("create", resource) - map.connect(resource.path, create_action_options) - map.connect("#{resource.path}.:format", create_action_options) + map_unnamed_routes(map, resource.path, create_action_options) end def map_new_actions(map, resource) @@ -510,11 +510,9 @@ module ActionController actions.each do |action| action_options = action_options_for(action, resource, method) if action == :new - map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options) - map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options) + map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options) else - map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options) - map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options) + map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options) end end end @@ -528,22 +526,28 @@ module ActionController 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_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) - map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options) + map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) end end show_action_options = action_options_for("show", resource) - map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options) - map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options) + map_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options) update_action_options = action_options_for("update", resource) - map.connect(resource.member_path, update_action_options) - map.connect("#{resource.member_path}.:format", update_action_options) + map_unnamed_routes(map, resource.member_path, update_action_options) destroy_action_options = action_options_for("destroy", resource) - map.connect(resource.member_path, destroy_action_options) - map.connect("#{resource.member_path}.:format", destroy_action_options) + map_unnamed_routes(map, resource.member_path, destroy_action_options) + 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) end def add_conditions_for(conditions, method) @@ -555,6 +559,7 @@ module ActionController def action_options_for(action, resource, method = nil) default_options = { :action => action.to_s } require_id = !resource.kind_of?(SingletonResource) + case default_options[:action] when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) @@ -569,4 +574,4 @@ end class ActionController::Routing::RouteSet::Mapper include ActionController::Resources -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index 1d9f6676ba..5dac4128bb 100755..100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -1,58 +1,161 @@ require 'digest/md5' -module ActionController - class AbstractResponse #:nodoc: +module ActionController # :nodoc: + # Represents an HTTP response generated by a controller action. One can use an + # ActionController::AbstractResponse object to retrieve the current state of the + # response, or customize the response. An AbstractResponse object can either + # represent a "real" HTTP response (i.e. one that is meant to be sent back to the + # web browser) or a test response (i.e. one that is generated from integration + # tests). See CgiResponse and TestResponse, respectively. + # + # AbstractResponse is mostly a Ruby on Rails framework implement detail, and should + # never be used directly in controllers. Controllers should use the methods defined + # in ActionController::Base instead. For example, if you want to set the HTTP + # response's content MIME type, then use ActionControllerBase#headers instead of + # AbstractResponse#headers. + # + # Nevertheless, integration tests may want to inspect controller responses in more + # detail, and that's when AbstractResponse can be useful for application developers. + # Integration test methods such as ActionController::Integration::Session#get and + # ActionController::Integration::Session#post return objects of type TestResponse + # (which are of course also of type AbstractResponse). + # + # For example, the following demo integration "test" prints the body of the + # controller response to the console: + # + # class DemoControllerTest < ActionController::IntegrationTest + # def test_print_root_path_to_console + # get('/') + # puts @response.body + # end + # end + class AbstractResponse DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request - attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout + + # The body content (e.g. HTML) of the response, as a String. + attr_accessor :body + # The headers of the response, as a Hash. It maps header names to header values. + attr_accessor :headers + attr_accessor :session, :cookies, :assigns, :template, :layout + attr_accessor :redirected_to, :redirected_to_method_params + + delegate :default_charset, :to => 'ActionController::Base' def initialize @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] end + def status; headers['Status'] end + def status=(status) headers['Status'] = status end + + def location; headers['Location'] end + def location=(url) headers['Location'] = url end + + + # Sets the HTTP response's content MIME type. For example, in the controller + # you could write this: + # + # response.content_type = "text/plain" + # + # If a character set has been defined for this response (see charset=) then + # the character set information will also be included in the content type + # information. def content_type=(mime_type) - self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type + self.headers["Content-Type"] = + if mime_type =~ /charset/ || (c = charset).nil? + mime_type.to_s + else + "#{mime_type}; charset=#{c}" + end end - + + # Returns the response's content MIME type, or nil if content type has been set. def content_type content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0] content_type.blank? ? nil : content_type end - - def charset=(encoding) - self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}" + + # Set the charset of the Content-Type header. Set to nil to remove it. + # If no content type is set, it defaults to HTML. + def charset=(charset) + headers["Content-Type"] = + if charset + "#{content_type || Mime::HTML}; charset=#{charset}" + else + content_type || Mime::HTML.to_s + end end - + def charset charset = String(headers["Content-Type"] || headers["type"]).split(";")[1] charset.blank? ? nil : charset.strip.split("=")[1] end - def redirect(to_url, response_status) - self.headers["Status"] = response_status - self.headers["Location"] = to_url + def last_modified + if last = headers['Last-Modified'] + Time.httpdate(last) + end + end + + def last_modified? + headers.include?('Last-Modified') + end + + def last_modified=(utc_time) + headers['Last-Modified'] = utc_time.httpdate + end + + def etag; headers['ETag'] end + def etag?; headers.include?('ETag') end + def etag=(etag) + headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}") + end + + def redirect(url, status) + self.status = status + self.location = url + self.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>" + end - self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>" + def sending_file? + headers["Content-Transfer-Encoding"] == "binary" + end + + def assign_default_content_type_and_charset! + self.content_type ||= Mime::HTML + self.charset ||= default_charset unless sending_file? end def prepare! + assign_default_content_type_and_charset! + set_content_length! handle_conditional_get! convert_content_type! - set_content_length! end - private def handle_conditional_get! - if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty? - self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}") - self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] - - if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag'] - self.headers['Status'] = '304 Not Modified' + if nonempty_ok_response? + self.etag ||= body + if request && request.etag_matches?(etag) + self.status = '304 Not Modified' self.body = '' end end + + set_conditional_cache_control! if etag? || last_modified? + end + + def nonempty_ok_response? + ok = !status || status[0..2] == '200' + ok && body.is_a?(String) && !body.empty? + end + + def set_conditional_cache_control! + if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] + headers['Cache-Control'] = 'private, max-age=0, must-revalidate' + end end def convert_content_type! @@ -70,7 +173,9 @@ module ActionController # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice # for, say, a 2GB streaming file. def set_content_length! - self.headers["Content-Length"] = body.size unless body.respond_to?(:call) + unless body.respond_to?(:call) || (status && status[0..2] == '304') + self.headers["Content-Length"] ||= body.size + end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index dfbaa53b7c..8d51e823a6 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -201,7 +201,7 @@ module ActionController # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>. # # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>, - # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>, + # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>, # <tt>:any</tt> means that any method can access the route. # # Example: @@ -213,7 +213,7 @@ module ActionController # # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same # URL will route to the <tt>show</tt> action. - # + # # == Reloading routes # # You can reload routes if you feel you must: @@ -281,9 +281,9 @@ module ActionController end class << self - # Expects an array of controller names as the first argument. - # Executes the passed block with only the named controllers named available. - # This method is used in internal Rails testing. + # Expects an array of controller names as the first argument. + # Executes the passed block with only the named controllers named available. + # This method is used in internal Rails testing. def with_controllers(names) prior_controllers = @possible_controllers use_controllers! names @@ -292,10 +292,10 @@ module ActionController use_controllers! prior_controllers end - # Returns an array of paths, cleaned of double-slashes and relative path references. - # * "\\\" and "//" become "\\" or "/". - # * "/foo/bar/../config" becomes "/foo/config". - # The returned array is sorted by length, descending. + # Returns an array of paths, cleaned of double-slashes and relative path references. + # * "\\\" and "//" become "\\" or "/". + # * "/foo/bar/../config" becomes "/foo/config". + # The returned array is sorted by length, descending. def normalize_paths(paths) # do the hokey-pokey of path normalization... paths = paths.collect do |path| @@ -314,7 +314,7 @@ module ActionController paths = paths.uniq.sort_by { |path| - path.length } end - # Returns the array of controller names currently available to ActionController::Routing. + # Returns the array of controller names currently available to ActionController::Routing. def possible_controllers unless @possible_controllers @possible_controllers = [] @@ -339,28 +339,27 @@ module ActionController @possible_controllers end - # Replaces the internal list of controllers available to ActionController::Routing with the passed argument. - # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ]) + # Replaces the internal list of controllers available to ActionController::Routing with the passed argument. + # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ]) def use_controllers!(controller_names) @possible_controllers = controller_names end - # Returns a controller path for a new +controller+ based on a +previous+ controller path. - # Handles 4 scenarios: - # - # * stay in the previous controller: - # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion" - # - # * stay in the previous namespace: - # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts" - # - # * forced move to the root namespace: - # controller_relative_to( "/posts", "groups/discussion" ) # => "posts" - # - # * previous namespace is root: - # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts" - # - + # Returns a controller path for a new +controller+ based on a +previous+ controller path. + # Handles 4 scenarios: + # + # * stay in the previous controller: + # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion" + # + # * stay in the previous namespace: + # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts" + # + # * forced move to the root namespace: + # controller_relative_to( "/posts", "groups/discussion" ) # => "posts" + # + # * previous namespace is root: + # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts" + # def controller_relative_to(controller, previous) if controller.nil? then previous elsif controller[0] == ?/ then controller[1..-1] @@ -369,12 +368,11 @@ module ActionController end end end - Routes = RouteSet.new ActiveSupport::Inflector.module_eval do - # Ensures that routes are reloaded when Rails inflections are updated. + # Ensures that routes are reloaded when Rails inflections are updated. def inflections_with_route_reloading(&block) returning(inflections_without_route_reloading(&block)) { ActionController::Routing::Routes.reload! if block_given? diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index b8323847fd..03427e41de 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -48,14 +48,10 @@ module ActionController end when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) when /\A\?(.*?)\?/ - returning segment = StaticSegment.new($1) do - segment.is_optional = true - end + StaticSegment.new($1, :optional => true) when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) when Regexp.new(separator_pattern) then - returning segment = DividerSegment.new($&) do - segment.is_optional = (optional_separators.include? $&) - end + DividerSegment.new($&, :optional => (optional_separators.include? $&)) end [segment, $~.post_match] end @@ -76,6 +72,8 @@ module ActionController defaults = (options.delete(:defaults) || {}).dup conditions = (options.delete(:conditions) || {}).dup + validate_route_conditions(conditions) + path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact options.each do |key, value| hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements @@ -174,30 +172,30 @@ module ActionController defaults, requirements, conditions = divide_route_options(segments, options) requirements = assign_route_options(segments, defaults, requirements) - route = Route.new - - route.segments = segments - route.requirements = requirements - route.conditions = conditions + # TODO: Segments should be frozen on initialize + segments.each { |segment| segment.freeze } - if !route.significant_keys.include?(:action) && !route.requirements[:action] - route.requirements[:action] = "index" - route.significant_keys << :action - end - - # Routes cannot use the current string interpolation method - # if there are user-supplied <tt>:requirements</tt> as the interpolation - # code won't raise RoutingErrors when generating - if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION - route.optimise = false - end + route = Route.new(segments, requirements, conditions) if !route.significant_keys.include?(:controller) raise ArgumentError, "Illegal route: the :controller must be specified!" end - route + route.freeze end + + private + def validate_route_conditions(conditions) + if method = conditions[:method] + if method == :head + raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" + end + + unless HTTP_METHODS.include?(method.to_sym) + raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}" + end + end + end end end end diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb index cd4a423e6b..0fe836606c 100644 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ b/actionpack/lib/action_controller/routing/optimisations.rb @@ -1,14 +1,14 @@ module ActionController module Routing - # Much of the slow performance from routes comes from the + # Much of the slow performance from routes comes from the # complexity of expiry, <tt>:requirements</tt> matching, defaults providing - # and figuring out which url pattern to use. With named routes - # we can avoid the expense of finding the right route. So if + # and figuring out which url pattern to use. With named routes + # we can avoid the expense of finding the right route. So if # they've provided the right number of arguments, and have no # <tt>:requirements</tt>, we can just build up a string and return it. - # - # To support building optimisations for other common cases, the - # generation code is separated into several classes + # + # To support building optimisations for other common cases, the + # generation code is separated into several classes module Optimisation def generate_optimisation_block(route, kind) return "" unless route.optimise? @@ -20,6 +20,7 @@ module ActionController class Optimiser attr_reader :route, :kind + def initialize(route, kind) @route = route @kind = kind @@ -53,12 +54,12 @@ module ActionController # map.person '/people/:id' # # If the user calls <tt>person_url(@person)</tt>, we can simply - # return a string like "/people/#{@person.to_param}" + # return a string like "/people/#{@person.to_param}" # rather than triggering the expensive logic in +url_for+. class PositionalArguments < Optimiser def guard_condition number_of_arguments = route.segment_keys.size - # if they're using foo_url(:id=>2) it's one + # 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 "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)" @@ -76,7 +77,7 @@ module ActionController elements << '#{request.host_with_port}' end - elements << '#{request.relative_url_root if request.relative_url_root}' + elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}' # The last entry in <tt>route.segments</tt> appears to *always* be a # 'divider segment' for '/' but we have assertions to ensure that @@ -94,14 +95,14 @@ module ActionController end # This case is mostly the same as the positional arguments case - # above, but it supports additional query parameters as the last + # above, but it supports additional query parameters as the last # argument class PositionalArgumentsWithAdditionalParams < PositionalArguments def guard_condition "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)" end - # This case uses almost the same code as positional arguments, + # This case uses almost the same code as positional arguments, # but add an args.last.to_query on the end def generation_code super.insert(-2, '?#{args.last.to_query}') @@ -110,7 +111,7 @@ module ActionController # To avoid generating "http://localhost/?host=foo.example.com" we # can't use this optimisation on routes without any segments def applicable? - super && route.segment_keys.size > 0 + super && route.segment_keys.size > 0 end end diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb index cf8f5232c1..6d54d0334c 100644 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -51,7 +51,6 @@ module ActionController # 3) segm test for /users/:id # (jump to list index = 5) # 4) full test for /users/:id => here we are! - class RouteSet def recognize_path(path, environment={}) result = recognize_optimized(path, environment) and return result @@ -68,28 +67,6 @@ module ActionController end end - def recognize_optimized(path, env) - write_recognize_optimized - recognize_optimized(path, env) - end - - def write_recognize_optimized - tree = segment_tree(routes) - body = generate_code(tree) - instance_eval %{ - def recognize_optimized(path, env) - segments = to_plain_segments(path) - index = #{body} - return nil unless index - while index < routes.size - result = routes[index].recognize(path, env) and return result - index += 1 - end - nil - end - }, __FILE__, __LINE__ - end - def segment_tree(routes) tree = [0] @@ -153,6 +130,23 @@ module ActionController segments end + private + def write_recognize_optimized! + tree = segment_tree(routes) + body = generate_code(tree) + instance_eval %{ + def recognize_optimized(path, env) + segments = to_plain_segments(path) + index = #{body} + return nil unless index + while index < routes.size + result = routes[index].recognize(path, env) and return result + index += 1 + end + nil + end + }, __FILE__, __LINE__ + end end end end diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb index a0d108ba03..2106ac09e0 100644 --- a/actionpack/lib/action_controller/routing/route.rb +++ b/actionpack/lib/action_controller/routing/route.rb @@ -3,11 +3,25 @@ module ActionController class Route #:nodoc: attr_accessor :segments, :requirements, :conditions, :optimise - def initialize - @segments = [] - @requirements = {} - @conditions = {} - @optimise = true + def initialize(segments = [], requirements = {}, conditions = {}) + @segments = segments + @requirements = requirements + @conditions = conditions + + if !significant_keys.include?(:action) && !requirements[:action] + @requirements[:action] = "index" + @significant_keys << :action + end + + # Routes cannot use the current string interpolation method + # if there are user-supplied <tt>:requirements</tt> as the interpolation + # code won't raise RoutingErrors when generating + has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp } + if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION + @optimise = false + else + @optimise = true + end end # Indicates whether the routes should be optimised with the string interpolation @@ -22,129 +36,6 @@ module ActionController end.compact end - # Write and compile a +generate+ method for this Route. - def write_generation - # Build the main body of the generation - body = "expired = false\n#{generation_extraction}\n#{generation_structure}" - - # If we have conditions that must be tested first, nest the body inside an if - body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements - args = "options, hash, expire_on = {}" - - # Nest the body inside of a def block, and then compile it. - raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash - # are the same as the keys that were recalled from the previous request. Thus, - # we can use the expire_on.keys to determine which keys ought to be used to build - # 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 - - # Build several lines of code that extract values from the options hash. If any - # of the values are missing or rejected then a return will be executed. - def generation_extraction - segments.collect do |segment| - segment.extraction_code - end.compact * "\n" - end - - # Produce a condition expression that will check the requirements of this route - # upon generation. - def generation_requirements - requirement_conditions = requirements.collect do |key, req| - if req.is_a? Regexp - value_regexp = Regexp.new "\\A#{req.to_s}\\Z" - "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" - else - "hash[:#{key}] == #{req.inspect}" - end - end - requirement_conditions * ' && ' unless requirement_conditions.empty? - end - - def generation_structure - segments.last.string_structure segments[0..-2] - end - - # Write and compile a +recognize+ method for this Route. - def write_recognition - # Create an if structure to extract the params from a match if it occurs. - body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" - body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" - - # Build the method declaration and compile it - method_decl = "def recognize(path, env={})\n#{body}\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - method_decl - end - - # Plugins may override this method to add other conditions, like checks on - # host, subdomain, and so forth. Note that changes here only affect route - # recognition, not generation. - def recognition_conditions - result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] - result << "conditions[:method] === env[:method]" if conditions[:method] - result - end - - # Build the regular expression pattern that will match this route. - def recognition_pattern(wrap = true) - pattern = '' - segments.reverse_each do |segment| - pattern = segment.build_pattern pattern - end - wrap ? ("\\A" + pattern + "\\Z") : pattern - end - - # Write the code to extract the parameters from a matched route. - def recognition_extraction - next_capture = 1 - extraction = segments.collect do |segment| - x = segment.match_extraction(next_capture) - next_capture += Regexp.new(segment.regexp_chunk).number_of_captures - x - end - extraction.compact - end - - # Write the real generation implementation and then resend the message. - def generate(options, hash, expire_on = {}) - write_generation - generate options, hash, expire_on - end - - def generate_extras(options, hash, expire_on = {}) - write_generation - generate_extras options, hash, expire_on - end - - # Generate the query string with any extra keys in the hash and append - # it to the given path, returning the new path. - def append_query_string(path, hash, query_keys=nil) - return nil unless path - query_keys ||= extra_keys(hash) - "#{path}#{build_query_string(hash, query_keys)}" - end - - # Determine which keys in the given hash are "extra". Extra keys are - # those that were not used to generate a particular route. The extra - # keys also do not include those recalled from the prior request, nor - # do they include any keys that were implied in the route (like a - # <tt>:controller</tt> that is required, but not explicitly used in the - # text of the route.) - def extra_keys(hash, recall={}) - (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys - 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 # the query string. The query string will correctly build array parameter @@ -161,12 +52,6 @@ module ActionController elements.empty? ? '' : "?#{elements.sort * '&'}" end - # Write the real recognition implementation and then resend the message. - def recognize(path, environment={}) - write_recognition - recognize path, environment - end - # A route's parameter shell contains parameter values that are not in the # route's path, but should be placed in the recognized hash. # @@ -186,7 +71,7 @@ module ActionController # includes keys that appear inside the path, and keys that have requirements # placed upon them. def significant_keys - @significant_keys ||= returning [] do |sk| + @significant_keys ||= returning([]) do |sk| segments.each { |segment| sk << segment.key if segment.respond_to? :key } sk.concat requirements.keys sk.uniq! @@ -209,12 +94,7 @@ module ActionController end def matches_controller_and_action?(controller, action) - unless defined? @matching_prepared - @controller_requirement = requirement_for(:controller) - @action_requirement = requirement_for(:action) - @matching_prepared = true - end - + prepare_matching! (@controller_requirement.nil? || @controller_requirement === controller) && (@action_requirement.nil? || @action_requirement === action) end @@ -226,15 +106,150 @@ module ActionController end end - protected - def requirement_for(key) - return requirements[key] if requirements.key? key - segments.each do |segment| - return segment.regexp if segment.respond_to?(:key) && segment.key == key + # TODO: Route should be prepared and frozen on initialize + def freeze + unless frozen? + write_generation! + write_recognition! + prepare_matching! + + parameter_shell + significant_keys + defaults + to_s end - nil + + super end + private + def requirement_for(key) + return requirements[key] if requirements.key? key + segments.each do |segment| + return segment.regexp if segment.respond_to?(:key) && segment.key == key + end + nil + end + + # Write and compile a +generate+ method for this Route. + def write_generation! + # Build the main body of the generation + body = "expired = false\n#{generation_extraction}\n#{generation_structure}" + + # If we have conditions that must be tested first, nest the body inside an if + body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements + args = "options, hash, expire_on = {}" + + # Nest the body inside of a def block, and then compile it. + raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + + # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash + # are the same as the keys that were recalled from the previous request. Thus, + # we can use the expire_on.keys to determine which keys ought to be used to build + # 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 + + # Build several lines of code that extract values from the options hash. If any + # of the values are missing or rejected then a return will be executed. + def generation_extraction + segments.collect do |segment| + segment.extraction_code + end.compact * "\n" + end + + # Produce a condition expression that will check the requirements of this route + # upon generation. + def generation_requirements + requirement_conditions = requirements.collect do |key, req| + if req.is_a? Regexp + value_regexp = Regexp.new "\\A#{req.to_s}\\Z" + "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" + else + "hash[:#{key}] == #{req.inspect}" + end + end + requirement_conditions * ' && ' unless requirement_conditions.empty? + end + + def generation_structure + segments.last.string_structure segments[0..-2] + end + + # Write and compile a +recognize+ method for this Route. + def write_recognition! + # Create an if structure to extract the params from a match if it occurs. + body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" + body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" + + # Build the method declaration and compile it + method_decl = "def recognize(path, env = {})\n#{body}\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + method_decl + end + + # Plugins may override this method to add other conditions, like checks on + # host, subdomain, and so forth. Note that changes here only affect route + # recognition, not generation. + def recognition_conditions + result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] + result << "conditions[:method] === env[:method]" if conditions[:method] + result + end + + # Build the regular expression pattern that will match this route. + def recognition_pattern(wrap = true) + pattern = '' + segments.reverse_each do |segment| + pattern = segment.build_pattern pattern + end + wrap ? ("\\A" + pattern + "\\Z") : pattern + end + + # Write the code to extract the parameters from a matched route. + def recognition_extraction + next_capture = 1 + extraction = segments.collect do |segment| + x = segment.match_extraction(next_capture) + next_capture += Regexp.new(segment.regexp_chunk).number_of_captures + x + end + extraction.compact + end + + # Generate the query string with any extra keys in the hash and append + # it to the given path, returning the new path. + def append_query_string(path, hash, query_keys = nil) + return nil unless path + query_keys ||= extra_keys(hash) + "#{path}#{build_query_string(hash, query_keys)}" + end + + # Determine which keys in the given hash are "extra". Extra keys are + # those that were not used to generate a particular route. The extra + # keys also do not include those recalled from the prior request, nor + # do they include any keys that were implied in the route (like a + # <tt>:controller</tt> that is required, but not explicitly used in the + # text of the route.) + def extra_keys(hash, recall = {}) + (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys + end + + def prepare_matching! + unless defined? @matching_prepared + @controller_requirement = requirement_for(:controller) + @action_requirement = requirement_for(:action) + @matching_prepared = true + end + end end end end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 5bc13cf268..8dfc22f94f 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -1,6 +1,6 @@ module ActionController module Routing - class RouteSet #:nodoc: + class RouteSet #:nodoc: # Mapper instances are used to build routes. The object passed to the draw # block in config/routes.rb is a Mapper instance. # @@ -194,6 +194,8 @@ module ActionController def initialize self.routes = [] self.named_routes = NamedRouteCollection.new + + write_recognize_optimized! end # Subclasses and plugins may override this method to specify a different @@ -231,7 +233,6 @@ module ActionController Routing.use_controllers! nil # Clear the controller cache so we may discover new ones clear! load_routes! - install_helpers end # reload! will always force a reload whereas load checks the timestamp first @@ -432,4 +433,4 @@ module ActionController end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb index 2ad20ee699..5f4ba90d0c 100644 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ b/actionpack/lib/action_controller/routing/routing_ext.rb @@ -1,4 +1,3 @@ - class Object def to_param to_s diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index f0ad066bad..9d4b740a44 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -2,13 +2,15 @@ module ActionController module Routing class Segment #:nodoc: RESERVED_PCHAR = ':@&=+$,;' - UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze + SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + # TODO: Convert :is_optional accessor to read only attr_accessor :is_optional alias_method :optional?, :is_optional def initialize - self.is_optional = false + @is_optional = false end def extraction_code @@ -63,12 +65,14 @@ module ActionController end class StaticSegment < Segment #:nodoc: - attr_accessor :value, :raw + attr_reader :value, :raw alias_method :raw?, :raw - def initialize(value = nil) + def initialize(value = nil, options = {}) super() - self.value = value + @value = value + @raw = options[:raw] if options.key?(:raw) + @is_optional = options[:optional] if options.key?(:optional) end def interpolation_chunk @@ -97,10 +101,8 @@ module ActionController end class DividerSegment < StaticSegment #:nodoc: - def initialize(value = nil) - super(value) - self.raw = true - self.is_optional = true + def initialize(value = nil, options = {}) + super(value, {:raw => true, :optional => true}.merge(options)) end def optionality_implied? @@ -109,13 +111,17 @@ module ActionController end class DynamicSegment < Segment #:nodoc: - attr_accessor :key, :default, :regexp + attr_reader :key + + # TODO: Convert these accessors to read only + attr_accessor :default, :regexp def initialize(key = nil, options = {}) super() - self.key = key - self.default = options[:default] if options.key? :default - self.is_optional = true if options[:optional] || options.key?(:default) + @key = key + @default = options[:default] if options.key?(:default) + @regexp = options[:regexp] if options.key?(:regexp) + @is_optional = true if options[:optional] || options.key?(:default) end def to_s @@ -130,6 +136,7 @@ module ActionController def extract_value "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" end + def value_check if default # Then we know it won't be nil "#{value_regexp.inspect} =~ #{local_name}" if regexp @@ -141,6 +148,7 @@ module ActionController "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" end end + def expiry_statement "expired, hash = true, options if !expired && expire_on[:#{key}]" end @@ -175,7 +183,7 @@ module ActionController end def regexp_chunk - if regexp + if regexp if regexp_has_modifiers? "(#{regexp.to_s})" else @@ -214,7 +222,6 @@ module ActionController def regexp_has_modifiers? regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 end - end class ControllerSegment < DynamicSegment #:nodoc: diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index b477c1f7da..5bf7503f04 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -129,7 +129,7 @@ class CGI::Session::CookieStore private # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) - data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop + data = ActiveSupport::Base64.encode64s(Marshal.dump(session)) "#{data}--#{generate_digest(data)}" end diff --git a/actionpack/lib/action_controller/session/drb_server.rb b/actionpack/lib/action_controller/session/drb_server.rb index 6f90db6747..2caa27f62a 100644..100755 --- a/actionpack/lib/action_controller/session/drb_server.rb +++ b/actionpack/lib/action_controller/session/drb_server.rb @@ -1,8 +1,8 @@ -#!/usr/local/bin/ruby -w - -# This is a really simple session storage daemon, basically just a hash, +#!/usr/bin/env ruby + +# This is a really simple session storage daemon, basically just a hash, # which is enabled for DRb access. - + require 'drb' session_hash = Hash.new @@ -14,13 +14,13 @@ class <<session_hash super(key, value) end end - + def [](key) @mutex.synchronize do super(key) end end - + def delete(key) @mutex.synchronize do super(key) @@ -29,4 +29,4 @@ class <<session_hash end DRb.start_service('druby://127.0.0.1:9192', session_hash) -DRb.thread.join
\ No newline at end of file +DRb.thread.join diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb index 186e0e5531..333fb61b45 100644 --- a/actionpack/lib/action_controller/streaming.rb +++ b/actionpack/lib/action_controller/streaming.rb @@ -12,19 +12,21 @@ module ActionController #:nodoc: X_SENDFILE_HEADER = 'X-Sendfile'.freeze protected - # Sends the file by streaming it 4096 bytes at a time. This way the - # whole file doesn't need to be read into memory at once. This makes - # it feasible to send even large files. + # Sends the file, by default streaming it 4096 bytes at a time. This way the + # whole file doesn't need to be read into memory at once. This makes it + # feasible to send even large files. You can optionally turn off streaming + # and send the whole file at once. # - # Be careful to sanitize the path parameter if it coming from a web + # Be careful to sanitize the path parameter if it is coming from a web # page. <tt>send_file(params[:path])</tt> allows a malicious user to # download any file on your server. # # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. # Defaults to <tt>File.basename(path)</tt>. - # * <tt>:type</tt> - specifies an HTTP content type. - # Defaults to 'application/octet-stream'. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # * <tt>:length</tt> - used to manually override the length (in bytes) of the content that + # is going to be sent to the client. Defaults to <tt>File.size(path)</tt>. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+) @@ -35,6 +37,12 @@ module ActionController #:nodoc: # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting <tt>:filename</tt> overrides this option). + # * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently + # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this + # uses the web server to send the file, this may lower memory consumption on your server and + # it will not block your application for further requests. + # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and + # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as @@ -99,8 +107,7 @@ module ActionController #:nodoc: # # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. - # * <tt>:type</tt> - specifies an HTTP content type. - # Defaults to 'application/octet-stream'. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. diff --git a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb index 385c6c1b09..b5483eccae 100644 --- a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb @@ -6,6 +6,6 @@ </h1> <pre><%=h @exception.clean_message %></pre> -<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %> +<%= render(:file => @rescues_path + "/_trace.erb") %> -<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %> +<%= render(:file => @rescues_path + "/_request_and_response.erb") %> diff --git a/actionpack/lib/action_controller/templates/rescues/template_error.erb b/actionpack/lib/action_controller/templates/rescues/template_error.erb index 4aecc68d18..76fa3df89d 100644 --- a/actionpack/lib/action_controller/templates/rescues/template_error.erb +++ b/actionpack/lib/action_controller/templates/rescues/template_error.erb @@ -15,7 +15,7 @@ <% @real_exception = @exception @exception = @exception.original_exception || @exception %> -<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %> +<%= render(:file => @rescues_path + "/_trace.erb") %> <% @exception = @real_exception %> -<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %> +<%= render(:file => @rescues_path + "/_request_and_response.erb") %> diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 77c6f26eac..3e66947d5f 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -15,6 +15,65 @@ module ActionController 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 + # "stories" that can involve multiple controllers and mutliple actions (i.e. multiple + # different HTTP requests). + # + # == Basic example + # + # Functional tests are written as follows: + # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate + # an HTTP request. + # 2. Then, one asserts whether the current state is as expected. "State" can be anything: + # the controller's HTTP response, the database contents, etc. + # + # For example: + # + # class BooksControllerTest < ActionController::TestCase + # def test_create + # # Simulate a POST response with the given HTTP parameters. + # post(:create, :book => { :title => "Love Hina" }) + # + # # Assert that the controller tried to redirect us to + # # the created book's URI. + # assert_response :found + # + # # Assert that the controller really put the book in the database. + # assert_not_nil Book.find_by_title("Love Hina") + # end + # end + # + # == Special instance variables + # + # ActionController::TestCase will also automatically provide the following instance + # variables for use in the tests: + # + # <b>@controller</b>:: + # The controller instance that will be tested. + # <b>@request</b>:: + # An ActionController::TestRequest, representing the current HTTP + # request. You can modify this object before sending the HTTP request. For example, + # you might want to set some session properties before sending a GET request. + # <b>@response</b>:: + # An ActionController::TestResponse object, representing the response + # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid + # after calling +post+. If the various assert methods are not sufficient, then you + # may use this object to inspect the HTTP response in detail. + # + # (Earlier versions of Rails required each functional test to subclass + # Test::Unit::TestCase and define @controller, @request, @response in +setup+.) + # + # == Controller is automatically inferred + # + # ActionController::TestCase will automatically infer the controller under test + # from the test class name. If the controller cannot be inferred from the test + # class name, you can explicity set it with +tests+. + # + # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase + # tests WidgetController + # end class TestCase < ActiveSupport::TestCase # 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 @@ -41,6 +100,8 @@ module ActionController @@controller_class = nil class << self + # Sets the controller class name. Useful if the name can't be inferred from test class. + # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>. def tests(controller_class) self.controller_class = controller_class end @@ -80,4 +141,4 @@ module ActionController @request.remote_addr = '208.77.188.166' # example.com end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index a6e0c98936..c6b1470070 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -23,7 +23,7 @@ module ActionController #:nodoc: class TestRequest < AbstractRequest #:nodoc: attr_accessor :cookies, :session_options - attr_accessor :query_parameters, :request_parameters, :path, :session, :env + attr_accessor :query_parameters, :request_parameters, :path, :session attr_accessor :host, :user_agent def initialize(query_parameters = nil, request_parameters = nil, session = nil) @@ -42,7 +42,7 @@ module ActionController #:nodoc: end # Wraps raw_post in a StringIO. - def body + def body_stream #:nodoc: StringIO.new(raw_post) end @@ -54,7 +54,7 @@ module ActionController #:nodoc: def port=(number) @env["SERVER_PORT"] = number.to_i - @port_as_int = nil + port(true) end def action=(action_name) @@ -68,6 +68,8 @@ module ActionController #:nodoc: @env["REQUEST_URI"] = value @request_uri = nil @path = nil + request_uri(true) + path(true) end def request_uri=(uri) @@ -77,21 +79,26 @@ module ActionController #:nodoc: def accept=(mime_types) @env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",") + accepts(true) end - def remote_addr=(addr) - @env['REMOTE_ADDR'] = addr + def if_modified_since=(last_modified) + @env["HTTP_IF_MODIFIED_SINCE"] = last_modified end - def remote_addr - @env['REMOTE_ADDR'] + def if_none_match=(etag) + @env["HTTP_IF_NONE_MATCH"] = etag end - def request_uri + def remote_addr=(addr) + @env['REMOTE_ADDR'] = addr + end + + def request_uri(*args) @request_uri || super end - def path + def path(*args) @path || super end @@ -113,17 +120,13 @@ module ActionController #:nodoc: end end @parameters = nil # reset TestRequest#parameters to use the new path_parameters - end - + end + def recycle! self.request_parameters = {} self.query_parameters = {} self.path_parameters = {} - @request_method, @accepts, @content_type = nil, nil, nil - end - - def referer - @env["HTTP_REFERER"] + unmemoize_all end private @@ -135,7 +138,7 @@ module ActionController #:nodoc: @host = "test.host" @request_uri = "/" @user_agent = "Rails Testing" - self.remote_addr = "0.0.0.0" + self.remote_addr = "0.0.0.0" @env["SERVER_PORT"] = 80 @env['REQUEST_METHOD'] = "GET" end @@ -157,21 +160,21 @@ module ActionController #:nodoc: module TestResponseBehavior #:nodoc: # The response code of the request def response_code - headers['Status'][0,3].to_i rescue 0 + status[0,3].to_i rescue 0 end - + # Returns a String to ensure compatibility with Net::HTTPResponse def code - headers['Status'].to_s.split(' ')[0] + status.to_s.split(' ')[0] end def message - headers['Status'].to_s.split(' ',2)[1] + status.to_s.split(' ',2)[1] end # Was the response successful? def success? - response_code == 200 + (200..299).include?(response_code) end # Was the URL not found? @@ -211,7 +214,7 @@ module ActionController #:nodoc: template._first_render end - # A shortcut to the flash. Returns an empyt hash if no session flash exists. + # A shortcut to the flash. Returns an empty hash if no session flash exists. def flash session['flash'] || {} end @@ -243,11 +246,11 @@ module ActionController #:nodoc: # Does the specified template object exist? def has_template_object?(name=nil) - !template_objects[name].nil? + !template_objects[name].nil? end # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs - # + # # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value def cookies headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } @@ -266,7 +269,13 @@ module ActionController #:nodoc: end end - class TestResponse < AbstractResponse #:nodoc: + # Integration test methods such as ActionController::Integration::Session#get + # and ActionController::Integration::Session#post return objects of class + # TestResponse, which represent the HTTP response results of the requested + # controller actions. + # + # See AbstractResponse for more information on controller response objects. + class TestResponse < AbstractResponse include TestResponseBehavior end @@ -313,7 +322,7 @@ module ActionController #:nodoc: # # Usage example, within a functional test: # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png') - # + # # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows): # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary) require 'tempfile' @@ -348,6 +357,7 @@ module ActionController #:nodoc: module TestProcess def self.included(base) # execute the request simulating a specific HTTP method and set/volley the response + # TODO: this should be un-DRY'ed for the sake of API documentation. %w( get post put delete head ).each do |method| base.class_eval <<-EOV, __FILE__, __LINE__ def #{method}(action, parameters = nil, session = nil, flash = nil) @@ -393,13 +403,13 @@ module ActionController #:nodoc: end alias xhr :xml_http_request - def assigns(key = nil) - if key.nil? - @response.template.assigns - else - @response.template.assigns[key.to_s] - end - end + def assigns(key = nil) + if key.nil? + @response.template.assigns + else + @response.template.assigns[key.to_s] + end + end def session @response.session @@ -441,10 +451,13 @@ module ActionController #:nodoc: end def method_missing(selector, *args) - return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) - return super + if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + @controller.send(selector, *args) + else + super + end end - + # Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>: # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') @@ -455,7 +468,7 @@ module ActionController #:nodoc: # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) ActionController::TestUploadedFile.new( - Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path, + Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path, mime_type, binary ) @@ -463,7 +476,7 @@ module ActionController #:nodoc: # A helper to make it easier to test different route configurations. # This method temporarily replaces ActionController::Routing::Routes - # with a new RouteSet instance. + # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block # will create some routes using <tt>map.draw { map.connect ... }</tt>: diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 3a38f23396..d86e2db67d 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -1,19 +1,96 @@ module ActionController - # Write URLs from arbitrary places in your codebase, such as your mailers. + # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse + # is also possible: an URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. # - # Example: + # See ActionController::Routing and ActionController::Resources for general + # information about routing and routes.rb. # - # class MyMailer - # include ActionController::UrlWriter - # default_url_options[:host] = 'www.basecamphq.com' + # <b>Tip:</b> If you need to generate URLs from your models or some other place, + # then ActionController::UrlWriter is what you're looking for. Read on for + # an introduction. # - # def signup_url(token) - # url_for(:controller => 'signup', action => 'index', :token => token) + # == URL generation from parameters + # + # As you may know, some functions - such as ActionController::Base#url_for + # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set + # of parameters. For example, you've probably had the chance to write code + # like this in one of your views: + # + # <%= link_to('Click here', :controller => 'users', + # :action => 'new', :message => 'Welcome!') %> + # + # #=> Generates a link to: /users/new?message=Welcome%21 + # + # link_to, and all other functions that require URL generation functionality, + # actually use ActionController::UrlWriter under the hood. And in particular, + # they use the ActionController::UrlWriter#url_for method. One can generate + # the same path as the above example by using the following code: + # + # include UrlWriter + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :only_path => true) + # # => "/users/new?message=Welcome%21" + # + # Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no + # information about the website hostname that your Rails app is serving. So if you + # want to include the hostname as well, then you must also pass the <tt>:host</tt> + # argument: + # + # include UrlWriter + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :host => 'www.example.com') # Changed this. + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of url_for, + # that already knows what the current hostname is. So if you use url_for in your + # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> + # argument. + # + # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for. + # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for' + # in full. However, mailers don't have hostname information, and what's why you'll still + # have to specify the <tt>:host</tt> argument when generating URLs in mailers. + # + # + # == URL generation for named routes + # + # UrlWriter also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # <b>routes.rb</b>: + # + # map.resources :users + # + # This generates, among other things, the method <tt>users_path</tt>. By default, + # this method is accessible from your controllers, views and mailers. If you need + # to access this auto-generated method from other places (such as a model), then + # you can do that in two ways. + # + # The first way is to include ActionController::UrlWriter in your class: + # + # class User < ActiveRecord::Base + # include ActionController::UrlWriter # !!! + # + # def name=(value) + # write_attribute('name', value) + # write_attribute('base_uri', users_path) # !!! # end - # end + # end # - # In addition to providing +url_for+, named routes are also accessible after - # including UrlWriter. + # The second way is to access them through ActionController::UrlWriter. + # The autogenerated named routes methods are available as class methods: + # + # class User < ActiveRecord::Base + # def name=(value) + # write_attribute('name', value) + # path = ActionController::UrlWriter.users_path # !!! + # write_attribute('base_uri', path) # !!! + # end + # end module UrlWriter # The default options for urls written by this writer. Typically a <tt>:host</tt> # pair is provided. @@ -37,7 +114,7 @@ module ActionController # * <tt>:port</tt> - Optionally specify the port to connect to. # * <tt>:anchor</tt> - An anchor name to be appended to the path. # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the - # +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root. + # +relative_url_root+ set in ActionController::Base.relative_url_root. # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" # # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to @@ -67,7 +144,7 @@ module ActionController [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) } end trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) - url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root] + url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] generated = Routing::Routes.generate(options, {}) url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) @@ -108,7 +185,7 @@ module ActionController end path = rewrite_path(options) - rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root] + rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) rewritten_url << "##{options[:anchor]}" if options[:anchor] diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb index 1a3c770254..376bb87409 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -64,7 +64,7 @@ module HTML # # When using a combination of the above, the element name comes first # followed by identifier, class names, attributes, pseudo classes and - # negation in any order. Do not seprate these parts with spaces! + # negation in any order. Do not separate these parts with spaces! # Space separation is used for descendant selectors. # # For example: @@ -158,7 +158,7 @@ module HTML # * <tt>:not(selector)</tt> -- Match the element only if the element does not # match the simple selector. # - # As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite + # As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite # tricky and the CSS specification doesn't do a much better job explaining it. # But after reading the examples and trying a few combinations, it's easy to # figure out. diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 9ab615c7a5..3590ab6d49 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -21,6 +21,15 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ +begin + require 'active_support' +rescue LoadError + activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" + if File.directory?(activesupport_path) + $:.unshift activesupport_path + require 'active_support' + end +end require 'action_view/template_handlers' require 'action_view/renderable' @@ -34,10 +43,13 @@ require 'action_view/base' require 'action_view/partials' require 'action_view/template_error' +I18n.backend.populate do + I18n.load_translations "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml" +end + +require 'action_view/helpers' + ActionView::Base.class_eval do include ActionView::Partials - - ActionView::Base.helper_modules.each do |helper_module| - include helper_module - end + include ActionView::Helpers end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 04e8d3a358..d174c784f3 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -158,6 +158,7 @@ module ActionView #:nodoc: # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base include ERB::Util + extend ActiveSupport::Memoizable attr_accessor :base_path, :assigns, :template_extension attr_accessor :controller @@ -169,15 +170,19 @@ module ActionView #:nodoc: class << self delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' + delegate :logger, :to => 'ActionController::Base' end - # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. - @@cache_template_loading = false - cattr_accessor :cache_template_loading + def self.cache_template_loading=(*args) + ActiveSupport::Deprecation.warn( + "config.action_view.cache_template_loading option has been deprecated" + + "and has no effect. Please remove it from your config files.", caller) + end def self.cache_template_extensions=(*args) - ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " << - "Please remove it from your config files.", caller) + ActiveSupport::Deprecation.warn( + "config.action_view.cache_template_extensions option has been" + + "deprecated and has no effect. Please remove it from your config files.", caller) end # Specify whether RJS responses should be wrapped in a try/catch block @@ -199,23 +204,6 @@ module ActionView #:nodoc: end include CompiledTemplates - # Cache public asset paths - cattr_reader :computed_public_paths - @@computed_public_paths = {} - - def self.helper_modules #:nodoc: - helpers = [] - 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}" - helper_module_name = $1.camelize - if Helpers.const_defined?(helper_module_name) - helpers << Helpers.const_get(helper_module_name) - end - end - return helpers - end - def self.process_view_paths(value) ActionView::PathSet.new(Array(value)) end @@ -239,7 +227,7 @@ module ActionView #:nodoc: local_assigns ||= {} if options.is_a?(String) - render_file(options, nil, local_assigns) + render(:file => options, :locals => local_assigns) elsif options == :update update_page(&block) elsif options.is_a?(Hash) @@ -247,31 +235,34 @@ module ActionView #:nodoc: if partial_layout = options.delete(:layout) if block_given? - wrap_content_for_layout capture(&block) do + begin + @_proc_for_layout = block concat(render(options.merge(:partial => partial_layout))) + ensure + @_proc_for_layout = nil end else - wrap_content_for_layout render(options) do + begin + original_content_for_layout, @content_for_layout = @content_for_layout, render(options) render(options.merge(:partial => partial_layout)) + ensure + @content_for_layout = original_content_for_layout end end elsif options[:file] - render_file(options[:file], nil, options[:locals]) - elsif options[:partial] && options[:collection] - render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as]) + if options[:use_full_path] + ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) + end + + pick_template(options[:file]).render_template(self, options[:locals]) elsif options[:partial] - render_partial(options[:partial], options[:object], options[:locals]) + render_partial(options) elsif options[:inline] - render_inline(options[:inline], options[:locals], options[:type]) + InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals]) end end end - # Returns true is the file may be rendered implicitly. - def file_public?(template_path)#:nodoc: - template_path.split('/').last[0,1] != '_' - end - # The format to be used when choosing between multiple templates with # the same name but differing formats. See +Request#template_format+ # for more details. @@ -301,6 +292,8 @@ module ActionView #:nodoc: # # => 'users/legacy.rhtml' # def pick_template(template_path) + return template_path if template_path.respond_to?(:render) + path = template_path.sub(/^\//, '') if m = path.match(/(.*)\.(\w+)$/) template_file_name, template_file_extension = m[1], m[2] @@ -321,54 +314,20 @@ module ActionView #:nodoc: else template = Template.new(template_path, view_paths) - if self.class.warn_cache_misses && logger = ActionController::Base.logger + if self.class.warn_cache_misses && logger logger.debug "[PERFORMANCE] Rendering a template that was " + "not found in view path. Templates outside the view path are " + - "not cached and result in expensive disk operations. Move this " + - "file into #{view_paths.join(':')} or add the folder to your " + + "not cached and result in expensive disk operations. Move this " + + "file into #{view_paths.join(':')} or add the folder to your " + "view path list" end template end end + memoize :pick_template private - # Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt> - # is made available as local variables. - def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc: - unless use_full_path == nil - ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) - end - - if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/") - raise ActionViewError, <<-END_ERROR - Due to changes in ActionMailer, you need to provide the mailer_name along with the template name. - - render "user_mailer/signup" - render :file => "user_mailer/signup" - - If you are rendering a subtemplate, you must now use controller-like partial syntax: - - render :partial => 'signup' # no mailer_name necessary - END_ERROR - end - - template = pick_template(template_path) - template.render_template(self, local_assigns) - end - - def render_inline(text, local_assigns = {}, type = nil) - InlineTemplate.new(text, type).render(self, local_assigns) - end - - def wrap_content_for_layout(content) - original_content_for_layout, @content_for_layout = @content_for_layout, content - yield - ensure - @content_for_layout = original_content_for_layout - end - # Evaluate the local assigns and pushes them to the view. def evaluate_assigns unless @assigns_added @@ -382,9 +341,9 @@ module ActionView #:nodoc: @assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - def execute(template, local_assigns = {}) - send(template.method(local_assigns), local_assigns) do |*names| - instance_variable_get "@content_for_#{names.first || 'layout'}" + def set_controller_content_type(content_type) + if controller.respond_to?(:response) + controller.response.content_type ||= content_type end end end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb new file mode 100644 index 0000000000..05e1cf990a --- /dev/null +++ b/actionpack/lib/action_view/helpers.rb @@ -0,0 +1,39 @@ +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: + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + include SanitizeHelper::ClassMethods + end + + include ActiveRecordHelper + include AssetTagHelper + include AtomFeedHelper + include BenchmarkHelper + include CacheHelper + include CaptureHelper + include DateHelper + include DebugHelper + include FormCountryHelper + include FormHelper + include FormOptionsHelper + include FormTagHelper + include NumberHelper + include PrototypeHelper + include RecordIdentificationHelper + include RecordTagHelper + include SanitizeHelper + include ScriptaculousHelper + include TagHelper + include TextHelper + include TranslationHelper + include UrlHelper + end +end diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index f3f204cc97..c339e10701 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -25,7 +25,7 @@ module ActionView # Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt> # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then # - # form("post") + # form("post") # # would yield a form like the following (modulus formatting): # @@ -90,23 +90,41 @@ module ActionView end # Returns a string containing the error message attached to the +method+ on the +object+ if one exists. - # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+ - # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or - # the actual object. As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute: + # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt> + # and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it + # accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be + # passed in either as a string or a symbol. + # As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute: # # <%= error_message_on "post", "title" %> # # => <div class="formError">can't be empty</div> # - # <%= error_message_on @post, "title" %> + # <%= error_message_on @post, :title %> # # => <div class="formError">can't be empty</div> # - # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> - # # => <div class="inputError">Title simply can't be empty (or it won't work).</div> - def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError") + # <%= error_message_on "post", "title", + # :prepend_text => "Title simply ", + # :append_text => " (or it won't work).", + # :css_class => "inputError" %> + def error_message_on(object, method, *args) + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' + + 'prepend_text, append_text, and css_class arguments', caller) + + options[:prepend_text] = args[0] || '' + options[:append_text] = args[1] || '' + options[:css_class] = args[2] || 'formError' + end + options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError') + if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) - content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class) - else + content_tag("div", + "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}", + :class => options[:css_class] + ) + else '' end end @@ -133,7 +151,7 @@ module ActionView # # To specify the display for one object, you simply provide its name as a parameter. # For example, for the <tt>@user</tt> model: - # + # # error_messages_for 'user' # # To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which @@ -141,7 +159,7 @@ module ActionView # # error_messages_for 'user_common', 'user', :object_name => 'user' # - # If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> paremeter which gives the actual + # If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual # object (or array of objects to use): # # error_messages_for 'user', :object => @question.user @@ -151,12 +169,14 @@ module ActionView # instance yourself and set it up. View the source of this method to see how easy it is. def error_messages_for(*params) options = params.extract_options!.symbolize_keys + if object = options.delete(:object) objects = [object].flatten else objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact end - count = objects.inject(0) {|sum, object| sum + object.errors.count } + + count = objects.inject(0) {|sum, object| sum + object.errors.count } unless count.zero? html = {} [:id, :class].each do |key| @@ -168,16 +188,25 @@ module ActionView end end options[:object_name] ||= params.first - options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) - options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join - contents = '' - contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank? - contents << content_tag(:p, options[:message]) unless options[:message].blank? - contents << content_tag(:ul, error_messages) + I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale| + header_message = if options.include?(:header_message) + options[:header_message] + else + object_name = options[:object_name].to_s.gsub('_', ' ') + object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1) + locale.t :header, :count => count, :model => object_name + end + message = options.include?(:message) ? options[:message] : locale.t(:body) + error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join - content_tag(:div, contents, html) + contents = '' + contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? + contents << content_tag(:p, message) unless message.blank? + contents << content_tag(:ul, error_messages) + + content_tag(:div, contents, html) + end else '' end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index bf13945844..623ed1e8df 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers #:nodoc: # This module provides methods for generating HTML that links views to assets such - # as images, javascripts, stylesheets, and feeds. These methods do not verify - # the assets exist before linking to them. + # as images, javascripts, stylesheets, and feeds. These methods do not verify + # the assets exist before linking to them. # # === Using asset hosts # By default, Rails links to these assets on the current host in the public - # folder, but you can direct Rails to link to assets from a dedicated assets server by + # folder, but you can direct Rails to link to assets from a dedicated assets server by # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example, # let's say your asset host is <tt>assets.example.com</tt>. # @@ -22,16 +22,16 @@ module ActionView # # This is useful since browsers typically open at most two connections to a single host, # which means your assets often wait in single file for their turn to load. You can - # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com") + # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com") # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com") - # so browsers will open eight connections rather than two. + # so browsers will open eight connections rather than two. # # image_tag("rails.png") # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" /> # stylesheet_link_tag("application") # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" /> # - # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME + # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from # your ISP. # @@ -86,7 +86,7 @@ module ActionView # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp, # which then updates the URL as the timestamp is part of that, which in turn busts the cache). # - # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take + # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take # advantage of this feature. Here's an example for Apache: # # # Asset Expiration @@ -95,16 +95,17 @@ module ActionView # ExpiresDefault "access plus 1 year" # </FilesMatch> # - # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must + # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use - # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being + # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being # requested over and over). module AssetTagHelper ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" - + JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) + # Returns a link tag that browsers and news readers can use to auto-detect # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or # <tt>:atom</tt>. Control the link options in url_for format using the @@ -154,10 +155,6 @@ module ActionView end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) - @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } - @@stylesheet_expansions = {} - # Returns an html script tag for each of the +sources+ provided. You # can pass in the filename (.js extension is optional) of javascript files # that exist in your public/javascripts directory for inclusion into the @@ -193,7 +190,7 @@ module ActionView # # * = The application.js file is only referenced if it exists # - # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason + # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method. # # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source: @@ -218,7 +215,7 @@ module ActionView # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development - # environment). + # environment). # # ==== Examples # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false => @@ -259,6 +256,8 @@ module ActionView end end + @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } + # Register one or more javascript files to be included when <tt>symbol</tt> # is passed to <tt>javascript_include_tag</tt>. This method is typically intended # to be called from plugin initialization to register javascript files @@ -274,6 +273,8 @@ module ActionView @@javascript_expansions.merge!(expansions) end + @@stylesheet_expansions = {} + # Register one or more stylesheet files to be included when <tt>symbol</tt> # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended # to be called from plugin initialization to register stylesheet files @@ -439,9 +440,9 @@ module ActionView # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> # image_tag("/icons/icon.gif", :class => "menu_icon") # => # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> - # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # => + # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # => # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> - # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # => + # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # => # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> def image_tag(source, options = {}) options.symbolize_keys! @@ -454,23 +455,15 @@ module ActionView end if mouseover = options.delete(:mouseover) - options[:onmouseover] = "this.src='#{image_path(mouseover)}'" - options[:onmouseout] = "this.src='#{image_path(options[:src])}'" + options[:onmouseover] = "this.src='#{image_path(mouseover)}'" + options[:onmouseout] = "this.src='#{image_path(options[:src])}'" end tag("img", options) end private - def file_exist?(path) - @@file_exist_cache ||= {} - if !(@@file_exist_cache[path] ||= File.exist?(path)) - @@file_exist_cache[path] = true - false - else - true - end - end + COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence! # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL @@ -483,14 +476,14 @@ module ActionView if has_request [ @controller.request.protocol, ActionController::Base.asset_host.to_s, - @controller.request.relative_url_root, + ActionController::Base.relative_url_root, dir, source, ext, include_host ].join else [ ActionController::Base.asset_host.to_s, dir, source, ext, include_host ].join end - ActionView::Base.computed_public_paths[cache_key] ||= + source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do begin source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")) @@ -499,25 +492,27 @@ module ActionView else source = "/#{dir}/#{source}" unless source[0] == ?/ if has_request - unless source =~ %r{^#{@controller.request.relative_url_root}/} - source = "#{@controller.request.relative_url_root}#{source}" + unless source =~ %r{^#{ActionController::Base.relative_url_root}/} + source = "#{ActionController::Base.relative_url_root}#{source}" end end - source = rewrite_asset_path(source) - if include_host - host = compute_asset_host(source) + rewrite_asset_path(source) + end + end + end - if has_request && !host.blank? && host !~ %r{^[-a-z]+://} - host = "#{@controller.request.protocol}#{host}" - end + if include_host && source !~ %r{^[-a-z]+://} + host = compute_asset_host(source) - "#{host}#{source}" - else - source - end - end + if has_request && !host.blank? && host !~ %r{^[-a-z]+://} + host = "#{@controller.request.protocol}#{host}" end + + "#{host}#{source}" + else + source + end end # Pick an asset host for this source. Returns +nil+ if no host is set, @@ -591,7 +586,7 @@ module ActionView expanded_sources = sources.collect do |source| determine_source(source, @@javascript_expansions) end.flatten - expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js")) expanded_sources end end @@ -617,12 +612,21 @@ module ActionView end def join_asset_file_contents(paths) - paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n") + paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n") end def write_asset_file_contents(joined_asset_path, asset_paths) FileUtils.mkdir_p(File.dirname(joined_asset_path)) File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) } + + # Set mtime to the latest of the combined files to allow for + # consistent ETag without a shared filesystem. + mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max + File.utime(mt, mt, joined_asset_path) + end + + def asset_file_path(path) + File.join(ASSETS_DIR, path.split('?').first) end def collect_asset_files(*path) diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index ebb1cb34bc..e65d5d1f60 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -17,7 +17,7 @@ module ActionView # # GET /posts.atom # def index # @posts = Post.find(:all) - # + # # respond_to do |format| # format.html # format.atom @@ -29,12 +29,12 @@ module ActionView # atom_feed do |feed| # feed.title("My great blog!") # feed.updated((@posts.first.created_at)) - # + # # for post in @posts # feed.entry(post) do |entry| # entry.title(post.title) # entry.content(post.body, :type => 'html') - # + # # entry.author do |author| # author.name("DHH") # end @@ -47,8 +47,9 @@ module ActionView # * <tt>:language</tt>: Defaults to "en-US". # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host. # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL. - # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you - # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, + # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}" + # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you + # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, # 2005 is used (as an "I don't care" value). # # Other namespaces can be added to the root element: @@ -81,7 +82,7 @@ module ActionView else options[:schema_date] = "2005" # The Atom spec copyright date end - + xml = options[:xml] || eval("xml", block.binding) xml.instruct! @@ -89,10 +90,10 @@ module ActionView feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} xml.feed(feed_opts) do - xml.id("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) - + yield AtomFeedBuilder.new(xml, self, options) end end @@ -102,7 +103,7 @@ module ActionView def initialize(xml, view, feed_options = {}) @xml, @view, @feed_options = xml, view, feed_options end - + # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used. def updated(date_or_time = nil) @xml.updated((date_or_time || Time.now.utc).xmlschema) @@ -115,9 +116,10 @@ module ActionView # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists. # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists. # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record. + # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" def entry(record, options = {}) - @xml.entry do - @xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") + @xml.entry do + @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") if options[:published] || (record.respond_to?(:created_at) && record.created_at) @xml.published((options[:published] || record.created_at).xmlschema) diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 2cdbae6e40..64d1ad2715 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,7 +32,7 @@ module ActionView # <i>Topics listed alphabetically</i> # <% end %> def cache(name = {}, options = nil, &block) - _last_render.handler.new(@controller).cache_fragment(block, name, options) + @controller.fragment_for(output_buffer, name, options, &block) end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 720e2da8cc..e86ca27f31 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -122,14 +122,15 @@ module ActionView nil end - private - def with_output_buffer(buf = '') - self.output_buffer, old_buffer = buf, output_buffer - yield - output_buffer - ensure - self.output_buffer = old_buffer - end + # Use an alternate output buffer for the duration of the block. + # Defaults to a new empty string. + def with_output_buffer(buf = '') #:nodoc: + self.output_buffer, old_buffer = buf, output_buffer + yield + output_buffer + ensure + self.output_buffer = old_buffer + end end end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index d018034ebe..953a2a9f86 100755..100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -3,18 +3,16 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers - # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods - # share a number of common options that are as follows: + # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the + # select-type methods share a number of common options that are as follows: # - # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give - # birthday[month] instead of date[month] if passed to the select_month method. + # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" + # would give birthday[month] instead of date[month] if passed to the select_month method. # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date. - # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month - # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]". + # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, + # the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of + # "date[month]". module DateHelper - include ActionView::Helpers::TagHelper - DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX') - # Reports the approximate distance in time between two Time or Date objects or integers as seconds. # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs # Distances are reported based on the following table: @@ -51,40 +49,45 @@ module ActionView # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year - # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years + # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => over 4 years # # to_time = Time.now + 6.years + 19.days # distance_of_time_in_words(from_time, to_time, true) # => over 6 years # distance_of_time_in_words(to_time, from_time, true) # => over 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute # - def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false) + def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {}) from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) distance_in_minutes = (((to_time - from_time).abs)/60).round distance_in_seconds = ((to_time - from_time).abs).round - case distance_in_minutes - when 0..1 - return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds - case distance_in_seconds - when 0..4 then 'less than 5 seconds' - when 5..9 then 'less than 10 seconds' - when 10..19 then 'less than 20 seconds' - when 20..39 then 'half a minute' - when 40..59 then 'less than a minute' - else '1 minute' - end - - when 2..44 then "#{distance_in_minutes} minutes" - when 45..89 then 'about 1 hour' - when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours" - when 1440..2879 then '1 day' - when 2880..43199 then "#{(distance_in_minutes / 1440).round} days" - when 43200..86399 then 'about 1 month' - when 86400..525599 then "#{(distance_in_minutes / 43200).round} months" - when 525600..1051199 then 'about 1 year' - else "over #{(distance_in_minutes / 525600).round} years" + I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| + case distance_in_minutes + when 0..1 + return distance_in_minutes == 0 ? + locale.t(:less_than_x_minutes, :count => 1) : + locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds + + case distance_in_seconds + when 0..4 then locale.t :less_than_x_seconds, :count => 5 + when 5..9 then locale.t :less_than_x_seconds, :count => 10 + when 10..19 then locale.t :less_than_x_seconds, :count => 20 + when 20..39 then locale.t :half_a_minute + when 40..59 then locale.t :less_than_x_minutes, :count => 1 + else locale.t :x_minutes, :count => 1 + end + + when 2..44 then locale.t :x_minutes, :count => distance_in_minutes + when 45..89 then locale.t :about_x_hours, :count => 1 + when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round + when 1440..2879 then locale.t :x_days, :count => 1 + when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round + when 43200..86399 then locale.t :about_x_months, :count => 1 + when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round + when 525600..1051199 then locale.t :about_x_years, :count => 1 + else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round + end end end @@ -102,17 +105,37 @@ module ActionView alias_method :distance_of_time_in_words_to_now, :time_ago_in_words - # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by - # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash, - # which accepts all the keys that each of the individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of - # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll - # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly - # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in - # the desired order. Symbols may be omitted and the respective select is not included. - # - # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>. - # - # Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change. + # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based + # attribute (identified by +method+) on an object assigned to the template (identified by +object+). You can + # the output in the +options+ hash. + # + # ==== Options + # * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g. + # "2" instead of "February"). + # * <tt>:use_short_month</tt> - Set to true if you want to use the abbreviated month name instead of the full + # name (e.g. "Feb" instead of "February"). + # * <tt>:add_month_number</tt> - Set to true if you want to show both, the month's number and name (e.g. + # "2 - February" instead of "February"). + # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names. + # Note: You can also use Rails' new i18n functionality for this. + # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). + # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>. + # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>. + # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day + # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the + # first of the given month in order to not create invalid dates like 31 February. + # * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month + # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true. + # * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year + # as a hidden field instead of showing a select field. + # * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> 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 <tt>:discard_xxx => true</tt>. Defaults to the order defined in + # the respective locale (e.g. [:year, :month, :day] in the en-US locale that ships with Rails). + # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty + # dates. + # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil. + # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # @@ -128,7 +151,7 @@ module ActionView # # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute, # # with the year in the year drop down box starting at 1995, numbers used for months instead of words, - # # and without a day select box. + # # and without a day select box. # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true, # :discard_day => true, :include_blank => true) # @@ -150,15 +173,15 @@ module ActionView # # The selects are prepared for multi-parameter assignment to an Active Record object. # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month - # choices are valid. + # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that + # all month choices are valid. def date_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options) end - # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified - # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+). - # You can include the seconds with <tt>:include_seconds</tt>. + # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a + # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by + # +object+). You can include the seconds with <tt>:include_seconds</tt>. # # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option # <tt>:ignore_date</tt> is set to +true+. @@ -169,18 +192,19 @@ module ActionView # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute # time_select("post", "sunrise") # - # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute + # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted + # # attribute # time_select("order", "submitted") # # # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute # time_select("mail", "sent_at") # - # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in - # # the sunrise attribute. + # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in + # # the sunrise attribute. # time_select("post", "start_time", :include_seconds => true) # - # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in - # # the submission_time attribute. + # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in + # # the submission_time attribute. # time_select("entry", "submission_time", :include_seconds => true) # # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45. @@ -188,31 +212,33 @@ module ActionView # # The selects are prepared for multi-parameter assignment to an Active Record object. # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month - # choices are valid. + # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that + # all month choices are valid. def time_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options) end - # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based - # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples: + # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a + # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified + # by +object+). Examples: # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # ==== Examples - # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute + # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on + # # attribute # datetime_select("post", "written_on") # - # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the + # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the # # post variable in the written_on attribute. # datetime_select("post", "written_on", :start_year => 1995) # - # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the - # # trip variable in the departing attribute. + # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will + # # be stored in the trip variable in the departing attribute. # datetime_select("trip", "departing", :default => 3.days.from_now) # - # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on - # # attribute. + # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable + # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) # # The selects are prepared for multi-parameter assignment to an Active Record object. @@ -220,11 +246,12 @@ module ActionView InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options) end - # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+. - # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of - # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it - # will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt> - # keys to the +options+ to control visual display of the elements. + # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the + # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with + # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not + # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add + # <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to + # control visual display of the elements. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # @@ -245,7 +272,12 @@ module ActionView # # with a '/' between each date field. # select_datetime(my_date_time, :date_separator => '/') # - # # Generates a datetime select that discards the type of the field and defaults to the datetime in + # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) + # # with a date fields separated by '/', time fields separated by '' and the date and time fields + # # separated by a comma (','). + # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',') + # + # # Generates a datetime select that discards the type of the field and defaults to the datetime in # # my_date_time (four days after today) # select_datetime(my_date_time, :discard_type => true) # @@ -254,14 +286,13 @@ module ActionView # select_datetime(my_date_time, :prefix => 'payday') # def select_datetime(datetime = Time.current, options = {}, html_options = {}) - separator = options[:datetime_separator] || '' - select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options) - end + DateTimeSelector.new(datetime, options, html_options).select_datetime + end # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of - # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it - # will be appended onto the <tt>:order</tt> passed in. + # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, + # it will be appended onto the <tt>:order</tt> passed in. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # @@ -278,27 +309,24 @@ module ActionView # # with the fields ordered year, month, day rather than month, day, year. # select_date(my_date, :order => [:year, :month, :day]) # - # # Generates a date select that discards the type of the field and defaults to the date in + # # Generates a date select that discards the type of the field and defaults to the date in # # my_date (six days after today) - # select_datetime(my_date_time, :discard_type => true) + # select_date(my_date, :discard_type => true) + # + # # Generates a date select that defaults to the date in my_date, + # # which has fields separated by '/' + # select_date(my_date, :date_separator => '/') # # # Generates a date select that defaults to the datetime in my_date (six days after today) # # prefixed with 'payday' rather than 'date' - # select_datetime(my_date_time, :prefix => 'payday') + # select_date(my_date, :prefix => 'payday') # def select_date(date = Date.current, options = {}, html_options = {}) - options[:order] ||= [] - [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) } - - select_date = '' - options[:order].each do |o| - select_date << self.send("select_#{o}", date, options, html_options) - end - select_date + DateTimeSelector.new(date, options, html_options).select_date end # Returns a set of html select-tags (one for hour and minute) - # You can set <tt>:time_separator</tt> key to format the output, and + # You can set <tt>:time_separator</tt> key to format the output, and # the <tt>:include_seconds</tt> option to include an input for seconds. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. @@ -313,7 +341,7 @@ module ActionView # select_time() # # # Generates a time select that defaults to the time in my_time, - # # which has fields separated by ':' + # # which has fields separated by ':' # select_time(my_time, :time_separator => ':') # # # Generates a time select that defaults to the time in my_time, @@ -325,8 +353,7 @@ module ActionView # select_time(my_time, :time_separator => ':', :include_seconds => true) # def select_time(datetime = Time.current, options = {}, html_options = {}) - separator = options[:time_separator] || '' - select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '') + DateTimeSelector.new(datetime, options, html_options).select_time end # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. @@ -341,31 +368,18 @@ module ActionView # # # Generates a select field for seconds that defaults to the number given # select_second(33) - # + # # # Generates a select field for seconds that defaults to the seconds for the time in my_time # # that is named 'interval' rather than 'second' # select_second(my_time, :field_name => 'interval') # def select_second(datetime, options = {}, html_options = {}) - val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : '' - if options[:use_hidden] - options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : '' - else - second_options = [] - 0.upto(59) do |second| - second_options << ((val == second) ? - content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second)) - ) - second_options << "\n" - end - select_html(options[:field_name] || 'second', second_options.join, options, html_options) - end + DateTimeSelector.new(datetime, options, html_options).select_second end # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. - # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected - # The <tt>minute</tt> can also be substituted for a minute number. + # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute + # selected. The <tt>minute</tt> can also be substituted for a minute number. # Override the field name using the <tt>:field_name</tt> option, 'minute' by default. # # ==== Examples @@ -376,26 +390,13 @@ module ActionView # # # Generates a select field for minutes that defaults to the number given # select_minute(14) - # + # # # Generates a select field for minutes that defaults to the minutes for the time in my_time # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # def select_minute(datetime, options = {}, html_options = {}) - val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'minute', val, options) - else - minute_options = [] - 0.step(59, options[:minute_step] || 1) do |minute| - minute_options << ((val == minute) ? - content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute)) - ) - minute_options << "\n" - end - select_html(options[:field_name] || 'minute', minute_options.join, options, html_options) - end + DateTimeSelector.new(datetime, options, html_options).select_minute end # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. @@ -410,26 +411,13 @@ module ActionView # # # Generates a select field for minutes that defaults to the number given # select_minute(14) - # + # # # Generates a select field for minutes that defaults to the minutes for the time in my_time # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # def select_hour(datetime, options = {}, html_options = {}) - val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'hour', val, options) - else - hour_options = [] - 0.upto(23) do |hour| - hour_options << ((val == hour) ? - content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour)) - ) - hour_options << "\n" - end - select_html(options[:field_name] || 'hour', hour_options.join, options, html_options) - end + DateTimeSelector.new(datetime, options, html_options).select_hour end # Returns a select tag with options for each of the days 1 through 31 with the current day selected. @@ -444,36 +432,23 @@ module ActionView # # # Generates a select field for days that defaults to the number given # select_day(5) - # + # # # Generates a select field for days that defaults to the day for the date in my_date # # that is named 'due' rather than 'day' # select_day(my_time, :field_name => 'due') # def select_day(date, options = {}, html_options = {}) - val = date ? (date.kind_of?(Fixnum) ? date : date.day) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'day', val, options) - else - day_options = [] - 1.upto(31) do |day| - day_options << ((val == day) ? - content_tag(:option, day, :value => day, :selected => "selected") : - content_tag(:option, day, :value => day) - ) - day_options << "\n" - end - select_html(options[:field_name] || 'day', day_options.join, options, html_options) - end + DateTimeSelector.new(date, options, html_options).select_day end - # Returns a select tag with options for each of the months January through December with the current month selected. - # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values - # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names -- - # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names, - # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations, - # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the - # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the - # <tt>:field_name</tt> option, 'month' by default. + # Returns a select tag with options for each of the months January through December with the current month + # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are + # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation + # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you + # want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer + # to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want + # to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. + # Override the field name using the <tt>:field_name</tt> option, 'month' by default. # # ==== Examples # # Generates a select field for months that defaults to the current month that @@ -485,7 +460,7 @@ module ActionView # select_month(Date.today, :field_name => 'start') # # # Generates a select field for months that defaults to the current month that - # # will use keys like "1", "3". + # # will use keys like "1", "3". # select_month(Date.today, :use_month_numbers => true) # # # Generates a select field for months that defaults to the current month that @@ -501,36 +476,14 @@ module ActionView # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # def select_month(date, options = {}, html_options = {}) - val = date ? (date.kind_of?(Fixnum) ? date : date.month) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'month', val, options) - else - month_options = [] - month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES) - month_names.unshift(nil) if month_names.size < 13 - 1.upto(12) do |month_number| - month_name = if options[:use_month_numbers] - month_number - elsif options[:add_month_numbers] - month_number.to_s + ' - ' + month_names[month_number] - else - month_names[month_number] - end - - month_options << ((val == month_number) ? - content_tag(:option, month_name, :value => month_number, :selected => "selected") : - content_tag(:option, month_name, :value => month_number) - ) - month_options << "\n" - end - select_html(options[:field_name] || 'month', month_options.join, options, html_options) - end + DateTimeSelector.new(date, options, html_options).select_month end - # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius - # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year - # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be - # substituted for a year given as a number. Override the field name using the <tt>:field_name</tt> option, 'year' by default. + # Returns a select tag with options for each of the five years on each side of the current, which is selected. + # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the + # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or + # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number. + # Override the field name using the <tt>:field_name</tt> option, 'year' by default. # # ==== Examples # # Generates a select field for years that defaults to the current year that @@ -550,159 +503,384 @@ module ActionView # select_year(2006, :start_year => 2000, :end_year => 2010) # def select_year(date, options = {}, html_options = {}) - if !date || date == 0 - value = '' - middle_year = Date.today.year - elsif date.kind_of?(Fixnum) - value = middle_year = date + DateTimeSelector.new(date, options, html_options).select_year + end + end + + class DateTimeSelector #:nodoc: + extend ActiveSupport::Memoizable + include ActionView::Helpers::TagHelper + + DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX') + POSITION = { + :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 + }.freeze unless const_defined?('POSITION') + + def initialize(datetime, options = {}, html_options = {}) + @options = options.dup + @html_options = html_options.dup + @datetime = datetime + end + + def select_datetime + # TODO: Remove tag conditional + # Ideally we could just join select_date and select_date for the tag case + if @options[:tag] && @options[:ignore_date] + select_time + elsif @options[:tag] + order = date_order.dup + order -= [:hour, :minute, :second] + + @options[:discard_year] ||= true unless order.include?(:year) + @options[:discard_month] ||= true unless order.include?(:month) + @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) + @options[:discard_minute] ||= true if @options[:discard_hour] + @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] + + # 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] + @datetime = @datetime.change(:day => 1) + end + + [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } + order += [:hour, :minute, :second] unless @options[:discard_hour] + + build_selects_from_types(order) else - value = middle_year = date.year + "#{select_date}#{@options[:datetime_separator]}#{select_time}" end + end - if options[:use_hidden] - hidden_html(options[:field_name] || 'year', value, options) - else - year_options = '' - start_year = options[:start_year] || middle_year - 5 - end_year = options[:end_year] || middle_year + 5 - step_val = start_year < end_year ? 1 : -1 - - start_year.step(end_year, step_val) do |year| - if value == year - year_options << content_tag(:option, year, :value => year, :selected => "selected") - else - year_options << content_tag(:option, year, :value => year) - end - year_options << "\n" + def select_date + order = date_order.dup + + # TODO: Remove tag conditional + if @options[:tag] + @options[:discard_hour] = true + @options[:discard_minute] = true + @options[:discard_second] = true + + @options[:discard_year] ||= true unless order.include?(:year) + @options[:discard_month] ||= true unless order.include?(:month) + @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) + + # 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] + @datetime = @datetime.change(:day => 1) end - select_html(options[:field_name] || 'year', year_options, options, html_options) end + + [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } + + build_selects_from_types(order) end - private + def select_time + order = [] - def select_html(type, html_options, options, select_tag_options = {}) - name_and_id_from_options(options, type) - select_options = {:id => options[:id], :name => options[:name]} - select_options.merge!(:disabled => 'disabled') if options[:disabled] - select_options.merge!(select_tag_options) unless select_tag_options.empty? - select_html = "\n" - select_html << content_tag(:option, '', :value => '') + "\n" if options[:include_blank] - select_html << html_options.to_s - content_tag(:select, select_html, select_options) + "\n" - end + # TODO: Remove tag conditional + if @options[:tag] + @options[:discard_month] = true + @options[:discard_year] = true + @options[:discard_day] = true + @options[:discard_second] ||= true unless @options[:include_seconds] - def hidden_html(type, value, options) - name_and_id_from_options(options, type) - hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n" + order += [:year, :month, :day] unless @options[:ignore_date] end - def name_and_id_from_options(options, type) - options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]") - options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') + order += [:hour, :minute] + order << :second if @options[:include_seconds] + + build_selects_from_types(order) + end + + def select_second + if @options[:use_hidden] || @options[:discard_second] + build_hidden(:second, sec) if @options[:include_seconds] + else + build_options_and_select(:second, sec) end + end - def leading_zero_on_single_digits(number) - number > 9 ? number : "0#{number}" + def select_minute + if @options[:use_hidden] || @options[:discard_minute] + build_hidden(:minute, min) + else + build_options_and_select(:minute, min, :step => @options[:minute_step]) end - end + end - class InstanceTag #:nodoc: - include DateHelper + def select_hour + if @options[:use_hidden] || @options[:discard_hour] + build_hidden(:hour, hour) + else + build_options_and_select(:hour, hour, :end => 23) + end + end - def to_date_select_tag(options = {}, html_options = {}) - date_or_time_select(options.merge(:discard_hour => true), html_options) + def select_day + if @options[:use_hidden] || @options[:discard_day] + build_hidden(:day, day) + else + build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false) + end end - def to_time_select_tag(options = {}, html_options = {}) - date_or_time_select(options.merge(:discard_year => true, :discard_month => true), html_options) + def select_month + if @options[:use_hidden] || @options[:discard_month] + build_hidden(:month, month) + else + month_options = [] + 1.upto(12) do |month_number| + options = { :value => month_number } + options[:selected] = "selected" if month == month_number + month_options << content_tag(:option, month_name(month_number), options) + "\n" + end + build_select(:month, month_options.join) + end end - def to_datetime_select_tag(options = {}, html_options = {}) - date_or_time_select(options, html_options) + def select_year + if !@datetime || @datetime == 0 + val = '' + middle_year = Date.today.year + else + val = middle_year = year + end + + if @options[:use_hidden] || @options[:discard_year] + build_hidden(:year, val) + else + options = {} + options[:start] = @options[:start_year] || middle_year - 5 + options[:end] = @options[:end_year] || middle_year + 5 + options[:step] = options[:start] < options[:end] ? 1 : -1 + options[:leading_zeros] = false + + build_options_and_select(:year, val, options) + end end private - def date_or_time_select(options, html_options = {}) - defaults = { :discard_type => true } - options = defaults.merge(options) - datetime = value(object) - datetime ||= default_time_from_options(options[:default]) unless options[:include_blank] - - position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 } - - order = (options[:order] ||= [:year, :month, :day]) - - # Discard explicit and implicit by not being included in the :order - discard = {} - discard[:year] = true if options[:discard_year] or !order.include?(:year) - discard[:month] = true if options[:discard_month] or !order.include?(:month) - discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day) - discard[:hour] = true if options[:discard_hour] - discard[:minute] = true if options[:discard_minute] or discard[:hour] - discard[:second] = true unless options[:include_seconds] && !discard[:minute] - - # 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 datetime && discard[:day] && !discard[:month] - datetime = datetime.change(:day => 1) + %w( sec min hour day month year ).each do |method| + define_method(method) do + @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime end + end - # Maintain valid dates by including hidden fields for discarded elements - [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } + # Returns translated month names, but also ensures that a custom month + # name array has a leading nil element + def month_names + month_names = @options[:use_month_names] || translated_month_names + month_names.unshift(nil) if month_names.size < 13 + month_names + end + memoize :month_names + + # Returns translated month names + # => [nil, "January", "February", "March", + # "April", "May", "June", "July", + # "August", "September", "October", + # "November", "December"] + # + # If :use_short_month option is set + # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun", + # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + def translated_month_names + begin + key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' + I18n.translate(key, :locale => @options[:locale]) + end + end - # Ensure proper ordering of :hour, :minute and :second - [:hour, :minute, :second].each { |o| order.delete(o); order.push(o) } + # Lookup month name for number + # month_name(1) => "January" + # + # If :use_month_numbers option is passed + # month_name(1) => 1 + # + # If :add_month_numbers option is passed + # month_name(1) => "1 - January" + def month_name(number) + if @options[:use_month_numbers] + number + elsif @options[:add_month_numbers] + "#{number} - #{month_names[number]}" + else + month_names[number] + end + end + + def date_order + @options[:order] || translated_date_order + end + memoize :date_order - date_or_time_select = '' - order.reverse.each do |param| - # Send hidden fields for discarded elements once output has started - # This ensures AR can reconstruct valid dates using ParseDate - next if discard[param] && (date_or_time_select.empty? || options[:ignore_date]) + def translated_date_order + begin + I18n.translate(:'date.order', :locale => @options[:locale]) || [] + end + end - date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options)) - date_or_time_select.insert(0, - case param - when :hour then (discard[:year] && discard[:day] ? "" : " — ") - when :minute then " : " - when :second then options[:include_seconds] ? " : " : "" - else "" - end) + # Build full select tag from date type and options + def build_options_and_select(type, selected, options = {}) + build_select(type, build_options(selected, options)) + end + # Build select option html from date value and options + # build_options(15, :start => 1, :end => 31) + # => "<option value="1">1</option> + # <option value=\"2\">2</option> + # <option value=\"3\">3</option>..." + def build_options(selected, options = {}) + start = options.delete(:start) || 0 + stop = options.delete(:end) || 59 + step = options.delete(:step) || 1 + leading_zeros = options.delete(:leading_zeros).nil? ? true : false + + select_options = [] + start.step(stop, step) do |i| + value = leading_zeros ? sprintf("%02d", i) : i + tag_options = { :value => value } + tag_options[:selected] = "selected" if selected == i + select_options << content_tag(:option, value, tag_options) end + select_options.join("\n") + "\n" + end - date_or_time_select + # Builds select tag from date type and html select options + # build_select(:month, "<option value="1">January</option>...") + # => "<select id="post_written_on_2i" name="post[written_on(2i)]"> + # <option value="1">January</option>... + # </select>" + def build_select(type, select_options_as_html) + select_options = { + :id => input_id_from_type(type), + :name => input_name_from_type(type) + }.merge(@html_options) + select_options.merge!(:disabled => 'disabled') if @options[:disabled] + + select_html = "\n" + select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] + select_html << select_options_as_html.to_s + + content_tag(:select, select_html, select_options) + "\n" end - def options_with_prefix(position, options) - prefix = "#{@object_name}" - if options[:index] - prefix << "[#{options[:index]}]" - elsif @auto_index - prefix << "[#{@auto_index}]" + # Builds hidden input tag for date part and value + # build_hidden(:year, 2008) + # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />" + def build_hidden(type, value) + tag(:input, { + :type => "hidden", + :id => input_id_from_type(type), + :name => input_name_from_type(type), + :value => value + }) + "\n" + end + + # Returns the name attribute for the input tag + # => post[written_on(1i)] + def input_name_from_type(type) + prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX + prefix += "[#{@options[:index]}]" if @options[:index] + + field_name = @options[:field_name] || type + if @options[:include_position] + field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)" end - options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]") + + @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]" + end + + # Returns the id attribute for the input tag + # => "post_written_on_1i" + def input_id_from_type(type) + input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') + end + + # Given an ordering of datetime components, create the selection html + # and join them with their appropriate seperators + def build_selects_from_types(order) + select = '' + order.reverse.each do |type| + separator = separator(type) unless type == order.first # don't add on last field + select.insert(0, separator.to_s + send("select_#{type}").to_s) + end + select + end + + # Returns the separator for a given datetime component + def separator(type) + case type + when :month, :day + @options[:date_separator] + when :hour + (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] + when :minute + @options[:time_separator] + when :second + @options[:include_seconds] ? @options[:time_separator] : "" + end + end + end + + class InstanceTag #:nodoc: + def to_date_select_tag(options = {}, html_options = {}) + datetime_selector(options, html_options).select_date + end + + def to_time_select_tag(options = {}, html_options = {}) + datetime_selector(options, html_options).select_time + end + + def to_datetime_select_tag(options = {}, html_options = {}) + datetime_selector(options, html_options).select_datetime + end + + private + def datetime_selector(options, html_options) + datetime = value(object) || default_datetime(options) + + options = options.dup + options[:field_name] = @method_name + options[:include_position] = true + options[:prefix] ||= @object_name + options[:index] ||= @auto_index + options[:datetime_separator] ||= ' — ' + options[:time_separator] ||= ' : ' + + DateTimeSelector.new(datetime, options.merge(:tag => true), html_options) end - def default_time_from_options(default) - case default + def default_datetime(options) + return if options[:include_blank] + + case options[:default] when nil Time.current when Date, Time - default + options[:default] else + default = options[:default].dup + # Rename :minute and :second to :min and :sec default[:min] ||= default[:minute] default[:sec] ||= default[:second] time = Time.current - + [:year, :month, :day, :hour, :min, :sec].each do |key| default[key] ||= time.send(key) end - Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec]) - end + Time.utc_time( + default[:year], default[:month], default[:day], + default[:hour], default[:min], default[:sec] + ) + end end end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index 20de7e465f..90863fca08 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -2,21 +2,28 @@ module ActionView module Helpers # Provides a set of methods for making it easier to debug Rails objects. module DebugHelper - # Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very - # readable way to inspect an object. + # Returns a YAML representation of +object+ wrapped with <pre> and </pre>. + # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. + # Useful for inspecting an object at the time of rendering. # # ==== Example - # my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} - # debug(my_hash) # - # => <pre class='debug_dump'>--- - # first: 1 - # second: two - # third: - # - 1 - # - 2 - # - 3 - # </pre> + # @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %> + # debug(@user) + # # => + # <pre class='debug_dump'>--- !ruby/object:User + # attributes: + # updated_at: + # username: testing + # + # age: 42 + # password: xyz + # created_at: + # attributes_cache: {} + # + # new_record: true + # </pre> + def debug(object) begin Marshal::dump(object) @@ -28,4 +35,4 @@ module ActionView end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/form_country_helper.rb b/actionpack/lib/action_view/helpers/form_country_helper.rb new file mode 100644 index 0000000000..84e811f61d --- /dev/null +++ b/actionpack/lib/action_view/helpers/form_country_helper.rb @@ -0,0 +1,92 @@ +require 'action_view/helpers/form_options_helper' + +module ActionView + module Helpers + module FormCountryHelper + + # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. + def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) + InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) + end + + # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to + # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so + # that they will be listed above the rest of the (long) list. + # + # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. + def country_options_for_select(selected = nil, priority_countries = nil) + country_options = "" + + if priority_countries + country_options += options_for_select(priority_countries, selected) + country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + end + + return country_options + options_for_select(COUNTRIES, selected) + end + + private + + # All the countries included in the country_options output. + COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", + "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", + "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", + "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil", + "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", + "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", + "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", + "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", + "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", + "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", + "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", + "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", + "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)", + "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", + "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", + "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan", + "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", + "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of", + "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", + "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", + "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", + "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", + "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", + "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", + "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", + "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", + "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", + "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", + "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", + "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", + "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", + "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste", + "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", + "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", + "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", + "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", + "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES") + end + + class InstanceTag #:nodoc: + include FormCountryHelper + + def to_country_select_tag(priority_countries, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + value = value(object) + content_tag("select", + add_options( + country_options_for_select(value, priority_countries), + options, value + ), html_options + ) + end + end + + class FormBuilder + def country_select(method, priority_countries = nil, options = {}, html_options = {}) + @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options)) + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index bafc635ad2..7bb82ba5bb 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -76,7 +76,7 @@ module ActionView # Creates a form and a scope around a specific model object that is used as # a base for questioning about values for the fields. # - # Rails provides succint resource-oriented form generation with +form_for+ + # Rails provides succinct resource-oriented form generation with +form_for+ # like this: # # <% form_for @offer do |f| %> @@ -304,10 +304,6 @@ module ActionView when String, Symbol object_name = record_or_name_or_array object = args.first - when Array - object = record_or_name_or_array.last - object_name = ActionController::RecordIdentifier.singular_class_name(object) - apply_form_for_options!(record_or_name_or_array, options) else object = record_or_name_or_array object_name = ActionController::RecordIdentifier.singular_class_name(object) @@ -449,8 +445,37 @@ module ActionView # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+ - # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything, - # we add a hidden value with the same name as the checkbox as a work around. + # is set to 0 which is convenient for boolean values. + # + # ==== Gotcha + # + # The HTML specification says unchecked check boxes are not successful, and + # thus web browsers do not send them. Unfortunately this introduces a gotcha: + # if an Invoice model has a +paid+ flag, and in the form that edits a paid + # invoice the user unchecks its check box, no +paid+ parameter is sent. So, + # any mass-assignment idiom like + # + # @invoice.update_attributes(params[:invoice]) + # + # wouldn't update the flag. + # + # To prevent this the helper generates a hidden field with the same name as + # the checkbox after the very check box. So, the client either sends only the + # hidden field (representing the check box is unchecked), or both fields. + # Since the HTML specification says key/value pairs have to be sent in the + # same order they appear in the form and Rails parameters extraction always + # gets the first occurrence of any given key, that works in ordinary forms. + # + # Unfortunately that workaround does not work when the check box goes + # within an array-like parameter, as in + # + # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> + # <%= form.check_box :paid %> + # ... + # <% end %> + # + # because parameter name repetition is precisely what Rails seeks to distinguish + # the elements of the array. # # ==== Examples # # Let's say that @post.validated? is 1: @@ -503,10 +528,10 @@ module ActionView def initialize(object_name, method_name, template_object, object = nil) @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup - @template_object= template_object + @template_object = template_object @object = object - if @object_name.sub!(/\[\]$/,"") - if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) + if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]") + if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param) @auto_index = object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" @@ -683,7 +708,7 @@ module ActionView end def sanitized_object_name - @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") end def sanitized_method_name @@ -701,6 +726,13 @@ module ActionView def initialize(object_name, object, template, options, proc) @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc @default_options = @options ? @options.slice(:index) : {} + if @object_name.to_s.match(/\[\]$/) + if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) + @auto_index = object.to_param + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + end + end end (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| @@ -713,16 +745,25 @@ module ActionView end def fields_for(record_or_name_or_array, *args, &block) + if options.has_key?(:index) + index = "[#{options[:index]}]" + elsif defined?(@auto_index) + self.object_name = @object_name.to_s.sub(/\[\]$/,"") + index = "[#{@auto_index}]" + else + index = "" + end + case record_or_name_or_array when String, Symbol - name = "#{object_name}[#{record_or_name_or_array}]" + name = "#{object_name}#{index}[#{record_or_name_or_array}]" when Array object = record_or_name_or_array.last - name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) else object = record_or_name_or_array - name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) end @@ -741,8 +782,8 @@ module ActionView @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end - def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError") - @template.error_message_on(@object, method, prepend_text, append_text, css_class) + def error_message_on(method, *args) + @template.error_message_on(@object, method, *args) end def error_messages(options = {}) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 87d49397c6..9aae945408 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -133,11 +133,6 @@ module ActionView InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end - # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. - def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) - InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) - end - # Return select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # @@ -274,22 +269,6 @@ module ActionView end end - # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to - # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so - # that they will be listed above the rest of the (long) list. - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def country_options_for_select(selected = nil, priority_countries = nil) - country_options = "" - - if priority_countries - country_options += options_for_select(priority_countries, selected) - country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" - end - - return country_options + options_for_select(COUNTRIES, selected) - end - # Returns a string of option tags for pretty much any time zone in the # world. Supply a TimeZone name as +selected+ to have it marked as the # selected option tag. You can also supply an array of TimeZone objects @@ -347,43 +326,7 @@ module ActionView end # All the countries included in the country_options output. - COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", - "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", - "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", - "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil", - "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", - "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", - "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", - "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", - "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", - "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", - "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", - "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", - "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)", - "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", - "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", - "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan", - "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", - "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of", - "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", - "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", - "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", - "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", - "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", - "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", - "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", - "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", - "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", - "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", - "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", - "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", - "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", - "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste", - "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", - "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", - "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", - "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", - "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES") + COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES' end class InstanceTag #:nodoc: @@ -406,18 +349,6 @@ module ActionView ) end - def to_country_select_tag(priority_countries, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - content_tag("select", - add_options( - country_options_for_select(value, priority_countries), - options, value - ), html_options - ) - end - def to_time_zone_select_tag(priority_zones, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) @@ -452,10 +383,6 @@ module ActionView @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end - def country_select(method, priority_countries = nil, options = {}, html_options = {}) - @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options)) - end - def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index bdfb2eebd7..e8ca02d760 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -129,7 +129,7 @@ module ActionView # label_tag 'name', nil, :class => 'small_label' # # => <label for="name" class="small_label">Name</label> def label_tag(name, text = nil, options = {}) - content_tag :label, text || name.humanize, { "for" => name }.update(options.stringify_keys) + content_tag :label, text || name.to_s.humanize, { "for" => 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 diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 22bd5cb440..32089442b7 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -44,13 +44,22 @@ module ActionView include PrototypeHelper - # Returns a link that will trigger a JavaScript +function+ using the + # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the # onclick handler and return false after the fact. # + # The first argument +name+ is used as the link text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # # The +function+ argument can be omitted in favor of an +update_page+ # block, which evaluates to a string when the template is rendered # (instead of making an Ajax request first). # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # + # # Examples: # link_to_function "Greeting", "alert('Hello world!')" # Produces: @@ -89,13 +98,21 @@ module ActionView content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) end - # Returns a button that'll trigger a JavaScript +function+ using the + # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the # onclick handler. # + # The first argument +name+ is used as the button's value or display text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # # The +function+ argument can be omitted in favor of an +update_page+ # block, which evaluates to a string when the template is rendered # (instead of making an Ajax request first). # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # # Examples: # button_to_function "Greeting", "alert('Hello world!')" # button_to_function "Delete", "if (confirm('Really?')) do_delete()" diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index 546f9fe449..2c70b8a7e8 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.0.1 - * (c) 2005-2007 Sam Stephenson +/* Prototype JavaScript framework, version 1.6.0.2 + * (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/ @@ -7,7 +7,7 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.1', + Version: '1.6.0.2', Browser: { IE: !!(window.attachEvent && !window.opera), @@ -110,7 +110,7 @@ Object.extend(Object, { try { if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; - return object.inspect ? object.inspect() : object.toString(); + return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; @@ -171,7 +171,8 @@ Object.extend(Object, { }, isArray: function(object) { - return object && object.constructor === Array; + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; }, isHash: function(object) { @@ -578,7 +579,7 @@ var Template = Class.create({ } return before + String.interpret(ctx); - }.bind(this)); + }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; @@ -806,20 +807,20 @@ Object.extend(Enumerable, { function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } if (Prototype.Browser.WebKit) { - function $A(iterable) { + $A = function(iterable) { if (!iterable) return []; if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; - } + }; } Array.from = $A; @@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, { var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' - || (this.options.evalJS && contentType + || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } @@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, { } }, + 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); + return this.transport.getResponseHeader(name) || null; } catch (e) { return null } }, @@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({ if (!json) return null; json = decodeURIComponent(escape(json)); try { - return json.evalJSON(this.request.options.sanitizeJSON); + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({ this.responseText.blank()) return null; try { - return this.responseText.evalJSON(options.sanitizeJSON); + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1608,24 +1620,28 @@ Element.Methods = { Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; - var content, t, range; + var content, insert, tagName, childNodes; - for (position in insertions) { + for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); - t = Element._insertionTranslations[position]; + insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { - t.insert(element, content); + insert(element, content); continue; } content = Object.toHTML(content); - range = element.ownerDocument.createRange(); - t.initializeRange(element, range); - t.insert(element, range.createContextualFragment(content.stripScripts())); + 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(); } @@ -1670,7 +1686,7 @@ Element.Methods = { }, descendants: function(element) { - return $(element).getElementsBySelector("*"); + return $(element).select("*"); }, firstDescendant: function(element) { @@ -1709,32 +1725,31 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); - return expression ? Selector.findElement(ancestors, expression, index) : - ancestors[index || 0]; + 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(); - var descendants = element.descendants(); - return expression ? Selector.findElement(descendants, expression, index) : - descendants[index || 0]; + return Object.isNumber(expression) ? element.descendants()[expression] : + element.select(expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); - return expression ? Selector.findElement(previousSiblings, expression, index) : - previousSiblings[index || 0]; + 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 expression ? Selector.findElement(nextSiblings, expression, index) : - nextSiblings[index || 0]; + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); }, select: function() { @@ -1860,7 +1875,8 @@ Element.Methods = { do { ancestor = ancestor.parentNode; } while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); } - if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); + if (nextAncestor && nextAncestor.sourceIndex) + return (e > a && e < nextAncestor.sourceIndex); } while (element = element.parentNode) @@ -2004,7 +2020,7 @@ Element.Methods = { if (element) { if (element.tagName == 'BODY') break; var p = Element.getStyle(element, 'position'); - if (p == 'relative' || p == 'absolute') break; + if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); @@ -2153,46 +2169,6 @@ Element._attributeTranslations = { } }; - -if (!document.createRange || Prototype.Browser.Opera) { - Element.Methods.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 t = Element._insertionTranslations, content, position, pos, tagName; - - for (position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - pos = t[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - pos.insert(element, content); - continue; - } - - content = Object.toHTML(content); - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - if (t.tags[tagName]) { - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - if (position == 'top' || position == 'after') fragments.reverse(); - fragments.each(pos.insert.curry(element)); - } - else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); - - content.evalScripts.bind(content).defer(); - } - - return element; - }; -} - if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { @@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + // 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); + 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); var position = element.getStyle('position'); - if (position != 'static') return proceed(element); + 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 }); @@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) { }; Element._attributeTranslations.write = { - names: Object.clone(Element._attributeTranslations.read.names), + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; @@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) { }; } -if (document.createElement('div').outerHTML) { +if ('outerHTML' in document.createElement('div')) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) { Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); + 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: { - adjacency: 'beforeBegin', - insert: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - initializeRange: function(element, range) { - range.setStartBefore(element); - } + before: function(element, node) { + element.parentNode.insertBefore(node, element); }, - top: { - adjacency: 'afterBegin', - insert: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - initializeRange: function(element, range) { - range.selectNodeContents(element); - range.collapse(true); - } + top: function(element, node) { + element.insertBefore(node, element.firstChild); }, - bottom: { - adjacency: 'beforeEnd', - insert: function(element, node) { - element.appendChild(node); - } + bottom: function(element, node) { + element.appendChild(node); }, - after: { - adjacency: 'afterEnd', - insert: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - initializeRange: function(element, range) { - range.setStartAfter(element); - } + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['<table>', '</table>', 1], @@ -2532,7 +2510,6 @@ Element._insertionTranslations = { }; (function() { - this.bottom.initializeRange = this.top.initializeRange; Object.extend(this.tags, { THEAD: this.tags.TBODY, TFOOT: this.tags.TBODY, @@ -2716,7 +2693,7 @@ 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. */ @@ -2959,13 +2936,13 @@ Object.extend(Selector, { }, 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 = false;', + 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 = false;').evaluate(m); + 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, '\\"'); @@ -2989,7 +2966,8 @@ Object.extend(Selector, { 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)|(?=:))/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, attrPresence: /^\[([\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3014,7 +2992,7 @@ Object.extend(Selector, { attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); - return Selector.operators[matches[2]](nodeValue, matches[3]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, @@ -3029,14 +3007,15 @@ Object.extend(Selector, { // marks an array of nodes for counting mark: function(nodes) { + var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) - node._counted = true; + node._countedByPrototype = _true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) - node._counted = undefined; + node._countedByPrototype = undefined; return nodes; }, @@ -3044,15 +3023,15 @@ Object.extend(Selector, { // "ofType" flag indicates whether we're indexing for nth-of-type // rather than nth-child index: function(parentNode, reverse, ofType) { - parentNode._counted = true; + 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._counted)) node.nodeIndex = j++; + 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._counted)) node.nodeIndex = j++; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, @@ -3061,8 +3040,8 @@ Object.extend(Selector, { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._counted) { - n._counted = true; + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); @@ -3114,7 +3093,7 @@ Object.extend(Selector, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { - tagName = tagName.toUpperCase(); + var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { @@ -3127,7 +3106,7 @@ Object.extend(Selector, { if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() == tagName) results.push(node); + if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, @@ -3174,16 +3153,18 @@ Object.extend(Selector, { return results; }, - attrPresence: function(nodes, root, attr) { + 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) { + 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); @@ -3262,7 +3243,7 @@ Object.extend(Selector, { var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._counted) { + if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } @@ -3300,7 +3281,7 @@ Object.extend(Selector, { var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._counted) results.push(node); + if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, @@ -3334,11 +3315,19 @@ Object.extend(Selector, { '|=': 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 = new Selector(expression).findElements(), h = Selector.handlers; + var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._counted) results.push(element); + if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, @@ -3351,11 +3340,7 @@ Object.extend(Selector, { }, findChildElements: function(element, expressions) { - var exprs = expressions.join(','); - expressions = []; - exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); + 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()); @@ -3366,13 +3351,22 @@ Object.extend(Selector, { }); if (Prototype.Browser.IE) { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - Selector.handlers.concat = function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }; + 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 $$() { @@ -3850,9 +3844,9 @@ Object.extend(Event, (function() { var cache = Event.cache; function getEventID(element) { - if (element._eventID) return element._eventID; + if (element._prototypeEventID) return element._prototypeEventID[0]; arguments.callee.id = arguments.callee.id || 1; - return element._eventID = ++arguments.callee.id; + return element._prototypeEventID = [++arguments.callee.id]; } function getDOMEventName(eventName) { @@ -3880,7 +3874,7 @@ Object.extend(Event, (function() { return false; Event.extend(event); - handler.call(element, event) + handler.call(element, event); }; wrapper.handler = handler; @@ -3962,11 +3956,12 @@ Object.extend(Event, (function() { if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; + var event; if (document.createEvent) { - var event = document.createEvent("HTMLEvents"); + event = document.createEvent("HTMLEvents"); event.initEvent("dataavailable", true, true); } else { - var event = document.createEventObject(); + event = document.createEventObject(); event.eventType = "ondataavailable"; } @@ -3995,20 +3990,21 @@ Element.addMethods({ Object.extend(document, { fire: Element.Methods.fire.methodize(), observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.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, fired = false; + var timer; function fireContentLoadedEvent() { - if (fired) return; + if (document.loaded) return; if (timer) window.clearInterval(timer); document.fire("dom:loaded"); - fired = true; + document.loaded = true; } if (document.addEventListener) { diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 120bb4cc1f..77f19b36a6 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -22,14 +22,14 @@ module ActionView # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234 # # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".") - # => +1.123.555.1234 x 1343 + # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) number = number.to_s.strip unless number.nil? - options = options.stringify_keys - area_code = options["area_code"] || nil - delimiter = options["delimiter"] || "-" - extension = options["extension"].to_s.strip || nil - country_code = options["country_code"] || nil + options = options.symbolize_keys + area_code = options[:area_code] || nil + delimiter = options[:delimiter] || "-" + extension = options[:extension].to_s.strip || nil + country_code = options[:country_code] || nil begin str = "" @@ -51,10 +51,10 @@ module ActionView # # ==== Options # * <tt>:precision</tt> - Sets the level of precision (defaults to 2). - # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$"). + # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$"). # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are: + # * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are: # # %u The currency unit # %n The number @@ -69,16 +69,25 @@ module ActionView # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") # # => 1234567890,50 £ def number_to_currency(number, options = {}) - options = options.stringify_keys - precision = options["precision"] || 2 - unit = options["unit"] || "$" - separator = precision > 0 ? options["separator"] || "." : "" - delimiter = options["delimiter"] || "," - format = options["format"] || "%u%n" + options.symbolize_keys! + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {} + defaults = defaults.merge(currency) + + precision = options[:precision] || defaults[:precision] + unit = options[:unit] || defaults[:unit] + separator = options[:separator] || defaults[:separator] + delimiter = options[:delimiter] || defaults[:delimiter] + format = options[:format] || defaults[:format] + separator = '' if precision == 0 begin - parts = number_with_precision(number, precision).split('.') - format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit) + format.gsub(/%n/, number_with_precision(number, + :precision => precision, + :delimiter => delimiter, + :separator => separator) + ).gsub(/%u/, unit) rescue number end @@ -90,96 +99,192 @@ module ActionView # ==== Options # * <tt>:precision</tt> - Sets the level of precision (defaults to 3). # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # # ==== Examples - # number_to_percentage(100) # => 100.000% - # number_to_percentage(100, :precision => 0) # => 100% - # - # number_to_percentage(302.24398923423, :precision => 5) - # # => 302.24399% + # number_to_percentage(100) # => 100.000% + # number_to_percentage(100, :precision => 0) # => 100% + # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000% + # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399% def number_to_percentage(number, options = {}) - options = options.stringify_keys - precision = options["precision"] || 3 - separator = options["separator"] || "." + options.symbolize_keys! + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {} + defaults = defaults.merge(percentage) + + precision = options[:precision] || defaults[:precision] + separator = options[:separator] || defaults[:separator] + delimiter = options[:delimiter] || defaults[:delimiter] begin - number = number_with_precision(number, precision) - parts = number.split('.') - if parts.at(1).nil? - parts[0] + "%" - else - parts[0] + separator + parts[1].to_s + "%" - end + number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter) + "%" rescue number end end - # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You - # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters. + # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can + # customize the format in the +options+ hash. # # ==== Options - # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). + # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). # # ==== Examples - # number_with_delimiter(12345678) # => 12,345,678 - # number_with_delimiter(12345678.05) # => 12,345,678.05 - # number_with_delimiter(12345678, ".") # => 12.345.678 - # - # number_with_delimiter(98765432.98, " ", ",") + # number_with_delimiter(12345678) # => 12,345,678 + # number_with_delimiter(12345678.05) # => 12,345,678.05 + # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 + # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 + # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 - def number_with_delimiter(number, delimiter=",", separator=".") + # + # You can still use <tt>number_with_delimiter</tt> with the old API that accepts the + # +delimiter+ as its optional second and the +separator+ as its + # optional third parameter: + # number_with_delimiter(12345678, " ") # => 12 345.678 + # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05 + def number_with_delimiter(number, *args) + options = args.extract_options! + options.symbolize_keys! + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + + unless args.empty? + ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' + + 'instead of separate delimiter and precision arguments.', caller) + delimiter = args[0] || defaults[:delimiter] + separator = args[1] || defaults[:separator] + end + + delimiter ||= (options[:delimiter] || defaults[:delimiter]) + separator ||= (options[:separator] || defaults[:separator]) + begin parts = number.to_s.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") - parts.join separator + parts.join(separator) rescue number end end - # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default - # level of precision is 3. + # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2). + # You can customize the format in the +options+ hash. + # + # ==== Options + # * <tt>:precision</tt> - Sets the level of precision (defaults to 3). + # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # # ==== Examples - # number_with_precision(111.2345) # => 111.235 - # number_with_precision(111.2345, 2) # => 111.23 - # number_with_precision(13, 5) # => 13.00000 - # number_with_precision(389.32314, 0) # => 389 - def number_with_precision(number, precision=3) - "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision) - rescue - number + # number_with_precision(111.2345) # => 111.235 + # number_with_precision(111.2345, :precision => 2) # => 111.23 + # number_with_precision(13, :precision => 5) # => 13.00000 + # number_with_precision(389.32314, :precision => 0) # => 389 + # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') + # # => 1.111,23 + # + # You can still use <tt>number_with_precision</tt> with the old API that accepts the + # +precision+ as its optional second parameter: + # number_with_precision(number_with_precision(111.2345, 2) # => 111.23 + def number_with_precision(number, *args) + options = args.extract_options! + options.symbolize_keys! + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], + :raise => true) rescue {} + defaults = defaults.merge(precision_defaults) + + unless args.empty? + ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' + + 'instead of a separate precision argument.', caller) + precision = args[0] || defaults[:precision] + end + + precision ||= (options[:precision] || defaults[:precision]) + separator ||= (options[:separator] || defaults[:separator]) + delimiter ||= (options[:delimiter] || defaults[:delimiter]) + + begin + rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision + number_with_delimiter("%01.#{precision}f" % rounded_number, + :separator => separator, + :delimiter => delimiter) + rescue + number + 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 + # (e.g., giving it 1500 yields 1.5 KB). This method is useful for # reporting file sizes to users. This method returns nil if - # +size+ cannot be converted into a number. You can change the default - # precision of 1 using the precision parameter +precision+. + # +size+ cannot be converted into a number. You can customize the + # format in the +options+ hash. + # + # ==== Options + # * <tt>:precision</tt> - Sets the level of precision (defaults to 1). + # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # # ==== Examples - # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.2 KB - # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.2 MB - # number_to_human_size(1234567890) # => 1.1 GB - # number_to_human_size(1234567890123) # => 1.1 TB + # number_to_human_size(123) # => 123 Bytes + # number_to_human_size(1234) # => 1.2 KB + # number_to_human_size(12345) # => 12.1 KB + # number_to_human_size(1234567) # => 1.2 MB + # number_to_human_size(1234567890) # => 1.1 GB + # number_to_human_size(1234567890123) # => 1.1 TB + # number_to_human_size(1234567, :precision => 2) # => 1.18 MB + # number_to_human_size(483989, :precision => 0) # => 473 KB + # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB + # + # You can still use <tt>number_to_human_size</tt> with the old API that accepts the + # +precision+ as its optional second parameter: # number_to_human_size(1234567, 2) # => 1.18 MB - # number_to_human_size(483989, 0) # => 4 MB - def number_to_human_size(size, precision=1) - size = Kernel.Float(size) - case - when size.to_i == 1; "1 Byte" - when size < 1.kilobyte; "%d Bytes" % size - when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte) - when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte) - when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte) - else "%.#{precision}f TB" % (size / 1.0.terabyte) - end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ') - rescue - nil + # number_to_human_size(483989, 0) # => 473 KB + def number_to_human_size(number, *args) + return number.nil? ? nil : pluralize(number.to_i, "Byte") if number.to_i < 1024 + + options = args.extract_options! + options.symbolize_keys! + + 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) + + unless args.empty? + ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' + + 'instead of a separate precision argument.', caller) + precision = args[0] || defaults[:precision] + end + + precision ||= (options[:precision] || defaults[:precision]) + separator ||= (options[:separator] || defaults[:separator]) + delimiter ||= (options[:delimiter] || defaults[:delimiter]) + + 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] + + begin + escaped_separator = Regexp.escape(separator) + number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter + ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}" + rescue + number + end end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index d0c281c803..ff83494e94 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -3,25 +3,25 @@ require 'set' module ActionView module Helpers # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides - # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, + # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php] - # functionality, and more traditional object-oriented facilities for JavaScript. + # functionality, and more traditional object-oriented facilities for JavaScript. # This module provides a set of helpers to make it more convenient to call - # functions from Prototype using Rails, including functionality to call remote - # Rails methods (that is, making a background request to a Rails action) using Ajax. - # This means that you can call actions in your controllers without - # reloading the page, but still update certain parts of it using + # functions from Prototype using Rails, including functionality to call remote + # Rails methods (that is, making a background request to a Rails action) using Ajax. + # This means that you can call actions in your controllers without + # reloading the page, but still update certain parts of it using # injections into the DOM. A common use case is having a form that adds # a new element to a list without reloading the page or updating a shopping # cart total when a new item is added. # # == Usage - # To be able to use these helpers, you must first include the Prototype - # JavaScript framework in your pages. + # To be able to use these helpers, you must first include the Prototype + # JavaScript framework in your pages. # # javascript_include_tag 'prototype' # - # (See the documentation for + # (See the documentation for # ActionView::Helpers::JavaScriptHelper for more information on including # this and other JavaScript files in your Rails templates.) # @@ -29,7 +29,7 @@ module ActionView # # link_to_remote "Add to cart", # :url => { :action => "add", :id => product.id }, - # :update => { :success => "cart", :failure => "error" } + # :update => { :success => "cart", :failure => "error" } # # ...through a form... # @@ -50,8 +50,8 @@ module ActionView # :update => :hits, # :with => 'query' # %> - # - # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than + # + # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than # are listed here); check out the documentation for each method to find out more about its usage and options. # # === Common Options @@ -61,9 +61,9 @@ module ActionView # # == Designing your Rails actions for Ajax # When building your action handlers (that is, the Rails actions that receive your background requests), it's - # important to remember a few things. First, whatever your action would normall return to the browser, it will + # important to remember a few things. First, whatever your action would normally return to the browser, it will # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause - # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up. + # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up. # You can turn the layout off on particular actions by doing the following: # # class SiteController < ActionController::Base @@ -74,8 +74,8 @@ module ActionView # # render :layout => false # - # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the - # method that Ajax uses to make background requests) method. + # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the + # method that Ajax uses to make background requests) method. # def name # # Is this an XmlHttpRequest request? # if (request.xhr?) @@ -93,7 +93,7 @@ module ActionView # # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request. # - # If you are just returning a little data or don't want to build a template for your output, you may opt to simply + # If you are just returning a little data or don't want to build a template for your output, you may opt to simply # render text output, like this: # # render :text => 'Return this from my method!' @@ -103,7 +103,7 @@ module ActionView # # == Updating multiple elements # See JavaScriptGenerator for information on updating multiple elements - # on the page in an Ajax response. + # on the page in an Ajax response. module PrototypeHelper unless const_defined? :CALLBACKS CALLBACKS = Set.new([ :uninitialized, :loading, :loaded, @@ -111,67 +111,67 @@ module ActionView (100..599).to_a) AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, :asynchronous, :method, :insertion, :position, - :form, :with, :update, :script ]).merge(CALLBACKS) + :form, :with, :update, :script, :type ]).merge(CALLBACKS) end - # Returns a link to a remote action defined by <tt>options[:url]</tt> - # (using the url_for format) that's called in the background using + # Returns a link to a remote action defined by <tt>options[:url]</tt> + # (using the url_for format) that's called in the background using # XMLHttpRequest. The result of that request can then be inserted into a - # DOM object whose id can be specified with <tt>options[:update]</tt>. + # DOM object whose id can be specified with <tt>options[:update]</tt>. # Usually, the result would be a partial prepared by the controller with - # render :partial. + # render :partial. # # Examples: - # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true}); + # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true}); # # return false;">Delete this post</a> - # link_to_remote "Delete this post", :update => "posts", + # link_to_remote "Delete this post", :update => "posts", # :url => { :action => "destroy", :id => post.id } # - # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true}); + # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true}); # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a> - # link_to_remote(image_tag("refresh"), :update => "emails", + # link_to_remote(image_tag("refresh"), :update => "emails", # :url => { :action => "list_emails" }) - # + # # You can override the generated HTML options by specifying a hash in # <tt>options[:html]</tt>. - # + # # link_to_remote "Delete this post", :update => "posts", - # :url => post_url(@post), :method => :delete, - # :html => { :class => "destructive" } + # :url => post_url(@post), :method => :delete, + # :html => { :class => "destructive" } # # You can also specify a hash for <tt>options[:update]</tt> to allow for - # easy redirection of output to an other DOM element if a server-side + # easy redirection of output to an other DOM element if a server-side # error occurs: # # Example: - # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5', + # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5', # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a> # link_to_remote "Delete this post", # :url => { :action => "destroy", :id => post.id }, # :update => { :success => "posts", :failure => "error" } # - # Optionally, you can use the <tt>options[:position]</tt> parameter to - # influence how the target DOM element is updated. It must be one of + # Optionally, you can use the <tt>options[:position]</tt> parameter to + # influence how the target DOM element is updated. It must be one of # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>. # # The method used is by default POST. You can also specify GET or you # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt> # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'}); + # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'}); # # return false;">Destroy</a> # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete # - # By default, these remote requests are processed asynchronous during - # which various JavaScript callbacks can be triggered (for progress - # indicators and the likes). All callbacks get access to the - # <tt>request</tt> object, which holds the underlying XMLHttpRequest. + # By default, these remote requests are processed asynchronous during + # which various JavaScript callbacks can be triggered (for progress + # indicators and the likes). All callbacks get access to the + # <tt>request</tt> object, which holds the underlying XMLHttpRequest. # # To access the server response, use <tt>request.responseText</tt>, to # find out the HTTP status, use <tt>request.status</tt>. # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true, + # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true, # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a> # word = 'hello' # link_to_remote word, @@ -180,43 +180,43 @@ module ActionView # # The callbacks that may be specified are (in order): # - # <tt>:loading</tt>:: Called when the remote document is being + # <tt>:loading</tt>:: Called when the remote document is being # loaded with data by the browser. # <tt>:loaded</tt>:: Called when the browser has finished loading # the remote document. - # <tt>:interactive</tt>:: Called when the user can interact with the - # remote document, even though it has not + # <tt>:interactive</tt>:: Called when the user can interact with the + # remote document, even though it has not # finished loading. # <tt>:success</tt>:: Called when the XMLHttpRequest is completed, # and the HTTP status code is in the 2XX range. # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed, # and the HTTP status code is not in the 2XX # range. - # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete - # (fires after success/failure if they are + # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete + # (fires after success/failure if they are # present). - # - # You can further refine <tt>:success</tt> and <tt>:failure</tt> by + # + # You can further refine <tt>:success</tt> and <tt>:failure</tt> by # adding additional callbacks for specific status codes. # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true, - # # on404:function(request){alert('Not found...? Wrong URL...?')}, + # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true, + # # on404:function(request){alert('Not found...? Wrong URL...?')}, # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a> # link_to_remote word, # :url => { :action => "action" }, # 404 => "alert('Not found...? Wrong URL...?')", # :failure => "alert('HTTP Error ' + request.status + '!')" # - # A status code callback overrides the success/failure handlers if + # A status code callback overrides the success/failure handlers if # present. # # If you for some reason or another need synchronous processing (that'll - # block the browser while the request is happening), you can specify + # block the browser while the request is happening), you can specify # <tt>options[:type] = :synchronous</tt>. # # You can customize further browser side call logic by passing in - # JavaScript code snippets via some optional parameters. In their order + # JavaScript code snippets via some optional parameters. In their order # of use these are: # # <tt>:confirm</tt>:: Adds confirmation dialog. @@ -228,7 +228,7 @@ module ActionView # <tt>:after</tt>:: Called immediately after request was # initiated and before <tt>:loading</tt>. # <tt>:submit</tt>:: Specifies the DOM element ID that's used - # as the parent of the form elements. By + # as the parent of the form elements. By # default this is the current form, but # it could just as well be the ID of a # table row or any other DOM element. @@ -238,10 +238,10 @@ module ActionView # URL query string. # # Example: - # + # # :with => "'name=' + $('name').value" # - # You can generate a link that uses AJAX in the general case, while + # You can generate a link that uses AJAX in the general case, while # degrading gracefully to plain link behavior in the absence of # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL. # Note the extra curly braces around the <tt>options</tt> hash separate @@ -251,7 +251,7 @@ module ActionView # link_to_remote "Delete this post", # { :update => "posts", :url => { :action => "destroy", :id => post.id } }, # :href => url_for(:action => "destroy", :id => post.id) - def link_to_remote(name, options = {}, html_options = nil) + def link_to_remote(name, options = {}, html_options = nil) link_to_function(name, remote_function(options), html_options || options.delete(:html)) end @@ -262,15 +262,15 @@ module ActionView # and defining callbacks is the same as link_to_remote. # Examples: # # Call get_averages and put its results in 'avg' every 10 seconds - # # Generates: - # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages', + # # Generates: + # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages', # # {asynchronous:true, evalScripts:true})}, 10) # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg') # # # Call invoice every 10 seconds with the id of the customer # # If it succeeds, update the invoice DIV; if it fails, update the error DIV # # Generates: - # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'}, + # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'}, # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10) # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id }, # :update => { :success => "invoice", :failure => "error" } @@ -286,11 +286,11 @@ module ActionView javascript_tag(code) end - # Returns a form tag that will submit using XMLHttpRequest in the - # background instead of the regular reloading POST arrangement. Even + # Returns a form tag that will submit using XMLHttpRequest in the + # background instead of the regular reloading POST arrangement. Even # though it's using JavaScript to serialize the form elements, the form # submission will work just like a regular submission as viewed by the - # receiving side (all elements available in <tt>params</tt>). The options for + # receiving side (all elements available in <tt>params</tt>). The options for # specifying the target with <tt>:url</tt> and defining callbacks is the same as # +link_to_remote+. # @@ -299,21 +299,21 @@ module ActionView # # Example: # # Generates: - # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('', + # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('', # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> - # form_remote_tag :html => { :action => + # form_remote_tag :html => { :action => # url_for(:controller => "some", :action => "place") } # # The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd) # argument in the FormTagHelper.form_tag method. # - # By default the fall-through action is the same as the one specified in + # By default the fall-through action is the same as the one specified in # the <tt>:url</tt> (and the default method is <tt>:post</tt>). # # form_remote_tag also takes a block, like form_tag: # # Generates: - # # <form action="/" method="post" onsubmit="new Ajax.Request('/', - # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); + # # <form action="/" method="post" onsubmit="new Ajax.Request('/', + # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); # # return false;"> <div><input name="commit" type="submit" value="Save" /></div> # # </form> # <% form_remote_tag :url => '/posts' do -%> @@ -323,19 +323,19 @@ module ActionView options[:form] = true options[:html] ||= {} - options[:html][:onsubmit] = - (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") + + options[:html][:onsubmit] = + (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") + "#{remote_function(options)}; return false;" form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block) end - # Creates a form that will submit using XMLHttpRequest in the background - # instead of the regular reloading POST arrangement and a scope around a + # Creates a form that will submit using XMLHttpRequest in the background + # instead of the regular reloading POST arrangement and a scope around a # specific resource that is used as a base for questioning about - # values for the fields. + # values for the fields. # - # === Resource + # === Resource # # Example: # <% remote_form_for(@post) do |f| %> @@ -348,7 +348,7 @@ module ActionView # ... # <% end %> # - # === Nested Resource + # === Nested Resource # # Example: # <% remote_form_for([@post, @comment]) do |f| %> @@ -387,23 +387,23 @@ module ActionView concat('</form>') end alias_method :form_remote_for, :remote_form_for - + # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+ # that will submit form using XMLHttpRequest in the background instead of a regular POST request that - # reloads the page. + # reloads the page. # # # Create a button that submits to the create action - # # - # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', - # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); + # # + # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', + # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Create" /> # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> # # # Submit to the remote action update and update the DIV succeed or fail based # # on the success or failure of the request # # - # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, - # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); + # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, + # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Update" /> # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, # :update => { :success => "succeed", :failure => "fail" } @@ -423,7 +423,7 @@ module ActionView tag("input", options[:html], false) end alias_method :submit_to_remote, :button_to_remote - + # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple # update return document using +update_element_function+ calls. @@ -433,11 +433,11 @@ module ActionView # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. - # + # # Example: - # # Generates: <select id="options" onchange="new Ajax.Updater('options', + # # Generates: <select id="options" onchange="new Ajax.Updater('options', # # '/testing/update_options', {asynchronous:true, evalScripts:true})"> - # <select id="options" onchange="<%= remote_function(:update => "options", + # <select id="options" onchange="<%= remote_function(:update => "options", # :url => { :action => :update_options }) %>"> # <option value="0">Hello</option> # <option value="1">World</option> @@ -455,7 +455,7 @@ module ActionView update << "'#{options[:update]}'" end - function = update.empty? ? + function = update.empty? ? "new Ajax.Request(" : "new Ajax.Updater(#{update}, " @@ -476,9 +476,9 @@ module ActionView # callback when its contents have changed. The default callback is an # Ajax call. By default the value of the observed field is sent as a # parameter with the Ajax call. - # + # # Example: - # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest', + # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest', # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})}) # <%= observe_field :suggest, :url => { :action => :find_suggestion }, # :frequency => 0.25, @@ -500,14 +500,14 @@ module ActionView # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')}) # The element parameter is the DOM element being observed, and the value is its value at the # time the observer is triggered. - # + # # Additional options are: # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to # this field will be detected. Not setting this # option at all or to a value equal to or less than # zero will use event based observation instead of # time based observation. - # <tt>:update</tt>:: Specifies the DOM ID of the element whose + # <tt>:update</tt>:: Specifies the DOM ID of the element whose # innerHTML should be updated with the # XMLHttpRequest response text. # <tt>:with</tt>:: A JavaScript expression specifying the parameters @@ -518,7 +518,7 @@ module ActionView # variable +value+. # # Examples - # + # # :with => "'my_custom_key=' + value" # :with => "'person[name]=' + prompt('New name')" # :with => "Form.Element.serialize('other-field')" @@ -544,7 +544,7 @@ module ActionView # observe_field 'book_title', # :url => 'http://example.com/books/edit/1', # :with => 'title' - # + # # # Sends params: {:book_title => 'Title of the book'} when the focus leaves # # the input field. # observe_field 'book_title', @@ -558,7 +558,7 @@ module ActionView build_observer('Form.Element.EventObserver', field_id, options) end end - + # Observes the form with the DOM ID specified by +form_id+ and calls a # callback when its contents have changed. The default callback is an # Ajax call. By default all fields of the observed field are sent as @@ -574,39 +574,57 @@ module ActionView build_observer('Form.EventObserver', form_id, options) end end - - # All the methods were moved to GeneratorMethods so that + + # All the methods were moved to GeneratorMethods so that # #include_helpers_from_context has nothing to overwrite. class JavaScriptGenerator #:nodoc: def initialize(context, &block) #:nodoc: @context, @lines = context, [] include_helpers_from_context - @context.instance_exec(self, &block) + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) + end end - + private def include_helpers_from_context - @context.extended_by.each do |mod| - extend mod unless mod.name =~ /^ActionView::Helpers/ + unless generator_methods_module = @context.instance_variable_get(:@__javascript_generator_methods__) + modules = @context.extended_by - ([ActionView::Helpers] + ActionView::Helpers.included_modules) + + generator_methods_module = Module.new do + modules.each do |mod| + begin + include mod + rescue Exception => e + # HACK: Probably not a good idea to suppress these warnings + # AFAIK exceptions are only raised in while testing with mocha + # because the module does not like to be included into other + # non TestUnit classes + end + end + include GeneratorMethods + end + @context.instance_variable_set(:@__javascript_generator_methods__, generator_methods_module) end - extend GeneratorMethods + + extend generator_methods_module end - - # JavaScriptGenerator generates blocks of JavaScript code that allow you - # to change the content and presentation of multiple DOM elements. Use + + # JavaScriptGenerator generates blocks of JavaScript code that allow you + # to change the content and presentation of multiple DOM elements. Use # this in your Ajax response bodies, either in a <script> tag or as plain # JavaScript sent with a Content-type of "text/javascript". # - # Create new instances with PrototypeHelper#update_page or with - # ActionController::Base#render, then call +insert_html+, +replace_html+, - # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in - # methods on the yielded generator in any order you like to modify the - # content and appearance of the current page. + # Create new instances with PrototypeHelper#update_page or with + # ActionController::Base#render, then call +insert_html+, +replace_html+, + # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in + # methods on the yielded generator in any order you like to modify the + # content and appearance of the current page. # # Example: # # # Generates: - # # new Insertion.Bottom("list", "<li>Some item</li>"); + # # new Element.insert("list", { bottom: <li>Some item</li>" }); # # new Effect.Highlight("list"); # # ["status-indicator", "cancel-link"].each(Element.hide); # update_page do |page| @@ -614,12 +632,12 @@ module ActionView # page.visual_effect :highlight, 'list' # page.hide 'status-indicator', 'cancel-link' # end - # + # # # Helper methods can be used in conjunction with JavaScriptGenerator. - # When a helper method is called inside an update block on the +page+ + # When a helper method is called inside an update block on the +page+ # object, that method will also have access to a +page+ object. - # + # # Example: # # module ApplicationHelper @@ -655,7 +673,7 @@ module ActionView # end # end # - # You can also use PrototypeHelper#update_page_tag instead of + # You can also use PrototypeHelper#update_page_tag instead of # PrototypeHelper#update_page to wrap the generated JavaScript in a # <script> tag. module GeneratorMethods @@ -668,7 +686,7 @@ module ActionView end end end - + # Returns a element reference by finding it through +id+ in the DOM. This element can then be # used for further method calls. Examples: # @@ -689,31 +707,31 @@ module ActionView JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id)) end end - - # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript + + # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript # expression as an argument to another JavaScriptGenerator method. def literal(code) ActiveSupport::JSON::Variable.new(code.to_s) end - + # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be # used for further method calls. Examples: # # page.select('p') # => $$('p'); # page.select('p.welcome b').first # => $$('p.welcome b').first(); # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); - # + # # You can also use prototype enumerations with the collection. Observe: - # + # # # Generates: $$('#items li').each(function(value) { value.hide(); }); # page.select('#items li').each do |value| # value.hide - # end + # end # - # Though you can call the block param anything you want, they are always rendered in the + # Though you can call the block param anything you want, they are always rendered in the # javascript as 'value, index.' Other enumerations, like collect() return the last statement: # - # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); }); + # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); }); # page.select('#items li').collect('hidden') do |item| # item.hide # end @@ -721,13 +739,13 @@ module ActionView def select(pattern) JavaScriptElementCollectionProxy.new(self, pattern) end - + # Inserts HTML at the specified +position+ relative to the DOM element # identified by the given +id+. - # + # # +position+ may be one of: - # - # <tt>:top</tt>:: HTML is inserted inside the element, before the + # + # <tt>:top</tt>:: HTML is inserted inside the element, before the # element's existing content. # <tt>:bottom</tt>:: HTML is inserted inside the element, after the # element's existing content. @@ -739,18 +757,18 @@ module ActionView # # # Insert the rendered 'navigation' partial just before the DOM # # element with ID 'content'. - # # Generates: new Insertion.Before("content", "-- Contents of 'navigation' partial --"); + # # Generates: Element.insert("content", { before: "-- Contents of 'navigation' partial --" }); # page.insert_html :before, 'content', :partial => 'navigation' # # # Add a list item to the bottom of the <ul> with ID 'list'. - # # Generates: new Insertion.Bottom("list", "<li>Last item</li>"); + # # Generates: Element.insert("list", { bottom: "<li>Last item</li>" }); # page.insert_html :bottom, 'list', '<li>Last item</li>' # def insert_html(position, id, *options_for_render) - insertion = position.to_s.camelize - call "new Insertion.#{insertion}", id, render(*options_for_render) + content = javascript_object_for(render(*options_for_render)) + record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });" end - + # Replaces the inner HTML of the DOM element with the given +id+. # # +options_for_render+ may be either a string of HTML to insert, or a hash @@ -764,7 +782,7 @@ module ActionView def replace_html(id, *options_for_render) call 'Element.update', id, render(*options_for_render) end - + # Replaces the "outer HTML" (i.e., the entire element, not just its # contents) of the DOM element with the given +id+. # @@ -786,7 +804,7 @@ module ActionView # </div> # # # Insert a new person - # # + # # # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, ""); # page.insert_html :bottom, :partial => 'person', :object => @person # @@ -798,7 +816,7 @@ module ActionView def replace(id, *options_for_render) call 'Element.replace', id, render(*options_for_render) end - + # Removes the DOM elements with the given +ids+ from the page. # # Example: @@ -810,9 +828,9 @@ module ActionView def remove(*ids) loop_on_multiple_args 'Element.remove', ids end - + # Shows hidden DOM elements with the given +ids+. - # + # # Example: # # # Show a few people @@ -822,7 +840,7 @@ module ActionView def show(*ids) loop_on_multiple_args 'Element.show', ids end - + # Hides the visible DOM elements with the given +ids+. # # Example: @@ -832,9 +850,9 @@ module ActionView # page.hide 'person_29', 'person_9', 'person_0' # def hide(*ids) - loop_on_multiple_args 'Element.hide', ids + loop_on_multiple_args 'Element.hide', ids end - + # Toggles the visibility of the DOM elements with the given +ids+. # Example: # @@ -844,9 +862,9 @@ module ActionView # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements # def toggle(*ids) - loop_on_multiple_args 'Element.toggle', ids + loop_on_multiple_args 'Element.toggle', ids end - + # Displays an alert dialog with the given +message+. # # Example: @@ -856,21 +874,21 @@ module ActionView def alert(message) call 'alert', message end - + # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+. # # Examples: # # # Generates: window.location.href = "/mycontroller"; # page.redirect_to(:action => 'index') - # + # # # Generates: window.location.href = "/account/signup"; # page.redirect_to(:controller => 'account', :action => 'signup') def redirect_to(location) url = location.is_a?(String) ? location : @context.url_for(location) record "window.location.href = #{url.inspect}" end - + # Reloads the browser's current +location+ using JavaScript # # Examples: @@ -884,17 +902,17 @@ module ActionView # Calls the JavaScript +function+, optionally with the given +arguments+. # # If a block is given, the block will be passed to a new JavaScriptGenerator; - # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt> + # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt> # and passed as the called function's final argument. - # + # # Examples: # # # Generates: Element.replace(my_element, "My content to replace with.") # page.call 'Element.replace', 'my_element', "My content to replace with." - # + # # # Generates: alert('My message!') # page.call 'alert', 'My message!' - # + # # # Generates: # # my_method(function() { # # $("one").show(); @@ -907,7 +925,7 @@ module ActionView def call(function, *arguments, &block) record "#{function}(#{arguments_for_call(arguments, block)})" end - + # Assigns the JavaScript +variable+ the given +value+. # # Examples: @@ -918,13 +936,13 @@ module ActionView # # Generates: record_count = 33; # page.assign 'record_count', 33 # - # # Generates: tabulated_total = 47 + # # Generates: tabulated_total = 47 # page.assign 'tabulated_total', @total_from_cart # def assign(variable, value) record "#{variable} = #{javascript_object_for(value)}" end - + # Writes raw JavaScript to the page. # # Example: @@ -933,10 +951,10 @@ module ActionView def <<(javascript) @lines << javascript end - + # Executes the content of the block after a delay of +seconds+. Example: # - # # Generates: + # # Generates: # # setTimeout(function() { # # ; # # new Effect.Fade("notice",{}); @@ -949,13 +967,13 @@ module ActionView yield record "}, #{(seconds * 1000).to_i})" end - - # Starts a script.aculo.us visual effect. See + + # Starts a script.aculo.us visual effect. See # ActionView::Helpers::ScriptaculousHelper for more information. def visual_effect(name, id = nil, options = {}) record @context.send(:visual_effect, name, id, options) end - + # Creates a script.aculo.us sortable element. Useful # to recreate sortable elements after items get added # or deleted. @@ -963,66 +981,66 @@ module ActionView def sortable(id, options = {}) record @context.send(:sortable_element_js, id, options) end - + # Creates a script.aculo.us draggable element. # See ActionView::Helpers::ScriptaculousHelper for more information. def draggable(id, options = {}) record @context.send(:draggable_element_js, id, options) end - + # Creates a script.aculo.us drop receiving element. # See ActionView::Helpers::ScriptaculousHelper for more information. def drop_receiving(id, options = {}) record @context.send(:drop_receiving_element_js, id, options) end - + private def loop_on_multiple_args(method, ids) - record(ids.size>1 ? - "#{javascript_object_for(ids)}.each(#{method})" : + record(ids.size>1 ? + "#{javascript_object_for(ids)}.each(#{method})" : "#{method}(#{ids.first.to_json})") end - + def page self end - + def record(line) returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do self << line end end - + def render(*options_for_render) old_format = @context && @context.template_format @context.template_format = :html if @context - Hash === options_for_render.first ? - @context.render(*options_for_render) : + Hash === options_for_render.first ? + @context.render(*options_for_render) : options_for_render.first.to_s ensure @context.template_format = old_format if @context end - + def javascript_object_for(object) object.respond_to?(:to_json) ? object.to_json : object.inspect end - + def arguments_for_call(arguments, block = nil) arguments << block_to_function(block) if block arguments.map { |argument| javascript_object_for(argument) }.join ', ' end - + def block_to_function(block) generator = self.class.new(@context, &block) literal("function() { #{generator.to_s} }") - end + end def method_missing(method, *arguments) JavaScriptProxy.new(self, method.to_s.camelize) end end end - + # Yields a JavaScriptGenerator and returns the generated JavaScript code. # Use this to update multiple elements on a page in an Ajax response. # See JavaScriptGenerator for more information. @@ -1035,13 +1053,13 @@ module ActionView def update_page(&block) JavaScriptGenerator.new(@template, &block).to_s end - + # Works like update_page but wraps the generated JavaScript in a <script> # tag. Use this to include generated JavaScript in an ERb template. # See JavaScriptGenerator for more information. # # +html_options+ may be a hash of <script> attributes to be passed - # to ActionView::Helpers::JavaScriptHelper#javascript_tag. + # to ActionView::Helpers::JavaScriptHelper#javascript_tag. def update_page_tag(html_options = {}, &block) javascript_tag update_page(&block), html_options end @@ -1049,10 +1067,10 @@ module ActionView protected def options_for_ajax(options) js_options = build_callbacks(options) - + js_options['asynchronous'] = options[:type] != :synchronous js_options['method'] = method_option_to_s(options[:method]) if options[:method] - js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position] + js_options['insertion'] = options[:position].to_s.downcase if options[:position] js_options['evalScripts'] = options[:script].nil? || options[:script] if options[:form] @@ -1062,7 +1080,7 @@ module ActionView elsif options[:with] js_options['parameters'] = options[:with] end - + if protect_against_forgery? && !options[:form] if js_options['parameters'] js_options['parameters'] << " + '&" @@ -1071,14 +1089,14 @@ module ActionView end js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')" end - + options_for_javascript(js_options) end - def method_option_to_s(method) + def method_option_to_s(method) (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'" end - + def build_observer(klass, name, options = {}) if options[:with] && (options[:with] !~ /[\{=(.]/) options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)" @@ -1095,7 +1113,7 @@ module ActionView javascript << ")" javascript_tag(javascript) end - + def build_callbacks(options) callbacks = {} options.each do |callback, code| @@ -1108,7 +1126,7 @@ module ActionView end end - # Converts chained method calls on DOM proxy elements into JavaScript chains + # Converts chained method calls on DOM proxy elements into JavaScript chains class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc: def initialize(generator, root = nil) @@ -1124,7 +1142,7 @@ module ActionView call("#{method.to_s.camelize(:lower)}", *arguments, &block) end end - + def call(function, *arguments, &block) append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})") self @@ -1133,23 +1151,23 @@ module ActionView def assign(variable, value) append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}") end - + def function_chain @function_chain ||= @generator.instance_variable_get(:@lines) end - + def append_to_function_chain!(call) function_chain[-1].chomp!(';') function_chain[-1] += ".#{call};" end end - + class JavaScriptElementProxy < JavaScriptProxy #:nodoc: def initialize(generator, id) @id = id super(generator, "$(#{id.to_json})") end - + # Allows access of element attributes through +attribute+. Examples: # # page['foo']['style'] # => $('foo').style; @@ -1160,11 +1178,11 @@ module ActionView append_to_function_chain!(attribute) self end - + def []=(variable, value) assign(variable, value) end - + def replace_html(*options_for_render) call 'update', @generator.send(:render, *options_for_render) end @@ -1172,11 +1190,11 @@ module ActionView def replace(*options_for_render) call 'replace', @generator.send(:render, *options_for_render) end - + def reload(options_for_replace = {}) replace(options_for_replace.merge({ :partial => @id.to_s })) end - + end class JavaScriptVariableProxy < JavaScriptProxy #:nodoc: @@ -1195,7 +1213,7 @@ module ActionView def to_json(options = nil) @variable end - + private def append_to_function_chain!(call) @generator << @variable if @empty @@ -1213,7 +1231,7 @@ module ActionView def initialize(generator, pattern) super(generator, @pattern = pattern) end - + def each_slice(variable, number, &block) if block enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block @@ -1222,18 +1240,18 @@ module ActionView append_enumerable_function!("eachSlice(#{number.to_json});") end end - + def grep(variable, pattern, &block) enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block end - + def in_groups_of(variable, number, fill_with = nil) arguments = [number] arguments << fill_with unless fill_with.nil? add_variable_assignment!(variable) append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});") - end - + end + def inject(variable, memo, &block) enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block end @@ -1295,13 +1313,13 @@ module ActionView function_chain.push("return #{function_chain.pop.chomp(';')};") end end - + def append_enumerable_function!(call) function_chain[-1].chomp!(';') function_chain[-1] += ".#{call}" end end - + class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\ def initialize(generator, pattern) super(generator, "$$(#{pattern.to_json})") diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index b0dacfe964..637caf203b 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -6,17 +6,13 @@ module ActionView # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend ActionView making them callable within your template files. module SanitizeHelper - def self.included(base) - base.extend(ClassMethods) - end - # This +sanitize+ helper will html encode all tags and strip all attributes that aren't specifically allowed. # It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out # the extensive test suite. # # <%= sanitize @article.body %> - # + # # You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the # available options. You can add tags/attributes for single uses of +sanitize+ by passing either the <tt>:attributes</tt> or <tt>:tags</tt> options: # @@ -27,27 +23,27 @@ module ActionView # Custom Use (only the mentioned tags and attributes are allowed, nothing else) # # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) - # + # # Add table tags to the default allowed tags - # + # # Rails::Initializer.run do |config| # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end - # + # # Remove tags to the default allowed tags - # + # # Rails::Initializer.run do |config| # config.after_initialize do # ActionView::Base.sanitized_allowed_tags.delete 'div' # end # end - # + # # Change allowed default attributes - # + # # Rails::Initializer.run do |config| # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' # end - # + # # Please note that sanitizing user-provided text does not guarantee that the # resulting markup is valid (conforming to a document type) or even well-formed. # The output may still contain e.g. unescaped '<', '>', '&' characters and @@ -62,8 +58,8 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from the +html+, including comments. This uses the - # html-scanner tokenizer and so its HTML parsing ability is limited by + # Strips all HTML tags from the +html+, including comments. This uses the + # html-scanner tokenizer and so its HTML parsing ability is limited by # that of html-scanner. # # ==== Examples @@ -73,10 +69,10 @@ module ActionView # # strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...") # # => Bold no more! See more here... - # + # # strip_tags("<div id='top-bar'>Welcome to my website!</div>") # # => Welcome to my website! - def strip_tags(html) + def strip_tags(html) self.class.full_sanitizer.sanitize(html) end @@ -96,21 +92,48 @@ module ActionView end module ClassMethods #:nodoc: - def self.extended(base) - class << base - attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer - - # we want these to be class methods on ActionView::Base, they'll get mattr_readers for these below. - helper_def = [:sanitized_protocol_separator, :sanitized_uri_attributes, :sanitized_bad_tags, :sanitized_allowed_tags, - :sanitized_allowed_attributes, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords, - :sanitized_shorthand_css_properties, :sanitized_allowed_protocols, :sanitized_protocol_separator=].collect! do |prop| - prop = prop.to_s - "def #{prop}(#{:value if prop =~ /=$/}) white_list_sanitizer.#{prop.sub /sanitized_/, ''} #{:value if prop =~ /=$/} end" - end.join("\n") - eval helper_def - end - end - + attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer + + def sanitized_protocol_separator + white_list_sanitizer.protocol_separator + end + + def sanitized_uri_attributes + white_list_sanitizer.uri_attributes + end + + def sanitized_bad_tags + white_list_sanitizer.bad_tags + end + + def sanitized_allowed_tags + white_list_sanitizer.allowed_tags + end + + def sanitized_allowed_attributes + white_list_sanitizer.allowed_attributes + end + + def sanitized_allowed_css_properties + white_list_sanitizer.allowed_css_properties + end + + def sanitized_allowed_css_keywords + white_list_sanitizer.allowed_css_keywords + end + + def sanitized_shorthand_css_properties + white_list_sanitizer.shorthand_css_properties + end + + def sanitized_allowed_protocols + white_list_sanitizer.allowed_protocols + end + + def sanitized_protocol_separator=(value) + white_list_sanitizer.protocol_separator = value + end + # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # @@ -184,7 +207,7 @@ module ActionView HTML::WhiteListSanitizer.allowed_attributes.merge(attributes) end - # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ heleprs. + # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. # # Rails::Initializer.run do |config| # config.action_view.sanitized_allowed_css_properties = 'expression' diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb index c9b2761cb8..b938c1a801 100644 --- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb +++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb @@ -193,7 +193,7 @@ module ActionView # # * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto # this element. Override this callback with a JavaScript expression to - # change the default drop behavour. Example: + # change the default drop behaviour. Example: # # :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }" # diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index aeafd3906d..de08672d2d 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -64,7 +64,7 @@ module ActionView # <% content_tag :div, :class => "strong" do -%> # Hello world! # <% end -%> - # # => <div class="strong"><p>Hello world!</p></div> + # # => <div class="strong">Hello world!</div> def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) @@ -110,12 +110,18 @@ module ActionView private BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - # Check whether we're called from an erb template. - # We'd return a string in any other case, but erb <%= ... %> - # can't take an <% end %> later on, so we have to use <% ... %> - # and implicitly concat. - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block) + if RUBY_VERSION < '1.9.0' + # Check whether we're called from an erb template. + # We'd return a string in any other case, but erb <%= ... %> + # can't take an <% end %> later on, so we have to use <% ... %> + # and implicitly concat. + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block) + end + else + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block.binding) + end end def content_tag_string(name, content, options, escape = true) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 3e3452b615..f9096d0029 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -34,40 +34,69 @@ module ActionView end if RUBY_VERSION < '1.9' - # If +text+ is longer than +length+, +text+ will be truncated to the length of - # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+ - # (defaults to "..."). + # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> + # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "..."). # # ==== Examples - # truncate("Once upon a time in a world far far away", 14) - # # => Once upon a... # # truncate("Once upon a time in a world far far away") # # => Once upon a time in a world f... # - # truncate("And they found that many people were sleeping better.", 25, "(clipped)") + # truncate("Once upon a time in a world far far away", :length => 14) + # # => Once upon a... + # + # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)") # # => And they found that many (clipped) # + # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) + # # => And they found... (continued) + # + # You can still use <tt>truncate</tt> with the old API that accepts the + # +length+ as its optional second and the +ellipsis+ as its + # optional third parameter: + # truncate("Once upon a time in a world far far away", 14) + # # => Once upon a time in a world f... + # # truncate("And they found that many people were sleeping better.", 15, "... (continued)") # # => And they found... (continued) - def truncate(text, length = 30, truncate_string = "...") + def truncate(text, *args) + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' + + 'length and omission arguments', caller) + + options[:length] = args[0] || 30 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:length => 30, :omission => "...") + if text - l = length - truncate_string.chars.length + l = options[:length] - options[:omission].chars.length chars = text.chars - (chars.length > length ? chars[0...l] + truncate_string : text).to_s + (chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s end end else - def truncate(text, length = 30, truncate_string = "...") #:nodoc: + def truncate(text, *args) #:nodoc: + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' + + 'length and omission arguments', caller) + + options[:length] = args[0] || 30 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:length => 30, :omission => "...") + if text - l = length - truncate_string.length - (text.length > length ? text[0...l] + truncate_string : text).to_s + l = options[:length].to_i - options[:omission].length + (text.length > options[:length].to_i ? text[0...l] + options[:omission] : text).to_s end end end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into - # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+ + # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt> # as a single-quoted string with \1 where the phrase is to be inserted (defaults to # '<strong class="highlight">\1</strong>') # @@ -78,52 +107,75 @@ module ActionView # highlight('You searched for: ruby, rails, dhh', 'actionpack') # # => You searched for: ruby, rails, dhh # - # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>') + # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>') # # => You searched <em>for</em>: <em>rails</em> # - # highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>") - # # => You searched for: <a href='search?q=rails>rails</a> - def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>') + # highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>') + # # => You searched for: <a href="search?q=rails">rails</a> + # + # You can still use <tt>highlight</tt> with the old API that accepts the + # +highlighter+ as its optional third parameter: + # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a> + def highlight(text, phrases, *args) + options = args.extract_options! + unless args.empty? + options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>' + end + options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>') + if text.blank? || phrases.blank? text else match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})/i, highlighter) + text.gsub(/(#{match})/i, options[:highlighter]) end end if RUBY_VERSION < '1.9' # Extracts an excerpt from +text+ that matches the first instance of +phrase+. - # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters - # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, - # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case. - # If the +phrase+ isn't found, nil is returned. + # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters + # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, + # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string + # will be stripped in any case. If the +phrase+ isn't found, nil is returned. # # ==== Examples - # excerpt('This is an example', 'an', 5) - # # => "...s is an exam..." + # excerpt('This is an example', 'an', :radius => 5) + # # => ...s is an exam... # - # excerpt('This is an example', 'is', 5) - # # => "This is a..." + # excerpt('This is an example', 'is', :radius => 5) + # # => This is a... # # excerpt('This is an example', 'is') - # # => "This is an example" + # # => This is an example # - # excerpt('This next thing is an example', 'ex', 2) - # # => "...next..." + # excerpt('This next thing is an example', 'ex', :radius => 2) + # # => ...next... # - # excerpt('This is also an example', 'an', 8, '<chop> ') - # # => "<chop> is also an example" - def excerpt(text, phrase, radius = 100, excerpt_string = "...") + # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ') + # # => <chop> is also an example + # + # You can still use <tt>excerpt</tt> with the old API that accepts the + # +radius+ as its optional third and the +ellipsis+ as its + # optional forth parameter: + # excerpt('This is an example', 'an', 5) # => ...s is an exam... + # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example + def excerpt(text, phrase, *args) + options = args.extract_options! + unless args.empty? + options[:radius] = args[0] || 100 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:radius => 100, :omission => "...") + if text && phrase phrase = Regexp.escape(phrase) if found_pos = text.chars =~ /(#{phrase})/i - start_pos = [ found_pos - radius, 0 ].max - end_pos = [ [ found_pos + phrase.chars.length + radius - 1, 0].max, text.chars.length ].min + start_pos = [ found_pos - options[:radius], 0 ].max + end_pos = [ [ found_pos + phrase.chars.length + options[:radius] - 1, 0].max, text.chars.length ].min - prefix = start_pos > 0 ? excerpt_string : "" - postfix = end_pos < text.chars.length - 1 ? excerpt_string : "" + prefix = start_pos > 0 ? options[:omission] : "" + postfix = end_pos < text.chars.length - 1 ? options[:omission] : "" prefix + text.chars[start_pos..end_pos].strip + postfix else @@ -132,16 +184,23 @@ module ActionView end end else - def excerpt(text, phrase, radius = 100, excerpt_string = "...") #:nodoc: + def excerpt(text, phrase, *args) #:nodoc: + options = args.extract_options! + unless args.empty? + options[:radius] = args[0] || 100 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:radius => 100, :omission => "...") + if text && phrase phrase = Regexp.escape(phrase) if found_pos = text =~ /(#{phrase})/i - start_pos = [ found_pos - radius, 0 ].max - end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min + start_pos = [ found_pos - options[:radius], 0 ].max + end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min - prefix = start_pos > 0 ? excerpt_string : "" - postfix = end_pos < text.length - 1 ? excerpt_string : "" + prefix = start_pos > 0 ? options[:omission] : "" + postfix = end_pos < text.length - 1 ? options[:omission] : "" prefix + text[start_pos..end_pos].strip + postfix else @@ -176,20 +235,31 @@ module ActionView # (which is 80 by default). # # ==== Examples - # word_wrap('Once upon a time', 4) - # # => Once\nupon\na\ntime - # - # word_wrap('Once upon a time', 8) - # # => Once upon\na time # # word_wrap('Once upon a time') # # => Once upon a time # - # word_wrap('Once upon a time', 1) + # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') + # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined... + # + # word_wrap('Once upon a time', :line_width => 8) + # # => Once upon\na time + # + # word_wrap('Once upon a time', :line_width => 1) # # => Once\nupon\na\ntime - def word_wrap(text, line_width = 80) + # + # You can still use <tt>word_wrap</tt> with the old API that accepts the + # +line_width+ as its optional second parameter: + # word_wrap('Once upon a time', 8) # => Once upon\na time + def word_wrap(text, *args) + options = args.extract_options! + unless args.blank? + options[:line_width] = args[0] || 80 + end + options.reverse_merge!(:line_width => 80) + text.split("\n").collect do |line| - line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line + line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line end * "\n" end @@ -336,12 +406,32 @@ module ActionView # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." # - def auto_link(text, link = :all, href_options = {}, &block) + # + # You can still use <tt>auto_link</tt> with the old API that accepts the + # +link+ as its optional second parameter and the +html_options+ hash + # as its optional third parameter: + # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." + # auto_link(post_body, :urls) # => Once upon\na time + # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>. + # Please e-mail me at me@email.com." + # + # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time + # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. + # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." + def auto_link(text, *args, &block)#link = :all, href_options = {}, &block) return '' if text.blank? - case link - when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block) - when :email_addresses then auto_link_email_addresses(text, &block) - when :urls then auto_link_urls(text, href_options, &block) + + options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter + unless args.empty? + options[:link] = args[0] || :all + options[:html] = args[1] || {} + end + 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 :urls then auto_link_urls(text, options[:html], &block) end end @@ -468,7 +558,7 @@ module ActionView [-\w]+ # subdomain or domain (?:\.[-\w]+)* # remaining subdomains or domain (?::\d+)? # port - (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path + (?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path (?:\?[\w\+@%&=.;-]+)? # query string (?:\#[\w\-]*)? # trailing anchor ) @@ -477,8 +567,8 @@ module ActionView # 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, href_options = {}) - extra_options = tag_options(href_options.stringify_keys) || "" + def auto_link_urls(text, html_options = {}) + extra_options = tag_options(html_options.stringify_keys) || "" text.gsub(AUTO_LINK_RE) do all, a, b, c, d = $&, $1, $2, $3, $4 if a =~ /<a\s/i # don't replace URL's that are already linked @@ -508,4 +598,4 @@ module ActionView end end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb new file mode 100644 index 0000000000..de4c1d7689 --- /dev/null +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -0,0 +1,22 @@ +require 'action_view/helpers/tag_helper' + +module ActionView + module Helpers + module TranslationHelper + def translate(*args) + args << args.extract_options!.merge(:raise => true) + I18n.translate *args + + rescue I18n::MissingTranslationData => e + keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope] + content_tag('span', keys.join(', '), :class => 'translation_missing') + end + alias :t :translate + + def localize(*args) + I18n.localize *args + end + alias :l :localize + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index e5178938fd..7ba42a3b72 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -3,8 +3,8 @@ require 'action_view/helpers/javascript_helper' module ActionView module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that - # depend on the routing subsystem (see ActionController::Routing). - # This allows you to use the same format for links in views + # depend on the routing subsystem (see ActionController::Routing). + # This allows you to use the same format for links in views # and controllers. module UrlHelper include JavaScriptHelper @@ -33,8 +33,8 @@ module ActionView # # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter, # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing - # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as - # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route). + # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as + # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route). # # ==== Examples # <%= url_for(:action => 'index') %> @@ -62,19 +62,33 @@ module ActionView # <%= url_for(@workshop) %> # # calls @workshop.to_s # # => /workshops/5 + # + # <%= url_for("http://www.example.com") %> + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is not set or is blank + # # => javascript:history.back() def url_for(options = {}) options ||= {} - case options + url = case options + when String + escape = true + options when Hash options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) escape = options.key?(:escape) ? options.delete(:escape) : true - url = @controller.send(:url_for, options) - when String - escape = true - url = options + @controller.send(:url_for, options) + when :back + escape = false + @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' else escape = false - url = polymorphic_path(options) + polymorphic_path(options) end escape ? escape_once(url) : url @@ -116,8 +130,8 @@ module ActionView # # Note that if the user has JavaScript disabled, the request will fall back # to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript disabled - # clicking the link will have no effect. If you are relying on the POST - # behavior, your should check for it in your controller's action by using the + # clicking the link will have no effect. If you are relying on the POST + # behavior, your should check for it in your controller's action by using the # request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>. # # You can mix and match the +html_options+ with the exception of @@ -141,8 +155,8 @@ module ActionView # # link_to "Profile", :controller => "profiles", :action => "show", :id => @profile # # => <a href="/profiles/show/1">Profile</a> - # - # Similarly, + # + # Similarly, # # link_to "Profiles", profiles_path # # => <a href="/profiles">Profiles</a> @@ -185,7 +199,7 @@ module ActionView # link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux") # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a> # - # The three options specfic to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows: + # The three options specific to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows: # # link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?" # # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a> @@ -197,9 +211,9 @@ module ActionView # # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a> # # link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete - # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); + # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); # f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; - # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); + # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); # m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a> def link_to(*args, &block) if block_given? @@ -211,14 +225,7 @@ module ActionView options = args.second || {} html_options = args.third - url = case options - when String - options - when :back - @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' - else - self.url_for(options) - end + url = url_for(options) if html_options html_options = html_options.stringify_keys @@ -228,7 +235,7 @@ module ActionView else tag_options = nil end - + href_attr = "href=\"#{url}\"" unless href "<a #{href_attr}#{tag_options}>#{name || url}</a>" end @@ -260,7 +267,7 @@ module ActionView # * <tt>:confirm</tt> - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the link is # processed normally, otherwise no action is taken. - # + # # ==== Examples # <%= button_to "New", :action => "new" %> # # => "<form method="post" action="/controller/new" class="button-to"> @@ -286,12 +293,12 @@ module ActionView end form_method = method.to_s == 'get' ? 'get' : 'post' - + request_token_tag = '' if form_method == 'post' && protect_against_forgery? request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) end - + if confirm = html_options.delete("confirm") html_options["onclick"] = "return #{confirm_javascript_function(confirm)};" end @@ -309,7 +316,7 @@ module ActionView # Creates a link tag of the given +name+ using a URL created by the set of # +options+ unless the current request URI is the same as the links, in # which case only the name is returned (or the given block is yielded, if - # one exists). You can give link_to_unless_current a block which will + # one exists). You can give link_to_unless_current a block which will # specialize the default behavior (e.g., show a "Start Here" link rather # than the link's text). # @@ -336,13 +343,13 @@ module ActionView # </ul> # # The implicit block given to link_to_unless_current is evaluated if the current - # action is the action given. So, if we had a comments page and wanted to render a + # action is the action given. So, if we had a comments page and wanted to render a # "Go Back" link instead of a link to the comments page, we could do something like this... - # - # <%= + # + # <%= # link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do - # link_to("Go back", { :controller => 'posts', :action => 'index' }) - # end + # link_to("Go back", { :controller => 'posts', :action => 'index' }) + # end # %> def link_to_unless_current(name, options = {}, html_options = {}, &block) link_to_unless current_page?(options), name, options, html_options, &block @@ -359,10 +366,10 @@ module ActionView # # If the user is logged in... # # => <a href="/controller/reply/">Reply</a> # - # <%= + # <%= # link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name| # link_to(name, { :controller => "accounts", :action => "signup" }) - # end + # end # %> # # If the user is logged in... # # => <a href="/controller/reply/">Reply</a> @@ -391,10 +398,10 @@ module ActionView # # If the user isn't logged in... # # => <a href="/sessions/new/">Login</a> # - # <%= + # <%= # link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do # link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user }) - # end + # end # %> # # If the user isn't logged in... # # => <a href="/sessions/new/">Login</a> @@ -431,20 +438,20 @@ module ActionView # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email. # # ==== Examples - # mail_to "me@domain.com" + # mail_to "me@domain.com" # # => <a href="mailto:me@domain.com">me@domain.com</a> # - # mail_to "me@domain.com", "My email", :encode => "javascript" - # # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script> + # mail_to "me@domain.com", "My email", :encode => "javascript" + # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script> # - # mail_to "me@domain.com", "My email", :encode => "hex" + # mail_to "me@domain.com", "My email", :encode => "hex" # # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a> # - # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" + # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a> # # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", - # :subject => "This is an example email" + # :subject => "This is an example email" # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a> def mail_to(email_address, name = nil, html_options = {}) html_options = html_options.stringify_keys @@ -469,7 +476,7 @@ module ActionView "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| string << sprintf("%%%x", c) end - "<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>" + "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>" elsif encode == "hex" email_address_encoded = '' email_address_obfuscated.each_byte do |c| diff --git a/actionpack/lib/action_view/locale/en-US.yml b/actionpack/lib/action_view/locale/en-US.yml new file mode 100644 index 0000000000..818f2f93bd --- /dev/null +++ b/actionpack/lib/action_view/locale/en-US.yml @@ -0,0 +1,91 @@ +"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/partials.rb b/actionpack/lib/action_view/partials.rb index 5aa4c83009..443c49b870 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -1,14 +1,15 @@ module ActionView - # There's also a convenience method for rendering sub templates within the current controller that depends on a single object - # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being - # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own. + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. # # In a template for Advertiser#account: # # <%= render :partial => "account" %> # - # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +account+ to - # the template for display. + # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable + # +account+ to the template for display. # # In another template for Advertiser#buy, we could have: # @@ -18,24 +19,24 @@ module ActionView # <%= render :partial => "ad", :locals => { :ad => ad } %> # <% end %> # - # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then render - # "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. + # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then + # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. # # == Rendering a collection of partials # - # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub - # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders - # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten - # with a single line: + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: # # <%= render :partial => "ad", :collection => @advertisements %> # - # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An iteration counter - # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the - # example above, the template would be fed +ad_counter+. + # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An + # iteration counter will automatically be made available to the template with a name of the form + # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. # - # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects, - # like Active Records, in there. + # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also + # just keep domain objects, like Active Records, in there. # # == Rendering shared partials # @@ -47,8 +48,9 @@ module ActionView # # == Rendering partials with layouts # - # Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally - # for the entire action, but they work in a similar fashion. Imagine a list with two types of users: + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: # # <%# app/views/users/index.html.erb &> # Here's the administrator: @@ -68,7 +70,7 @@ module ActionView # # <%# app/views/users/_editor.html.erb &> # <div id="editor"> - # Deadline: $<%= user.deadline %> + # Deadline: <%= user.deadline %> # <%= yield %> # </div> # @@ -82,7 +84,7 @@ module ActionView # # Here's the editor: # <div id="editor"> - # Deadline: $<%= user.deadline %> + # Deadline: <%= user.deadline %> # Name: <%= user.name %> # </div> # @@ -101,42 +103,83 @@ module ActionView # </div> # # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. + # + # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass + # an array to layout and treat it as an enumerable. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # Budget: $<%= user.budget %> + # <%= yield user %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <% render :layout => @users do |user| %> + # Title: <%= user.title %> + # <% end %> + # + # This will render the layout for each user and yield to the block, passing the user, each time. + # + # You can also yield multiple times in one layout and use block arguments to differentiate the sections. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # <%= yield user, :header %> + # Budget: $<%= user.budget %> + # <%= yield user, :footer %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <% render :layout => @users do |user, section| %> + # <%- case section when :header -%> + # Title: <%= user.title %> + # <%- when :footer -%> + # Deadline: <%= user.deadline %> + # <%- end -%> + # <% end %> module Partials + extend ActiveSupport::Memoizable + private - def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc: - local_assigns ||= {} + def render_partial(options = {}) #:nodoc: + local_assigns = options[:locals] || {} - case partial_path + case partial_path = options[:partial] when String, Symbol, NilClass - pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns) + if options.has_key?(:collection) + render_partial_collection(options) + else + _pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns) + end when ActionView::Helpers::FormBuilder builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '') - render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path)) + local_assigns.merge!(builder_partial_path.to_sym => partial_path) + render_partial(:partial => builder_partial_path, :object => options[:object], :locals => local_assigns) when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - if partial_path.any? - collection = partial_path - render_partial_collection(nil, collection, nil, local_assigns) - else - "" - end + render_partial_collection(options.except(:partial).merge(:collection => partial_path)) else - render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns) + object = partial_path + render_partial( + :partial => ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path), + :object => object, + :locals => local_assigns + ) end end - def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}, as = nil) #:nodoc: - return " " if collection.empty? + def render_partial_collection(options = {}) #:nodoc: + return nil if options[:collection].blank? - local_assigns = local_assigns ? local_assigns.clone : {} - spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : '' - _paths = {} - _templates = {} + partial = options[:partial] + spacer = options[:spacer_template] ? render(:partial => options[:spacer_template]) : '' + local_assigns = options[:locals] ? options[:locals].clone : {} + as = options[:as] index = 0 - collection.map do |object| - _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) - path = _paths[_partial_path] ||= find_partial_path(_partial_path) - template = _templates[path] ||= pick_template(path) + options[:collection].map do |object| + _partial_path ||= partial || + ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) + template = _pick_partial_template(_partial_path) local_assigns[template.counter_name] = index result = template.render_partial(self, object, local_assigns, as) index += 1 @@ -144,14 +187,17 @@ module ActionView end.join(spacer) end - def find_partial_path(partial_path) + def _pick_partial_template(partial_path) #:nodoc: if partial_path.include?('/') - "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}" - elsif respond_to?(:controller) - "#{controller.class.controller_path}/_#{partial_path}" + path = File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}") + elsif controller + path = "#{controller.class.controller_path}/_#{partial_path}" else - "_#{partial_path}" + path = "_#{partial_path}" end + + pick_template(path) end + memoize :_pick_partial_template end end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index b0ab7d0c67..d6bf2137af 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -3,7 +3,7 @@ module ActionView #:nodoc: def self.type_cast(obj) if obj.is_a?(String) if Base.warn_cache_misses && defined?(Rails) && Rails.initialized? - Rails.logger.debug "[PERFORMANCE] Processing view path during a " + + Base.logger.debug "[PERFORMANCE] Processing view path during a " + "request. This an expense disk operation that should be done at " + "boot. You can manually process this view path with " + "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " + @@ -15,70 +15,104 @@ module ActionView #:nodoc: end end + def initialize(*args) + super(*args).map! { |obj| self.class.type_cast(obj) } + end + + def <<(obj) + super(self.class.type_cast(obj)) + end + + def concat(array) + super(array.map! { |obj| self.class.type_cast(obj) }) + end + + def insert(index, obj) + super(index, self.class.type_cast(obj)) + end + + def push(*objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def unshift(*objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + 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, :inspect, :to => :path + delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path) + def initialize(path, load = true) + raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze - reload! + reload! if load end def ==(path) to_str == path.to_str end + def eql?(path) + to_str == path.to_str + end + def [](path) + raise "Unloaded view path! #{@path}" unless @loaded @paths[path] end + def loaded? + @loaded ? true : false + end + + def load + reload! unless loaded? + self + end + # Rebuild load path directory cache def reload! @paths = {} templates_in_path do |template| + # 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 @paths.freeze + @loaded = true end private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| unless File.directory?(file) - template = Template.new(file.split("#{self}/").last, self) - # Eager load memoized methods and freeze cached template - template.freeze if Base.cache_template_loading - yield template + yield Template.new(file.split("#{self}/").last, self) end end end end - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } + def load + each { |path| path.load } end def reload! each { |path| path.reload! } end - def <<(obj) - super(self.class.type_cast(obj)) - end - - def push(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - - def unshift(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - def [](template_path) each do |path| if template = path[template_path] @@ -87,10 +121,5 @@ module ActionView #:nodoc: end nil end - - private - def delete_paths!(paths) - paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } } - end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index f66356c939..c011f21550 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -3,11 +3,15 @@ module ActionView # NOTE: The template that this mixin is beening include into is frozen # So you can not set or modify any instance variables + extend ActiveSupport::Memoizable + def self.included(base) @@mutex = Mutex.new end - include ActiveSupport::Memoizable + def filename + 'compiled-template' + end def handler Template.handler_class_for_extension(extension) @@ -15,19 +19,29 @@ module ActionView memoize :handler def compiled_source - handler.new(nil).compile(self) if handler.compilable? + handler.call(self) end memoize :compiled_source def render(view, local_assigns = {}) + compile(local_assigns) + view._first_render ||= self view._last_render = self + view.send(:evaluate_assigns) - compile(local_assigns) if handler.compilable? - handler.new(view).render(self, local_assigns) + view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type) + + view.send(method_name(local_assigns), local_assigns) do |*names| + if proc = view.instance_variable_get("@_proc_for_layout") + view.capture(*names, &proc) + else + view.instance_variable_get("@content_for_#{names.first || 'layout'}") + end + end end - def method(local_assigns) + def method_name(local_assigns) if local_assigns && local_assigns.any? local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" end @@ -35,35 +49,41 @@ module ActionView end private - # Compile and evaluate the template's code + # Compile and evaluate the template's code (if necessary) def compile(local_assigns) - render_symbol = method(local_assigns) + render_symbol = method_name(local_assigns) @@mutex.synchronize do - return false unless recompile?(render_symbol) - - locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join - - source = <<-end_src - def #{render_symbol}(local_assigns) - old_output_buffer = output_buffer;#{locals_code};#{compiled_source} - ensure - self.output_buffer = old_output_buffer - end - end_src - - begin - file_name = respond_to?(:filename) ? filename : 'compiled-template' - ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0) - rescue Exception => e # errors from template code - if logger = ActionController::Base.logger - logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" - logger.debug "Function body: #{source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise ActionView::TemplateError.new(self, {}, e) + if recompile?(render_symbol) + compile!(render_symbol, local_assigns) + end + end + end + + def compile!(render_symbol, local_assigns) + locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join + + source = <<-end_src + def #{render_symbol}(local_assigns) + old_output_buffer = output_buffer;#{locals_code};#{compiled_source} + ensure + self.output_buffer = old_output_buffer end + end_src + + begin + logger = Base.logger + logger.debug "Compiling template #{render_symbol}" if logger + + ActionView::Base::CompiledTemplates.module_eval(source, filename, 0) + rescue Exception => e # errors from template code + if logger + logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" + logger.debug "Function body: #{source}" + logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + raise ActionView::TemplateError.new(self, {}, e) end end @@ -71,8 +91,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) - meth = Base::CompiledTemplates.instance_method(template.method) rescue nil - !(meth && Base.cache_template_loading) + !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) end end end diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb index fdb1a5e6a7..342850f0f0 100644 --- a/actionpack/lib/action_view/renderable_partial.rb +++ b/actionpack/lib/action_view/renderable_partial.rb @@ -3,7 +3,7 @@ module ActionView # NOTE: The template that this mixin is beening include into is frozen # So you can not set or modify any instance variables - include ActiveSupport::Memoizable + extend ActiveSupport::Memoizable def variable_name name.sub(/\A_/, '').to_sym diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 304aec3a4c..5dc6708431 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,7 +1,7 @@ module ActionView #:nodoc: class Template extend TemplateHandlers - include ActiveSupport::Memoizable + extend ActiveSupport::Memoizable include Renderable attr_accessor :filename, :load_path, :base_path, :name, :format, :extension @@ -22,6 +22,19 @@ module ActionView #:nodoc: end memoize :format_and_extension + def multipart? + format && format.include?('.') + end + + def content_type + format.gsub('.', '/') + end + + def mime_type + Mime::Type.lookup_by_extension(format) if format + end + memoize :mime_type + def path [base_path, [name, format, extension].compact.join('.')].compact.join('/') end @@ -70,7 +83,7 @@ module ActionView #:nodoc: load_paths = Array(load_paths) + [nil] load_paths.each do |load_path| file = [load_path, path].compact.join('/') - return load_path, file if File.exist?(file) + return load_path, file if File.file?(file) end raise MissingTemplate.new(load_paths, path) end @@ -79,7 +92,7 @@ module ActionView #:nodoc: # [base_path, name, format, extension] def split(file) if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) - if m[5] # Mulipart formats + if m[5] # Multipart formats [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] elsif m[4] # Single format [m[1], m[2], m[3], m[4]] diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb index 35fc07bdb2..2368662f31 100644 --- a/actionpack/lib/action_view/template_error.rb +++ b/actionpack/lib/action_view/template_error.rb @@ -7,7 +7,7 @@ module ActionView attr_reader :original_exception def initialize(template, assigns, original_exception) - @base_path = template.base_path + @base_path = template.base_path.to_s @assigns, @source, @original_exception = assigns.dup, template.source, original_exception @file_path = template.filename @backtrace = compute_backtrace diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index 1afea21f67..d7e7c9b199 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,25 +1,14 @@ -module ActionView - class TemplateHandler - def self.compilable? - false - end - - def initialize(view) - @view = view - end - - def render(template, local_assigns = {}) - end - - def compile(template) - end +# Legacy TemplateHandler stub - def compilable? - self.class.compilable? +module ActionView + module TemplateHandlers + module Compilable end + end - # Called by CacheHelper#cache - def cache_fragment(block, name = {}, options = nil) + class TemplateHandler + def self.call(template) + new.compile(template) end end end diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb index 1471e99e01..6c8aa4c2a7 100644 --- a/actionpack/lib/action_view/template_handlers.rb +++ b/actionpack/lib/action_view/template_handlers.rb @@ -1,5 +1,4 @@ require 'action_view/template_handler' -require 'action_view/template_handlers/compilable' require 'action_view/template_handlers/builder' require 'action_view/template_handlers/erb' require 'action_view/template_handlers/rjs' diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb index cbe53e11d8..7d24a5c423 100644 --- a/actionpack/lib/action_view/template_handlers/builder.rb +++ b/actionpack/lib/action_view/template_handlers/builder.rb @@ -6,18 +6,12 @@ module ActionView include Compilable def compile(template) - # ActionMailer does not have a response - "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" + + "set_controller_content_type(Mime::XML);" + "xml = ::Builder::XmlMarkup.new(:indent => 2);" + + "self.output_buffer = xml.target!;" + template.source + ";xml.target!;" end - - def cache_fragment(block, name = {}, options = nil) - @view.fragment_for(block, name, options) do - eval('xml.target!', block.binding) - end - end end end end diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb deleted file mode 100644 index a0ebaefeef..0000000000 --- a/actionpack/lib/action_view/template_handlers/compilable.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActionView - module TemplateHandlers - module Compilable - def self.included(base) - base.extend ClassMethod - end - - module ClassMethod - # If a handler is mixin this module, set compilable to true - def compilable? - true - end - end - - def render(template, local_assigns = {}) - @view.send(:execute, template, local_assigns) - end - end - end -end diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb index ac715e30c2..3def949f1e 100644 --- a/actionpack/lib/action_view/template_handlers/erb.rb +++ b/actionpack/lib/action_view/template_handlers/erb.rb @@ -48,14 +48,11 @@ module ActionView self.erb_trim_mode = '-' def compile(template) - src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src - "__in_erb_template=true;#{src}" - end + src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src - def cache_fragment(block, name = {}, options = nil) #:nodoc: - @view.fragment_for(block, name, options) do - @view.response.template.output_buffer - end + # Ruby 1.9 prepends an encoding to the source. However this is + # useless because you can only set an encoding on the first line + RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src end end end diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb index 3892bf1d6e..a700655c9a 100644 --- a/actionpack/lib/action_view/template_handlers/rjs.rb +++ b/actionpack/lib/action_view/template_handlers/rjs.rb @@ -7,17 +7,6 @@ module ActionView "controller.response.content_type ||= Mime::JS;" + "update_page do |page|;#{template.source}\nend" end - - def cache_fragment(block, name = {}, options = nil) #:nodoc: - @view.fragment_for(block, name, options) do - begin - debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false - eval('page.to_s', block.binding) - ensure - ActionView::Base.debug_rjs = debug_mode - end - end - end end end end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 1a3c93c283..adbb37fd09 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -25,9 +25,7 @@ module ActionView end end - ActionView::Base.helper_modules.each do |helper_module| - include helper_module - end + include ActionView::Helpers include ActionController::PolymorphicRoutes include ActionController::RecordIdentifier diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 0d2e0f273a..9db4cddd6a 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -22,8 +22,8 @@ ActiveSupport::Deprecation.debug = true ActionController::Base.logger = nil ActionController::Routing::Routes.reload rescue nil -ActionView::Base.cache_template_loading = true FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') +ActionView::PathSet::Path.eager_load_templates! ActionController::Base.view_paths = FIXTURE_LOAD_PATH # Wrap tests that use Mocha and skip if unavailable. 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 a82a1a3023..d75cb2b53a 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -39,6 +39,11 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base @developers = Developer.find(:all) render :partial => @developers end + + def render_with_record_collection_and_spacer_template + @developer = Developer.find(1) + render :partial => @developer.projects, :spacer_template => 'test/partial_only' + end end class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase @@ -81,6 +86,11 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body end + def test_render_with_record_collection_and_spacer_template + get :render_with_record_collection_and_spacer_template + assert_equal 'Active Recordonly partialActive Controller', @response.body + end + def test_rendering_partial_with_has_one_association mascot = Company.find(1).mascot get :render_with_has_one_association diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 5af579f3e3..1531e7c21a 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -17,6 +17,8 @@ unless defined?(ActionMailer) end end +ActionMailer::Base.template_root = FIXTURE_LOAD_PATH + class AssertSelectTest < Test::Unit::TestCase class AssertSelectController < ActionController::Base def response_with=(content) @@ -69,11 +71,10 @@ class AssertSelectTest < Test::Unit::TestCase ActionMailer::Base.deliveries = [] end - def teardown ActionMailer::Base.deliveries.clear end - + def assert_failure(message, &block) e = assert_raises(AssertionFailedError, &block) assert_match(message, e.message) if Regexp === message @@ -91,7 +92,6 @@ class AssertSelectTest < Test::Unit::TestCase assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" } end - def test_equality_true_false render_html %Q{<div id="1"></div><div id="2"></div>} assert_nothing_raised { assert_select "div" } @@ -102,7 +102,6 @@ class AssertSelectTest < Test::Unit::TestCase assert_nothing_raised { assert_select "p", false } end - def test_equality_string_and_regexp render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", "foo" } @@ -116,7 +115,6 @@ class AssertSelectTest < Test::Unit::TestCase assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ } end - def test_equality_of_html render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>} text = "\"This is not a big problem,\" he said." @@ -135,7 +133,6 @@ class AssertSelectTest < Test::Unit::TestCase assert_raises(AssertionFailedError) { assert_select "pre", :html=>text } end - def test_counts render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", 2 } @@ -166,7 +163,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - def test_substitution_values render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_select "div#?", /\d+/ do |elements| @@ -181,7 +177,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - def test_nested_assert_select render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_select "div" do |elements| @@ -200,7 +195,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "#3", false end end - + assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do assert_select "div" do assert_select "#4" @@ -208,7 +203,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - def test_assert_select_text_match render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>} assert_select "div" do @@ -225,7 +219,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - # With single result. def test_assert_select_from_rjs_with_single_result render_rjs do |page| @@ -255,19 +248,16 @@ class AssertSelectTest < Test::Unit::TestCase end end - # # Test css_select. # - def test_css_select render_html %Q{<div id="1"></div><div id="2"></div>} assert 2, css_select("div").size assert 0, css_select("p").size end - def test_nested_css_select render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_select "div#?", /\d+/ do |elements| @@ -286,7 +276,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - # With one result. def test_css_select_from_rjs_with_single_result render_rjs do |page| @@ -309,12 +298,10 @@ class AssertSelectTest < Test::Unit::TestCase assert_equal 1, css_select("#2").size end - # # Test assert_select_rjs. # - # Test that we can pick up all statements in the result. def test_assert_select_rjs_picks_up_all_statements render_rjs do |page| @@ -381,7 +368,6 @@ class AssertSelectTest < Test::Unit::TestCase assert_raises(AssertionFailedError) { assert_select_rjs "test4" } end - def test_assert_select_rjs_for_replace render_rjs do |page| page.replace "test1", "<div id=\"1\">foo</div>" @@ -479,7 +465,7 @@ class AssertSelectTest < Test::Unit::TestCase end end end - + # Simple hide def test_assert_select_rjs_for_hide render_rjs do |page| @@ -500,7 +486,7 @@ class AssertSelectTest < Test::Unit::TestCase end end end - + # Simple toggle def test_assert_select_rjs_for_toggle render_rjs do |page| @@ -521,7 +507,7 @@ class AssertSelectTest < Test::Unit::TestCase end end end - + # Non-positioned insert. def test_assert_select_rjs_for_nonpositioned_insert render_rjs do |page| @@ -568,7 +554,7 @@ class AssertSelectTest < Test::Unit::TestCase assert_select "div", 4 end end - + # Simple selection from a single result. def test_nested_assert_select_rjs_with_single_result render_rjs do |page| @@ -600,7 +586,6 @@ class AssertSelectTest < Test::Unit::TestCase end end - def test_feed_item_encoded render_xml <<-EOF <rss version="2.0"> @@ -654,7 +639,6 @@ EOF end end - # # Test assert_select_email # @@ -670,7 +654,6 @@ EOF end end - protected def render_html(html) @controller.response_with = html diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 34c0200fe8..d49cc2a9aa 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -7,6 +7,7 @@ module Submodule end class ContainedNonEmptyController < ActionController::Base def public_action + render :nothing => true end hide_action :hidden_action @@ -105,6 +106,18 @@ end class PerformActionTest < Test::Unit::TestCase + class MockLogger + attr_reader :logged + + def initialize + @logged = [] + end + + def method_missing(method, *args) + @logged << args.first + end + end + def use_controller(controller_class) @controller = controller_class.new @@ -142,6 +155,13 @@ class PerformActionTest < Test::Unit::TestCase get :another_hidden_action assert_response 404 end + + def test_namespaced_action_should_log_module_name + use_controller Submodule::ContainedNonEmptyController + @controller.logger = MockLogger.new + get :public_action + assert_match /Processing\sSubmodule::ContainedNonEmptyController#public_action/, @controller.logger.logged[1] + end end class DefaultUrlOptionsTest < Test::Unit::TestCase diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index c30f7be700..b6cdd116e5 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -109,7 +109,7 @@ class PageCachingTest < Test::Unit::TestCase uses_mocha("should_cache_ok_at_custom_path") do def test_should_cache_ok_at_custom_path - @request.expects(:path).returns("/index.html") + @request.stubs(:path).returns("/index.html") get :ok assert_response :ok assert File.exist?("#{FILE_STORE_PATH}/index.html") @@ -148,7 +148,6 @@ class PageCachingTest < Test::Unit::TestCase end end - class ActionCachingTestController < ActionController::Base caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour caches_action :show, :cache_path => 'http://test.host/custom/show' @@ -489,54 +488,54 @@ class FragmentCachingTest < Test::Unit::TestCase def test_fragment_cache_key assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') - assert_equal( "views/test.host/fragment_caching_test/some_action", - @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')) + assert_equal "views/test.host/fragment_caching_test/some_action", + @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action') end - def test_read_fragment__with_caching_enabled + def test_read_fragment_with_caching_enabled @store.write('views/name', 'value') assert_equal 'value', @controller.read_fragment('name') end - def test_read_fragment__with_caching_disabled + def test_read_fragment_with_caching_disabled ActionController::Base.perform_caching = false @store.write('views/name', 'value') assert_nil @controller.read_fragment('name') end - def test_fragment_exist__with_caching_enabled + def test_fragment_exist_with_caching_enabled @store.write('views/name', 'value') assert @controller.fragment_exist?('name') assert !@controller.fragment_exist?('other_name') end - def test_fragment_exist__with_caching_disabled + def test_fragment_exist_with_caching_disabled ActionController::Base.perform_caching = false @store.write('views/name', 'value') assert !@controller.fragment_exist?('name') assert !@controller.fragment_exist?('other_name') end - def test_write_fragment__with_caching_enabled + def test_write_fragment_with_caching_enabled assert_nil @store.read('views/name') assert_equal 'value', @controller.write_fragment('name', 'value') assert_equal 'value', @store.read('views/name') end - def test_write_fragment__with_caching_disabled + def test_write_fragment_with_caching_disabled assert_nil @store.read('views/name') ActionController::Base.perform_caching = false assert_equal nil, @controller.write_fragment('name', 'value') assert_nil @store.read('views/name') end - def test_expire_fragment__with_simple_key + def test_expire_fragment_with_simple_key @store.write('views/name', 'value') @controller.expire_fragment 'name' assert_nil @store.read('views/name') end - def test_expire_fragment__with__regexp + def test_expire_fragment_with_regexp @store.write('views/name', 'value') @store.write('views/another_name', 'another_value') @store.write('views/primalgrasp', 'will not expire ;-)') @@ -548,14 +547,14 @@ class FragmentCachingTest < Test::Unit::TestCase assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') end - def test_fragment_for__with_disabled_caching + def test_fragment_for_with_disabled_caching ActionController::Base.perform_caching = false @store.write('views/expensive', 'fragment content') fragment_computed = false buffer = 'generated till now -> ' - @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer } + @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } assert fragment_computed assert_equal 'generated till now -> ', buffer @@ -566,53 +565,13 @@ class FragmentCachingTest < Test::Unit::TestCase fragment_computed = false buffer = 'generated till now -> ' - @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer} + @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } assert !fragment_computed assert_equal 'generated till now -> fragment content', buffer end - - def test_cache_erb_fragment - @store.write('views/expensive', 'fragment content') - @controller.response.template.output_buffer = 'generated till now -> ' - - assert_equal( 'generated till now -> fragment content', - ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) - end - - def test_cache_rxml_fragment - @store.write('views/expensive', 'fragment content') - xml = 'generated till now -> ' - class << xml; def target!; to_s; end; end - - assert_equal( 'generated till now -> fragment content', - ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) - end - - def test_cache_rjs_fragment - @store.write('views/expensive', 'fragment content') - page = 'generated till now -> ' - - assert_equal( 'generated till now -> fragment content', - ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) - end - - def test_cache_rjs_fragment_debug_mode_does_not_interfere - @store.write('views/expensive', 'fragment content') - page = 'generated till now -> ' - - begin - debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true - assert_equal( 'generated till now -> fragment content', - ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) - assert ActionView::Base.debug_rjs - ensure - ActionView::Base.debug_rjs = debug_mode - end - end end - class FunctionalCachingController < ActionController::Base def fragment_cached end @@ -629,6 +588,13 @@ class FunctionalCachingController < ActionController::Base end end + def formatted_fragment_cached + respond_to do |format| + format.html + format.xml + format.js + end + end def rescue_action(e) raise e @@ -678,4 +644,35 @@ CACHED assert_match /Fragment caching in a partial/, @response.body assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial') end + + def test_html_formatted_fragment_caching + get :formatted_fragment_cached, :format => "html" + assert_response :success + expected_body = "<body>\n<p>ERB</p>\n</body>" + + assert_equal expected_body, @response.body + + assert_equal "<p>ERB</p>", @store.read('views/test.host/functional_caching/formatted_fragment_cached') + end + + def test_xml_formatted_fragment_caching + get :formatted_fragment_cached, :format => "xml" + assert_response :success + expected_body = "<body>\n <p>Builder</p>\n</body>\n" + + assert_equal expected_body, @response.body + + assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached') + end + + def test_js_formatted_fragment_caching + get :formatted_fragment_cached, :format => "js" + assert_response :success + expected_body = %(title = "Hey";\n$("element_1").visualEffect("highlight");\n) + + %($("element_2").visualEffect("highlight");\nfooter = "Bye";) + assert_equal expected_body, @response.body + + assert_equal ['$("element_1").visualEffect("highlight");', '$("element_2").visualEffect("highlight");'], + @store.read('views/test.host/functional_caching/formatted_fragment_cached') + end end diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb index bf3b8b788e..813171857a 100755..100644 --- a/actionpack/test/controller/cgi_test.rb +++ b/actionpack/test/controller/cgi_test.rb @@ -53,6 +53,15 @@ class BaseCgiTest < Test::Unit::TestCase end def default_test; end + + private + + def set_content_data(data) + @request.env['REQUEST_METHOD'] = 'POST' + @request.env['CONTENT_LENGTH'] = data.length + @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' + @request.env['RAW_POST_DATA'] = data + end end class CgiRequestTest < BaseCgiTest @@ -66,7 +75,7 @@ class CgiRequestTest < BaseCgiTest assert_equal "rubyonrails.org:8080", @request.host_with_port @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" - assert_equal "www.secondhost.org", @request.host + assert_equal "www.secondhost.org", @request.host(true) end def test_http_host_with_default_port_overrides_server_port @@ -155,10 +164,8 @@ end class CgiRequestParamsParsingTest < BaseCgiTest def test_doesnt_break_when_content_type_has_charset - data = 'flamenco=love' - @request.env['CONTENT_LENGTH'] = data.length - @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' - @request.env['RAW_POST_DATA'] = data + set_content_data 'flamenco=love' + assert_equal({"flamenco"=> "love"}, @request.request_parameters) end @@ -168,6 +175,41 @@ class CgiRequestParamsParsingTest < BaseCgiTest end end +class CgiRequestContentTypeTest < BaseCgiTest + def test_html_content_type_verification + @request.env['CONTENT_TYPE'] = Mime::HTML.to_s + assert @request.content_type.verify_request? + end + + def test_xml_content_type_verification + @request.env['CONTENT_TYPE'] = Mime::XML.to_s + assert !@request.content_type.verify_request? + end +end + +class CgiRequestMethodTest < BaseCgiTest + def test_get + assert_equal :get, @request.request_method + end + + def test_post + @request.env['REQUEST_METHOD'] = 'POST' + assert_equal :post, @request.request_method + end + + def test_put + set_content_data '_method=put' + + assert_equal :put, @request.request_method + end + + def test_delete + set_content_data '_method=delete' + + assert_equal :delete, @request.request_method + end +end + class CgiRequestNeedsRewoundTest < BaseCgiTest def test_body_should_be_rewound data = 'foo' diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index d457d13aef..ae71d62e11 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -19,6 +19,11 @@ class ContentTypeController < ActionController::Base render :text => "hello world!" end + def render_nil_charset_from_body + response.charset = nil + render :text => "hello world!" + end + def render_default_for_rhtml end @@ -85,8 +90,23 @@ class ContentTypeTest < Test::Unit::TestCase def test_charset_from_body get :render_charset_from_body + assert_equal Mime::HTML, @response.content_type assert_equal "utf-16", @response.charset + end + + def test_nil_charset_from_body + get :render_nil_charset_from_body assert_equal Mime::HTML, @response.content_type + assert_equal "utf-8", @response.charset, @response.headers.inspect + end + + def test_nil_default_for_rhtml + ContentTypeController.default_charset = nil + get :render_default_for_rhtml + assert_equal Mime::HTML, @response.content_type + assert_nil @response.charset, @response.headers.inspect + ensure + ContentTypeController.default_charset = "utf-8" end def test_default_for_rhtml @@ -128,23 +148,23 @@ class AcceptBasedContentTypeTest < ActionController::TestCase def test_render_default_content_types_for_respond_to - @request.env["HTTP_ACCEPT"] = Mime::HTML.to_s + @request.accept = Mime::HTML.to_s get :render_default_content_types_for_respond_to assert_equal Mime::HTML, @response.content_type - @request.env["HTTP_ACCEPT"] = Mime::JS.to_s + @request.accept = Mime::JS.to_s get :render_default_content_types_for_respond_to assert_equal Mime::JS, @response.content_type end def test_render_default_content_types_for_respond_to_with_template - @request.env["HTTP_ACCEPT"] = Mime::XML.to_s + @request.accept = Mime::XML.to_s get :render_default_content_types_for_respond_to assert_equal Mime::XML, @response.content_type end def test_render_default_content_types_for_respond_to_with_overwrite - @request.env["HTTP_ACCEPT"] = Mime::RSS.to_s + @request.accept = Mime::RSS.to_s get :render_default_content_types_for_respond_to assert_equal Mime::XML, @response.content_type end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index b45fbb17d3..5a6fb49861 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -60,7 +60,7 @@ class CookieTest < Test::Unit::TestCase end def test_setting_cookie_for_fourteen_days_with_symbols - get :authenticate_for_fourteen_days + get :authenticate_for_fourteen_days_with_symbols assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"] end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 475e13897b..c986941140 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -253,7 +253,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest session :off def get - render :text => "OK", :status => 200 + respond_to do |format| + format.html { render :text => "OK", :status => 200 } + format.js { render :text => "JS OK", :status => 200 } + end + end + + def get_with_params + render :text => "foo: #{params[:foo]}", :status => 200 end def post @@ -265,6 +272,10 @@ class IntegrationProcessTest < ActionController::IntegrationTest cookies["cookie_3"] = "chocolate" render :text => "Gone", :status => 410 end + + def redirect + redirect_to :action => "get" + end end def test_get @@ -274,6 +285,9 @@ class IntegrationProcessTest < ActionController::IntegrationTest assert_equal "OK", status_message assert_equal "200 OK", response.headers["Status"] assert_equal ["200 OK"], headers["status"] + assert_response 200 + assert_response :success + assert_response :ok assert_equal [], response.headers["cookie"] assert_equal [], headers["cookie"] assert_equal({}, cookies) @@ -290,6 +304,9 @@ class IntegrationProcessTest < ActionController::IntegrationTest assert_equal "Created", status_message assert_equal "201 Created", response.headers["Status"] assert_equal ["201 Created"], headers["status"] + assert_response 201 + assert_response :success + assert_response :created assert_equal [], response.headers["cookie"] assert_equal [], headers["cookie"] assert_equal({}, cookies) @@ -308,23 +325,84 @@ class IntegrationProcessTest < ActionController::IntegrationTest assert_equal "Gone", status_message assert_equal "410 Gone", response.headers["Status"] assert_equal ["410 Gone"], headers["status"] - assert_equal nil, response.headers["Set-Cookie"] + assert_response 410 + assert_response :gone + assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], response.headers["Set-Cookie"] assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], headers['set-cookie'] - assert_equal [[], ["chocolate"]], response.headers["cookie"] + assert_equal [ + CGI::Cookie::new("name" => "cookie_1", "value" => ""), + CGI::Cookie::new("name" => "cookie_3", "value" => "chocolate") + ], response.headers["cookie"] assert_equal [], headers["cookie"] assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies) assert_equal "Gone", response.body end end + def test_redirect + with_test_route_set do + get '/redirect' + assert_equal 302, status + assert_equal "Found", status_message + assert_equal "302 Found", response.headers["Status"] + assert_equal ["302 Found"], headers["status"] + assert_response 302 + assert_response :redirect + assert_response :found + assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body + assert_kind_of HTML::Document, html_document + assert_equal 1, request_count + end + end + + def test_xml_http_request_get + with_test_route_set do + xhr :get, '/get' + assert_equal 200, status + assert_equal "OK", status_message + assert_equal "200 OK", response.headers["Status"] + assert_equal ["200 OK"], headers["status"] + assert_response 200 + assert_response :success + assert_response :ok + assert_equal "JS OK", response.body + end + end + + def test_get_with_query_string + with_test_route_set do + get '/get_with_params?foo=bar' + assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"] + assert_equal '/get_with_params?foo=bar', request.request_uri + assert_equal nil, request.env["QUERY_STRING"] + assert_equal 'foo=bar', request.query_string + assert_equal 'bar', request.parameters['foo'] + + assert_equal 200, status + assert_equal "foo: bar", response.body + end + end + + def test_get_with_parameters + with_test_route_set do + get '/get_with_params', :foo => "bar" + assert_equal '/get_with_params', request.env["REQUEST_URI"] + assert_equal '/get_with_params', request.request_uri + assert_equal 'foo=bar', request.env["QUERY_STRING"] + assert_equal 'foo=bar', request.query_string + assert_equal 'bar', request.parameters['foo'] + + assert_equal 200, status + assert_equal "foo: bar", response.body + end + end + private def with_test_route_set with_routing do |set| set.draw do |map| map.with_options :controller => "IntegrationProcessTest::Integration" do |c| - c.connect '/get', :action => "get" - c.connect '/post', :action => "post" - c.connect '/cookie_monster', :action => "cookie_monster" + c.connect "/:action" end end yield diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 92b6aa4f2f..71f110f241 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -31,16 +31,8 @@ end class MultipleExtensions < LayoutTest end -class MabView < ActionView::TemplateHandler - def initialize(view) - end - - def render(template, local_assigns) - template.source - end -end - -ActionView::Template::register_template_handler :mab, MabView +ActionView::Template::register_template_handler :mab, + lambda { |template| template.source.inspect } class LayoutAutoDiscoveryTest < Test::Unit::TestCase def setup @@ -49,19 +41,19 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase @request.host = "www.nextangle.com" end - + def test_application_layout_is_default_when_no_controller_match @controller = ProductController.new get :hello assert_equal 'layout_test.rhtml hello.rhtml', @response.body end - + def test_controller_name_layout_name_match @controller = ItemController.new get :hello assert_equal 'item.rhtml hello.rhtml', @response.body end - + def test_third_party_template_library_auto_discovers_layout ThirdPartyTemplateLibraryController.view_paths.reload! @controller = ThirdPartyTemplateLibraryController.new @@ -71,14 +63,14 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase assert_response :success assert_equal 'Mab', @response.body end - + 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 'controller_name_space/nested.rhtml hello.rhtml', @response.body end - + def test_namespaced_controllers_auto_detect_layouts @controller = MultipleExtensions.new get :hello @@ -123,7 +115,7 @@ class ExemptFromLayoutTest < Test::Unit::TestCase def test_rhtml_exempt_from_layout_status_should_prevent_layout_render ActionController::Base.exempt_from_layout :rhtml - + assert @controller.send!(:template_exempt_from_layout?, 'test.rhtml') assert @controller.send!(:template_exempt_from_layout?, 'hello.rhtml') @@ -164,19 +156,19 @@ class LayoutSetInResponseTest < Test::Unit::TestCase get :hello assert_equal 'layouts/layout_test', @response.layout end - + def test_layout_set_when_set_in_controller @controller = HasOwnLayoutController.new get :hello assert_equal 'layouts/item', @response.layout end - + def test_layout_set_when_using_render @controller = SetsLayoutInRenderController.new get :hello assert_equal 'layouts/third_party_template_library', @response.layout end - + def test_layout_is_not_set_when_none_rendered @controller = RendersNoLayoutController.new get :hello @@ -257,4 +249,3 @@ class LayoutSymlinkedIsRenderedTest < Test::Unit::TestCase assert_equal "layouts/symlinked/symlinked_layout", @response.layout end end -
\ No newline at end of file diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 1701431858..0d508eb8df 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -177,7 +177,7 @@ class MimeControllerTest < Test::Unit::TestCase end def test_html - @request.env["HTTP_ACCEPT"] = "text/html" + @request.accept = "text/html" get :js_or_html assert_equal 'HTML', @response.body @@ -189,7 +189,7 @@ class MimeControllerTest < Test::Unit::TestCase end def test_all - @request.env["HTTP_ACCEPT"] = "*/*" + @request.accept = "*/*" get :js_or_html assert_equal 'HTML', @response.body # js is not part of all @@ -201,13 +201,13 @@ class MimeControllerTest < Test::Unit::TestCase end def test_xml - @request.env["HTTP_ACCEPT"] = "application/xml" + @request.accept = "application/xml" get :html_xml_or_rss assert_equal 'XML', @response.body end def test_js_or_html - @request.env["HTTP_ACCEPT"] = "text/javascript, text/html" + @request.accept = "text/javascript, text/html" get :js_or_html assert_equal 'JS', @response.body @@ -232,7 +232,7 @@ class MimeControllerTest < Test::Unit::TestCase 'JSON' => %w(application/json text/x-json) }.each do |body, content_types| content_types.each do |content_type| - @request.env['HTTP_ACCEPT'] = content_type + @request.accept = content_type get :json_or_yaml assert_equal body, @response.body end @@ -240,7 +240,7 @@ class MimeControllerTest < Test::Unit::TestCase end def test_js_or_anything - @request.env["HTTP_ACCEPT"] = "text/javascript, */*" + @request.accept = "text/javascript, */*" get :js_or_html assert_equal 'JS', @response.body @@ -252,34 +252,34 @@ class MimeControllerTest < Test::Unit::TestCase end def test_using_defaults - @request.env["HTTP_ACCEPT"] = "*/*" + @request.accept = "*/*" get :using_defaults assert_equal "text/html", @response.content_type assert_equal 'Hello world!', @response.body - @request.env["HTTP_ACCEPT"] = "text/javascript" + @request.accept = "text/javascript" get :using_defaults assert_equal "text/javascript", @response.content_type assert_equal '$("body").visualEffect("highlight");', @response.body - @request.env["HTTP_ACCEPT"] = "application/xml" + @request.accept = "application/xml" get :using_defaults assert_equal "application/xml", @response.content_type assert_equal "<p>Hello world!</p>\n", @response.body end def test_using_defaults_with_type_list - @request.env["HTTP_ACCEPT"] = "*/*" + @request.accept = "*/*" get :using_defaults_with_type_list assert_equal "text/html", @response.content_type assert_equal 'Hello world!', @response.body - @request.env["HTTP_ACCEPT"] = "text/javascript" + @request.accept = "text/javascript" get :using_defaults_with_type_list assert_equal "text/javascript", @response.content_type assert_equal '$("body").visualEffect("highlight");', @response.body - @request.env["HTTP_ACCEPT"] = "application/xml" + @request.accept = "application/xml" get :using_defaults_with_type_list assert_equal "application/xml", @response.content_type assert_equal "<p>Hello world!</p>\n", @response.body @@ -298,55 +298,55 @@ class MimeControllerTest < Test::Unit::TestCase end def test_synonyms - @request.env["HTTP_ACCEPT"] = "application/javascript" + @request.accept = "application/javascript" get :js_or_html assert_equal 'JS', @response.body - @request.env["HTTP_ACCEPT"] = "application/x-xml" + @request.accept = "application/x-xml" get :html_xml_or_rss assert_equal "XML", @response.body end def test_custom_types - @request.env["HTTP_ACCEPT"] = "application/crazy-xml" + @request.accept = "application/crazy-xml" get :custom_type_handling assert_equal "application/crazy-xml", @response.content_type assert_equal 'Crazy XML', @response.body - @request.env["HTTP_ACCEPT"] = "text/html" + @request.accept = "text/html" get :custom_type_handling assert_equal "text/html", @response.content_type assert_equal 'HTML', @response.body end def test_xhtml_alias - @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml" + @request.accept = "application/xhtml+xml,application/xml" get :html_or_xml assert_equal 'HTML', @response.body end def test_firefox_simulation - @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" + @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" get :html_or_xml assert_equal 'HTML', @response.body end def test_handle_any - @request.env["HTTP_ACCEPT"] = "*/*" + @request.accept = "*/*" get :handle_any assert_equal 'HTML', @response.body - @request.env["HTTP_ACCEPT"] = "text/javascript" + @request.accept = "text/javascript" get :handle_any assert_equal 'Either JS or XML', @response.body - @request.env["HTTP_ACCEPT"] = "text/xml" + @request.accept = "text/xml" get :handle_any assert_equal 'Either JS or XML', @response.body end def test_handle_any_any - @request.env["HTTP_ACCEPT"] = "*/*" + @request.accept = "*/*" get :handle_any_any assert_equal 'HTML', @response.body end @@ -357,31 +357,31 @@ class MimeControllerTest < Test::Unit::TestCase end def test_handle_any_any_explicit_html - @request.env["HTTP_ACCEPT"] = "text/html" + @request.accept = "text/html" get :handle_any_any assert_equal 'HTML', @response.body end def test_handle_any_any_javascript - @request.env["HTTP_ACCEPT"] = "text/javascript" + @request.accept = "text/javascript" get :handle_any_any assert_equal 'Whatever you ask for, I got it', @response.body end def test_handle_any_any_xml - @request.env["HTTP_ACCEPT"] = "text/xml" + @request.accept = "text/xml" get :handle_any_any assert_equal 'Whatever you ask for, I got it', @response.body end def test_rjs_type_skips_layout - @request.env["HTTP_ACCEPT"] = "text/javascript" + @request.accept = "text/javascript" get :all_types_with_layout assert_equal 'RJS for all_types_with_layout', @response.body end def test_html_type_with_layout - @request.env["HTTP_ACCEPT"] = "text/html" + @request.accept = "text/html" get :all_types_with_layout assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body end @@ -460,7 +460,7 @@ class MimeControllerTest < Test::Unit::TestCase end def test_format_with_custom_response_type_and_request_headers - @request.env["HTTP_ACCEPT"] = "text/iphone" + @request.accept = "text/iphone" get :iphone_with_html_response_type assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body assert_equal "text/html", @response.content_type @@ -470,7 +470,7 @@ class MimeControllerTest < Test::Unit::TestCase get :iphone_with_html_response_type_without_layout assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body - @request.env["HTTP_ACCEPT"] = "text/iphone" + @request.accept = "text/iphone" assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } end end @@ -522,7 +522,7 @@ class MimeControllerLayoutsTest < Test::Unit::TestCase get :index assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body - @request.env["HTTP_ACCEPT"] = "text/iphone" + @request.accept = "text/iphone" get :index assert_equal 'Hello iPhone', @response.body end @@ -533,7 +533,7 @@ class MimeControllerLayoutsTest < Test::Unit::TestCase get :index assert_equal 'Super Firefox', @response.body - @request.env["HTTP_ACCEPT"] = "text/iphone" + @request.accept = "text/iphone" get :index assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body end diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb index d2a3a2b0b0..82919b7777 100644 --- a/actionpack/test/controller/new_render_test.rb +++ b/actionpack/test/controller/new_render_test.rb @@ -136,6 +136,10 @@ class NewRenderTestController < ActionController::Base render :partial => "partial_only", :layout => true end + def partial_with_counter + render :partial => "counter", :locals => { :counter_counter => 5 } + end + def partial_with_locals render :partial => "customer", :locals => { :customer => Customer.new("david") } end @@ -431,6 +435,10 @@ class NewRenderTestController < ActionController::Base render :action => "using_layout_around_block" end + def render_using_layout_around_block_with_args + render :action => "using_layout_around_block_with_args" + end + def render_using_layout_around_block_in_main_layout_and_within_content_for_layout render :action => "using_layout_around_block" end @@ -741,6 +749,11 @@ EOS assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body end + def test_partial_with_counter + get :partial_with_counter + assert_equal "5", @response.body + end + def test_partials_list get :partials_list assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body @@ -960,4 +973,9 @@ EOS get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body end + + def test_using_layout_around_block_with_args + get :render_using_layout_around_block_with_args + assert_equal "Before\narg1arg2\nAfter", @response.body + end end diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 3f52526f08..6ddf2826cd 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -60,6 +60,18 @@ uses_mocha 'polymorphic URL helpers' do edit_polymorphic_url(@article) end + def test_url_helper_prefixed_with_edit_with_url_options + @article.save + expects(:edit_article_url).with(@article, :param1 => '10') + edit_polymorphic_url(@article, :param1 => '10') + end + + def test_url_helper_with_url_options + @article.save + expects(:article_url).with(@article, :param1 => '10') + polymorphic_url(@article, :param1 => '10') + end + def test_formatted_url_helper expects(:formatted_article_url).with(@article, :pdf) formatted_polymorphic_url([@article, :pdf]) @@ -67,10 +79,16 @@ uses_mocha 'polymorphic URL helpers' do def test_format_option @article.save - expects(:article_url).with(@article, :pdf) + expects(:formatted_article_url).with(@article, :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') + polymorphic_url(@article, :format => :pdf, :param1 => '10') + end + def test_id_and_format_option @article.save expects(:article_url).with(:id => @article, :format => :pdf) @@ -147,7 +165,7 @@ uses_mocha 'polymorphic URL helpers' do def test_nesting_with_array_containing_singleton_resource_and_format_option @tag = Tag.new @tag.save - expects(:article_response_tag_url).with(@article, @tag, :pdf) + expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf) polymorphic_url([@article, :response, @tag], :format => :pdf) end diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb index 486fe49737..d5e56b9584 100644 --- a/actionpack/test/controller/rack_test.rb +++ b/actionpack/test/controller/rack_test.rb @@ -51,62 +51,74 @@ class BaseRackTest < Test::Unit::TestCase end def default_test; end + + private + + def set_content_data(data) + @request.env['REQUEST_METHOD'] = 'POST' + @request.env['CONTENT_LENGTH'] = data.length + @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' + @request.env['RAW_POST_DATA'] = data + end end class RackRequestTest < BaseRackTest def test_proxy_request - assert_equal 'glu.ttono.us', @request.host_with_port + assert_equal 'glu.ttono.us', @request.host_with_port(true) end def test_http_host @env.delete "HTTP_X_FORWARDED_HOST" @env['HTTP_HOST'] = "rubyonrails.org:8080" - assert_equal "rubyonrails.org:8080", @request.host_with_port + assert_equal "rubyonrails.org", @request.host(true) + assert_equal "rubyonrails.org:8080", @request.host_with_port(true) @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" - assert_equal "www.secondhost.org", @request.host + assert_equal "www.secondhost.org", @request.host(true) end def test_http_host_with_default_port_overrides_server_port @env.delete "HTTP_X_FORWARDED_HOST" @env['HTTP_HOST'] = "rubyonrails.org" - assert_equal "rubyonrails.org", @request.host_with_port + assert_equal "rubyonrails.org", @request.host_with_port(true) end def test_host_with_port_defaults_to_server_name_if_no_host_headers @env.delete "HTTP_X_FORWARDED_HOST" @env.delete "HTTP_HOST" - assert_equal "glu.ttono.us:8007", @request.host_with_port + assert_equal "glu.ttono.us:8007", @request.host_with_port(true) end def test_host_with_port_falls_back_to_server_addr_if_necessary @env.delete "HTTP_X_FORWARDED_HOST" @env.delete "HTTP_HOST" @env.delete "SERVER_NAME" - assert_equal "207.7.108.53:8007", @request.host_with_port + assert_equal "207.7.108.53", @request.host(true) + assert_equal 8007, @request.port(true) + assert_equal "207.7.108.53:8007", @request.host_with_port(true) end def test_host_with_port_if_http_standard_port_is_specified @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80" - assert_equal "glu.ttono.us", @request.host_with_port + assert_equal "glu.ttono.us", @request.host_with_port(true) end def test_host_with_port_if_https_standard_port_is_specified @env['HTTP_X_FORWARDED_PROTO'] = "https" @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443" - assert_equal "glu.ttono.us", @request.host_with_port + assert_equal "glu.ttono.us", @request.host_with_port(true) end def test_host_if_ipv6_reference @env.delete "HTTP_X_FORWARDED_HOST" @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]" - assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host + assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true) end def test_host_if_ipv6_reference_with_port @env.delete "HTTP_X_FORWARDED_HOST" @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008" - assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host + assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host(true) end def test_cgi_environment_variables @@ -153,10 +165,8 @@ end class RackRequestParamsParsingTest < BaseRackTest def test_doesnt_break_when_content_type_has_charset - data = 'flamenco=love' - @request.env['CONTENT_LENGTH'] = data.length - @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' - @request.env['RAW_POST_DATA'] = data + set_content_data 'flamenco=love' + assert_equal({"flamenco"=> "love"}, @request.request_parameters) end @@ -166,6 +176,41 @@ class RackRequestParamsParsingTest < BaseRackTest end end +class RackRequestContentTypeTest < BaseRackTest + def test_html_content_type_verification + @request.env['CONTENT_TYPE'] = Mime::HTML.to_s + assert @request.content_type.verify_request? + end + + def test_xml_content_type_verification + @request.env['CONTENT_TYPE'] = Mime::XML.to_s + assert !@request.content_type.verify_request? + end +end + +class RackRequestMethodTest < BaseRackTest + def test_get + assert_equal :get, @request.request_method + end + + def test_post + @request.env['REQUEST_METHOD'] = 'POST' + assert_equal :post, @request.request_method + end + + def test_put + set_content_data '_method=put' + + assert_equal :put, @request.request_method + end + + def test_delete + set_content_data '_method=delete' + + assert_equal :delete, @request.request_method + end +end + class RackRequestNeedsRewoundTest < BaseRackTest def test_body_should_be_rewound data = 'foo' @@ -191,10 +236,17 @@ class RackResponseTest < BaseRackTest def test_simple_output @response.body = "Hello, World!" + @response.prepare! status, headers, body = @response.out(@output) assert_equal "200 OK", status - assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) + assert_equal({ + "Content-Type" => "text/html; charset=utf-8", + "Cache-Control" => "private, max-age=0, must-revalidate", + "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"', + "Set-Cookie" => [], + "Content-Length" => "13" + }, headers) parts = [] body.each { |part| parts << part } @@ -205,10 +257,11 @@ class RackResponseTest < BaseRackTest @response.body = Proc.new do |response, output| 5.times { |n| output.write(n) } end + @response.prepare! status, headers, body = @response.out(@output) assert_equal "200 OK", status - assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) + assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) parts = [] body.each { |part| parts << part } @@ -220,13 +273,16 @@ class RackResponseTest < BaseRackTest @request.cgi.send :instance_variable_set, '@output_cookies', [cookie] @response.body = "Hello, World!" + @response.prepare! status, headers, body = @response.out(@output) assert_equal "200 OK", status assert_equal({ - "Content-Type" => "text/html", - "Cache-Control" => "no-cache", - "Set-Cookie" => ["name=Josh; path="] + "Content-Type" => "text/html; charset=utf-8", + "Cache-Control" => "private, max-age=0, must-revalidate", + "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"', + "Set-Cookie" => ["name=Josh; path="], + "Content-Length" => "13" }, headers) parts = [] @@ -234,3 +290,34 @@ class RackResponseTest < BaseRackTest assert_equal ["Hello, World!"], parts end end + +class RackResponseHeadersTest < BaseRackTest + def setup + super + @response = ActionController::RackResponse.new(@request) + @output = StringIO.new('') + @response.headers['Status'] = "200 OK" + end + + def test_content_type + [204, 304].each do |c| + @response.headers['Status'] = c.to_s + assert !response_headers.has_key?("Content-Type"), "#{c} should not have Content-Type header" + end + + [200, 302, 404, 500].each do |c| + @response.headers['Status'] = c.to_s + assert response_headers.has_key?("Content-Type"), "#{c} did not have Content-Type header" + end + end + + def test_status + assert !response_headers.has_key?('Status') + end + + private + def response_headers + @response.prepare! + @response.out(@output)[1] + end +end diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 28da5c6163..2f8bf7b6ee 100755..100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -9,11 +9,11 @@ class Workshop def initialize(id, new_record) @id, @new_record = id, new_record end - + def new_record? @new_record end - + def to_s id.to_s end @@ -24,32 +24,32 @@ class RedirectController < ActionController::Base redirect_to :action => "hello_world" end - def redirect_with_status + def redirect_with_status redirect_to({:action => "hello_world", :status => 301}) - end + end def redirect_with_status_hash redirect_to({:action => "hello_world"}, {:status => 301}) - end + end - def url_redirect_with_status + def url_redirect_with_status redirect_to("http://www.example.com", :status => :moved_permanently) - end - - def url_redirect_with_status_hash + end + + def url_redirect_with_status_hash redirect_to("http://www.example.com", {:status => 301}) - end + end - def relative_url_redirect_with_status + def relative_url_redirect_with_status redirect_to("/things/stuff", :status => :found) - end - + end + def relative_url_redirect_with_status_hash redirect_to("/things/stuff", {:status => 301}) - end - - def redirect_to_back_with_status - redirect_to :back, :status => 307 + end + + def redirect_to_back_with_status + redirect_to :back, :status => 307 end def host_redirect @@ -90,9 +90,9 @@ class RedirectController < ActionController::Base end def rescue_errors(e) raise e end - + def rescue_action(e) raise end - + protected def dashbord_url(id, message) url_for :action => "dashboard", :params => { "id" => id, "message" => message } @@ -118,48 +118,48 @@ class RedirectTest < Test::Unit::TestCase assert_equal "http://test.host/redirect/hello_world", redirect_to_url end - def test_redirect_with_status - get :redirect_with_status - assert_response 301 - assert_equal "http://test.host/redirect/hello_world", redirect_to_url - end + def test_redirect_with_status + get :redirect_with_status + assert_response 301 + assert_equal "http://test.host/redirect/hello_world", redirect_to_url + end - def test_redirect_with_status_hash + def test_redirect_with_status_hash get :redirect_with_status_hash - assert_response 301 - assert_equal "http://test.host/redirect/hello_world", redirect_to_url + assert_response 301 + assert_equal "http://test.host/redirect/hello_world", redirect_to_url + end + + def test_url_redirect_with_status + get :url_redirect_with_status + assert_response 301 + assert_equal "http://www.example.com", redirect_to_url end - - def test_url_redirect_with_status - get :url_redirect_with_status - assert_response 301 - assert_equal "http://www.example.com", redirect_to_url - end def test_url_redirect_with_status_hash get :url_redirect_with_status_hash - assert_response 301 - assert_equal "http://www.example.com", redirect_to_url - end + assert_response 301 + assert_equal "http://www.example.com", redirect_to_url + end - - def test_relative_url_redirect_with_status - get :relative_url_redirect_with_status + + def test_relative_url_redirect_with_status + get :relative_url_redirect_with_status assert_response 302 - assert_equal "http://test.host/things/stuff", redirect_to_url - end - + assert_equal "http://test.host/things/stuff", redirect_to_url + end + def test_relative_url_redirect_with_status_hash get :relative_url_redirect_with_status_hash - assert_response 301 - assert_equal "http://test.host/things/stuff", redirect_to_url - end - - def test_redirect_to_back_with_status - @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" - get :redirect_to_back_with_status - assert_response 307 - assert_equal "http://www.example.com/coming/from", redirect_to_url + assert_response 301 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + + def test_redirect_to_back_with_status + @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from" + get :redirect_to_back_with_status + assert_response 307 + assert_equal "http://www.example.com/coming/from", redirect_to_url end def test_simple_redirect_using_options @@ -204,20 +204,20 @@ class RedirectTest < Test::Unit::TestCase assert_response :redirect assert_equal "http://www.example.com/coming/from", redirect_to_url end - + def test_redirect_to_back_with_no_referer assert_raises(ActionController::RedirectBackError) { @request.env["HTTP_REFERER"] = nil get :redirect_to_back } end - + def test_redirect_to_record ActionController::Routing::Routes.draw do |map| map.resources :workshops map.connect ':controller/:action/:id' end - + get :redirect_to_existing_record assert_equal "http://test.host/workshops/5", redirect_to_url assert_redirected_to Workshop.new(5, false) @@ -237,7 +237,6 @@ class RedirectTest < Test::Unit::TestCase get :redirect_to_nil end end - end module ModuleTest diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index a857810b78..3008f5ca03 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -8,14 +8,23 @@ module Fun end end - -# FIXME: crashes Ruby 1.9 class TestController < ActionController::Base layout :determine_layout def hello_world end + def conditional_hello + response.last_modified = Time.now.utc.beginning_of_day + response.etag = [:foo, 123] + + if request.fresh?(response) + head :not_modified + else + render :action => 'hello_world' + end + end + def render_hello_world render :template => "test/hello_world" end @@ -101,12 +110,7 @@ class TestController < ActionController::Base end def render_line_offset - begin - render :inline => '<% raise %>', :locals => {:foo => 'bar'} - rescue RuntimeError => exc - end - line = exc.backtrace.first - render :text => line + render :inline => '<% raise %>', :locals => {:foo => 'bar'} end def heading @@ -198,11 +202,11 @@ class TestController < ActionController::Base def render_alternate_default # For this test, the method "default_render" is overridden: - @alternate_default_render = lambda { - render :update do |page| - page.replace :foo, :partial => 'partial' - end - } + @alternate_default_render = lambda do + render :update do |page| + page.replace :foo, :partial => 'partial' + end + end end def rescue_action(e) raise end @@ -238,10 +242,15 @@ class RenderTest < Test::Unit::TestCase end def test_line_offset - get :render_line_offset - line = @response.body - assert(line =~ %r{:(\d+):}) - assert_equal "1", $1 + begin + get :render_line_offset + flunk "the action should have raised an exception" + rescue RuntimeError => exc + line = exc.backtrace.first + assert(line =~ %r{:(\d+):}) + assert_equal "1", $1, + "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" + end end def test_render_with_forward_slash @@ -320,7 +329,7 @@ class RenderTest < Test::Unit::TestCase def test_render_text_with_nil get :render_text_with_nil assert_response 200 - assert_equal '', @response.body + assert_equal ' ', @response.body end def test_render_text_with_false @@ -408,58 +417,6 @@ class RenderTest < Test::Unit::TestCase assert_equal "Goodbye, Local David", @response.body end - def test_render_200_should_set_etag - get :render_hello_world_from_variable - assert_equal etag_for("hello david"), @response.headers['ETag'] - assert_equal "private, max-age=0, must-revalidate", @response.headers['Cache-Control'] - end - - def test_render_against_etag_request_should_304_when_match - @request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello david") - get :render_hello_world_from_variable - assert_equal "304 Not Modified", @response.headers['Status'] - assert @response.body.empty? - end - - def test_render_against_etag_request_should_200_when_no_match - @request.headers["HTTP_IF_NONE_MATCH"] = etag_for("hello somewhere else") - get :render_hello_world_from_variable - assert_equal "200 OK", @response.headers['Status'] - assert !@response.body.empty? - end - - def test_render_with_etag - get :render_hello_world_from_variable - expected_etag = etag_for('hello david') - assert_equal expected_etag, @response.headers['ETag'] - - @request.headers["HTTP_IF_NONE_MATCH"] = expected_etag - get :render_hello_world_from_variable - assert_equal "304 Not Modified", @response.headers['Status'] - - @request.headers["HTTP_IF_NONE_MATCH"] = "\"diftag\"" - get :render_hello_world_from_variable - assert_equal "200 OK", @response.headers['Status'] - end - - def render_with_404_shouldnt_have_etag - get :render_custom_code - assert_nil @response.headers['ETag'] - end - - def test_etag_should_not_be_changed_when_already_set - expected_etag = etag_for("hello somewhere else") - @response.headers["ETag"] = expected_etag - get :render_hello_world_from_variable - assert_equal expected_etag, @response.headers['ETag'] - end - - def test_etag_should_govern_renders_with_layouts_too - get :builder_layout_test - assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body - assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag'] - end - def test_should_render_formatted_template get :formatted_html_erb assert_equal 'formatted html erb', @response.body @@ -476,7 +433,7 @@ class RenderTest < Test::Unit::TestCase end def test_should_render_formatted_html_erb_template_with_faulty_accepts_header - @request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" + @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" get :formatted_xml_erb assert_equal '<test>passed formatted html erb</test>', @response.body end @@ -516,8 +473,107 @@ class RenderTest < Test::Unit::TestCase assert_equal "application/atomsvc+xml", @response.content_type end + def test_should_use_implicit_content_type + get :implicit_content_type, :format => 'atom' + assert_equal Mime::ATOM, @response.content_type + end +end + +class EtagRenderTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = TestController.new + + @request.host = "www.nextangle.com" + end + + def test_render_200_should_set_etag + get :render_hello_world_from_variable + assert_equal etag_for("hello david"), @response.headers['ETag'] + assert_equal "private, max-age=0, must-revalidate", @response.headers['Cache-Control'] + end + + def test_render_against_etag_request_should_304_when_match + @request.if_none_match = etag_for("hello david") + get :render_hello_world_from_variable + assert_equal "304 Not Modified", @response.status + assert @response.body.empty? + end + + def test_render_against_etag_request_should_200_when_no_match + @request.if_none_match = etag_for("hello somewhere else") + get :render_hello_world_from_variable + assert_equal "200 OK", @response.status + assert !@response.body.empty? + end + + def test_render_with_etag + get :render_hello_world_from_variable + expected_etag = etag_for('hello david') + assert_equal expected_etag, @response.headers['ETag'] + + @request.if_none_match = expected_etag + get :render_hello_world_from_variable + assert_equal "304 Not Modified", @response.status + + @request.if_none_match = "\"diftag\"" + get :render_hello_world_from_variable + assert_equal "200 OK", @response.status + end + + def render_with_404_shouldnt_have_etag + get :render_custom_code + assert_nil @response.headers['ETag'] + end + + def test_etag_should_not_be_changed_when_already_set + expected_etag = etag_for("hello somewhere else") + @response.headers["ETag"] = expected_etag + get :render_hello_world_from_variable + assert_equal expected_etag, @response.headers['ETag'] + end + + def test_etag_should_govern_renders_with_layouts_too + get :builder_layout_test + assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body + assert_equal etag_for("<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n"), @response.headers['ETag'] + end + protected def etag_for(text) %("#{Digest::MD5.hexdigest(text)}") end end + +class LastModifiedRenderTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = TestController.new + + @request.host = "www.nextangle.com" + @last_modified = Time.now.utc.beginning_of_day.httpdate + end + + def test_responds_with_last_modified + get :conditional_hello + assert_equal @last_modified, @response.headers['Last-Modified'] + end + + def test_request_not_modified + @request.if_modified_since = @last_modified + get :conditional_hello + assert_equal "304 Not Modified", @response.status + assert @response.body.blank?, @response.body + assert_equal @last_modified, @response.headers['Last-Modified'] + end + + def test_request_modified + @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' + get :conditional_hello + assert_equal "200 OK", @response.status + assert !@response.body.blank? + assert_equal @last_modified, @response.headers['Last-Modified'] + end +end diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index 932c0e21a1..045dab4141 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -3,64 +3,69 @@ require 'action_controller/integration' class RequestTest < Test::Unit::TestCase def setup + ActionController::Base.relative_url_root = nil @request = ActionController::TestRequest.new end + def teardown + ActionController::Base.relative_url_root = nil + end + def test_remote_ip assert_equal '0.0.0.0', @request.remote_ip @request.remote_addr = '1.2.3.4' - assert_equal '1.2.3.4', @request.remote_ip + assert_equal '1.2.3.4', @request.remote_ip(true) @request.env['HTTP_CLIENT_IP'] = '2.3.4.5' - assert_equal '1.2.3.4', @request.remote_ip + assert_equal '1.2.3.4', @request.remote_ip(true) @request.remote_addr = '192.168.0.1' - assert_equal '2.3.4.5', @request.remote_ip + assert_equal '2.3.4.5', @request.remote_ip(true) @request.env.delete 'HTTP_CLIENT_IP' @request.remote_addr = '1.2.3.4' @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6' - assert_equal '1.2.3.4', @request.remote_ip + assert_equal '1.2.3.4', @request.remote_ip(true) @request.remote_addr = '127.0.0.1' @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip - + assert_equal '3.4.5.6', @request.remote_ip(true) + @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1, 10.0.0.1, 3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1' - assert_equal 'unknown', @request.remote_ip + assert_equal 'unknown', @request.remote_ip(true) @request.env['HTTP_X_FORWARDED_FOR'] = '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' - assert_equal '3.4.5.6', @request.remote_ip + assert_equal '3.4.5.6', @request.remote_ip(true) @request.env['HTTP_CLIENT_IP'] = '8.8.8.8' e = assert_raises(ActionController::ActionControllerError) { - @request.remote_ip + @request.remote_ip(true) } assert_match /IP spoofing attack/, e.message assert_match /HTTP_X_FORWARDED_FOR="9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4"/, e.message assert_match /HTTP_CLIENT_IP="8.8.8.8"/, e.message @request.env['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 9.9.9.9' - assert_equal '8.8.8.8', @request.remote_ip + assert_equal '8.8.8.8', @request.remote_ip(true) @request.env.delete 'HTTP_CLIENT_IP' @request.env.delete 'HTTP_X_FORWARDED_FOR' @@ -120,169 +125,118 @@ class RequestTest < Test::Unit::TestCase assert_equal ":8080", @request.port_string end - def test_relative_url_root - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3' - assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd" - - @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' - - @request.env['SCRIPT_NAME'] = nil - assert_equal "", @request.relative_url_root - - @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "", @request.relative_url_root - - @request.env['SCRIPT_NAME'] = "/myapp.rb" - assert_equal "", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - assert_equal "/hieraki", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi" - assert_equal "/collaboration/hieraki", @request.relative_url_root - - # apache/scgi case - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/collaboration/hieraki" - assert_equal "/collaboration/hieraki", @request.relative_url_root - - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3' - @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki" - assert_equal "/hieraki", @request.relative_url_root - - # @env overrides path guess - @request.relative_url_root = nil - @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" - @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' - @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url" - assert_equal "/real_url", @request.relative_url_root - end - def test_request_uri @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432' - @request.relative_url_root = nil @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1" assert_equal "/path/of/some/uri?mapped=1", @request.request_uri assert_equal "/path/of/some/uri", @request.path - @request.relative_url_root = nil @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri" assert_equal "/path/of/some/uri", @request.request_uri assert_equal "/path/of/some/uri", @request.path - @request.relative_url_root = nil @request.set_REQUEST_URI "/path/of/some/uri" assert_equal "/path/of/some/uri", @request.request_uri assert_equal "/path/of/some/uri", @request.path - @request.relative_url_root = nil @request.set_REQUEST_URI "/" assert_equal "/", @request.request_uri assert_equal "/", @request.path - @request.relative_url_root = nil @request.set_REQUEST_URI "/?m=b" assert_equal "/?m=b", @request.request_uri assert_equal "/", @request.path - @request.relative_url_root = nil @request.set_REQUEST_URI "/" @request.env['SCRIPT_NAME'] = "/dispatch.cgi" assert_equal "/", @request.request_uri assert_equal "/", @request.path - @request.relative_url_root = nil + ActionController::Base.relative_url_root = "/hieraki" @request.set_REQUEST_URI "/hieraki/" @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" assert_equal "/hieraki/", @request.request_uri assert_equal "/", @request.path + ActionController::Base.relative_url_root = nil - @request.relative_url_root = nil + ActionController::Base.relative_url_root = "/collaboration/hieraki" @request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2" @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi" assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri assert_equal "/books/edit/2", @request.path + ActionController::Base.relative_url_root = nil # The following tests are for when REQUEST_URI is not supplied (as in IIS) - @request.relative_url_root = nil - @request.set_REQUEST_URI nil @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1" @request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb" + @request.set_REQUEST_URI nil assert_equal "/path/of/some/uri?mapped=1", @request.request_uri assert_equal "/path/of/some/uri", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil + ActionController::Base.relative_url_root = '/path' @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1" @request.env['SCRIPT_NAME'] = "/path/dispatch.rb" - assert_equal "/path/of/some/uri?mapped=1", @request.request_uri - assert_equal "/of/some/uri", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil + assert_equal "/path/of/some/uri?mapped=1", @request.request_uri(true) + assert_equal "/of/some/uri", @request.path(true) + ActionController::Base.relative_url_root = nil + @request.env['PATH_INFO'] = "/path/of/some/uri" @request.env['SCRIPT_NAME'] = nil + @request.set_REQUEST_URI nil assert_equal "/path/of/some/uri", @request.request_uri assert_equal "/path/of/some/uri", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil @request.env['PATH_INFO'] = "/" + @request.set_REQUEST_URI nil assert_equal "/", @request.request_uri assert_equal "/", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil @request.env['PATH_INFO'] = "/?m=b" + @request.set_REQUEST_URI nil assert_equal "/?m=b", @request.request_uri assert_equal "/", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil @request.env['PATH_INFO'] = "/" @request.env['SCRIPT_NAME'] = "/dispatch.cgi" + @request.set_REQUEST_URI nil assert_equal "/", @request.request_uri assert_equal "/", @request.path - @request.set_REQUEST_URI nil - @request.relative_url_root = nil + ActionController::Base.relative_url_root = '/hieraki' @request.env['PATH_INFO'] = "/hieraki/" @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" + @request.set_REQUEST_URI nil assert_equal "/hieraki/", @request.request_uri assert_equal "/", @request.path + ActionController::Base.relative_url_root = nil @request.set_REQUEST_URI '/hieraki/dispatch.cgi' - @request.relative_url_root = '/hieraki' - assert_equal "/dispatch.cgi", @request.path - @request.relative_url_root = nil + ActionController::Base.relative_url_root = '/hieraki' + assert_equal "/dispatch.cgi", @request.path(true) + ActionController::Base.relative_url_root = nil @request.set_REQUEST_URI '/hieraki/dispatch.cgi' - @request.relative_url_root = '/foo' - assert_equal "/hieraki/dispatch.cgi", @request.path - @request.relative_url_root = nil + ActionController::Base.relative_url_root = '/foo' + assert_equal "/hieraki/dispatch.cgi", @request.path(true) + ActionController::Base.relative_url_root = nil # This test ensures that Rails uses REQUEST_URI over PATH_INFO - @request.relative_url_root = nil + ActionController::Base.relative_url_root = nil @request.env['REQUEST_URI'] = "/some/path" @request.env['PATH_INFO'] = "/another/path" @request.env['SCRIPT_NAME'] = "/dispatch.cgi" - assert_equal "/some/path", @request.request_uri - assert_equal "/some/path", @request.path + assert_equal "/some/path", @request.request_uri(true) + assert_equal "/some/path", @request.path(true) end - def test_host_with_default_port @request.host = "rubyonrails.org" @request.port = 80 assert_equal "rubyonrails.org", @request.host_with_port end - + def test_host_with_non_default_port @request.host = "rubyonrails.org" @request.port = 81 @@ -290,13 +244,13 @@ class RequestTest < Test::Unit::TestCase end def test_server_software - assert_equal nil, @request.server_software + assert_equal nil, @request.server_software(true) @request.env['SERVER_SOFTWARE'] = 'Apache3.422' - assert_equal 'apache', @request.server_software + assert_equal 'apache', @request.server_software(true) @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)' - assert_equal 'lighttpd', @request.server_software + assert_equal 'lighttpd', @request.server_software(true) end def test_xml_http_request @@ -326,44 +280,44 @@ class RequestTest < Test::Unit::TestCase def test_symbolized_request_methods [:get, :post, :put, :delete].each do |method| - set_request_method_to method + self.request_method = method assert_equal method, @request.method end end def test_invalid_http_method_raises_exception - set_request_method_to :random_method assert_raises(ActionController::UnknownHttpMethod) do - @request.method + self.request_method = :random_method end end def test_allow_method_hacking_on_post - set_request_method_to :post + self.request_method = :post [:get, :head, :options, :put, :post, :delete].each do |method| - @request.instance_eval { @parameters = { :_method => method } ; @request_method = nil } + @request.instance_eval { @parameters = { :_method => method.to_s } ; @request_method = nil } + @request.request_method(true) assert_equal(method == :head ? :get : method, @request.method) end end def test_invalid_method_hacking_on_post_raises_exception - set_request_method_to :post + self.request_method = :post @request.instance_eval { @parameters = { :_method => :random_method } ; @request_method = nil } assert_raises(ActionController::UnknownHttpMethod) do - @request.method + @request.request_method(true) end end def test_restrict_method_hacking @request.instance_eval { @parameters = { :_method => 'put' } } [:get, :put, :delete].each do |method| - set_request_method_to method + self.request_method = method assert_equal method, @request.method end end - def test_head_masquarading_as_get - set_request_method_to :head + def test_head_masquerading_as_get + self.request_method = :head assert_equal :get, @request.method assert @request.get? assert @request.head? @@ -385,9 +339,16 @@ class RequestTest < Test::Unit::TestCase end def test_nil_format - @request.instance_eval { @parameters = { :format => nil } } + ActionController::Base.use_accept_header, old = + false, ActionController::Base.use_accept_header + + @request.instance_eval { @parameters = {} } @request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + assert @request.xhr? assert_equal Mime::JS, @request.format + + ensure + ActionController::Base.use_accept_header = old end def test_content_type @@ -415,28 +376,27 @@ class RequestTest < Test::Unit::TestCase @request.env["CONTENT_TYPE"] = "application/xml; charset=UTF-8" assert_equal Mime::XML, @request.content_type end - + def test_user_agent assert_not_nil @request.user_agent end - + def test_parameters @request.instance_eval { @request_parameters = { "foo" => 1 } } @request.instance_eval { @query_parameters = { "bar" => 2 } } - + assert_equal({"foo" => 1, "bar" => 2}, @request.parameters) assert_equal({"foo" => 1}, @request.request_parameters) assert_equal({"bar" => 2}, @request.query_parameters) end protected - def set_request_method_to(method) + def request_method=(method) @request.env['REQUEST_METHOD'] = method.to_s.upcase - @request.instance_eval { @request_method = nil } + @request.request_method(true) end end - class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase def setup @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" @@ -548,7 +508,6 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase ) end - def test_request_hash_parsing query = { "note[viewers][viewer][][type]" => ["User", "Group"], @@ -560,7 +519,6 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query)) end - def test_parse_params input = { "customers[boston][first][name]" => [ "David" ], @@ -743,7 +701,6 @@ class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase end end - class MultipartRequestParameterParsingTest < Test::Unit::TestCase FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart' @@ -774,19 +731,19 @@ class MultipartRequestParameterParsingTest < Test::Unit::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_equal 'file.txt', file.original_filename assert_equal "text/plain", file.content_type - + assert_equal 'bar', foo end - + def test_large_text_file params = process('large_text_file') assert_equal %w(file foo), params.keys.sort @@ -891,20 +848,20 @@ class XmlParamsParsingTest < Test::Unit::TestCase private def parse_body(body) - env = { 'CONTENT_TYPE' => 'application/xml', + env = { 'rack.input' => StringIO.new(body), + 'CONTENT_TYPE' => 'application/xml', 'CONTENT_LENGTH' => body.size.to_s } - cgi = ActionController::Integration::Session::StubCGI.new(env, body) - ActionController::CgiRequest.new(cgi).request_parameters + ActionController::RackRequest.new(env).request_parameters end end class LegacyXmlParamsParsingTest < XmlParamsParsingTest private def parse_body(body) - env = { 'HTTP_X_POST_DATA_FORMAT' => 'xml', - 'CONTENT_LENGTH' => body.size.to_s } - cgi = ActionController::Integration::Session::StubCGI.new(env, body) - ActionController::CgiRequest.new(cgi).request_parameters + env = { 'rack.input' => StringIO.new(body), + 'HTTP_X_POST_DATA_FORMAT' => 'xml', + 'CONTENT_LENGTH' => body.size.to_s } + ActionController::RackRequest.new(env).request_parameters end end @@ -923,9 +880,9 @@ class JsonParamsParsingTest < Test::Unit::TestCase private def parse_body(body,content_type) - env = { 'CONTENT_TYPE' => content_type, + env = { 'rack.input' => StringIO.new(body), + 'CONTENT_TYPE' => content_type, 'CONTENT_LENGTH' => body.size.to_s } - cgi = ActionController::Integration::Session::StubCGI.new(env, body) - ActionController::CgiRequest.new(cgi).request_parameters + ActionController::RackRequest.new(env).request_parameters end end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 27fcc5e04c..da076d2090 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -62,6 +62,11 @@ class RescueController < ActionController::Base render :text => exception.message end + # This is a Dispatcher exception and should be in ApplicationController. + rescue_from ActionController::RoutingError do + render :text => 'no way' + end + def raises render :text => 'already rendered' raise "don't panic!" @@ -378,6 +383,10 @@ class RescueTest < Test::Unit::TestCase assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body end + def test_rescue_dispatcher_exceptions + RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found")) + assert_equal "no way", @response.body + end protected def with_all_requests_local(local = true) diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 0f7924649a..e153b0cc98 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -516,6 +516,26 @@ class ResourcesTest < Test::Unit::TestCase end end + def test_should_not_allow_invalid_head_method_for_member_routes + with_routing do |set| + set.draw do |map| + assert_raises(ArgumentError) do + map.resources :messages, :member => {:something => :head} + end + end + end + end + + def test_should_not_allow_invalid_http_methods_for_member_routes + with_routing do |set| + set.draw do |map| + assert_raises(ArgumentError) do + map.resources :messages, :member => {:something => :invalid} + end + end + end + end + def test_resource_action_separator with_routing do |set| set.draw do |map| diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index c5ccb71582..6cf134c26f 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -67,66 +67,56 @@ class SegmentTest < Test::Unit::TestCase end def test_interpolation_statement - s = ROUTING::StaticSegment.new - s.value = "Hello" + s = ROUTING::StaticSegment.new("Hello") assert_equal "Hello", eval(s.interpolation_statement([])) assert_equal "HelloHello", eval(s.interpolation_statement([s])) - s2 = ROUTING::StaticSegment.new - s2.value = "-" + s2 = ROUTING::StaticSegment.new("-") assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2])) - s3 = ROUTING::StaticSegment.new - s3.value = "World" + s3 = ROUTING::StaticSegment.new("World") assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2])) end end class StaticSegmentTest < Test::Unit::TestCase def test_interpolation_chunk_should_respect_raw - s = ROUTING::StaticSegment.new - s.value = 'Hello World' - assert ! s.raw? + s = ROUTING::StaticSegment.new('Hello World') + assert !s.raw? assert_equal 'Hello%20World', s.interpolation_chunk - s.raw = true + s = ROUTING::StaticSegment.new('Hello World', :raw => true) assert s.raw? assert_equal 'Hello World', s.interpolation_chunk end def test_regexp_chunk_should_escape_specials - s = ROUTING::StaticSegment.new - - s.value = 'Hello*World' + s = ROUTING::StaticSegment.new('Hello*World') assert_equal 'Hello\*World', s.regexp_chunk - s.value = 'HelloWorld' + s = ROUTING::StaticSegment.new('HelloWorld') assert_equal 'HelloWorld', s.regexp_chunk end def test_regexp_chunk_should_add_question_mark_for_optionals - s = ROUTING::StaticSegment.new - s.value = "/" - s.is_optional = true + s = ROUTING::StaticSegment.new("/", :optional => true) assert_equal "/?", s.regexp_chunk - s.value = "hello" + s = ROUTING::StaticSegment.new("hello", :optional => true) assert_equal "(?:hello)?", s.regexp_chunk end end class DynamicSegmentTest < Test::Unit::TestCase - def segment + def segment(options = {}) unless @segment - @segment = ROUTING::DynamicSegment.new - @segment.key = :a + @segment = ROUTING::DynamicSegment.new(:a, options) end @segment end def test_extract_value - s = ROUTING::DynamicSegment.new - s.key = :a + s = ROUTING::DynamicSegment.new(:a) hash = {:a => '10', :b => '20'} assert_equal '10', eval(s.extract_value) @@ -149,31 +139,31 @@ class DynamicSegmentTest < Test::Unit::TestCase end def test_regexp_value_check_rejects_nil - segment.regexp = /\d+/ + segment = segment(:regexp => /\d+/) + a_value = nil - assert ! eval(segment.value_check) + assert !eval(segment.value_check) end def test_optional_regexp_value_check_should_accept_nil - segment.regexp = /\d+/ - segment.is_optional = true + segment = segment(:regexp => /\d+/, :optional => true) + a_value = nil assert eval(segment.value_check) end def test_regexp_value_check_rejects_no_match - segment.regexp = /\d+/ + segment = segment(:regexp => /\d+/) a_value = "Hello20World" - assert ! eval(segment.value_check) + assert !eval(segment.value_check) a_value = "20Hi" - assert ! eval(segment.value_check) + assert !eval(segment.value_check) end def test_regexp_value_check_accepts_match - segment.regexp = /\d+/ - + segment = segment(:regexp => /\d+/) a_value = "30" assert eval(segment.value_check) end @@ -184,14 +174,14 @@ class DynamicSegmentTest < Test::Unit::TestCase end def test_optional_value_needs_no_check - segment.is_optional = true + segment = segment(:optional => true) + a_value = nil assert_equal nil, segment.value_check end def test_regexp_value_check_should_accept_match_with_default - segment.regexp = /\d+/ - segment.default = '200' + segment = segment(:regexp => /\d+/, :default => '200') a_value = '100' assert eval(segment.value_check) @@ -234,7 +224,7 @@ class DynamicSegmentTest < Test::Unit::TestCase end def test_extraction_code_should_return_on_mismatch - segment.regexp = /\d+/ + segment = segment(:regexp => /\d+/) hash = merged = {:a => 'Hi', :b => '3'} options = {:b => '3'} a_value = nil @@ -292,7 +282,7 @@ class DynamicSegmentTest < Test::Unit::TestCase end def test_value_regexp_should_match_exacly - segment.regexp = /\d+/ + segment = segment(:regexp => /\d+/) assert_no_match segment.value_regexp, "Hello 10 World" assert_no_match segment.value_regexp, "Hello 10" assert_no_match segment.value_regexp, "10 World" @@ -300,40 +290,36 @@ class DynamicSegmentTest < Test::Unit::TestCase end def test_regexp_chunk_should_return_string - segment.regexp = /\d+/ + segment = segment(:regexp => /\d+/) assert_kind_of String, segment.regexp_chunk end def test_build_pattern_non_optional_with_no_captures # Non optional - a_segment = ROUTING::DynamicSegment.new - a_segment.regexp = /\d+/ #number_of_captures is 0 + a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /\d+/) assert_equal "(\\d+)stuff", a_segment.build_pattern('stuff') end def test_build_pattern_non_optional_with_captures # Non optional - a_segment = ROUTING::DynamicSegment.new - a_segment.regexp = /(\d+)(.*?)/ #number_of_captures is 2 + a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /(\d+)(.*?)/) assert_equal "((\\d+)(.*?))stuff", a_segment.build_pattern('stuff') end def test_optionality_implied - a_segment = ROUTING::DynamicSegment.new - a_segment.key = :id + a_segment = ROUTING::DynamicSegment.new(:id) assert a_segment.optionality_implied? - a_segment.key = :action + a_segment = ROUTING::DynamicSegment.new(:action) assert a_segment.optionality_implied? end def test_modifiers_must_be_handled_sensibly - a_segment = ROUTING::DynamicSegment.new - a_segment.regexp = /david|jamis/i + a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/i) assert_equal "((?i-mx:david|jamis))stuff", a_segment.build_pattern('stuff') - a_segment.regexp = /david|jamis/x + a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/x) assert_equal "((?x-mi:david|jamis))stuff", a_segment.build_pattern('stuff') - a_segment.regexp = /david|jamis/ + a_segment = ROUTING::DynamicSegment.new(nil, :regexp => /david|jamis/) assert_equal "(david|jamis)stuff", a_segment.build_pattern('stuff') end end @@ -560,7 +546,7 @@ class RouteBuilderTest < Test::Unit::TestCase action = segments[-4] assert_equal :action, action.key - action.regexp = /show|in/ # Use 'in' to check partial matches + segments[-4] = ROUTING::DynamicSegment.new(:action, :regexp => /show|in/) builder.assign_default_route_options(segments) @@ -661,10 +647,10 @@ class RoutingTest < Test::Unit::TestCase ActionController::Routing.controller_paths = [] assert_equal [], ActionController::Routing.possible_controllers - ActionController::Routing::Routes.load! ActionController::Routing.controller_paths = [ root, root + '/app/controllers', root + '/vendor/plugins/bad_plugin/lib' ] + ActionController::Routing::Routes.load! assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort ensure @@ -731,15 +717,10 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def request @request ||= MockRequest.new(:host => "named.route.test", :method => :get) end - - def relative_url_root=(value) - request.relative_url_root=value - end end class MockRequest - attr_accessor :path, :path_parameters, :host, :subdomains, :domain, - :method, :relative_url_root + attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method def initialize(values={}) values.each { |key, value| send("#{key}=", value) } @@ -839,6 +820,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do puts "#{1 / per_url} url/s\n\n" end end + def test_time_generation n = 5000 if RunTimeTests @@ -920,10 +902,11 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_basic_named_route_with_relative_url_root rs.add_named_route :home, '', :controller => 'content', :action => 'list' x = setup_for_named_route - x.relative_url_root="/foo" + ActionController::Base.relative_url_root = "/foo" assert_equal("http://named.route.test/foo/", x.send(:home_url)) assert_equal "/foo/", x.send(:home_path) + ActionController::Base.relative_url_root = nil end def test_named_route_with_option @@ -1377,34 +1360,20 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def slash_segment(is_optional = false) - returning ROUTING::DividerSegment.new('/') do |s| - s.is_optional = is_optional - end + ROUTING::DividerSegment.new('/', :optional => is_optional) end def default_route unless defined?(@default_route) - @default_route = ROUTING::Route.new - - @default_route.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :controller - - @default_route.segments << slash_segment(:optional) - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :action - s.default = 'index' - s.is_optional = true - - @default_route.segments << slash_segment(:optional) - @default_route.segments << (s = ROUTING::DynamicSegment.new) - s.key = :id - s.is_optional = true - - @default_route.segments << slash_segment(:optional) + segments = [] + segments << ROUTING::StaticSegment.new('/', :raw => true) + segments << ROUTING::DynamicSegment.new(:controller) + segments << slash_segment(:optional) + segments << ROUTING::DynamicSegment.new(:action, :default => 'index', :optional => true) + segments << slash_segment(:optional) + segments << ROUTING::DynamicSegment.new(:id, :optional => true) + segments << slash_segment(:optional) + @default_route = ROUTING::Route.new(segments).freeze end @default_route end @@ -1492,29 +1461,16 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def test_significant_keys - user_url = ROUTING::Route.new - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = 'user' - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - s.is_optional = true - - user_url.segments << (s = ROUTING::DynamicSegment.new) - s.key = :user - - user_url.segments << (s = ROUTING::StaticSegment.new) - s.value = '/' - s.raw = true - s.is_optional = true + segments = [] + segments << ROUTING::StaticSegment.new('/', :raw => true) + segments << ROUTING::StaticSegment.new('user') + segments << ROUTING::StaticSegment.new('/', :raw => true, :optional => true) + segments << ROUTING::DynamicSegment.new(:user) + segments << ROUTING::StaticSegment.new('/', :raw => true, :optional => true) - user_url.requirements = {:controller => 'users', :action => 'show'} + requirements = {:controller => 'users', :action => 'show'} + user_url = ROUTING::Route.new(segments, requirements) keys = user_url.significant_keys.sort_by { |k| k.to_s } assert_equal [:action, :controller, :user], keys end @@ -1801,6 +1757,22 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end end + def test_route_requirements_with_invalid_http_method_is_invalid + assert_raises ArgumentError do + set.draw do |map| + map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :invalid} + end + end + end + + def test_route_requirements_with_head_method_condition_is_invalid + assert_raises ArgumentError do + set.draw do |map| + map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :head} + end + end + end + def test_non_path_route_requirements_match_all set.draw do |map| map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 5adaeaf5c5..5adaeaf5c5 100755..100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index b624005a57..58d9ca537f 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -64,7 +64,7 @@ class TestTest < Test::Unit::TestCase </html> HTML end - + def test_xml_output response.content_type = "application/xml" render :text => <<XML @@ -117,8 +117,8 @@ XML @controller = TestController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - ActionController::Routing::Routes.reload ActionController::Routing.use_controllers! %w(content admin/user test_test/test) + ActionController::Routing::Routes.load_routes! end def teardown @@ -366,7 +366,7 @@ XML :children => { :count => 1, :only => { :tag => "img" } } } } end - + def test_should_not_impose_childless_html_tags_in_xml process :test_xml_output @@ -412,7 +412,7 @@ XML def test_assert_routing_with_method with_routing do |set| - set.draw { |map| map.resources(:content) } + set.draw { |map| map.resources(:content) } assert_routing({ :method => 'post', :path => 'content' }, { :controller => 'content', :action => 'create' }) end end @@ -486,7 +486,7 @@ XML assert_nil @request.env['HTTP_X_REQUESTED_WITH'] end - def test_header_properly_reset_after_get_request + def test_header_properly_reset_after_get_request get :test_params @request.recycle! assert_nil @request.instance_variable_get("@request_method") @@ -532,15 +532,15 @@ XML assert_equal file.path, file.local_path assert_equal expected, file.read end - + def test_test_uploaded_file_with_binary filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" content_type = 'image/png' - + binary_uploaded_file = ActionController::TestUploadedFile.new(path, content_type, :binary) assert_equal File.open(path, READ_BINARY).read, binary_uploaded_file.read - + plain_uploaded_file = ActionController::TestUploadedFile.new(path, content_type) assert_equal File.open(path, READ_PLAIN).read, plain_uploaded_file.read end @@ -549,10 +549,10 @@ XML filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" content_type = 'image/jpg' - + binary_file_upload = fixture_file_upload(path, content_type, :binary) assert_equal File.open(path, READ_BINARY).read, binary_file_upload.read - + plain_file_upload = fixture_file_upload(path, content_type) assert_equal File.open(path, READ_PLAIN).read, plain_file_upload.read end @@ -584,7 +584,7 @@ XML get :test_send_file assert_nothing_raised(NoMethodError) { @response.binary_content } end - + protected def with_foo_routing with_routing do |set| @@ -597,7 +597,6 @@ XML end end - class CleanBacktraceTest < Test::Unit::TestCase def test_should_reraise_the_same_object exception = Test::Unit::AssertionFailedError.new('message') @@ -658,7 +657,7 @@ end class NamedRoutesControllerTest < ActionController::TestCase tests ContentController - + def test_should_be_able_to_use_named_routes_before_a_request_is_done with_routing do |set| set.draw { |map| map.resources :contents } diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index a9974db5d5..64e9a085ca 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -7,7 +7,7 @@ class UrlRewriterTests < Test::Unit::TestCase @request = ActionController::TestRequest.new @params = {} @rewriter = ActionController::UrlRewriter.new(@request, @params) - end + end def test_port assert_equal('http://test.host:1271/c/a/i', @@ -24,7 +24,7 @@ class UrlRewriterTests < Test::Unit::TestCase @rewriter.rewrite(:protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') ) end - + def test_user_name_and_password assert_equal( 'http://david:secret@test.host/c/a/i', @@ -38,12 +38,12 @@ class UrlRewriterTests < Test::Unit::TestCase @rewriter.rewrite(:user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') ) end - - def test_anchor - assert_equal( - 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') - ) + + def test_anchor + assert_equal( + 'http://test.host/c/a/i#anchor', + @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') + ) end def test_overwrite_params @@ -55,12 +55,12 @@ class UrlRewriterTests < Test::Unit::TestCase u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'}) assert_match %r(/hi/hi/2$), u end - + def test_overwrite_removes_original @params[:controller] = 'search' @params[:action] = 'list' @params[:list_page] = 1 - + assert_equal '/search/list?list_page=2', @rewriter.rewrite(:only_path => true, :overwrite_params => {"list_page" => 2}) u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:list_page => 2}) assert_equal 'http://test.host/search/list?list_page=2', u @@ -86,19 +86,19 @@ class UrlRewriterTests < Test::Unit::TestCase end class UrlWriterTests < Test::Unit::TestCase - + class W include ActionController::UrlWriter end - + def teardown W.default_url_options.clear end - + def add_host! W.default_url_options[:host] = 'www.basecamphq.com' end - + def test_exception_is_thrown_without_host assert_raises RuntimeError do W.new.url_for :controller => 'c', :action => 'a', :id => 'i' @@ -110,35 +110,35 @@ class UrlWriterTests < Test::Unit::TestCase W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') ) end - + def test_default_host add_host! assert_equal('http://www.basecamphq.com/c/a/i', W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') ) end - + def test_host_may_be_overridden add_host! assert_equal('http://37signals.basecamphq.com/c/a/i', W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') ) end - + def test_port add_host! assert_equal('http://www.basecamphq.com:3000/c/a/i', W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) ) end - + def test_protocol add_host! assert_equal('https://www.basecamphq.com/c/a/i', W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') ) end - + def test_protocol_with_and_without_separator add_host! assert_equal('https://www.basecamphq.com/c/a/i', @@ -184,15 +184,15 @@ class UrlWriterTests < Test::Unit::TestCase end def test_relative_url_root_is_respected - orig_relative_url_root = ActionController::AbstractRequest.relative_url_root - ActionController::AbstractRequest.relative_url_root = '/subdir' + orig_relative_url_root = ActionController::Base.relative_url_root + ActionController::Base.relative_url_root = '/subdir' add_host! assert_equal('https://www.basecamphq.com/subdir/c/a/i', W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') ) ensure - ActionController::AbstractRequest.relative_url_root = orig_relative_url_root + ActionController::Base.relative_url_root = orig_relative_url_root end def test_named_routes @@ -217,8 +217,8 @@ class UrlWriterTests < Test::Unit::TestCase end def test_relative_url_root_is_respected_for_named_routes - orig_relative_url_root = ActionController::AbstractRequest.relative_url_root - ActionController::AbstractRequest.relative_url_root = '/subdir' + orig_relative_url_root = ActionController::Base.relative_url_root + ActionController::Base.relative_url_root = '/subdir' ActionController::Routing::Routes.draw do |map| map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' @@ -231,7 +231,7 @@ class UrlWriterTests < Test::Unit::TestCase controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') ensure ActionController::Routing::Routes.load! - ActionController::AbstractRequest.relative_url_root = orig_relative_url_root + ActionController::Base.relative_url_root = orig_relative_url_root end def test_only_path @@ -239,14 +239,14 @@ class UrlWriterTests < Test::Unit::TestCase map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' 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 assert controller.respond_to?(:home_url) assert_equal '/brave/new/world', controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true) - + assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true)) assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama')) ensure @@ -306,5 +306,4 @@ class UrlWriterTests < Test::Unit::TestCase def extract_params(url) url.split('?', 2).last.split('&') end - end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 85fa58a45b..b859a92cbd 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -54,10 +54,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths @controller.append_view_path(FIXTURE_LOAD_PATH) - assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths - - @controller.append_view_path([FIXTURE_LOAD_PATH]) - assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths + assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths end def test_controller_prepends_view_path_correctly @@ -68,10 +65,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths @controller.prepend_view_path(FIXTURE_LOAD_PATH) - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths - - @controller.prepend_view_path([FIXTURE_LOAD_PATH]) - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths + assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths end def test_template_appends_view_path_correctly diff --git a/actionpack/test/fixtures/_top_level_partial.html.erb b/actionpack/test/fixtures/_top_level_partial.html.erb new file mode 100644 index 0000000000..0b1c2e46e0 --- /dev/null +++ b/actionpack/test/fixtures/_top_level_partial.html.erb @@ -0,0 +1 @@ +top level partial html
\ No newline at end of file diff --git a/actionpack/test/fixtures/_top_level_partial_only.erb b/actionpack/test/fixtures/_top_level_partial_only.erb new file mode 100644 index 0000000000..44f25b61d0 --- /dev/null +++ b/actionpack/test/fixtures/_top_level_partial_only.erb @@ -0,0 +1 @@ +top level partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb new file mode 100644 index 0000000000..d7f43ad95e --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb @@ -0,0 +1,3 @@ +<body> +<% cache do %><p>ERB</p><% end %> +</body>
\ No newline at end of file diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs new file mode 100644 index 0000000000..057f15e62f --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs @@ -0,0 +1,6 @@ +page.assign 'title', 'Hey' +cache do + page['element_1'].visual_effect :highlight + page['element_2'].visual_effect :highlight +end +page.assign 'footer', 'Bye' diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder new file mode 100644 index 0000000000..efdcc28e0f --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder @@ -0,0 +1,5 @@ +xml.body do + cache do + xml.p "Builder" + end +end diff --git a/actionpack/test/fixtures/test/_counter.html.erb b/actionpack/test/fixtures/test/_counter.html.erb new file mode 100644 index 0000000000..fd245bfc70 --- /dev/null +++ b/actionpack/test/fixtures/test/_counter.html.erb @@ -0,0 +1 @@ +<%= counter_counter %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_layout_for_block_with_args.html.erb b/actionpack/test/fixtures/test/_layout_for_block_with_args.html.erb new file mode 100644 index 0000000000..307533208d --- /dev/null +++ b/actionpack/test/fixtures/test/_layout_for_block_with_args.html.erb @@ -0,0 +1,3 @@ +Before +<%= yield 'arg1', 'arg2' %> +After
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/implicit_content_type.atom.builder b/actionpack/test/fixtures/test/implicit_content_type.atom.builder new file mode 100644 index 0000000000..2fcb32d247 --- /dev/null +++ b/actionpack/test/fixtures/test/implicit_content_type.atom.builder @@ -0,0 +1,2 @@ +xml.atom do +end diff --git a/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb b/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb new file mode 100644 index 0000000000..71b1f30ad0 --- /dev/null +++ b/actionpack/test/fixtures/test/using_layout_around_block_with_args.html.erb @@ -0,0 +1 @@ +<% render(:layout => "layout_for_block_with_args") do |*args| %><%= args.join %><% end %>
\ No newline at end of file diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb new file mode 100644 index 0000000000..7ba9659814 --- /dev/null +++ b/actionpack/test/template/active_record_helper_i18n_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' + +class ActiveRecordHelperI18nTest < Test::Unit::TestCase + include ActionView::Helpers::ActiveRecordHelper + + attr_reader :request + uses_mocha 'active_record_helper_i18n_test' do + def setup + @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages']) + @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:' + 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') + 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('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' + error_messages_for(:object => @object, :locale => 'en-US') + 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('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' + error_messages_for(:object => @object, :message => 'message', :locale => 'en-US') + 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('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' + error_messages_for(:object => @object, :locale => 'en-US') + 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(@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) + end + end +end diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb index dfc30e651a..e46f95d18b 100644 --- a/actionpack/test/template/active_record_helper_test.rb +++ b/actionpack/test/template/active_record_helper_test.rb @@ -10,17 +10,17 @@ class ActiveRecordHelperTest < ActionView::TestCase alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) end - + User = Struct.new("User", :email) User.class_eval do alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast) end - + Column = Struct.new("Column", :type, :name, :human_name) end def setup_post - @post = Post.new + @post = Post.new def @post.errors Class.new { def on(field) @@ -33,12 +33,12 @@ class ActiveRecordHelperTest < ActionView::TestCase false end end - def empty?() false end - def count() 1 end + def empty?() false end + def count() 1 end def full_messages() [ "Author name can't be empty" ] end }.new end - + def @post.new_record?() true end def @post.to_param() nil end @@ -58,16 +58,16 @@ class ActiveRecordHelperTest < ActionView::TestCase end def setup_user - @user = User.new + @user = User.new def @user.errors Class.new { def on(field) field == "email" end - def empty?() false end - def count() 1 end + def empty?() false end + def count() 1 end def full_messages() [ "User email can't be empty" ] end }.new end - + def @user.new_record?() true end def @user.to_param() nil end @@ -81,7 +81,7 @@ class ActiveRecordHelperTest < ActionView::TestCase @user.email = "" end - + def protect_against_forgery? @protect_against_forgery ? true : false end @@ -92,7 +92,7 @@ class ActiveRecordHelperTest < ActionView::TestCase setup_user @response = ActionController::TestResponse.new - + @controller = Object.new def @controller.url_for(options) options = options.symbolize_keys @@ -111,7 +111,7 @@ class ActiveRecordHelperTest < ActionView::TestCase assert_dom_equal( %(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>), text_area("post", "body") - ) + ) end def test_text_field_with_errors @@ -140,7 +140,7 @@ class ActiveRecordHelperTest < ActionView::TestCase form("post") ) end - + def test_form_with_protect_against_forgery @protect_against_forgery = true @request_forgery_protection_token = 'authenticity_token' @@ -150,7 +150,7 @@ class ActiveRecordHelperTest < ActionView::TestCase form("post") ) end - + def test_form_with_method_option assert_dom_equal( %(<form action="create" method="get"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>), @@ -211,9 +211,9 @@ class ActiveRecordHelperTest < ActionView::TestCase other_post = @post assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_message_on(other_post, :author_name) end - - def test_error_message_on_should_use_options - assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter</div>", error_message_on(:post, :author_name, "before", "after", "differentError") + + def test_error_message_on_with_options_hash + assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter</div>", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after') end def test_error_messages_for_many_objects @@ -224,10 +224,10 @@ class ActiveRecordHelperTest < ActionView::TestCase # add the default to put post back in the title assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object_name => "post") - + # symbols work as well assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => :post) - + # any default works too assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this monkey from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => "monkey") @@ -242,7 +242,7 @@ class ActiveRecordHelperTest < ActionView::TestCase message = "Please fix the following fields and resubmit:" assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>#{header_message}</h2><p>#{message}</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :header_message => header_message, :message => message) end - + def test_error_messages_for_non_instance_variable actual_user = @user actual_post = @post @@ -251,14 +251,14 @@ class ActiveRecordHelperTest < ActionView::TestCase #explicitly set object assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :object => actual_post) - + #multiple objects assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object => [actual_user, actual_post]) - + #nil object assert_equal '', error_messages_for('user', :object => nil) end - + def test_form_with_string_multipart assert_dom_equal( %(<form action="create" enctype="multipart/form-data" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>), diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 020e112fd0..7e40a55dc5 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -30,7 +30,6 @@ class AssetTagHelperTest < ActionView::TestCase end.new @request = Class.new do - def relative_url_root() "" end def protocol() 'http://' end def ssl?() false end def host_with_port() 'localhost' end @@ -39,8 +38,7 @@ class AssetTagHelperTest < ActionView::TestCase @controller.request = @request ActionView::Helpers::AssetTagHelper::reset_javascript_include_default - - ActionView::Base.computed_public_paths.clear + COMPUTED_PUBLIC_PATHS.clear end def teardown @@ -119,7 +117,7 @@ class AssetTagHelperTest < ActionView::TestCase %(image_path("xml")) => %(/images/xml), %(image_path("xml.png")) => %(/images/xml.png), %(image_path("dir/xml.png")) => %(/images/dir/xml.png), - %(image_path("/dir/xml.png")) => %(/dir/xml.png) + %(image_path("/dir/xml.png")) => %(/dir/xml.png) } PathToImageToTag = { @@ -161,7 +159,7 @@ class AssetTagHelperTest < ActionView::TestCase ENV["RAILS_ASSET_ID"] = "" JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } - ActionView::Base.computed_public_paths.clear + COMPUTED_PUBLIC_PATHS.clear ENV["RAILS_ASSET_ID"] = "1" assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults)) @@ -174,7 +172,7 @@ class AssetTagHelperTest < ActionView::TestCase ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2' assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/elsewhere/blub/lib2.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults) end - + def test_custom_javascript_expansions ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => ["head", "body", "tail"] assert_dom_equal %(<script src="/javascripts/first.js" type="text/javascript"></script>\n<script src="/javascripts/head.js" type="text/javascript"></script>\n<script src="/javascripts/body.js" type="text/javascript"></script>\n<script src="/javascripts/tail.js" type="text/javascript"></script>\n<script src="/javascripts/last.js" type="text/javascript"></script>), javascript_include_tag('first', :monkey, 'last') @@ -217,7 +215,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_image_path ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end - + def test_path_to_image_alias_for_image_path PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -225,7 +223,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_image_tag ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end - + def test_timebased_asset_id expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png") @@ -234,7 +232,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_should_skip_asset_id_on_complete_url assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png") end - + def test_should_use_preset_asset_id ENV["RAILS_ASSET_ID"] = "4500" assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png") @@ -256,14 +254,14 @@ class AssetTagHelperTest < ActionView::TestCase ENV["RAILS_ASSET_ID"] = "" ActionController::Base.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true - + assert_dom_equal( %(<script src="http://a0.example.com/javascripts/all.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => true) ) assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - + assert_dom_equal( %(<script src="http://a0.example.com/javascripts/money.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => "money") @@ -335,8 +333,9 @@ class AssetTagHelperTest < ActionView::TestCase ActionController::Base.asset_host = 'http://a%d.example.com' ActionController::Base.perform_caching = true + hash = '/javascripts/cache/money.js'.hash % 4 assert_dom_equal( - %(<script src="http://a3.example.com/javascripts/cache/money.js" type="text/javascript"></script>), + %(<script src="http://a#{hash}.example.com/javascripts/cache/money.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => "cache/money") ) @@ -344,7 +343,7 @@ class AssetTagHelperTest < ActionView::TestCase ensure FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js')) end - + def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" ActionController::Base.asset_host = 'http://a0.example.com' @@ -390,7 +389,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" ActionController::Base.perform_caching = false - + assert_dom_equal( %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => true) @@ -402,7 +401,7 @@ class AssetTagHelperTest < ActionView::TestCase ) assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - + assert_dom_equal( %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>), javascript_include_tag(:all, :cache => "money") @@ -420,13 +419,14 @@ class AssetTagHelperTest < ActionView::TestCase ENV["RAILS_ASSET_ID"] = "" ActionController::Base.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true - + assert_dom_equal( %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:all, :cache => true) ) - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) + expected = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/*.css"].map { |p| File.mtime(p) }.max + assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) assert_dom_equal( %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />), @@ -459,7 +459,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" ActionController::Base.perform_caching = false - + assert_dom_equal( %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:all, :cache => true) @@ -471,7 +471,7 @@ class AssetTagHelperTest < ActionView::TestCase ) assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - + assert_dom_equal( %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:all, :cache => "money") @@ -490,6 +490,8 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper def setup + ActionController::Base.relative_url_root = "/collaboration/hieraki" + @controller = Class.new do attr_accessor :request @@ -497,22 +499,22 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase "http://www.example.com/collaboration/hieraki" end end.new - - @request = Class.new do - def relative_url_root - "/collaboration/hieraki" - end + @request = Class.new do def protocol 'gopher://' end end.new - + @controller.request = @request - + ActionView::Helpers::AssetTagHelper::reset_javascript_include_default end + def teardown + ActionController::Base.relative_url_root = nil + end + def test_should_compute_proper_path assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) @@ -521,7 +523,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse.png'" src="/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse2.png'" src="/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end - + def test_should_ignore_relative_root_path_on_complete_url assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png")) end diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 9f7e5b4c6c..ef31ab2c76 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -74,12 +74,30 @@ class ScrollsController < ActionController::Base end end EOT + FEEDS["feed_with_overridden_ids"] = <<-EOT + atom_feed({:id => 'tag:test.rubyonrails.org,2008:test/'}) do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll, :id => "tag:test.rubyonrails.org,2008:"+scroll.id.to_s) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + entry.tag!('app:edited', Time.now) + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT def index @scrolls = [ Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)), Scroll.new(2, "2", "Hello Two", "Something Boring", Time.utc(2007, 12, 12, 15)), ] - + render :inline => FEEDS[params[:id]], :type => :builder end @@ -98,21 +116,21 @@ class AtomFeedTest < Test::Unit::TestCase @request.host = "www.nextangle.com" end - + def test_feed_should_use_default_language_if_none_is_given with_restful_routing(:scrolls) do get :index, :id => "defaults" assert_match %r{xml:lang="en-US"}, @response.body end end - + def test_feed_should_include_two_entries with_restful_routing(:scrolls) do get :index, :id => "defaults" assert_select "entry", 2 end end - + def test_entry_should_only_use_published_if_created_at_is_present with_restful_routing(:scrolls) do get :index, :id => "defaults" @@ -167,7 +185,16 @@ class AtomFeedTest < Test::Unit::TestCase end end - private + def test_feed_should_allow_overriding_ids + with_restful_routing(:scrolls) do + get :index, :id => "feed_with_overridden_ids" + assert_select "id", :text => "tag:test.rubyonrails.org,2008:test/" + assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:1" + assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:2" + end + end + +private def with_restful_routing(resources) with_routing do |set| set.draw do |map| diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb new file mode 100644 index 0000000000..e005aa0f03 --- /dev/null +++ b/actionpack/test/template/compiled_templates_test.rb @@ -0,0 +1,47 @@ +require 'abstract_unit' +require 'controller/fake_models' + +uses_mocha 'TestTemplateRecompilation' do + class CompiledTemplatesTest < Test::Unit::TestCase + def setup + @compiled_templates = ActionView::Base::CompiledTemplates + @compiled_templates.instance_methods.each do |m| + @compiled_templates.send(:remove_method, m) if m =~ /^_run_/ + end + end + + def test_template_gets_compiled + assert_equal 0, @compiled_templates.instance_methods.size + assert_equal "Hello world!", render("test/hello_world.erb") + assert_equal 1, @compiled_templates.instance_methods.size + end + + def test_template_gets_recompiled_when_using_different_keys_in_local_assigns + assert_equal 0, @compiled_templates.instance_methods.size + assert_equal "Hello world!", render("test/hello_world.erb") + assert_equal "Hello world!", render("test/hello_world.erb", {:foo => "bar"}) + assert_equal 2, @compiled_templates.instance_methods.size + end + + def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_local_assigns + assert_equal 0, @compiled_templates.instance_methods.size + assert_equal "Hello world!", render("test/hello_world.erb") + ActionView::Template.any_instance.expects(:compile!).never + assert_equal "Hello world!", render("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("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") + ActionView::Template.any_instance.expects(:compile!).times(3) + 3.times { assert_equal "Hello world!", render("#{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) + end + end +end diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb new file mode 100644 index 0000000000..bf3b2588c8 --- /dev/null +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -0,0 +1,113 @@ +require 'abstract_unit' + +class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase + include ActionView::Helpers::DateHelper + attr_reader :request + + def setup + @from = Time.mktime(2004, 6, 6, 21, 45, 0) + end + + uses_mocha 'date_helper_distance_of_time_in_words_i18n_test' do + # distance_of_time_in_words + + def test_distance_of_time_in_words_calls_i18n + { # with include_seconds + [2.seconds, true] => [:'less_than_x_seconds', 5], + [9.seconds, true] => [:'less_than_x_seconds', 10], + [19.seconds, true] => [:'less_than_x_seconds', 20], + [30.seconds, true] => [:'half_a_minute', nil], + [59.seconds, true] => [:'less_than_x_minutes', 1], + [60.seconds, true] => [:'x_minutes', 1], + + # without include_seconds + [29.seconds, false] => [:'less_than_x_minutes', 1], + [60.seconds, false] => [:'x_minutes', 1], + [44.minutes, false] => [:'x_minutes', 44], + [61.minutes, false] => [:'about_x_hours', 1], + [24.hours, false] => [:'x_days', 1], + [30.days, false] => [:'about_x_months', 1], + [60.days, false] => [:'x_months', 2], + [1.year, false] => [:'about_x_years', 1], + [3.years, false] => [:'over_x_years', 3] + + }.each do |passed, expected| + assert_distance_of_time_in_words_translates_key passed, expected + end + end + + def assert_distance_of_time_in_words_translates_key(passed, expected) + diff, include_seconds = *passed + key, count = *expected + to = @from + diff + + options = {:locale => 'en-US', :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') + end + + def test_distance_of_time_pluralizations + { [:'less_than_x_seconds', 1] => 'less than 1 second', + [:'less_than_x_seconds', 2] => 'less than 2 seconds', + [:'less_than_x_minutes', 1] => 'less than a minute', + [:'less_than_x_minutes', 2] => 'less than 2 minutes', + [:'x_minutes', 1] => '1 minute', + [:'x_minutes', 2] => '2 minutes', + [:'about_x_hours', 1] => 'about 1 hour', + [:'about_x_hours', 2] => 'about 2 hours', + [:'x_days', 1] => '1 day', + [:'x_days', 2] => '2 days', + [:'about_x_years', 1] => 'about 1 year', + [:'about_x_years', 2] => 'about 2 years', + [:'over_x_years', 1] => 'over 1 year', + [:'over_x_years', 2] => 'over 2 years' + + }.each do |args, expected| + key, count = *args + assert_equal expected, I18n.t(key, :count => count, :scope => 'datetime.distance_in_words') + end + end + end +end + +class DateHelperSelectTagsI18nTests < Test::Unit::TestCase + include ActionView::Helpers::DateHelper + attr_reader :request + + uses_mocha 'date_helper_select_tags_i18n_tests' do + def setup + I18n.stubs(:translate).with(:'date.month_names', :locale => 'en-US').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) + 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') + 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) + 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') + 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') + end + end +end
\ No newline at end of file diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 8b4e94c67f..1a645bccc6 100755..100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -17,7 +17,7 @@ class DateHelperTest < ActionView::TestCase end end end - + def assert_distance_of_time_in_words(from, to=nil) to ||= from @@ -86,13 +86,13 @@ class DateHelperTest < ActionView::TestCase from = Time.mktime(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words(from) end - + def test_distance_in_words_with_time_zones from = Time.mktime(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words(from.in_time_zone('Alaska')) assert_distance_of_time_in_words(from.in_time_zone('Hawaii')) end - + def test_distance_in_words_with_different_time_zones from = Time.mktime(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words( @@ -100,13 +100,13 @@ class DateHelperTest < ActionView::TestCase from.in_time_zone('Hawaii') ) end - + def test_distance_in_words_with_dates start_date = Date.new 1975, 1, 31 end_date = Date.new 1977, 1, 31 assert_equal("over 2 years", distance_of_time_in_words(start_date, end_date)) end - + def test_distance_in_words_with_integers assert_equal "less than a minute", distance_of_time_in_words(59) assert_equal "about 1 hour", distance_of_time_in_words(60*60) @@ -557,11 +557,8 @@ class DateHelperTest < ActionView::TestCase end def test_select_date_with_incomplete_order - expected = %(<select id="date_first_day" name="date[first][day]">\n) - expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) - expected << "</select>\n" - - expected << %(<select id="date_first_year" name="date[first][year]">\n) + # NOTE: modified this test because of minimal API change + expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" @@ -569,6 +566,10 @@ class DateHelperTest < ActionView::TestCase expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) expected << "</select>\n" + expected << %(<select id="date_first_day" name="date[first][day]">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day]) end @@ -757,6 +758,26 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => "selector") end + def test_select_date_with_separator + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << " / " + + expected << %(<select id="date_first_month" name="date[first][month]">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << " / " + + expected << %(<select id="date_first_day" name="date[first][day]">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + end + def test_select_datetime expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) @@ -857,6 +878,42 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector') end + def test_select_datetime_with_all_separators + expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << "/" + + expected << %(<select id="date_first_month" name="date[first][month]" class="selector">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << "/" + + expected << %(<select id="date_first_day" name="date[first][day]" class="selector">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + expected << "—" + + expected << %(<select id="date_first_hour" name="date[first][hour]" class="selector">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) + expected << "</select>\n" + + expected << ":" + + expected << %(<select id="date_first_minute" name="date[first][minute]" class="selector">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { :datetime_separator => "—", :date_separator => "/", :time_separator => ":", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector') + end + + def test_select_datetime_should_work_with_date + assert_nothing_raised { select_datetime(Date.today) } + end + def test_select_time expected = %(<select id="date_hour" name="date[hour]">\n) expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) @@ -933,32 +990,9 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector') assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {:include_seconds => false}, :class => 'selector') end - - uses_mocha 'TestDatetimeAndTimeSelectUseTimeCurrentAsDefault' do - def test_select_datetime_uses_time_current_as_default - time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0) - Time.expects(:current).returns time - expects(:select_date).with(time, anything, anything).returns('') - expects(:select_time).with(time, anything, anything).returns('') - select_datetime - end - - def test_select_time_uses_time_current_as_default - time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0) - Time.expects(:current).returns time - expects(:select_hour).with(time, anything, anything).returns('') - expects(:select_minute).with(time, anything, anything).returns('') - select_time - end - - def test_select_date_uses_date_current_as_default - date = stub(:year => 2004, :month => 6, :day => 15) - Date.expects(:current).returns date - expects(:select_year).with(date, anything, anything).returns('') - expects(:select_month).with(date, anything, anything).returns('') - expects(:select_day).with(date, anything, anything).returns('') - select_date - end + + def test_select_time_should_work_with_date + assert_nothing_raised { select_time(Date.today) } end def test_date_select @@ -1179,6 +1213,30 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_date_select_with_separator + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " }) + end + def test_time_select @post = Post.new @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) @@ -1188,11 +1246,11 @@ class DateHelperTest < ActionView::TestCase expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, time_select("post", "written_on") @@ -1203,11 +1261,11 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) expected = %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, time_select("post", "written_on", :ignore_date => true) @@ -1222,15 +1280,15 @@ class DateHelperTest < ActionView::TestCase expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 35}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, time_select("post", "written_on", :include_seconds => true) @@ -1245,11 +1303,11 @@ class DateHelperTest < ActionView::TestCase expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n) - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, time_select("post", "written_on", {}, :class => 'selector') @@ -1268,16 +1326,43 @@ class DateHelperTest < ActionView::TestCase expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n) - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n) - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, output_buffer end + def test_time_select_with_separator + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " - " + + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " - " + + expected << %(<select id="post_written_on_6i" name="post[written_on(6i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", { :time_separator => " - ", :include_seconds => true }) + end + def test_datetime_select @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) @@ -1306,7 +1391,7 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at") end - + uses_mocha 'TestDatetimeSelectDefaultsToTimeZoneNowWhenConfigTimeZoneIsSet' do def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0) @@ -1360,6 +1445,47 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_datetime_select_with_separators + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + expected << "</select>\n" + + expected << " , " + + expected << %(<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " - " + + expected << %(<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + expected << " - " + + expected << %(<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true }) + end + def test_date_select_with_zero_value_and_no_start_year expected = %(<select id="date_first_year" name="date[first][year]">\n) (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } @@ -1370,8 +1496,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %(<select id="date_first_day" name="date[first][day]">\n) - expected << -%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" assert_dom_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]") @@ -1388,8 +1513,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %(<select id="date_first_day" name="date[first][day]">\n) - expected << -%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" assert_dom_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]") @@ -1405,8 +1529,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %(<select id="date_first_day" name="date[first][day]">\n) - expected << -%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" assert_dom_equal expected, select_date(0, :prefix => "date[first]") @@ -1422,8 +1545,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %(<select id="date_first_day" name="date[first][day]">\n) - expected << -%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) expected << "</select>\n" assert_dom_equal expected, select_date(nil, :prefix => "date[first]") @@ -1530,15 +1652,15 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_6i" name="post[updated_at(6i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 35}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 35}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :include_seconds => true) @@ -1559,11 +1681,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true) @@ -1582,11 +1704,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :discard_month => true) @@ -1601,11 +1723,11 @@ class DateHelperTest < ActionView::TestCase expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n} expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true) @@ -1628,11 +1750,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:minute, :day, :hour, :month, :year, :second]) @@ -1653,11 +1775,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:day, :month]) @@ -1680,11 +1802,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35)) @@ -1727,11 +1849,11 @@ class DateHelperTest < ActionView::TestCase expected << " — " expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} - 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 9}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 9}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" expected << " : " expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} - 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 42}>#{leading_zero_on_single_digits(i)}</option>\n) } + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 42}>#{sprintf("%02d", i)}</option>\n) } expected << "</select>\n" assert_dom_equal expected, datetime_select("post", "updated_at", :default => { :month => 10, :minute => 42, :hour => 9 }) @@ -1766,33 +1888,158 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", {}, :class => 'selector') end - uses_mocha 'TestInstanceTagDefaultTimeFromOptions' do - def test_instance_tag_default_time_from_options_uses_time_current_as_default_when_hash_passed_as_arg - dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3) - Time.expects(:current).returns Time.now - dummy_instance_tag.send!(:default_time_from_options, :hour => 2) - end - - def test_instance_tag_default_time_from_options_respects_hash_arg_settings_when_time_falls_in_system_local_dst_spring_gap - with_env_tz('US/Central') do - dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3) - Time.stubs(:now).returns Time.local(2006, 4, 2, 1) - assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour - end - end - - def test_instance_tag_default_time_from_options_handles_far_future_date - dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3) - time = dummy_instance_tag.send!(:default_time_from_options, :year => 2050, :month => 2, :day => 10, :hour => 15, :min => 30, :sec => 45) - assert_equal 2050, time.year - end + def test_date_select_should_not_change_passed_options_hash + @post = Post.new + @post.updated_at = Time.local(2008, 7, 16, 23, 30) + + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + date_select(@post, :updated_at, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) + end + + def test_datetime_select_should_not_change_passed_options_hash + @post = Post.new + @post.updated_at = Time.local(2008, 7, 16, 23, 30) + + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + datetime_select(@post, :updated_at, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) + end + + def test_time_select_should_not_change_passed_options_hash + @post = Post.new + @post.updated_at = Time.local(2008, 7, 16, 23, 30) + + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + time_select(@post, :updated_at, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) + end + + def test_select_date_should_not_change_passed_options_hash + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + select_date(Date.today, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) + end + + def test_select_datetime_should_not_change_passed_options_hash + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + select_datetime(Time.now, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) + end + + def test_select_time_should_not_change_passed_options_hash + options = { + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + } + select_time(Time.now, options) + + # note: the literal hash is intentional to show that the actual options hash isn't modified + # don't change this! + assert_equal({ + :order => [ :year, :month, :day ], + :default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 }, + :discard_type => false, + :include_blank => false, + :ignore_date => false, + :include_seconds => true + }, options) end - + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz yield ensure old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end + end end diff --git a/actionpack/test/template/form_country_helper_test.rb b/actionpack/test/template/form_country_helper_test.rb new file mode 100644 index 0000000000..8862e08222 --- /dev/null +++ b/actionpack/test/template/form_country_helper_test.rb @@ -0,0 +1,1549 @@ +require 'abstract_unit' + +class FormCountryHelperTest < ActionView::TestCase + tests ActionView::Helpers::FormCountryHelper + + silence_warnings do + Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin) + end + + def test_country_select + @post = Post.new + @post.origin = "Denmark" + expected_select = <<-COUNTRIES +<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option selected="selected" value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + assert_dom_equal(expected_select[0..-2], country_select("post", "origin")) + end + + def test_country_select_with_priority_countries + @post = Post.new + @post.origin = "Denmark" + expected_select = <<-COUNTRIES +<select id="post_origin" name="post[origin]"><option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option> +<option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option selected="selected" value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"])) + end + + def test_country_select_with_selected_priority_country + @post = Post.new + @post.origin = "New Zealand" + expected_select = <<-COUNTRIES +<select id="post_origin" name="post[origin]"><option selected="selected" value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option> +<option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option selected="selected" value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"])) + end + + def test_country_select_under_fields_for + @post = Post.new + @post.origin = "Australia" + expected_select = <<-COUNTRIES +<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option selected="selected" value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + + fields_for :post, @post do |f| + concat f.country_select("origin") + end + + assert_dom_equal(expected_select[0..-2], output_buffer) + end + + def test_country_select_under_fields_for_with_index + @post = Post.new + @post.origin = "United States" + expected_select = <<-COUNTRIES +<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option selected="selected" value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + + fields_for :post, @post, :index => 325 do |f| + concat f.country_select("origin") + end + + assert_dom_equal(expected_select[0..-2], output_buffer) + end + + def test_country_select_under_fields_for_with_auto_index + @post = Post.new + @post.origin = "Iraq" + def @post.to_param; 325; end + + expected_select = <<-COUNTRIES +<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option> +<option value="Aland Islands">Aland Islands</option> +<option value="Albania">Albania</option> +<option value="Algeria">Algeria</option> +<option value="American Samoa">American Samoa</option> +<option value="Andorra">Andorra</option> +<option value="Angola">Angola</option> +<option value="Anguilla">Anguilla</option> +<option value="Antarctica">Antarctica</option> +<option value="Antigua And Barbuda">Antigua And Barbuda</option> +<option value="Argentina">Argentina</option> +<option value="Armenia">Armenia</option> +<option value="Aruba">Aruba</option> +<option value="Australia">Australia</option> +<option value="Austria">Austria</option> +<option value="Azerbaijan">Azerbaijan</option> +<option value="Bahamas">Bahamas</option> +<option value="Bahrain">Bahrain</option> +<option value="Bangladesh">Bangladesh</option> +<option value="Barbados">Barbados</option> +<option value="Belarus">Belarus</option> +<option value="Belgium">Belgium</option> +<option value="Belize">Belize</option> +<option value="Benin">Benin</option> +<option value="Bermuda">Bermuda</option> +<option value="Bhutan">Bhutan</option> +<option value="Bolivia">Bolivia</option> +<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> +<option value="Botswana">Botswana</option> +<option value="Bouvet Island">Bouvet Island</option> +<option value="Brazil">Brazil</option> +<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> +<option value="Brunei Darussalam">Brunei Darussalam</option> +<option value="Bulgaria">Bulgaria</option> +<option value="Burkina Faso">Burkina Faso</option> +<option value="Burundi">Burundi</option> +<option value="Cambodia">Cambodia</option> +<option value="Cameroon">Cameroon</option> +<option value="Canada">Canada</option> +<option value="Cape Verde">Cape Verde</option> +<option value="Cayman Islands">Cayman Islands</option> +<option value="Central African Republic">Central African Republic</option> +<option value="Chad">Chad</option> +<option value="Chile">Chile</option> +<option value="China">China</option> +<option value="Christmas Island">Christmas Island</option> +<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> +<option value="Colombia">Colombia</option> +<option value="Comoros">Comoros</option> +<option value="Congo">Congo</option> +<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> +<option value="Cook Islands">Cook Islands</option> +<option value="Costa Rica">Costa Rica</option> +<option value="Cote d'Ivoire">Cote d'Ivoire</option> +<option value="Croatia">Croatia</option> +<option value="Cuba">Cuba</option> +<option value="Cyprus">Cyprus</option> +<option value="Czech Republic">Czech Republic</option> +<option value="Denmark">Denmark</option> +<option value="Djibouti">Djibouti</option> +<option value="Dominica">Dominica</option> +<option value="Dominican Republic">Dominican Republic</option> +<option value="Ecuador">Ecuador</option> +<option value="Egypt">Egypt</option> +<option value="El Salvador">El Salvador</option> +<option value="Equatorial Guinea">Equatorial Guinea</option> +<option value="Eritrea">Eritrea</option> +<option value="Estonia">Estonia</option> +<option value="Ethiopia">Ethiopia</option> +<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> +<option value="Faroe Islands">Faroe Islands</option> +<option value="Fiji">Fiji</option> +<option value="Finland">Finland</option> +<option value="France">France</option> +<option value="French Guiana">French Guiana</option> +<option value="French Polynesia">French Polynesia</option> +<option value="French Southern Territories">French Southern Territories</option> +<option value="Gabon">Gabon</option> +<option value="Gambia">Gambia</option> +<option value="Georgia">Georgia</option> +<option value="Germany">Germany</option> +<option value="Ghana">Ghana</option> +<option value="Gibraltar">Gibraltar</option> +<option value="Greece">Greece</option> +<option value="Greenland">Greenland</option> +<option value="Grenada">Grenada</option> +<option value="Guadeloupe">Guadeloupe</option> +<option value="Guam">Guam</option> +<option value="Guatemala">Guatemala</option> +<option value="Guernsey">Guernsey</option> +<option value="Guinea">Guinea</option> +<option value="Guinea-Bissau">Guinea-Bissau</option> +<option value="Guyana">Guyana</option> +<option value="Haiti">Haiti</option> +<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> +<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> +<option value="Honduras">Honduras</option> +<option value="Hong Kong">Hong Kong</option> +<option value="Hungary">Hungary</option> +<option value="Iceland">Iceland</option> +<option value="India">India</option> +<option value="Indonesia">Indonesia</option> +<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> +<option selected="selected" value="Iraq">Iraq</option> +<option value="Ireland">Ireland</option> +<option value="Isle of Man">Isle of Man</option> +<option value="Israel">Israel</option> +<option value="Italy">Italy</option> +<option value="Jamaica">Jamaica</option> +<option value="Japan">Japan</option> +<option value="Jersey">Jersey</option> +<option value="Jordan">Jordan</option> +<option value="Kazakhstan">Kazakhstan</option> +<option value="Kenya">Kenya</option> +<option value="Kiribati">Kiribati</option> +<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> +<option value="Korea, Republic of">Korea, Republic of</option> +<option value="Kuwait">Kuwait</option> +<option value="Kyrgyzstan">Kyrgyzstan</option> +<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> +<option value="Latvia">Latvia</option> +<option value="Lebanon">Lebanon</option> +<option value="Lesotho">Lesotho</option> +<option value="Liberia">Liberia</option> +<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> +<option value="Liechtenstein">Liechtenstein</option> +<option value="Lithuania">Lithuania</option> +<option value="Luxembourg">Luxembourg</option> +<option value="Macao">Macao</option> +<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> +<option value="Madagascar">Madagascar</option> +<option value="Malawi">Malawi</option> +<option value="Malaysia">Malaysia</option> +<option value="Maldives">Maldives</option> +<option value="Mali">Mali</option> +<option value="Malta">Malta</option> +<option value="Marshall Islands">Marshall Islands</option> +<option value="Martinique">Martinique</option> +<option value="Mauritania">Mauritania</option> +<option value="Mauritius">Mauritius</option> +<option value="Mayotte">Mayotte</option> +<option value="Mexico">Mexico</option> +<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> +<option value="Moldova, Republic of">Moldova, Republic of</option> +<option value="Monaco">Monaco</option> +<option value="Mongolia">Mongolia</option> +<option value="Montenegro">Montenegro</option> +<option value="Montserrat">Montserrat</option> +<option value="Morocco">Morocco</option> +<option value="Mozambique">Mozambique</option> +<option value="Myanmar">Myanmar</option> +<option value="Namibia">Namibia</option> +<option value="Nauru">Nauru</option> +<option value="Nepal">Nepal</option> +<option value="Netherlands">Netherlands</option> +<option value="Netherlands Antilles">Netherlands Antilles</option> +<option value="New Caledonia">New Caledonia</option> +<option value="New Zealand">New Zealand</option> +<option value="Nicaragua">Nicaragua</option> +<option value="Niger">Niger</option> +<option value="Nigeria">Nigeria</option> +<option value="Niue">Niue</option> +<option value="Norfolk Island">Norfolk Island</option> +<option value="Northern Mariana Islands">Northern Mariana Islands</option> +<option value="Norway">Norway</option> +<option value="Oman">Oman</option> +<option value="Pakistan">Pakistan</option> +<option value="Palau">Palau</option> +<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> +<option value="Panama">Panama</option> +<option value="Papua New Guinea">Papua New Guinea</option> +<option value="Paraguay">Paraguay</option> +<option value="Peru">Peru</option> +<option value="Philippines">Philippines</option> +<option value="Pitcairn">Pitcairn</option> +<option value="Poland">Poland</option> +<option value="Portugal">Portugal</option> +<option value="Puerto Rico">Puerto Rico</option> +<option value="Qatar">Qatar</option> +<option value="Reunion">Reunion</option> +<option value="Romania">Romania</option> +<option value="Russian Federation">Russian Federation</option> +<option value="Rwanda">Rwanda</option> +<option value="Saint Barthelemy">Saint Barthelemy</option> +<option value="Saint Helena">Saint Helena</option> +<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> +<option value="Saint Lucia">Saint Lucia</option> +<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> +<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> +<option value="Samoa">Samoa</option> +<option value="San Marino">San Marino</option> +<option value="Sao Tome and Principe">Sao Tome and Principe</option> +<option value="Saudi Arabia">Saudi Arabia</option> +<option value="Senegal">Senegal</option> +<option value="Serbia">Serbia</option> +<option value="Seychelles">Seychelles</option> +<option value="Sierra Leone">Sierra Leone</option> +<option value="Singapore">Singapore</option> +<option value="Slovakia">Slovakia</option> +<option value="Slovenia">Slovenia</option> +<option value="Solomon Islands">Solomon Islands</option> +<option value="Somalia">Somalia</option> +<option value="South Africa">South Africa</option> +<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> +<option value="Spain">Spain</option> +<option value="Sri Lanka">Sri Lanka</option> +<option value="Sudan">Sudan</option> +<option value="Suriname">Suriname</option> +<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> +<option value="Swaziland">Swaziland</option> +<option value="Sweden">Sweden</option> +<option value="Switzerland">Switzerland</option> +<option value="Syrian Arab Republic">Syrian Arab Republic</option> +<option value="Taiwan, Province of China">Taiwan, Province of China</option> +<option value="Tajikistan">Tajikistan</option> +<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> +<option value="Thailand">Thailand</option> +<option value="Timor-Leste">Timor-Leste</option> +<option value="Togo">Togo</option> +<option value="Tokelau">Tokelau</option> +<option value="Tonga">Tonga</option> +<option value="Trinidad and Tobago">Trinidad and Tobago</option> +<option value="Tunisia">Tunisia</option> +<option value="Turkey">Turkey</option> +<option value="Turkmenistan">Turkmenistan</option> +<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> +<option value="Tuvalu">Tuvalu</option> +<option value="Uganda">Uganda</option> +<option value="Ukraine">Ukraine</option> +<option value="United Arab Emirates">United Arab Emirates</option> +<option value="United Kingdom">United Kingdom</option> +<option value="United States">United States</option> +<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> +<option value="Uruguay">Uruguay</option> +<option value="Uzbekistan">Uzbekistan</option> +<option value="Vanuatu">Vanuatu</option> +<option value="Venezuela">Venezuela</option> +<option value="Viet Nam">Viet Nam</option> +<option value="Virgin Islands, British">Virgin Islands, British</option> +<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> +<option value="Wallis and Futuna">Wallis and Futuna</option> +<option value="Western Sahara">Western Sahara</option> +<option value="Yemen">Yemen</option> +<option value="Zambia">Zambia</option> +<option value="Zimbabwe">Zimbabwe</option></select> +COUNTRIES + + fields_for "post[]", @post do |f| + concat f.country_select("origin") + end + + assert_dom_equal(expected_select[0..-2], output_buffer) + end + +end
\ No newline at end of file diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 39649c3622..52e8bf376a 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -6,7 +6,7 @@ silence_warnings do alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast) alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast) alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast) - alias_method :secret?, :secret + alias_method :secret?, :secret def new_record=(boolean) @new_record = boolean @@ -22,6 +22,7 @@ silence_warnings do attr_reader :post_id def save; @id = 1; @post_id = 1 end def new_record?; @id.nil? end + def to_param; @id; end def name @id.nil? ? 'new comment' : "comment ##{@id}" end @@ -30,7 +31,6 @@ end class Comment::Nested < Comment; end - class FormHelperTest < ActionView::TestCase tests ActionView::Helpers::FormHelper @@ -447,6 +447,117 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_nested_collections + form_for('post[]', @post) do |f| + concat f.text_field(:title) + f.fields_for('comment[]', @comment) do |c| + concat c.text_field(:name) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + + "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_index + form_for('post', @post, :index => 1) do |c| + concat c.text_field(:title) + c.fields_for('comment', @comment, :index => 1) do |r| + concat r.text_field(:name) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + + "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_index + form_for(:post, @post, :index => 1) do |f| + f.fields_for(:comment, @post) do |c| + concat c.text_field(:title) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_index_on_both + form_for(:post, @post, :index => 1) do |f| + f.fields_for(:comment, @post, :index => 5) do |c| + concat c.text_field(:title) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_auto_index + form_for("post[]", @post) do |f| + f.fields_for(:comment, @post) do |c| + concat c.text_field(:title) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_auto_index_on_both + form_for("post[]", @post) do |f| + f.fields_for("comment[]", @post) do |c| + concat c.text_field(:title) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_index_and_auto_index + form_for("post[]", @post) do |f| + f.fields_for(:comment, @post, :index => 5) do |c| + concat c.text_field(:title) + end + end + + form_for(:post, @post, :index => 1) do |f| + f.fields_for("comment[]", @post) do |c| + concat c.text_field(:title) + end + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" + + "</form>" + + "<form action='http://www.example.com' method='post'>" + + "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" + + "</form>" + + assert_dom_equal expected, output_buffer + end + def test_fields_for fields_for(:post, @post) do |f| concat f.text_field(:title) @@ -831,7 +942,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - protected def comments_path(post) "/posts/#{post.id}/comments" diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 9dd43d7b4f..a33eb85b66 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -472,1545 +472,6 @@ uses_mocha "FormOptionsHelperTest" do assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true) end - def test_country_select - @post = Post.new - @post.origin = "Denmark" - expected_select = <<-COUNTRIES -<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option selected="selected" value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - assert_dom_equal(expected_select[0..-2], country_select("post", "origin")) - end - - def test_country_select_with_priority_countries - @post = Post.new - @post.origin = "Denmark" - expected_select = <<-COUNTRIES -<select id="post_origin" name="post[origin]"><option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option> -<option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option selected="selected" value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"])) - end - - def test_country_select_with_selected_priority_country - @post = Post.new - @post.origin = "New Zealand" - expected_select = <<-COUNTRIES -<select id="post_origin" name="post[origin]"><option selected="selected" value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option> -<option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option selected="selected" value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"])) - end - - def test_country_select_under_fields_for - @post = Post.new - @post.origin = "Australia" - expected_select = <<-COUNTRIES -<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option selected="selected" value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - - fields_for :post, @post do |f| - concat f.country_select("origin") - end - - assert_dom_equal(expected_select[0..-2], output_buffer) - end - - def test_country_select_under_fields_for_with_index - @post = Post.new - @post.origin = "United States" - expected_select = <<-COUNTRIES -<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option selected="selected" value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - - fields_for :post, @post, :index => 325 do |f| - concat f.country_select("origin") - end - - assert_dom_equal(expected_select[0..-2], output_buffer) - end - - def test_country_select_under_fields_for_with_auto_index - @post = Post.new - @post.origin = "Iraq" - def @post.to_param; 325; end - - expected_select = <<-COUNTRIES -<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option> -<option value="Aland Islands">Aland Islands</option> -<option value="Albania">Albania</option> -<option value="Algeria">Algeria</option> -<option value="American Samoa">American Samoa</option> -<option value="Andorra">Andorra</option> -<option value="Angola">Angola</option> -<option value="Anguilla">Anguilla</option> -<option value="Antarctica">Antarctica</option> -<option value="Antigua And Barbuda">Antigua And Barbuda</option> -<option value="Argentina">Argentina</option> -<option value="Armenia">Armenia</option> -<option value="Aruba">Aruba</option> -<option value="Australia">Australia</option> -<option value="Austria">Austria</option> -<option value="Azerbaijan">Azerbaijan</option> -<option value="Bahamas">Bahamas</option> -<option value="Bahrain">Bahrain</option> -<option value="Bangladesh">Bangladesh</option> -<option value="Barbados">Barbados</option> -<option value="Belarus">Belarus</option> -<option value="Belgium">Belgium</option> -<option value="Belize">Belize</option> -<option value="Benin">Benin</option> -<option value="Bermuda">Bermuda</option> -<option value="Bhutan">Bhutan</option> -<option value="Bolivia">Bolivia</option> -<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option> -<option value="Botswana">Botswana</option> -<option value="Bouvet Island">Bouvet Island</option> -<option value="Brazil">Brazil</option> -<option value="British Indian Ocean Territory">British Indian Ocean Territory</option> -<option value="Brunei Darussalam">Brunei Darussalam</option> -<option value="Bulgaria">Bulgaria</option> -<option value="Burkina Faso">Burkina Faso</option> -<option value="Burundi">Burundi</option> -<option value="Cambodia">Cambodia</option> -<option value="Cameroon">Cameroon</option> -<option value="Canada">Canada</option> -<option value="Cape Verde">Cape Verde</option> -<option value="Cayman Islands">Cayman Islands</option> -<option value="Central African Republic">Central African Republic</option> -<option value="Chad">Chad</option> -<option value="Chile">Chile</option> -<option value="China">China</option> -<option value="Christmas Island">Christmas Island</option> -<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option> -<option value="Colombia">Colombia</option> -<option value="Comoros">Comoros</option> -<option value="Congo">Congo</option> -<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option> -<option value="Cook Islands">Cook Islands</option> -<option value="Costa Rica">Costa Rica</option> -<option value="Cote d'Ivoire">Cote d'Ivoire</option> -<option value="Croatia">Croatia</option> -<option value="Cuba">Cuba</option> -<option value="Cyprus">Cyprus</option> -<option value="Czech Republic">Czech Republic</option> -<option value="Denmark">Denmark</option> -<option value="Djibouti">Djibouti</option> -<option value="Dominica">Dominica</option> -<option value="Dominican Republic">Dominican Republic</option> -<option value="Ecuador">Ecuador</option> -<option value="Egypt">Egypt</option> -<option value="El Salvador">El Salvador</option> -<option value="Equatorial Guinea">Equatorial Guinea</option> -<option value="Eritrea">Eritrea</option> -<option value="Estonia">Estonia</option> -<option value="Ethiopia">Ethiopia</option> -<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option> -<option value="Faroe Islands">Faroe Islands</option> -<option value="Fiji">Fiji</option> -<option value="Finland">Finland</option> -<option value="France">France</option> -<option value="French Guiana">French Guiana</option> -<option value="French Polynesia">French Polynesia</option> -<option value="French Southern Territories">French Southern Territories</option> -<option value="Gabon">Gabon</option> -<option value="Gambia">Gambia</option> -<option value="Georgia">Georgia</option> -<option value="Germany">Germany</option> -<option value="Ghana">Ghana</option> -<option value="Gibraltar">Gibraltar</option> -<option value="Greece">Greece</option> -<option value="Greenland">Greenland</option> -<option value="Grenada">Grenada</option> -<option value="Guadeloupe">Guadeloupe</option> -<option value="Guam">Guam</option> -<option value="Guatemala">Guatemala</option> -<option value="Guernsey">Guernsey</option> -<option value="Guinea">Guinea</option> -<option value="Guinea-Bissau">Guinea-Bissau</option> -<option value="Guyana">Guyana</option> -<option value="Haiti">Haiti</option> -<option value="Heard and McDonald Islands">Heard and McDonald Islands</option> -<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option> -<option value="Honduras">Honduras</option> -<option value="Hong Kong">Hong Kong</option> -<option value="Hungary">Hungary</option> -<option value="Iceland">Iceland</option> -<option value="India">India</option> -<option value="Indonesia">Indonesia</option> -<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option> -<option selected="selected" value="Iraq">Iraq</option> -<option value="Ireland">Ireland</option> -<option value="Isle of Man">Isle of Man</option> -<option value="Israel">Israel</option> -<option value="Italy">Italy</option> -<option value="Jamaica">Jamaica</option> -<option value="Japan">Japan</option> -<option value="Jersey">Jersey</option> -<option value="Jordan">Jordan</option> -<option value="Kazakhstan">Kazakhstan</option> -<option value="Kenya">Kenya</option> -<option value="Kiribati">Kiribati</option> -<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option> -<option value="Korea, Republic of">Korea, Republic of</option> -<option value="Kuwait">Kuwait</option> -<option value="Kyrgyzstan">Kyrgyzstan</option> -<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option> -<option value="Latvia">Latvia</option> -<option value="Lebanon">Lebanon</option> -<option value="Lesotho">Lesotho</option> -<option value="Liberia">Liberia</option> -<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option> -<option value="Liechtenstein">Liechtenstein</option> -<option value="Lithuania">Lithuania</option> -<option value="Luxembourg">Luxembourg</option> -<option value="Macao">Macao</option> -<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option> -<option value="Madagascar">Madagascar</option> -<option value="Malawi">Malawi</option> -<option value="Malaysia">Malaysia</option> -<option value="Maldives">Maldives</option> -<option value="Mali">Mali</option> -<option value="Malta">Malta</option> -<option value="Marshall Islands">Marshall Islands</option> -<option value="Martinique">Martinique</option> -<option value="Mauritania">Mauritania</option> -<option value="Mauritius">Mauritius</option> -<option value="Mayotte">Mayotte</option> -<option value="Mexico">Mexico</option> -<option value="Micronesia, Federated States of">Micronesia, Federated States of</option> -<option value="Moldova, Republic of">Moldova, Republic of</option> -<option value="Monaco">Monaco</option> -<option value="Mongolia">Mongolia</option> -<option value="Montenegro">Montenegro</option> -<option value="Montserrat">Montserrat</option> -<option value="Morocco">Morocco</option> -<option value="Mozambique">Mozambique</option> -<option value="Myanmar">Myanmar</option> -<option value="Namibia">Namibia</option> -<option value="Nauru">Nauru</option> -<option value="Nepal">Nepal</option> -<option value="Netherlands">Netherlands</option> -<option value="Netherlands Antilles">Netherlands Antilles</option> -<option value="New Caledonia">New Caledonia</option> -<option value="New Zealand">New Zealand</option> -<option value="Nicaragua">Nicaragua</option> -<option value="Niger">Niger</option> -<option value="Nigeria">Nigeria</option> -<option value="Niue">Niue</option> -<option value="Norfolk Island">Norfolk Island</option> -<option value="Northern Mariana Islands">Northern Mariana Islands</option> -<option value="Norway">Norway</option> -<option value="Oman">Oman</option> -<option value="Pakistan">Pakistan</option> -<option value="Palau">Palau</option> -<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option> -<option value="Panama">Panama</option> -<option value="Papua New Guinea">Papua New Guinea</option> -<option value="Paraguay">Paraguay</option> -<option value="Peru">Peru</option> -<option value="Philippines">Philippines</option> -<option value="Pitcairn">Pitcairn</option> -<option value="Poland">Poland</option> -<option value="Portugal">Portugal</option> -<option value="Puerto Rico">Puerto Rico</option> -<option value="Qatar">Qatar</option> -<option value="Reunion">Reunion</option> -<option value="Romania">Romania</option> -<option value="Russian Federation">Russian Federation</option> -<option value="Rwanda">Rwanda</option> -<option value="Saint Barthelemy">Saint Barthelemy</option> -<option value="Saint Helena">Saint Helena</option> -<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option> -<option value="Saint Lucia">Saint Lucia</option> -<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option> -<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option> -<option value="Samoa">Samoa</option> -<option value="San Marino">San Marino</option> -<option value="Sao Tome and Principe">Sao Tome and Principe</option> -<option value="Saudi Arabia">Saudi Arabia</option> -<option value="Senegal">Senegal</option> -<option value="Serbia">Serbia</option> -<option value="Seychelles">Seychelles</option> -<option value="Sierra Leone">Sierra Leone</option> -<option value="Singapore">Singapore</option> -<option value="Slovakia">Slovakia</option> -<option value="Slovenia">Slovenia</option> -<option value="Solomon Islands">Solomon Islands</option> -<option value="Somalia">Somalia</option> -<option value="South Africa">South Africa</option> -<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option> -<option value="Spain">Spain</option> -<option value="Sri Lanka">Sri Lanka</option> -<option value="Sudan">Sudan</option> -<option value="Suriname">Suriname</option> -<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option> -<option value="Swaziland">Swaziland</option> -<option value="Sweden">Sweden</option> -<option value="Switzerland">Switzerland</option> -<option value="Syrian Arab Republic">Syrian Arab Republic</option> -<option value="Taiwan, Province of China">Taiwan, Province of China</option> -<option value="Tajikistan">Tajikistan</option> -<option value="Tanzania, United Republic of">Tanzania, United Republic of</option> -<option value="Thailand">Thailand</option> -<option value="Timor-Leste">Timor-Leste</option> -<option value="Togo">Togo</option> -<option value="Tokelau">Tokelau</option> -<option value="Tonga">Tonga</option> -<option value="Trinidad and Tobago">Trinidad and Tobago</option> -<option value="Tunisia">Tunisia</option> -<option value="Turkey">Turkey</option> -<option value="Turkmenistan">Turkmenistan</option> -<option value="Turks and Caicos Islands">Turks and Caicos Islands</option> -<option value="Tuvalu">Tuvalu</option> -<option value="Uganda">Uganda</option> -<option value="Ukraine">Ukraine</option> -<option value="United Arab Emirates">United Arab Emirates</option> -<option value="United Kingdom">United Kingdom</option> -<option value="United States">United States</option> -<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option> -<option value="Uruguay">Uruguay</option> -<option value="Uzbekistan">Uzbekistan</option> -<option value="Vanuatu">Vanuatu</option> -<option value="Venezuela">Venezuela</option> -<option value="Viet Nam">Viet Nam</option> -<option value="Virgin Islands, British">Virgin Islands, British</option> -<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option> -<option value="Wallis and Futuna">Wallis and Futuna</option> -<option value="Western Sahara">Western Sahara</option> -<option value="Yemen">Yemen</option> -<option value="Zambia">Zambia</option> -<option value="Zimbabwe">Zimbabwe</option></select> - COUNTRIES - - fields_for "post[]", @post do |f| - concat f.country_select("origin") - end - - assert_dom_equal(expected_select[0..-2], output_buffer) - end - def test_time_zone_select @firm = Firm.new("D") html = time_zone_select( "firm", "time_zone" ) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 4e4102aec7..9b41ff8179 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -190,6 +190,12 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_label_tag_with_symbol + actual = label_tag :title + expected = %(<label for="title">Title</label>) + assert_dom_equal expected, actual + end + def test_label_tag_with_text actual = label_tag "title", "My Title" expected = %(<label for="title">My Title</label>) diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index b2c4a130c8..d41111127b 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -3,6 +3,12 @@ require 'abstract_unit' class JavaScriptHelperTest < ActionView::TestCase tests ActionView::Helpers::JavaScriptHelper + attr_accessor :template_format, :output_buffer + + def setup + @template = self + end + def test_escape_javascript assert_equal '', escape_javascript(nil) assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos')) diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb new file mode 100644 index 0000000000..2ee7f43a65 --- /dev/null +++ b/actionpack/test/template/number_helper_i18n_test.rb @@ -0,0 +1,54 @@ +require 'abstract_unit' + +class NumberHelperI18nTests < Test::Unit::TestCase + include ActionView::Helpers::NumberHelper + + attr_reader :request + + uses_mocha 'number_helper_i18n_tests' do + def setup + @number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' } + @currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 } + @human_defaults = { :precision => 1 } + @percentage_defaults = { :delimiter => '' } + @precision_defaults = { :delimiter => '' } + + I18n.backend.store_translations 'en-US', :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', + :raise => true).returns(@currency_defaults) + number_to_currency(1, :locale => 'en-US') + 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', + :raise => true).returns(@precision_defaults) + number_with_precision(1, :locale => 'en-US') + 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') + 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', + :raise => true).returns(@percentage_defaults) + number_to_percentage(1, :locale => 'en-US') + 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', + :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') + end + end +end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 4a8d09b544..9c9f54936c 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -26,7 +26,8 @@ class NumberHelperTest < ActionView::TestCase assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50")) assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - assert_equal("$x.", number_to_currency("x")) + #assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation + assert_equal("$x", number_to_currency("x")) assert_nil number_to_currency(nil) end @@ -35,7 +36,9 @@ class NumberHelperTest < ActionView::TestCase assert_equal("100%", number_to_percentage(100, {:precision => 0})) assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2})) assert_equal("100.000%", number_to_percentage("100")) + assert_equal("1000.000%", number_to_percentage("1000")) assert_equal("x%", number_to_percentage("x")) + assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ',')) assert_nil number_to_percentage(nil) end @@ -54,21 +57,33 @@ class NumberHelperTest < ActionView::TestCase assert_nil number_with_delimiter(nil) end + def test_number_with_delimiter_with_options_hash + assert_equal '12 345 678', number_with_delimiter(12345678, :delimiter => ' ') + assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-') + assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.') + assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',') + end + def test_number_with_precision assert_equal("111.235", number_with_precision(111.2346)) - assert_equal("31.83", number_with_precision(31.825, 2)) - assert_equal("111.23", number_with_precision(111.2346, 2)) - assert_equal("111.00", number_with_precision(111, 2)) + assert_equal("31.83", number_with_precision(31.825, :precision => 2)) + assert_equal("111.23", number_with_precision(111.2346, :precision => 2)) + assert_equal("111.00", number_with_precision(111, :precision => 2)) assert_equal("111.235", number_with_precision("111.2346")) - assert_equal("31.83", number_with_precision("31.825", 2)) - assert_equal("112", number_with_precision(111.50, 0)) - assert_equal("1234567892", number_with_precision(1234567891.50, 0)) + assert_equal("31.83", number_with_precision("31.825", :precision => 2)) + assert_equal("112", number_with_precision(111.50, :precision => 0)) + assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) # Return non-numeric params unchanged. assert_equal("x", number_with_precision("x")) assert_nil number_with_precision(nil) end + def test_number_with_precision_with_custom_delimiter_and_separator + assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',') + assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.') + end + def test_number_to_human_size assert_equal '0 Bytes', number_to_human_size(0) assert_equal '1 Byte', number_to_human_size(1) @@ -80,18 +95,33 @@ class NumberHelperTest < ActionView::TestCase assert_equal '1.2 MB', number_to_human_size(1234567) assert_equal '1.1 GB', number_to_human_size(1234567890) assert_equal '1.1 TB', number_to_human_size(1234567890123) + assert_equal '1025 TB', number_to_human_size(1025.terabytes) assert_equal '444 KB', number_to_human_size(444.kilobytes) assert_equal '1023 MB', number_to_human_size(1023.megabytes) assert_equal '3 TB', number_to_human_size(3.terabytes) - assert_equal '1.18 MB', number_to_human_size(1234567, 2) - assert_equal '3 Bytes', number_to_human_size(3.14159265, 4) + assert_equal '1.18 MB', number_to_human_size(1234567, :precision => 2) + assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) assert_equal("123 Bytes", number_to_human_size("123")) - assert_equal '1.01 KB', number_to_human_size(1.0123.kilobytes, 2) - assert_equal '1.01 KB', number_to_human_size(1.0100.kilobytes, 4) - assert_equal '10 KB', number_to_human_size(10.000.kilobytes, 4) + assert_equal '1.01 KB', number_to_human_size(1.0123.kilobytes, :precision => 2) + assert_equal '1.01 KB', number_to_human_size(1.0100.kilobytes, :precision => 4) + assert_equal '10 KB', number_to_human_size(10.000.kilobytes, :precision => 4) assert_equal '1 Byte', number_to_human_size(1.1) assert_equal '10 Bytes', number_to_human_size(10) - assert_nil number_to_human_size('x') + #assert_nil number_to_human_size('x') # fails due to API consolidation assert_nil number_to_human_size(nil) end + + def test_number_to_human_size_with_options_hash + assert_equal '1.18 MB', number_to_human_size(1234567, :precision => 2) + assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) + assert_equal '1.01 KB', number_to_human_size(1.0123.kilobytes, :precision => 2) + assert_equal '1.01 KB', number_to_human_size(1.0100.kilobytes, :precision => 4) + assert_equal '10 KB', number_to_human_size(10.000.kilobytes, :precision => 4) + end + + def test_number_to_human_size_with_custom_delimiter_and_separator + assert_equal '1,01 KB', number_to_human_size(1.0123.kilobytes, :precision => 2, :separator => ',') + assert_equal '1,01 KB', number_to_human_size(1.0100.kilobytes, :precision => 4, :separator => ',') + assert_equal '1.000,1 TB', number_to_human_size(1000.1.terabytes, :delimiter => '.', :separator => ',') + end end diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index 1d9bc5eb9b..abc9f930dd 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -25,10 +25,10 @@ class Author::Nested < Author; end class PrototypeHelperBaseTest < ActionView::TestCase - attr_accessor :template_format + attr_accessor :template_format, :output_buffer def setup - @template = nil + @template = self @controller = Class.new do def url_for(options) if options.is_a?(String) @@ -77,6 +77,8 @@ class PrototypeHelperTest < PrototypeHelperBaseTest link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot" }) assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>), link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' }) + assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:false, evalScripts:true}); return false;\">Remote outauthor</a>), + link_to_remote("Remote outauthor", :url => { :action => "whatnot" }, :type => :synchronous) end def test_link_to_remote_html_options @@ -243,8 +245,12 @@ class PrototypeHelperTest < PrototypeHelperBaseTest end def test_update_page + old_output_buffer = output_buffer + block = Proc.new { |page| page.replace_html('foo', 'bar') } assert_equal create_generator(&block).to_s, update_page(&block) + + assert_equal old_output_buffer, output_buffer end def test_update_page_tag @@ -283,13 +289,13 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest end def test_insert_html_with_string - assert_equal 'new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");', + assert_equal 'Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" });', @generator.insert_html(:top, 'element', '<p>This is a test</p>') - assert_equal 'new Insertion.Bottom("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");', + assert_equal 'Element.insert("element", { bottom: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });', @generator.insert_html(:bottom, 'element', '<p>This is a test</p>') - assert_equal 'new Insertion.Before("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");', + assert_equal 'Element.insert("element", { before: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });', @generator.insert_html(:before, 'element', '<p>This is a test</p>') - assert_equal 'new Insertion.After("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");', + assert_equal 'Element.insert("element", { after: "\\u003Cp\u003EThis is a test\\u003C/p\u003E" });', @generator.insert_html(:after, 'element', '<p>This is a test</p>') end @@ -362,8 +368,8 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest @generator.replace_html('baz', '<p>This is a test</p>') assert_equal <<-EOS.chomp, @generator.to_s -new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); -new Insertion.Bottom("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); +Element.insert("element", { top: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" }); +Element.insert("element", { bottom: "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E" }); ["foo", "bar"].each(Element.remove); Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); EOS @@ -425,6 +431,8 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); def test_sortable assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});), @generator.sortable('blah', :url => { :action => "order" }) + assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:Sortable.serialize("blah")})}});), + @generator.sortable('blah', :url => { :action => "order" }, :type => :synchronous) end def test_draggable @@ -435,6 +443,8 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E"); def test_drop_receiving assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});), @generator.drop_receiving('blah', :url => { :action => "order" }) + assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:false, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});), + @generator.drop_receiving('blah', :url => { :action => "order" }, :type => :synchronous) end def test_collection_first_and_last diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index cc5b4900dc..f3c8dbcae9 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -19,6 +19,10 @@ class ViewRenderTest < Test::Unit::TestCase assert_equal "Hello world!", @view.render("test/hello_world") end + def test_render_file_at_top_level + assert_equal 'Elastica', @view.render('/shared') + end + def test_render_file_with_full_path template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world.erb') assert_equal "Hello world!", @view.render(:file => template_path) @@ -47,6 +51,24 @@ class ViewRenderTest < Test::Unit::TestCase assert_equal "only partial", @view.render(:partial => "test/partial_only") end + def test_render_partial_with_format + assert_equal 'partial html', @view.render(:partial => 'test/partial') + end + + def test_render_partial_at_top_level + # file fixtures/_top_level_partial_only.erb (not fixtures/test) + assert_equal 'top level partial', @view.render(:partial => '/top_level_partial_only') + end + + def test_render_partial_with_format_at_top_level + # file fixtures/_top_level_partial.html.erb (not fixtures/test, with format extension) + assert_equal 'top level partial html', @view.render(:partial => '/top_level_partial') + end + + def test_render_partial_with_locals + assert_equal "5", @view.render(:partial => "test/counter", :locals => { :counter_counter => 5 }) + end + def test_render_partial_with_errors assert_raise(ActionView::TemplateError) { @view.render(:partial => "test/raise") } end @@ -54,17 +76,29 @@ class ViewRenderTest < Test::Unit::TestCase def test_render_partial_collection assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) end - + def test_render_partial_collection_as - assert_equal "david david davidmary mary mary", + assert_equal "david david davidmary mary mary", @view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer) end - + def test_render_partial_collection_without_as - assert_equal "local_inspector,local_inspector_counter,object", + assert_equal "local_inspector,local_inspector_counter,object", @view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ]) end + def test_render_partial_with_empty_collection_should_return_nil + assert_nil @view.render(:partial => "test/customer", :collection => []) + end + + def test_render_partial_with_nil_collection_should_return_nil + assert_nil @view.render(:partial => "test/customer", :collection => nil) + end + + def test_render_partial_with_empty_array_should_return_nil + assert_nil @view.render(:partial => []) + end + # TODO: The reason for this test is unclear, improve documentation def test_render_partial_and_fallback_to_layout assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" }) @@ -94,38 +128,18 @@ class ViewRenderTest < Test::Unit::TestCase assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo) end - class CustomHandler < ActionView::TemplateHandler - def render(template, local_assigns) - [template.source, local_assigns].inspect - end - end - - def test_render_inline_with_custom_type - ActionView::Template.register_template_handler :foo, CustomHandler - assert_equal '["Hello, World!", {}]', @view.render(:inline => "Hello, World!", :type => :foo) - end - - def test_render_inline_with_locals_and_custom_type - ActionView::Template.register_template_handler :foo, CustomHandler - assert_equal '["Hello, <%= name %>!", {:name=>"Josh"}]', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) - end - - class CompilableCustomHandler < ActionView::TemplateHandler - include ActionView::TemplateHandlers::Compilable - - def compile(template) - "@output_buffer = ''\n" + - "@output_buffer << 'source: #{template.source.inspect}'\n" - end + CustomHandler = lambda do |template| + "@output_buffer = ''\n" + + "@output_buffer << 'source: #{template.source.inspect}'\n" end def test_render_inline_with_compilable_custom_type - ActionView::Template.register_template_handler :foo, CompilableCustomHandler + ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo) end def test_render_inline_with_locals_and_compilable_custom_type - ActionView::Template.register_template_handler :foo, CompilableCustomHandler + ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end end diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionpack/test/template/sanitize_helper_test.rb index e5427d9dc1..f715071bbc 100644 --- a/actionpack/test/template/sanitize_helper_test.rb +++ b/actionpack/test/template/sanitize_helper_test.rb @@ -11,9 +11,9 @@ class SanitizeHelperTest < ActionView::TestCase assert_equal "Dont touch me", strip_links("Dont touch me") assert_equal "<a<a", strip_links("<a<a") assert_equal "on my mind\nall day long", strip_links("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>") - assert_equal "0wn3d", strip_links("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>") - assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic") - assert_equal "FrrFox", strip_links("<href onlclick='steal()'>FrrFox</a></href>") + assert_equal "0wn3d", strip_links("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>") + assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic") + assert_equal "FrrFox", strip_links("<href onlclick='steal()'>FrrFox</a></href>") assert_equal "My mind\nall <b>day</b> long", strip_links("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>") assert_equal "all <b>day</b> long", strip_links("<<a>a href='hello'>all <b>day</b> long<</A>/a>") end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 4999525939..a31d532567 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -35,8 +35,8 @@ class TextHelperTest < ActionView::TestCase end def test_truncate - assert_equal "Hello World!", truncate("Hello World!", 12) - assert_equal "Hello Wor...", truncate("Hello World!!", 12) + assert_equal "Hello World!", truncate("Hello World!", :length => 12) + assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12) end def test_truncate_should_use_default_length_of_30 @@ -44,23 +44,29 @@ class TextHelperTest < ActionView::TestCase assert_equal str[0...27] + "...", truncate(str) end + def test_truncate_with_options_hash + assert_equal "This is a string that wil[...]", truncate("This is a string that will go longer then the default truncate length of 30", :omission => "[...]") + assert_equal "Hello W...", truncate("Hello World!", :length => 10) + assert_equal "Hello[...]", truncate("Hello World!", :omission => "[...]", :length => 10) + end + if RUBY_VERSION < '1.9.0' def test_truncate_multibyte with_kcode 'none' do - assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10) + assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10) end with_kcode 'u' do assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...", - truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", 10) + truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", :length => 10) end end else def test_truncate_multibyte assert_equal "\354\225\210\353\205\225\355...", - truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10) + truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10) assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), - truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), 10) + truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10) end end @@ -88,7 +94,7 @@ class TextHelperTest < ActionView::TestCase assert_equal ' ', highlight(' ', 'blank text is returned verbatim') end - def test_highlighter_with_regexp + def test_highlight_with_regexp assert_equal( "This is a <strong class=\"highlight\">beautiful!</strong> morning", highlight("This is a beautiful! morning", "beautiful!") @@ -105,10 +111,17 @@ class TextHelperTest < ActionView::TestCase ) end - def test_highlighting_multiple_phrases_in_one_pass + def test_highlight_with_multiple_phrases_in_one_pass assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), '<em>\1</em>') end + def test_highlight_with_options_hash + assert_equal( + "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day", + highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>') + ) + end + def test_excerpt assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5)) assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5)) @@ -138,6 +151,16 @@ class TextHelperTest < ActionView::TestCase assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', 5)) end + def test_excerpt_with_options_hash + assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5)) + assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5)) + assert_equal( + "This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]", + excerpt("This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome temperatures. So what are you gonna do about it?", "very", + :omission => "[...]") + ) + end + if RUBY_VERSION < '1.9' def test_excerpt_with_utf8 with_kcode('u') do @@ -162,6 +185,10 @@ class TextHelperTest < ActionView::TestCase assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15)) end + def test_word_wrap_with_options_hash + assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15)) + end + def test_pluralization assert_equal("1 count", pluralize(1, "count")) assert_equal("2 counts", pluralize(2, "count")) @@ -285,7 +312,13 @@ class TextHelperTest < ActionView::TestCase url = "http://api.rubyonrails.com/Foo.html" email = "fantabulous@shiznadel.ic" - assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, 10) } + assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, :length => 10) } + end + + def test_auto_link_with_options_hash + assert_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.', + 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 def test_cycle_class diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb new file mode 100644 index 0000000000..7b94221ec0 --- /dev/null +++ b/actionpack/test/template/translation_helper_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' + +class TranslationHelperTest < Test::Unit::TestCase + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TranslationHelper + + attr_reader :request + uses_mocha 'translation_helper_test' do + def setup + end + + def test_delegates_to_i18n_setting_the_raise_option + I18n.expects(:translate).with(:foo, 'en-US', :raise => true) + translate :foo, 'en-US' + end + + def test_returns_missing_translation_message_wrapped_into_span + expected = '<span class="translation_missing">en-US, foo</span>' + assert_equal expected, translate(:foo) + end + + def test_delegates_localize_to_i18n + @time = Time.utc(2008, 7, 8, 12, 18, 38) + I18n.expects(:localize).with(@time) + localize @time + end + end +end
\ No newline at end of file diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 91d5c6ffb5..85e967ac1c 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'abstract_unit' RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env) @@ -28,6 +29,16 @@ class UrlHelperTest < ActionView::TestCase assert_equal "http://www.example.com?a=b&c=d", url_for("http://www.example.com?a=b&c=d") end + def test_url_for_with_back + @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + assert_equal 'http://www.example.com/referer', url_for(:back) + end + + def test_url_for_with_back_and_no_referer + @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + assert_equal 'javascript:history.back()', url_for(:back) + end + # todo: missing test cases def test_button_to_with_straight_url assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") @@ -267,7 +278,11 @@ class UrlHelperTest < ActionView::TestCase end def test_mail_to_with_javascript - assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript") + assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript") + end + + def test_mail_to_with_javascript_unicode + assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%22%3e%c3%ba%6e%69%63%6f%64%65%3c%2f%61%3e%27%29%3b'))</script>", mail_to("unicode@example.com", "Ășnicode", :encode => "javascript") end def test_mail_with_options @@ -291,8 +306,8 @@ class UrlHelperTest < ActionView::TestCase assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)") assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)") assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end def protect_against_forgery? @@ -419,7 +434,6 @@ class LinkToUnlessCurrentWithControllerTest < ActionView::TestCase end end - class Workshop attr_accessor :id, :new_record |