diff options
Diffstat (limited to 'actionpack/lib')
61 files changed, 636 insertions, 410 deletions
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb index dd5f9a1942..822254b1a4 100644 --- a/actionpack/lib/abstract_controller/asset_paths.rb +++ b/actionpack/lib/abstract_controller/asset_paths.rb @@ -1,5 +1,5 @@ module AbstractController - module AssetPaths + module AssetPaths #:nodoc: extend ActiveSupport::Concern included do diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index b068846a13..97a9eec144 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -5,8 +5,11 @@ require 'active_support/descendants_tracker' require 'active_support/core_ext/module/anonymous' module AbstractController - class Error < StandardError; end - class ActionNotFound < StandardError; end + class Error < StandardError #:nodoc: + end + + class ActionNotFound < StandardError #:nodoc: + end # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be # using it directly, and subclasses (like ActionController::Base) are diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 6f13ebe0d0..bc9f6fc3e8 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -249,6 +249,7 @@ module AbstractController # Symbol:: call the method specified by the symbol, which will return the template name # false:: There is no layout # true:: raise an ArgumentError + # nil:: Force default layout behavior with inheritance # # ==== Parameters # * <tt>layout</tt> - The layout to use. diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index a4e31cd2e5..c31ea6c5b5 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,7 +1,7 @@ require "active_support/benchmarkable" module AbstractController - module Logger + module Logger #:nodoc: extend ActiveSupport::Concern included do diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index ba96735e56..59ec197347 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -40,7 +40,7 @@ module ActionController #:nodoc: # # You can modify the default action cache path by passing a # <tt>:cache_path</tt> option. This will be passed directly to - # <tt>ActionCachePath.path_for</tt>. This is handy for actions with + # <tt>ActionCachePath.new</tt>. This is handy for actions with # multiple possible routes that should be cached differently. If a # block is given, it is called with the current controller instance. # @@ -170,14 +170,14 @@ module ActionController #:nodoc: options.reverse_merge!(:format => @extension) if options.is_a?(Hash) end - path = controller.url_for(options).split(%r{://}).last + path = controller.url_for(options).split('://', 2).last @path = normalize!(path) end private def normalize!(path) path << 'index' if path[-1] == ?/ - path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}") + path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}") URI.parser.unescape(path) end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 159f718029..307594d54a 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: # caches_page :index # # # cache the index action except for JSON requests - # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + # caches_page :index, :if => Proc.new { !request.format.json? } # # # don't gzip images # caches_page :image, :gzip => false diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index bb176ca3f9..cc1fa23935 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -93,7 +93,7 @@ module ActionController #:nodoc: end def method_missing(method, *arguments, &block) - super unless @controller + return super unless @controller @controller.__send__(method, *arguments, &block) end end diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 30ddf6c16e..374645c9ce 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -8,10 +8,8 @@ module ActionController #:nodoc: include ActionController::Rendering - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - }.freeze + DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) @@ -74,7 +72,27 @@ module ActionController #:nodoc: self.status = options[:status] || 200 self.content_type = options[:content_type] if options.key?(:content_type) - self.response_body = File.open(path, "rb") + self.response_body = FileBody.new(path) + end + + # Avoid having to pass an open file handle as the response body. + # Rack::Sendfile will usually intercepts the response and just uses + # the path directly, so no reason to open the file. + class FileBody #:nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, 'rb') do |file| + while chunk = file.read(16384) + yield chunk + end + end + end end # Sends the given binary data to the browser. This method is similar to @@ -107,7 +125,7 @@ module ActionController #:nodoc: # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: - send_file_headers! options.dup + send_file_headers! options render options.slice(:status, :content_type).merge(:text => data) end @@ -115,15 +133,8 @@ module ActionController #:nodoc: def send_file_headers!(options) type_provided = options.has_key?(:type) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition] - disposition += %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + raise ArgumentError, ":type option required" if content_type.nil? if content_type.is_a?(Symbol) extension = Mime[content_type] @@ -132,15 +143,18 @@ module ActionController #:nodoc: else if !type_provided && options[:filename] # If type wasn't provided, try guessing from file extension. - content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type end self.content_type = content_type end - headers.merge!( - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) + disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) + unless disposition.nil? + disposition += %(; filename="#{options[:filename]}") if options[:filename] + headers['Content-Disposition'] = disposition + end + + headers['Content-Transfer-Encoding'] = 'binary' response.sending_file = true diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index ece9ba3725..9a9db0fe5f 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -14,8 +14,6 @@ module ActionController end class MethodNotAllowed < ActionControllerError #:nodoc: - attr_reader :allowed_methods - def initialize(*allowed_methods) super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") end @@ -30,9 +28,6 @@ module ActionController class MissingFile < ActionControllerError #:nodoc: end - class RenderError < ActionControllerError #:nodoc: - end - class SessionOverflowError < ActionControllerError #:nodoc: DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.' diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 44d2f740e6..87225d74c1 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -229,7 +229,7 @@ module ActionController def decode_credentials(header) Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| key, value = pair.split('=', 2) - [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')] + [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index fbb5d01e86..f467b74256 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -74,7 +74,7 @@ module ActionController #:nodoc: # # respond_to do |format| # format.html - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # end # @@ -389,7 +389,7 @@ module ActionController #:nodoc: # # respond_to do |format| # format.html - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # # In this usage, the argument passed to the block (+format+ above) is an diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index fa760f2658..17e2db74d4 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -66,7 +66,7 @@ module ActionController # class Admin::UsersController < ApplicationController # end # - # will try to check if +Admin::User+ or +User+ model exists, and use it to + # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 3ffb7ef426..5e7bd44562 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -45,6 +45,16 @@ module ActionController # integer, or a symbol representing the downcased, underscored and symbolized description. # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # + # If you are using XHR requests other than GET or POST and redirecting after the + # request then some browsers will follow the redirect using the original request + # method. This may lead to undesirable behavior such as a double DELETE. To work + # around this you can return a <tt>303 See Other</tt> status code which will be + # followed using a GET request. + # + # Examples: + # redirect_to posts_url, :status => :see_other + # redirect_to :action => 'index', :status => 303 + # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. # @@ -93,7 +103,7 @@ module ActionController _compute_redirect_to_location options.call else url_for(options) - end.gsub(/[\0\r\n]/, '') + end.delete("\0\r\n") end end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 6e9ce450ac..4a0c1c7dd7 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -91,9 +91,14 @@ module ActionController add :json do |json, options| json = json.to_json(options) unless json.kind_of?(String) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - json + + if options[:callback].present? + self.content_type ||= Mime::JS + "#{options[:callback]}(#{json})" + else + self.content_type ||= Mime::JSON + json + end end add :js do |js, options| diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 3081c14c09..0bff1825d9 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -17,7 +17,6 @@ module ActionController #:nodoc: # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, # which checks the token and resets the session if it doesn't match what was expected. # A call to this method is generated for new \Rails applications by default. - # You can customize the error message by editing public/422.html. # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 1e8990495c..83407846dc 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -63,7 +63,7 @@ module ActionController #:nodoc: # # def create # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) + # @task = @project.tasks.build(params[:task]) # flash[:notice] = 'Task was successfully created.' if @task.save # respond_with(@project, @task) # end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index e9783e6919..eeb37db2e7 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -139,17 +139,17 @@ module ActionController #:nodoc: # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt> # will be raised, showing those objects are closed for modification. # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. # You should disable those middlewares whenever streaming in development - # or production. For instance, +Rack::Bug+ won't work when streaming as it + # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it # needs to inject contents in the HTML body. # - # Also +Rack::Cache+ won't work with streaming as it does not support + # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support # streaming bodies yet. Whenever streaming Cache-Control is automatically # set to "no-cache". # @@ -162,7 +162,7 @@ module ActionController #:nodoc: # Currently, when an exception happens in development or production, Rails # will automatically stream to the client: # - # "><script type="text/javascript">window.location = "/500.html"</script></html> + # "><script>window.location = "/500.html"</script></html> # # The first two characters (">) are required in case the exception happens # while rendering attributes for a given tag. You can check the real cause diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 18dda978b3..e7af3f5b8d 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -53,6 +53,7 @@ module ActionController # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # # dom_id(Post.find(45), :edit) # => "edit_post_45" + # dom_id(Post.new, :custom) # => "custom_post" def dom_id(record, prefix = nil) if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" @@ -74,12 +75,7 @@ module ActionController def record_key_for_dom_id(record) record = record.to_model if record.respond_to?(:to_model) key = record.to_key - key ? sanitize_dom_id(key.join('_')) : key - end - - # Replaces characters that are invalid in HTML DOM ids with valid ones. - def sanitize_dom_id(candidate_id) - candidate_id # TODO implement conversion to valid DOM id values + key ? key.join('_') : key end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 9bd2e622ad..21997c4d79 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -27,13 +27,13 @@ module ActionController path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ + if partial @partials[path] += 1 @partials[path.split("/").last] += 1 - @templates[path] += 1 - else - @templates[path] += 1 end + + @templates[path] += 1 end end @@ -140,9 +140,6 @@ module ActionController class Result < ::Array #:nodoc: def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| uri_parser.unescape str} - end end def assign_parameters(routes, controller_path, action, parameters = {}) @@ -150,17 +147,23 @@ module ActionController extra_keys = routes.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v }) - elsif value.is_a? String + if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?)) + value = value.map{ |v| v.duplicable? ? v.dup : v } + elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? }) + value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }] + elsif value.frozen? && value.duplicable? value = value.dup end if extra_keys.include?(key.to_sym) non_path_parameters[key] = value else + if value.is_a?(Array) + value = Result.new(value.map(&:to_param)) + else + value = value.to_param + end + path_parameters[key.to_s] = value end end @@ -456,7 +459,7 @@ module ActionController # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. - parameters = paramify_values(parameters) + parameters = paramify_values(parameters) if html_format?(parameters) @request.recycle! @response.recycle! @@ -469,14 +472,13 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? - "anonymous_controller" : + "anonymous" : @controller.class.name.underscore.sub(/_controller$/, '') @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) - @request.session = ActionController::TestSession.new(session) if session + @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @request.session["flash"].sweep @controller.request = @request build_request_uri(action, parameters) @@ -549,6 +551,12 @@ module ActionController @request.env["QUERY_STRING"] = query_string || "" end end + + def html_format?(parameters) + return true unless parameters.is_a?(Hash) + format = Mime[parameters[:format]] + format.nil? || format.html? + end end # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 24ffc28710..114b0e73c9 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/class/attribute' module HTML class Sanitizer def sanitize(text, options = {}) + validate_options(options) return text unless sanitizeable?(text) tokenize(text, options).join end @@ -27,6 +28,16 @@ module HTML def process_node(node, result, options) result << node.to_s end + + def validate_options(options) + if options[:tags] && !options[:tags].is_a?(Enumerable) + raise ArgumentError, "You should pass :tags as an Enumerable" + end + + if options[:attributes] && !options[:attributes].is_a?(Enumerable) + raise ArgumentError, "You should pass :attributes as an Enumerable" + end + end end class FullSanitizer < Sanitizer @@ -88,7 +99,7 @@ module HTML self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto feed svn urn aim rsync tag ssh sftp rtsp afs)) - # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. + # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse border-color border-left-color border-right-color border-top-color clear color cursor direction display elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 5c48a60469..e31f3b823d 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/attribute_accessors' + module ActionDispatch module Http module MimeNegotiation diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index e039eb1288..26f4c16218 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -38,7 +38,7 @@ module Mime # respond_to do |format| # format.html # format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] } - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # end # end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 078229efd2..cc46f9983c 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -29,7 +29,7 @@ module ActionDispatch # :nodoc: # class DemoControllerTest < ActionDispatch::IntegrationTest # def test_print_root_path_to_console # get('/') - # puts @response.body + # puts response.body # end # end class Response diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index c4a915d1ad..ce8c2729e9 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -17,7 +17,7 @@ module ActionDispatch end # Delegate these methods to the tempfile. - [:open, :path, :rewind, :size].each do |method| + [:open, :path, :rewind, :size, :eof?].each do |method| class_eval "def #{method}; @tempfile.#{method}; end" end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index f9dae5dad7..4266ec042e 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -23,23 +23,6 @@ module ActionDispatch end def url_for(options = {}) - if options[:host].blank? && options[:only_path].blank? - raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' - end - - rewritten_url = "" - - unless options[:only_path] - unless options[:protocol] == false - rewritten_url << (options[:protocol] || "http") - rewritten_url << ":" unless rewritten_url.match(%r{:|//}) - end - rewritten_url << "//" unless rewritten_url.match("//") - rewritten_url << rewrite_authentication(options) - rewritten_url << host_or_subdomain_and_domain(options) - rewritten_url << ":#{options.delete(:port)}" if options[:port] - end - path = "" path << options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s @@ -47,14 +30,36 @@ module ActionDispatch params = options[:params] || {} params.reject! {|k,v| v.to_param.nil? } - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "?#{params.to_query}" unless params.empty? - rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] - rewritten_url + result = build_host_url(options) + + result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + result << "?#{params.to_query}" unless params.empty? + result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] + result end private + def build_host_url(options) + if options[:host].blank? && options[:only_path].blank? + raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' + end + + result = "" + + unless options[:only_path] + unless options[:protocol] == false + result << (options[:protocol] || "http") + result << ":" unless result.match(%r{:|//}) + end + result << "//" unless result.match("//") + result << rewrite_authentication(options) + result << host_or_subdomain_and_domain(options) + result << ":#{options.delete(:port)}" if options[:port] + end + result + end + def named_host?(host) host && IP_HOST_REGEXP !~ host end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 25f1db8228..e1016977ab 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -273,10 +273,6 @@ module ActionDispatch @parent_jar[key] = options end - def signed - @signed ||= SignedCookieJar.new(self, @secret) - end - def method_missing(method, *arguments, &block) @parent_jar.send(method, *arguments, &block) end @@ -343,7 +339,6 @@ module ActionDispatch end def call(env) - cookie_jar = nil status, headers, body = @app.call(env) if cookie_jar = env['action_dispatch.cookies'] diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index cff0877030..54f2669f43 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -4,7 +4,7 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new) + @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep) end end @@ -17,7 +17,7 @@ module ActionDispatch # def create # # save post # flash[:notice] = "Post successfully created" - # redirect_to posts_path(@post) + # redirect_to @post # end # # def show @@ -217,10 +217,6 @@ module ActionDispatch end def call(env) - if (session = env['rack.session']) && (flash = session['flash']) - flash.sweep - end - @app.call(env) ensure session = env['rack.session'] || {} @@ -237,7 +233,8 @@ module ActionDispatch env[KEY] = new_hash end - if session.key?('flash') && session['flash'].empty? + if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) + session.key?('flash') && session['flash'].empty? session.delete('flash') end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index d924f21fad..ec15a2a715 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -5,11 +5,14 @@ module ActionDispatch # IP addresses that are "trusted proxies" that can be stripped from # the comma-delimited list in the X-Forwarded-For header. See also: # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces + # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses. TRUSTED_PROXIES = %r{ ^127\.0\.0\.1$ | # localhost + ^::1$ | ^(10 | # private IP 10.x.x.x 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 # private IP 192.168.x.x + 192\.168 | # private IP 192.168.x.x + fc00:: # private IP fc00 )\. }x @@ -19,13 +22,13 @@ module ActionDispatch @app = app @check_ip = check_ip_spoofing @proxies = case custom_proxies - when Regexp - custom_proxies - when nil - TRUSTED_PROXIES - else - Regexp.union(TRUSTED_PROXIES, custom_proxies) - end + when Regexp + custom_proxies + when nil + TRUSTED_PROXIES + else + Regexp.union(TRUSTED_PROXIES, custom_proxies) + end end def call(env) @@ -34,6 +37,31 @@ module ActionDispatch end class GetIp + + # IP v4 and v6 (with compression) validation regexp + # https://gist.github.com/1289635 + VALID_IP = %r{ + (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4 + (^( + (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated + (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end + (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6 + (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with + (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon + (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle + (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4 + (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining + (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending + )$) + }x + def initialize(env, middleware) @env = env @middleware = middleware @@ -44,25 +72,31 @@ module ActionDispatch # but will be wrong if the user is behind a proxy. Proxies will set # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of - # multiple chained proxies. The last address which is not a known proxy - # will be the originating IP. + # multiple chained proxies. The first address which is in this list + # if it's not a known proxy will be the originating IP. + # Format of HTTP_X_FORWARDED_FOR: + # client_ip, proxy_ip1, proxy_ip2... + # http://en.wikipedia.org/wiki/X-Forwarded-For def calculate_ip - client_ip = @env['HTTP_CLIENT_IP'] - forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR') - remote_addrs = ips_from('REMOTE_ADDR') + client_ip = @env['HTTP_CLIENT_IP'] + forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first + remote_addrs = ips_from('REMOTE_ADDR') check_ip = client_ip && @middleware.check_ip - if check_ip && !forwarded_ips.include?(client_ip) + if check_ip && forwarded_ip != client_ip # We don't know which came from the proxy, and which from the user raise IpSpoofAttackError, "IP spoofing attack?!" \ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - not_proxy = client_ip || forwarded_ips.first || remote_addrs.first - - # Return first REMOTE_ADDR if there are no other options - not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first + client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten + if client_ips.present? + client_ips.first + else + # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR + remote_addrs.find { |ip| valid_ip? ip } + end end def to_s @@ -71,12 +105,24 @@ module ActionDispatch @ip = calculate_ip end - protected + private - def ips_from(header, allow_proxies = false) - ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] - allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies } + def ips_from(header) + @env[header] ? @env[header].strip.split(/[,\s]+/) : [] end + + def valid_ip?(ip) + ip =~ VALID_IP + end + + def not_a_proxy?(ip) + ip !~ @middleware.proxies + end + + def remove_proxies(ips) + ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) } + end + end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 8ebf870b95..a4866f5a8f 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -59,7 +59,8 @@ module ActionDispatch end def set_session(env, sid, session_data, options) - session_data.merge("session_id" => sid) + session_data["session_id"] = sid + session_data end def set_cookie(env, session_id, cookie) diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 28e8fbdab8..12bc438be3 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -110,7 +110,7 @@ module ActionDispatch def build(app = nil, &block) app ||= block raise "MiddlewareStack#build requires an app" unless app - middlewares.reverse.inject(app) { |a, e| e.build(a) } + middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } end protected diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index cdc29fb304..1a1a054985 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/enumerable' require 'active_support/inflector' @@ -58,6 +59,16 @@ module ActionDispatch @options = (@scope[:options] || {}).merge(options) @path = normalize_path(path) normalize_options! + + via_all = @options.delete(:via) if @options[:via] == :all + + if !via_all && request_method_condition.empty? + msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n\n" \ + " Instead of: match \"controller#action\"\n" \ + " Do: get \"controller#action\"" + raise msg + end end def to_route @@ -244,7 +255,7 @@ module ActionDispatch end def self.normalize_name(name) - normalize_path(name)[1..-1].gsub("/", "_") + normalize_path(name)[1..-1].tr("/", "_") end module Base @@ -263,7 +274,7 @@ module ActionDispatch # of most Rails applications, this is beneficial. def root(options = {}) options = { :to => options } if options.is_a?(String) - match '/', { :as => :root }.merge(options) + match '/', { :as => :root, :via => :get }.merge(options) end # Matches a url pattern to one or more routes. Any symbols in a pattern @@ -416,7 +427,7 @@ module ActionDispatch options[:as] ||= app_name(app) - match(path, options.merge(:to => app, :anchor => false, :format => false)) + match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all)) define_generate_prefix(app, options[:as]) self @@ -441,7 +452,7 @@ module ActionDispatch app.railtie_name else class_name = app.class.is_a?(Class) ? app.name : app.class.name - ActiveSupport::Inflector.underscore(class_name).gsub("/", "_") + ActiveSupport::Inflector.underscore(class_name).tr("/", "_") end end @@ -880,17 +891,18 @@ module ActionDispatch # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] - RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param] CANONICAL_ACTIONS = %w(index create new show update destroy) class Resource #:nodoc: - attr_reader :controller, :path, :options + attr_reader :controller, :path, :options, :param def initialize(entities, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @as = options[:as] + @param = options[:param] || :id @options = options end @@ -935,7 +947,7 @@ module ActionDispatch alias :collection_scope :path def member_scope - "#{path}/:id" + "#{path}/:#{param}" end def new_scope(new_path) @@ -943,7 +955,7 @@ module ActionDispatch end def nested_scope - "#{path}/:#{singular}_id" + "#{path}/:#{singular}_#{param}" end end @@ -1134,6 +1146,25 @@ module ActionDispatch # comment PATCH/PUT /sekret/comments/:id(.:format) # comment DELETE /sekret/comments/:id(.:format) # + # [:shallow_prefix] + # Prefixes nested shallow route names with specified prefix. + # + # scope :shallow_prefix => "sekret" do + # resources :posts do + # resources :comments, :shallow => true + # end + # end + # + # The +comments+ resource here will have the following routes generated for it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_sekret_comment GET /comments/:id/edit(.:format) + # sekret_comment GET /comments/:id(.:format) + # sekret_comment PATCH/PUT /comments/:id(.:format) + # sekret_comment DELETE /comments/:id(.:format) + # # === Examples # # # routes call <tt>Admin::PostsController</tt> @@ -1274,6 +1305,21 @@ module ActionDispatch parent_resource.instance_of?(Resource) && @scope[:shallow] end + def draw(name) + path = @draw_paths.find do |_path| + _path.join("#{name}.rb").file? + end + + unless path + msg = "Your router tried to #draw the external file #{name}.rb,\n" \ + "but the file was not found in:\n\n" + msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n") + raise msg + end + + instance_eval(path.join("#{name}.rb").read) + end + # match 'path' => 'controller#action' # match 'path', to: 'controller#action' # match 'path', 'otherpath', on: :member, via: :get @@ -1523,6 +1569,7 @@ module ActionDispatch def initialize(set) #:nodoc: @set = set + @draw_paths = set.draw_paths @scope = { :path_names => @set.resources_path_names } end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 617b24b46a..444a79c50d 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,4 +1,6 @@ require 'action_dispatch/http/request' +require 'active_support/core_ext/uri' +require 'rack/utils' module ActionDispatch module Routing @@ -46,8 +48,17 @@ module ActionDispatch :params => request.query_parameters }.merge options + if !params.empty? && url_options[:path].match(/%\{\w*\}/) + url_options[:path] = (url_options[:path] % escape_path(params)) + end + ActionDispatch::Http::URL.url_for url_options end + + private + def escape_path(params) + Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }] + end end module Redirection @@ -67,10 +78,13 @@ module ActionDispatch # params, depending of how many arguments your block accepts. A string is required as a # return value. # - # match 'jokes/:number', :to => redirect do |params, request| - # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp") + # match 'jokes/:number', :to => redirect { |params, request| + # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") # "http://#{request.host_with_port}/#{path}" - # end + # } + # + # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass + # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead. # # The options version of redirect allows you to supply only the parts of the url which need # to change, it also supports interpolation of the path similar to the first example. @@ -93,13 +107,18 @@ module ActionDispatch path = args.shift block = lambda { |params, request| - (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) + (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % escape(params)) } if String === path block = path if path.respond_to? :call raise ArgumentError, "redirection argument not supported" unless block Redirect.new status, block end + + private + def escape(params) + Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + end end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 30e9e5634b..7a7810a95c 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -96,7 +96,25 @@ module ActionDispatch def initialize @routes = {} @helpers = [] - @module = Module.new + @module = Module.new do + protected + + def handle_positional_args(args, options, route) + inner_options = args.extract_options! + result = options.dup + + if args.any? + keys = route.segment_keys + if args.size < keys.size - 1 # take format into account + keys -= self.url_options.keys if self.respond_to?(:url_options) + keys -= options.keys + end + result.merge!(Hash[keys.zip(args)]) + end + + result.merge!(inner_options) + end + end end def helper_names @@ -135,40 +153,37 @@ module ActionDispatch end private - def url_helper_name(name, kind = :url) - :"#{name}_#{kind}" + def url_helper_name(name, only_path) + if only_path + :"#{name}_path" + else + :"#{name}_url" + end end - def hash_access_name(name, kind = :url) - :"hash_for_#{name}_#{kind}" + def hash_access_name(name, only_path) + if only_path + :"hash_for_#{name}_path" + else + :"hash_for_#{name}_url" + end end def define_named_route_methods(name, route) - {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts| - hash = route.defaults.merge(:use_route => name).merge(opts) - define_hash_access route, name, kind, hash - define_url_helper route, name, kind, hash + [true, false].each do |only_path| + hash = route.defaults.merge(:use_route => name, :only_path => only_path) + define_hash_access route, name, hash + define_url_helper route, name, hash end end - def define_hash_access(route, name, kind, options) - selector = hash_access_name(name, kind) + def define_hash_access(route, name, options) + selector = hash_access_name(name, options[:only_path]) @module.module_eval do - remove_possible_method selector - - define_method(selector) do |*args| - inner_options = args.extract_options! - result = options.dup - - if args.any? - result[:_positional_args] = args - result[:_positional_keys] = route.segment_keys - end - - result.merge(inner_options) + redefine_method(selector) do |*args| + self.handle_positional_args(args, options, route) end - protected selector end helpers << selector @@ -187,9 +202,9 @@ module ActionDispatch # # foo_url(bar, baz, bang, :sort_by => 'baz') # - def define_url_helper(route, name, kind, options) - selector = url_helper_name(name, kind) - hash_access_method = hash_access_name(name, kind) + def define_url_helper(route, name, options) + selector = url_helper_name(name, options[:only_path]) + hash_access_method = hash_access_name(name, options[:only_path]) if optimize_helper?(route) @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 @@ -240,6 +255,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions + attr_accessor :draw_paths alias :routes :set @@ -251,6 +267,7 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} + self.draw_paths = [] self.request_class = request_class @valid_conditions = {} @@ -605,10 +622,9 @@ module ActionDispatch nil end + # The +options+ argument must be +nil+ or a hash whose keys are *symbols*. def url_for(options) - options = (options || {}).reverse_merge!(default_url_options) - - handle_positional_args(options) + options = default_url_options.merge(options || {}) user, password = extract_authentication(options) path_segments = options.delete(:_path_segments) @@ -679,16 +695,6 @@ module ActionDispatch end end - def handle_positional_args(options) - return unless args = options.delete(:_positional_args) - - keys = options.delete(:_positional_keys) - keys -= options.keys if args.size < keys.size - 1 # take format into account - - # Tell url_for to skip default_url_options - options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }]) - end - end end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 94db36ce1f..d75bb1c2de 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -144,10 +144,12 @@ module ActionDispatch # # => 'http://somehost.org/tasks/testing?number=33' def url_for(options = nil) case options + when nil + _routes.url_for(url_options.symbolize_keys) + when Hash + _routes.url_for(options.symbolize_keys.reverse_merge!(url_options)) when String options - when nil, Hash - _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options)) else polymorphic_url(options) end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index a5e7a8c715..40d38c59d6 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -4,11 +4,9 @@ module ActionDispatch module Assertions # A small suite of assertions that test responses from \Rails applications. module ResponseAssertions - extend ActiveSupport::Concern - # Asserts that the response is one of the following types: # - # * <tt>:success</tt> - Status code was 200 + # * <tt>:success</tt> - Status code was in the 200-299 range # * <tt>:redirect</tt> - Status code was in the 300-399 range # * <tt>:missing</tt> - Status code was 404 # * <tt>:error</tt> - Status code was in the 500-599 range @@ -83,7 +81,7 @@ module ActionDispatch refer else @controller.url_for(fragment) - end.gsub(/[\0\r\n]/, '') + end.delete("\0\r\n") end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 8eed85bce2..ea1ed20f3c 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -269,6 +269,7 @@ module ActionDispatch end end text.strip! unless NO_STRIP.include?(match.name) + text.sub!(/\A\n/, '') if match.name == "textarea" unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s) content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text) true diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index 7280e9a93b..d04be2099c 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/hash/reverse_merge' require 'rack/utils' module ActionDispatch diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 23329d7f35..5f81f24a2e 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -139,7 +139,13 @@ module ActionView #:nodoc: # How to complete the streaming when an exception occurs. # This is our best guess: first try to close the attribute, then the tag. cattr_accessor :streaming_completion_on_exception - @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>) + @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>) + + # Specify whether rendering within namespaced controllers should prefix + # the partial paths for ActiveModel objects with the namespace. + # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb) + cattr_accessor :prefix_partial_path_with_controller_namespace + @@prefix_partial_path_with_controller_namespace = true class_attribute :helpers class_attribute :_routes @@ -194,7 +200,7 @@ module ActionView #:nodoc: # TODO Provide a new API for AV::Base and deprecate this one. if context.is_a?(ActionView::Renderer) @view_renderer = context - elsif + else lookup_context = context.is_a?(ActionView::LookupContext) ? context : ActionView::LookupContext.new(context) lookup_context.formats = formats if formats diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 6dd52d8186..7985683a72 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -15,7 +15,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="/images/rails.png?1230601161" /> # stylesheet_link_tag("application") - # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # === Using asset hosts # @@ -34,7 +34,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" /> # stylesheet_link_tag("application") - # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # Browsers typically open at most two simultaneous connections to a single # host, which means your assets often have to wait for other assets to finish @@ -47,7 +47,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" /> # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # To do this, you can either setup four actual hosts, or you can use wildcard # DNS to CNAME the wildcard to a single asset host. You can read more about @@ -66,7 +66,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" /> # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # The example above generates "http://assets1.example.com" and # "http://assets2.example.com". This option is useful for example if @@ -86,7 +86,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" /> # stylesheet_link_tag("application") - # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # Alternatively you may ask for a second parameter +request+. That one is # particularly useful for serving assets from an SSL-protected page. The @@ -163,7 +163,7 @@ module ActionView # image_tag("rails.png") # # => <img alt="Rails" src="/release-12345/images/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> + # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> # # Changing the asset_path does require that your web servers have # knowledge of the asset template paths that you rewrite to so it's not @@ -385,7 +385,7 @@ module ActionView # 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 = options.dup.symbolize_keys! + options = options.symbolize_keys src = options[:src] = path_to_image(source) @@ -442,7 +442,7 @@ module ActionView # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> # video_tag(["trailer.ogg", "trailer.flv"]) # => # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # => + # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # => # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> def video_tag(*sources) multiple_sources_tag('video', sources) do |options| @@ -478,7 +478,7 @@ module ActionView end def multiple_sources_tag(type, sources) - options = sources.extract_options!.dup.symbolize_keys! + options = sources.extract_options!.symbolize_keys sources.flatten! yield options if block_given? diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index dd4e9ae4cc..35f91cec18 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,5 +1,6 @@ require 'thread' require 'active_support/core_ext/file' +require 'active_support/core_ext/module/attribute_accessors' module ActionView module Helpers diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index c67f81dcf4..e3329c0345 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -16,7 +16,7 @@ module ActionView end def asset_tag(source, options) - content_tag("script", "", { "type" => Mime::JS, "src" => path_to_asset(source) }.merge(options)) + content_tag("script", "", { "src" => path_to_asset(source) }.merge(options)) end def custom_dir @@ -60,9 +60,9 @@ module ActionView # ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] # # javascript_include_tag :monkey # => - # <script type="text/javascript" src="/javascripts/head.js"></script> - # <script type="text/javascript" src="/javascripts/body.js"></script> - # <script type="text/javascript" src="/javascripts/tail.js"></script> + # <script src="/javascripts/head.js"></script> + # <script src="/javascripts/body.js"></script> + # <script src="/javascripts/tail.js"></script> def register_javascript_expansion(expansions) js_expansions = JavascriptIncludeTag.expansions expansions.each do |key, values| @@ -116,36 +116,36 @@ module ActionView # # ==== Examples # javascript_include_tag "xmlhr" - # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script> + # # => <script src="/javascripts/xmlhr.js?1284139606"></script> # # javascript_include_tag "xmlhr.js" - # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script> + # # => <script src="/javascripts/xmlhr.js?1284139606"></script> # # javascript_include_tag "common.javascript", "/elsewhere/cools" - # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script> - # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script> + # # => <script src="/javascripts/common.javascript?1284139606"></script> + # # <script src="/elsewhere/cools.js?1423139606"></script> # # javascript_include_tag "http://www.example.com/xmlhr" - # # => <script type="text/javascript" src="http://www.example.com/xmlhr"></script> + # # => <script src="http://www.example.com/xmlhr"></script> # # javascript_include_tag "http://www.example.com/xmlhr.js" - # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js"></script> + # # => <script src="http://www.example.com/xmlhr.js"></script> # # javascript_include_tag :defaults - # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> + # # => <script src="/javascripts/jquery.js?1284139606"></script> + # # <script src="/javascripts/rails.js?1284139606"></script> + # # <script src="/javascripts/application.js?1284139606"></script> # # * = The application.js file is only referenced if it exists # # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source: # # javascript_include_tag :all - # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script> + # # => <script src="/javascripts/jquery.js?1284139606"></script> + # # <script src="/javascripts/rails.js?1284139606"></script> + # # <script src="/javascripts/application.js?1284139606"></script> + # # <script src="/javascripts/shop.js?1284139606"></script> + # # <script src="/javascripts/checkout.js?1284139606"></script> # # Note that your defaults of choice will be included first, so they will be available to all subsequently # included files. @@ -166,25 +166,25 @@ module ActionView # # # assuming config.perform_caching is false # javascript_include_tag :all, :cache => true - # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/rails.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script> + # # => <script src="/javascripts/jquery.js?1284139606"></script> + # # <script src="/javascripts/rails.js?1284139606"></script> + # # <script src="/javascripts/application.js?1284139606"></script> + # # <script src="/javascripts/shop.js?1284139606"></script> + # # <script src="/javascripts/checkout.js?1284139606"></script> # # # assuming config.perform_caching is true # javascript_include_tag :all, :cache => true - # # => <script type="text/javascript" src="/javascripts/all.js?1344139789"></script> + # # => <script src="/javascripts/all.js?1344139789"></script> # # # assuming config.perform_caching is false # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> - # # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script> - # # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script> + # # => <script src="/javascripts/jquery.js?1284139606"></script> + # # <script src="/javascripts/cart.js?1289139157"></script> + # # <script src="/javascripts/checkout.js?1299139816"></script> # # # assuming config.perform_caching is true # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script> + # # => <script src="/javascripts/shop.js?1299139816"></script> # # The <tt>:recursive</tt> option is also available for caching: # diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index 2584b67548..60d4af178a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -17,7 +17,7 @@ module ActionView def asset_tag(source, options) # We force the :request protocol here to avoid a double-download bug in IE7 and IE8 - tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options)) + tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options)) end def custom_dir @@ -38,9 +38,9 @@ module ActionView # ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] # # stylesheet_link_tag :monkey # => - # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" /> def register_stylesheet_expansion(expansions) style_expansions = StylesheetIncludeTag.expansions expansions.each do |key, values| @@ -81,30 +81,30 @@ module ActionView # # ==== Examples # stylesheet_link_tag "style" # => - # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag "style.css" # => - # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag "http://www.example.com/style.css" # => - # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag "style", :media => "all" # => - # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style.css" media="all" rel="stylesheet" /> # # stylesheet_link_tag "style", :media => "print" # => - # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style.css" media="print" rel="stylesheet" /> # # stylesheet_link_tag "random.styles", "/css/stylish" # => - # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" /> + # <link href="/css/stylish.css" media="screen" rel="stylesheet" /> # # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source: # # stylesheet_link_tag :all # => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> # # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>: # @@ -119,20 +119,20 @@ module ActionView # # ==== Examples # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true => - # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false => - # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" /> - # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" /> + # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" /> # # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true => - # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" /> + # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" /> # # The <tt>:recursive</tt> option is also available for caching: # diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index 73824dc1f8..f9aa8d7cee 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -176,6 +176,7 @@ module ActionView # * <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}" + # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html". def entry(record, options = {}) @xml.entry do @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") @@ -188,7 +189,9 @@ module ActionView @xml.updated((options[:updated] || record.updated_at).xmlschema) end - @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record)) + type = options.fetch(:type, 'text/html') + + @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record)) yield AtomBuilder.new(@xml) end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 278139cadb..d9d6f90211 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -96,7 +96,7 @@ module ActionView # Please login! # # <% content_for :script do %> - # <script type="text/javascript">alert('You are not authorized to view this page!')</script> + # <script>alert('You are not authorized to view this page!')</script> # <% end %> # # Then, in another view, you could to do something like this: diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index be67042999..81f856feda 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -12,11 +12,11 @@ module ActionView # elements. All of the select-type methods share a number of common options that are as follows: # # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" - # would give birthday[month] instead of date[month] if passed to the <tt>select_month</tt> method. + # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> 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 <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead - # of "date[month]". + # of \date[month]. module DateHelper # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs. @@ -64,11 +64,15 @@ module ActionView # distance_of_time_in_words(to_time, from_time, true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute # + # distance_of_time_in_words(70) # => 1 minute + # distance_of_time_in_words(60*60) # => about 1 hour + # 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 + from_time, to_time = to_time, from_time if from_time > to_time + distance_in_minutes = ((to_time - from_time)/60.0).round + distance_in_seconds = (to_time - from_time).round I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| case distance_in_minutes @@ -94,18 +98,22 @@ module ActionView when 43200..86399 then locale.t :about_x_months, :count => 1 when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round else - fyear = from_time.year - fyear += 1 if from_time.month >= 3 - tyear = to_time.year - tyear -= 1 if to_time.month < 3 - leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} - minute_offset_for_leap_year = leap_years * 1440 - # Discount the leap year days when calculating year distance. - # e.g. if there are 20 leap year days between 2 dates having the same day - # and month then the based on 365 days calculation - # the distance in years will come out to over 80 years when in written - # english it would read better as about 80 years. - minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + if from_time.acts_like?(:time) && to_time.acts_like?(:time) + fyear = from_time.year + fyear += 1 if from_time.month >= 3 + tyear = to_time.year + tyear -= 1 if to_time.month < 3 + leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # english it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + else + minutes_with_offset = distance_in_minutes + end remainder = (minutes_with_offset % 525600) distance_in_years = (minutes_with_offset / 525600) if remainder < 131400 @@ -626,7 +634,7 @@ module ActionView # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> # # <%= time_tag Time.now do %> - # <span>Right now</span> + # <span>Right now</span> # <% end %> # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> # @@ -674,11 +682,7 @@ module ActionView @options[:discard_minute] ||= true if @options[:discard_hour] @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] - # If the day is hidden, the day should be set to the 1st so all month and year choices are valid. Otherwise, - # February 31st or February 29th, 2011 can be selected, which are invalid. - if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded if @options[:tag] && @options[:ignore_date] select_time @@ -701,11 +705,7 @@ module ActionView @options[:discard_month] ||= true unless order.include?(:month) @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) - # If the day is hidden, the day should be set to the 1st so all month and year choices are valid. Otherwise, - # February 31st or February 29th, 2011 can be selected, which are invalid. - if @datetime && @options[:discard_day] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } @@ -803,7 +803,15 @@ module ActionView private %w( sec min hour day month year ).each do |method| define_method(method) do - @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime + @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime + end + end + + # If the day is hidden, the day should be set to the 1st so all month and year choices are + # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. + def set_day_if_discarded + if @datetime && @options[:discard_day] + @datetime = @datetime.change(:day => 1) end end @@ -977,7 +985,10 @@ module ActionView # 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(/[\]\)]/, '') + id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') + id = @options[:namespace] + '_' + id if @options[:namespace] + + id end # Given an ordering of datetime components, create the selection HTML diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 43d5cf1471..67f2abe509 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -9,6 +9,8 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' +require 'active_support/deprecation' +require 'active_support/core_ext/string/inflections' module ActionView # = Action View Form Helpers @@ -22,7 +24,7 @@ module ActionView # being routed to the appropriate controller action (with the appropriate <tt>:id</tt> # parameter in the case of an existing resource), (ii) input fields should # be named in such a way that in the controller their values appear in the - # appropriate places within the +params+ hash, and (iii) for an existing record, + # appropriate places within the +params+ hash, and (iii) for an existing record, # when the form is initially displayed, input fields corresponding to attributes # of the resource should show the current values of those attributes. # @@ -156,7 +158,7 @@ module ActionView # if <tt>:person</tt> also happens to be the name of an instance variable # <tt>@person</tt>, the default value of the field shown when the form is # initially displayed (e.g. in the situation where you are editing an - # existing record) will be the value of the corresponding attribute of + # existing record) will be the value of the corresponding attribute of # <tt>@person</tt>. # # The rightmost argument to +form_for+ is an @@ -183,7 +185,7 @@ module ActionView # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> - # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> + # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> # <%= f.submit %> # <% end %> # @@ -672,6 +674,19 @@ module ActionView # <% end %> # ... # <% end %> + # + # When a collection is used you might want to know the index of each + # object into the array. For this purpose, the <tt>index</tt> method + # is available in the FormBuilder object. + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # Project #<%= project_fields.index %> + # ... + # <% end %> + # ... + # <% end %> def fields_for(record_name, record_object = nil, options = {}, &block) builder = instantiate_builder(record_name, record_object, options) output = capture(builder, &block) @@ -888,7 +903,7 @@ module ActionView # # Let's say that @post.validated? is 1: # check_box("post", "validated") # # => <input name="post[validated]" type="hidden" value="0" /> - # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> + # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") @@ -1025,9 +1040,14 @@ module ActionView object_name = ActiveModel::Naming.param_key(object) end - builder = options[:builder] || ActionView::Base.default_form_builder + builder = options[:builder] || default_form_builder builder.new(object_name, object, self, options) end + + def default_form_builder + builder = ActionView::Base.default_form_builder + builder.respond_to?(:constantize) ? builder.constantize : builder + end end class FormBuilder @@ -1037,7 +1057,7 @@ module ActionView attr_accessor :object_name, :object, :options - attr_reader :multipart, :parent_builder + attr_reader :multipart, :parent_builder, :index alias :multipart? :multipart def multipart=(multipart) @@ -1057,7 +1077,12 @@ module ActionView self end - def initialize(object_name, object, template, options) + def initialize(object_name, object, template, options, block=nil) + if block + ActiveSupport::Deprecation.warn( + "Giving a block to FormBuilder is deprecated and has no effect anymore.") + end + @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @parent_builder = options[:parent_builder] @@ -1070,6 +1095,7 @@ module ActionView end end @multipart = nil + @index = options[:index] || options[:child_index] end (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| @@ -1101,12 +1127,14 @@ module ActionView end index = if options.has_key?(:index) - "[#{options[:index]}]" + options[:index] elsif defined?(@auto_index) self.object_name = @object_name.to_s.sub(/\[\]$/,"") - "[#{@auto_index}]" + @auto_index end - record_name = "#{object_name}#{index}[#{record_name}]" + + record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]" + fields_options[:child_index] = index @template.fields_for(record_name, record_object, fields_options, &block) end @@ -1244,7 +1272,8 @@ module ActionView explicit_child_index = options[:child_index] output = ActiveSupport::SafeBuffer.new association.each do |child| - output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block) + options[:child_index] = nested_child_index(name) unless explicit_child_index + output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block) end output elsif association diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 41d895c15e..b5e0970612 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/module/attribute_accessors' module ActionView # = Action View Form Tag Helpers @@ -17,6 +18,9 @@ module ActionView include UrlHelper include TextHelper + mattr_accessor :embed_authenticity_token_in_remote_forms + self.embed_authenticity_token_in_remote_forms = false + # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like # ActionController::Base#url_for. The method for the form defaults to POST. # @@ -27,9 +31,11 @@ module ActionView # is added to simulate the verb over post. # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all - # (by passing <tt>false</tt>). If this is a remote form, the authenticity_token will by default - # not be included as the ajax handler will get it from the meta-tag (but you can force it to be - # rendered anyway in that case by passing <tt>true</tt>). + # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token + # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. + # This is helpful when you're fragment-caching the form. Remote forms get the + # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you + # support browsers without JavaScript. # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. @@ -95,7 +101,7 @@ module ActionView # # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option> # # <option>Green</option><option>Blue</option></select> # - # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>".html_safe + # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe # # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option> # # <option>Out</option></select> # @@ -116,11 +122,11 @@ module ActionView html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name if options.delete(:include_blank) - option_tags = "<option value=\"\"></option>".html_safe + option_tags + option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags) end if prompt = options.delete(:prompt) - option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags + option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags) end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -618,16 +624,18 @@ module ActionView # responsibility of the caller to escape all the values. html_options["action"] = url_for(url_for_options) html_options["accept-charset"] = "UTF-8" - + html_options["data-remote"] = true if html_options.delete("remote") - if html_options["data-remote"] && html_options["authenticity_token"] == true + if html_options["data-remote"] && + !embed_authenticity_token_in_remote_forms && + html_options["authenticity_token"].blank? + # The authenticity token is taken from the meta tag in this case + html_options["authenticity_token"] = false + elsif html_options["authenticity_token"] == true # Include the default authenticity_token, which is only generated when its set to nil, # but we needed the true value to override the default of no authenticity_token on data-remote. html_options["authenticity_token"] = nil - elsif html_options["data-remote"] - # The authenticity token is taken from the meta tag in this case - html_options["authenticity_token"] = false end end end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index d88f5babb9..cc20518b93 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -15,7 +15,6 @@ module ActionView JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
' - # Escapes carriage returns and single and double quotes for JavaScript segments. # @@ -37,7 +36,7 @@ module ActionView # javascript_tag "alert('All is good')" # # Returns: - # <script type="text/javascript"> + # <script> # //<![CDATA[ # alert('All is good') # //]]> @@ -46,7 +45,7 @@ module ActionView # +html_options+ may be a hash of attributes for the <tt>\<script></tt> # tag. Example: # javascript_tag "alert('All is good')", :defer => 'defer' - # # => <script defer="defer" type="text/javascript">alert('All is good')</script> + # # => <script defer="defer">alert('All is good')</script> # # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +html_options+ as the first parameter. @@ -62,46 +61,12 @@ module ActionView content_or_options_with_block end - content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) + content_tag(:script, javascript_cdata_section(content), html_options) end def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end - - # Returns a button whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as button label and the JavaScript code goes into its +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. - # - # button_to_function "Greeting", "alert('Hello world!')", :class => "ok" - # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" /> - # - def button_to_function(name, function=nil, html_options={}) - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" - - tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) - end - - # Returns a link whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as the link text and the JavaScript code goes into the +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all - # the JavaScript is set, the helper appends "; return false;". - # - # The +href+ attribute of the tag is set to "#" unless +html_options+ has one. - # - # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link" - # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a> - # - def link_to_function(name, function, html_options={}) - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" - href = html_options[:href] || '#' - - content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) - end end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index b0860f87c4..2011351bd2 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -74,11 +74,11 @@ module ActionView number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank? end - str = [] + str = '' str << "+#{country_code}#{delimiter}" unless country_code.blank? str << number str << " x #{extension}" unless extension.blank? - ERB::Util.html_escape(str.join) + ERB::Util.html_escape(str) end # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format @@ -137,12 +137,12 @@ module ActionView begin value = number_with_precision(number, options.merge(:raise => true)) - format.gsub(/%n/, value).gsub(/%u/, unit).html_safe + format.gsub('%n', value).gsub('%u', unit).html_safe rescue InvalidNumberError => e if options[:raise] raise else - formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit) + formatted_number = format.gsub('%n', e.number).gsub('%u', unit) e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number end end @@ -233,7 +233,7 @@ module ActionView parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") - parts.join(options[:separator]).html_safe + safe_join(parts, options[:separator]) end # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision @@ -292,6 +292,7 @@ module ActionView precision = precision > 0 ? precision : 0 #don't let it be negative else rounded_number = BigDecimal.new(number.to_s).round(precision).to_f + rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros end formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options) if strip_insignificant_zeros @@ -391,10 +392,10 @@ module ActionView # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are: - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. - # # %u The quantifier (ex.: 'thousand') # %n The number + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # # # ==== Examples # number_to_human(123) # => "123" diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 1a15459406..9b35f076e5 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -94,10 +94,10 @@ module ActionView # for each record. def content_tag_for_single_record(tag_name, record, prefix, options, &block) options = options ? options.dup : {} - options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".rstrip, :id => dom_id(record, prefix)) + options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip + options[:id] = dom_id(record, prefix) - content = block.arity == 0 ? capture(&block) : capture(record, &block) - content_tag(tag_name, content, options) + content_tag(tag_name, capture(record, &block), options) end end end diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ecd26891d6..23777721f6 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -14,9 +14,13 @@ module ActionView BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seemless muted required - autofocus novalidate formnovalidate open pubdate).to_set + autofocus novalidate formnovalidate open pubdate itemscope).to_set BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) + PRE_CONTENT_STRINGS = { + :textarea => "\n" + } + # Returns an empty HTML tag of type +name+ which by default is XHTML # compliant. Set +open+ to true to create an open tag compatible # with HTML 4.0 and below. Add HTML attributes by passing an attributes @@ -126,7 +130,7 @@ module ActionView def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options content = ERB::Util.h(content) if escape - "<#{name}#{tag_options}>#{content}</#{name}>".html_safe + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe end def tag_options(options, escape = true) diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index 7c2f12d8e7..e4f431a6d7 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -123,7 +123,7 @@ module ActionView add_default_name_and_id(html_options) select = content_tag("select", add_options(option_tags, options, value(object)), html_options) - if html_options["multiple"] && options.fetch(:include_hidden) { true } + if html_options["multiple"] && options.fetch(:include_hidden, true) tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select else select diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb index 1a4aebb936..9d17a1dde3 100644 --- a/actionpack/lib/action_view/helpers/tags/check_box.rb +++ b/actionpack/lib/action_view/helpers/tags/check_box.rb @@ -41,17 +41,15 @@ module ActionView def checked?(value) case value when TrueClass, FalseClass - value + value == !!@checked_value when NilClass false - when Integer - value != 0 when String value == @checked_value when Array value.include?(@checked_value) else - value.to_i != 0 + value.to_i == @checked_value.to_i end end diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb index 1bd71c2778..16135fcd5a 100644 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ b/actionpack/lib/action_view/helpers/tags/label.rb @@ -3,16 +3,16 @@ module ActionView module Tags class Label < Base #:nodoc: def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) + options ||= {} + content_is_options = content_or_options.is_a?(Hash) if content_is_options - options = content_or_options + options.merge! content_or_options @content = nil else @content = content_or_options end - options ||= {} - super(object_name, method_name, template_object, options) end @@ -33,7 +33,7 @@ module ActionView options["for"] = name_and_id["id"] unless options.key?("for") if block_given? - @template_object.label_tag(name_and_id["id"], options, &block) + content = @template_object.capture(&block) else content = if @content.blank? @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1') @@ -55,9 +55,9 @@ module ActionView end content ||= @method_name.humanize - - label_tag(name_and_id["id"], content, options) end + + label_tag(name_and_id["id"], content, options) end end end diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb index 2e48850d5c..f74652c5e7 100644 --- a/actionpack/lib/action_view/helpers/tags/text_area.rb +++ b/actionpack/lib/action_view/helpers/tags/text_area.rb @@ -10,7 +10,7 @@ module ActionView options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end - content_tag("textarea", "\n#{options.delete('value') || value_before_type_cast(object)}", options) + content_tag("textarea", options.delete('value') || value_before_type_cast(object), options) end end end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 3dc651501e..12bb162da2 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/filters' +require 'active_support/core_ext/array/extract_options' module ActionView # = Action View Text Helpers @@ -111,9 +112,9 @@ module ActionView def highlight(text, phrases, *args) options = args.extract_options! unless args.empty? - options[:highlighter] = args[0] || '<mark>\1</mark>' + options[:highlighter] = args[0] end - options.reverse_merge!(:highlighter => '<mark>\1</mark>') + options[:highlighter] ||= '<mark>\1</mark>' text = sanitize(text) unless options[:sanitize] == false if text.blank? || phrases.blank? @@ -156,19 +157,20 @@ module ActionView options = args.extract_options! unless args.empty? - options[:radius] = args[0] || 100 - options[:omission] = args[1] || "..." + options[:radius] = args[0] + options[:omission] = args[1] end - options.reverse_merge!(:radius => 100, :omission => "...") + radius = options[:radius] || 100 + omission = options[:omission] || "..." phrase = Regexp.escape(phrase) return unless found_pos = text =~ /(#{phrase})/i - start_pos = [ found_pos - options[:radius], 0 ].max - end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min + start_pos = [ found_pos - radius, 0 ].max + end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min - prefix = start_pos > 0 ? options[:omission] : "" - postfix = end_pos < text.length - 1 ? options[:omission] : "" + prefix = start_pos > 0 ? omission : "" + postfix = end_pos < text.length - 1 ? omission : "" prefix + text[start_pos..end_pos].strip + postfix end @@ -217,12 +219,12 @@ module ActionView def word_wrap(text, *args) options = args.extract_options! unless args.blank? - options[:line_width] = args[0] || 80 + options[:line_width] = args[0] end - options.reverse_merge!(:line_width => 80) + line_width = options[:line_width] || 80 text.split("\n").collect do |line| - line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line + line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line end * "\n" end @@ -306,12 +308,9 @@ module ActionView # </tr> # <% end %> def cycle(first_value, *values) - if (values.last.instance_of? Hash) - params = values.pop - name = params[:name] - else - name = "default" - end + options = values.extract_options! + name = options.fetch(:name, "default") + values.unshift(first_value) cycle = get_cycle(name) diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index cc74eff53a..fd06bfa2a8 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -45,6 +45,7 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options.merge!(:rescue_format => :html) unless options.key?(:rescue_format) + options[:default] = wrap_translate_defaults(options[:default]) if options[:default] if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| @@ -83,6 +84,21 @@ module ActionView def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ end + + def wrap_translate_defaults(defaults) + new_defaults = [] + defaults = Array(defaults) + while key = defaults.shift + if key.is_a?(Symbol) + new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } + break + else + new_defautls << key + end + end + + new_defaults + end end end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 29f556502b..d0f716cc80 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -334,7 +334,7 @@ module ActionView remote = html_options.delete('remote') method = html_options.delete('method').to_s - method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : "" + method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} @@ -347,7 +347,8 @@ module ActionView html_options = convert_options_to_data_attributes(options, html_options) html_options.merge!("type" => "submit", "value" => name || url) - "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe + inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag + content_tag('form', content_tag('div', inner_tags), form_options) end @@ -480,7 +481,7 @@ module ActionView # # => <a href="mailto:me@domain.com">me@domain.com</a> # # mail_to "me@domain.com", "My email", :encode => "javascript" - # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script> + # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script> # # 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> @@ -514,7 +515,7 @@ module ActionView "document.write('#{html}');".each_byte do |c| string << sprintf("%%%x", c) end - "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe + "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe when "hex" email_address_encoded = email_address_obfuscated.unpack('C*').map {|c| sprintf("&#%d;", c) diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 43371a1c49..9f5e3be454 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -7,6 +7,14 @@ module ActionView config.action_view = ActiveSupport::OrderedOptions.new config.action_view.stylesheet_expansions = {} config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } + config.action_view.embed_authenticity_token_in_remote_forms = false + + initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = + app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) + end + end initializer "action_view.logger" do ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 245a19deec..c5d5540510 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -32,7 +32,7 @@ module ActionView # # <%= render :partial => "account", :object => @buyer %> # - # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is + # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is # equivalent to: # # <%= render :partial => "account", :locals => { :account => @buyer } %> @@ -158,8 +158,8 @@ module ActionView # Name: <%= user.name %> # </div> # - # If a collection is given, the layout will be rendered once for each item in the collection. Just think - # these two snippets have the same output: + # If a collection is given, the layout will be rendered once for each item in + # the collection. Just think these two snippets have the same output: # # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> @@ -184,7 +184,7 @@ module ActionView # <%= render :partial => "user", :layout => "li_layout", :collection => users %> # </ul> # - # Given two users whose names are Alice and Bob, these snippets return: + # Given two users whose names are Alice and Bob, these snippets return: # # <ul> # <li> @@ -195,6 +195,10 @@ module ActionView # </li> # </ul> # + # The current object being rendered, as well as the object_counter, will be + # available as local variables inside the layout template under the same names + # as available in the partial. + # # You can also apply a layout to a block within any template: # # <%# app/views/users/_chief.html.erb &> @@ -245,18 +249,25 @@ module ActionView # <%- end -%> # <% end %> class PartialRenderer < AbstractRenderer - PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} } + PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} } def initialize(*) super @context_prefix = @lookup_context.prefixes.first - @partial_names = PARTIAL_NAMES[@context_prefix] end def render(context, options, block) setup(context, options, block) identifier = (@template = find_partial) ? @template.identifier : @path + @lookup_context.rendered_format ||= begin + if @template && @template.formats.present? + @template.formats.first + else + formats.first + end + end + if @collection instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do render_collection @@ -275,14 +286,7 @@ module ActionView spacer = find_template(@options[:spacer_template]).render(@view, @locals) end - if layout = @options[:layout] - layout = find_template(layout) - end - result = @template ? collection_with_template : collection_without_template - - result.map!{|content| layout.render(@view, @locals) { content } } if layout - result.join(spacer).html_safe end @@ -291,7 +295,7 @@ module ActionView object, as = @object, @variable if !block && (layout = @options[:layout]) - layout = find_template(layout) + layout = find_template(layout, @locals.keys + [@variable]) end object ||= locals[as] @@ -316,8 +320,6 @@ module ActionView @block = block @details = extract_details(options) - @lookup_context.rendered_format ||= formats.first - if String === partial @object = options[:object] @path = partial @@ -379,17 +381,23 @@ module ActionView segments, locals, template = [], @locals, @template as, counter = @variable, @variable_counter + if layout = @options[:layout] + layout = find_template(layout, @locals.keys + [@variable, @variable_counter]) + end + locals[counter] = -1 @collection.each do |object| locals[counter] += 1 locals[as] = object - segments << template.render(@view, locals) + + content = template.render(@view, locals) + content = layout.render(@view, locals) { content } if layout + segments << content end - + segments end - def collection_without_template segments, locals, collection_data = [], @locals, @collection_data @@ -417,7 +425,15 @@ module ActionView raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") end - @partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + if @view.prefix_partial_path_with_controller_namespace + prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + else + path + end + end + + def prefixed_partial_names + @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] end def merge_prefix_into_object_path(prefix, object_path) @@ -438,7 +454,7 @@ module ActionView end def retrieve_variable(path) - variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym + variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym) variable_counter = :"#{variable}_counter" if @collection [variable, variable_counter] end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index f7df9a6322..ae923de24e 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -9,8 +9,8 @@ module ActionView context = @lookup_context unless context.rendered_format - context.rendered_format = template.formats.first - context.formats = template.formats + context.formats = template.formats unless template.formats.empty? + context.rendered_format = context.formats.first end render_template(template, options[:layout], options[:locals]) |