diff options
Diffstat (limited to 'actionpack/lib')
33 files changed, 440 insertions, 158 deletions
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 492329c401..09b9e7ddf0 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -16,6 +16,10 @@ module AbstractController generate_method_for_mime(mime) end + Mime::Type.register_callback do |mime| + generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) + end + protected def method_missing(symbol, &block) diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 6d68cf4944..b6c484d188 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,6 +1,12 @@ module AbstractController module Translation def translate(*args) + key = args.first + if key.is_a?(String) && (key[0] == '.') + key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }" + args[0] = key + end + I18n.translate(*args) end alias :t :translate @@ -10,4 +16,4 @@ module AbstractController end alias :l :localize end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 39da15e26a..73291ce083 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -68,14 +68,14 @@ module ActionController #:nodoc: def after(controller) self.controller = controller callback(:after) if controller.perform_caching - # Clean up, so that the controller can be collected after this request - self.controller = nil end def around(controller) before(controller) yield after(controller) + ensure + clean_up end protected @@ -90,6 +90,11 @@ module ActionController #:nodoc: end private + def clean_up + # Clean up, so that the controller can be collected after this request + self.controller = nil + end + def callback(timing) controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index bd768b634e..b078beb675 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -3,19 +3,34 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - delegate :flash, :to => :request - delegate :alert, :notice, :to => "request.flash" - helper_method :alert, :notice + class_attribute :_flash_types, instance_accessor: false + self._flash_types = [] + + delegate :flash, to: :request + add_flash_types(:alert, :notice) end - protected - def redirect_to(options = {}, response_status_and_flash = {}) #:doc: - if alert = response_status_and_flash.delete(:alert) - flash[:alert] = alert + module ClassMethods + def add_flash_types(*types) + types.each do |type| + next if _flash_types.include?(type) + + define_method(type) do + request.flash[type] + end + helper_method type + + _flash_types << type end + end + end - if notice = response_status_and_flash.delete(:notice) - flash[:notice] = notice + protected + def redirect_to(options = {}, response_status_and_flash = {}) #:doc: + self.class._flash_types.each do |flash_type| + if type = response_status_and_flash.delete(flash_type) + flash[flash_type] = type + end end if other_flashes = response_status_and_flash.delete(:flash) diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index a0d1064094..d84588d3df 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -229,9 +229,9 @@ module ActionController end def decode_credentials(header) - Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| + HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| key, value = pair.split('=', 2) - [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')] + [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] end @@ -436,10 +436,12 @@ module ActionController values = Hash[$1.split(',').map do |value| value.strip! # remove any spaces between commas and values key, value = value.split(/\=\"?/) # split key=value pairs - value.chomp!('"') # chomp trailing " in value - value.gsub!(/\\\"/, '"') # unescape remaining quotes - [key, value] - end] + if value + value.chomp!('"') # chomp trailing " in value + value.gsub!(/\\\"/, '"') # unescape remaining quotes + [key, value] + end + end.compact] [values.delete("token"), values.with_indifferent_access] end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 0b800c3c62..4665fea91a 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -182,7 +182,8 @@ module ActionController #:nodoc: # end # end # - # Be sure to check respond_with and respond_to documentation for more examples. + # Be sure to check the documentation of +respond_with+ and + # <tt>ActionController::MimeResponds.respond_to</tt> for more examples. def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index f01f4b99a9..bdf6e88699 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -9,8 +9,7 @@ module ActionController :status, :location, :content_type, :to => "@_response" def dispatch(action, request) - @_response = ActionDispatch::Response.new - @_response.request = request + set_response!(request) super(action, request) end @@ -22,5 +21,12 @@ module ActionController def reset_session @_request.reset_session end + + private + + def set_response!(request) + @_response = ActionDispatch::Response.new + @_response.request = request + end end end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 83407846dc..d9c89a74f1 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -90,7 +90,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, :status => 201) # end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index eeb37db2e7..9f3c997024 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -139,9 +139,6 @@ 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 <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. diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index d1813ee745..0377b8c4cf 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -4,30 +4,25 @@ module ActionController include RackDelegation - def recycle! - @_url_options = nil - end - - - # TODO: Clean this up - def process_with_new_base_test(request, response) - @_request = request - @_response = response - @_response.request = request - ret = process(request.parameters[:action]) - if cookies = @_request.env['action_dispatch.cookies'] - cookies.write(@_response) - end - @_response.prepare! - ret - end - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new @_response.headers.replace(new_headers) end + # Behavior specific to functional tests + module Functional # :nodoc: + def set_response!(request) + end + + def recycle! + @_url_options = nil + self.response_body = nil + self.formats = nil + self.params = nil + end + end + module ClassMethods def before_filters _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a1f29ea1bc..5f50bf5de6 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -143,6 +143,9 @@ module ActionController end class TestRequest < ActionDispatch::TestRequest #:nodoc: + DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup + DEFAULT_ENV.delete 'PATH_INFO' + def initialize(env = {}) super @@ -150,10 +153,6 @@ module ActionController self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16)) end - class Result < ::Array #:nodoc: - def to_s() join '/' end - end - def assign_parameters(routes, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = routes.extra_keys(parameters) @@ -171,7 +170,7 @@ module ActionController non_path_parameters[key] = value else if value.is_a?(Array) - value = Result.new(value.map(&:to_param)) + value = value.map(&:to_param) else value = value.to_param end @@ -211,6 +210,12 @@ module ActionController cookie_jar.update(@set_cookies) cookie_jar.recycle! end + + private + + def default_env + DEFAULT_ENV + end end class TestResponse < ActionDispatch::TestResponse @@ -353,7 +358,7 @@ module ActionController # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| - desc < ActionController::Base + Class === desc && desc < ActionController::Base end module Behavior @@ -430,8 +435,13 @@ module ActionController end # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, "HEAD", parameters, session, flash) + def head(action, *args) + process(action, "HEAD", *args) + end + + # Executes a request simulating OPTIONS HTTP method and set/volley the response + def options(action, *args) + process(action, "OPTIONS", *args) end def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @@ -471,13 +481,17 @@ module ActionController # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) + @html_document = nil + + unless @controller.respond_to?(:recycle!) + @controller.extend(Testing::Functional) + @controller.class.class_eval { include Testing } + end + @request.recycle! @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil + @controller.recycle! - @html_document = nil @request.env['REQUEST_METHOD'] = http_method parameters ||= {} @@ -490,26 +504,34 @@ module ActionController @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @controller.request = @request + @controller.request = @request + @controller.response = @response + build_request_uri(action, parameters) - @controller.class.class_eval { include Testing } - @controller.recycle! - @controller.process_with_new_base_test(@request, @response) + + name = @request.parameters[:action] + + @controller.process(name) + + if cookies = @request.env['action_dispatch.cookies'] + cookies.write(@response) + end + @response.prepare! + @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} @request.session.delete('flash') if @request.session['flash'].blank? @response end def setup_controller_request_and_response - @request = TestRequest.new - @response = TestResponse.new + @request = TestRequest.new + @response = TestResponse.new + @response.request = @request if klass = self.class.controller_class @controller ||= klass.new rescue nil end - @request.env.delete('PATH_INFO') - if defined?(@controller) && @controller @controller.request = @request @controller.params = {} @@ -523,7 +545,7 @@ module ActionController setup :setup_controller_request_and_response end - private + private def check_required_ivars # Sanity check for required instance variables so we can give an # understandable error message. @@ -564,8 +586,7 @@ module ActionController def html_format?(parameters) return true unless parameters.is_a?(Hash) - format = Mime[parameters[:format]] - format.nil? || format.html? + Mime.fetch(parameters[:format]) { Mime['html'] }.html? end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index ee1913dbf9..fe39c220a5 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -29,6 +29,11 @@ module Mime Type.lookup_by_extension(type.to_s) end + def self.fetch(type) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + end + # Encapsulates the notion of a mime type. Can be used at render time, for example, with: # # class PostsController < ActionController::Base @@ -53,6 +58,8 @@ module Mime cattr_reader :browser_generated_types attr_reader :symbol + @register_callbacks = [] + # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -84,6 +91,10 @@ module Mime TRAILING_STAR_REGEXP = /(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ + def register_callback(&block) + @register_callbacks << block + end + def lookup(string) LOOKUP[string] end @@ -101,10 +112,15 @@ module Mime def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms)) - SET << Mime.const_get(symbol.upcase) + new_mime = Mime.const_get(symbol.upcase) + SET << new_mime ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last } + + @register_callbacks.each do |callback| + callback.call(new_mime) + end end def parse(accept_header) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 8cea17c7a6..1377e53ce8 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -119,9 +119,9 @@ module ActionDispatch end # Is this a HEAD request? - # Equivalent to <tt>request.method_symbol == :head</tt>. + # Equivalent to <tt>request.request_method_symbol == :head</tt>. def head? - HTTP_METHOD_LOOKUP[method] == :head + HTTP_METHOD_LOOKUP[request_method] == :head end # Provides access to the request's HTTP headers, for example: diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index b903f98761..0f0589a844 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,5 +1,7 @@ require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' +require 'action_dispatch/routing/inspector' + module ActionDispatch # This middleware is responsible for logging exceptions and @@ -7,8 +9,9 @@ module ActionDispatch class DebugExceptions RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates') - def initialize(app) - @app = app + def initialize(app, routes_app = nil) + @app = app + @routes_app = routes_app end def call(env) @@ -39,7 +42,8 @@ module ActionDispatch :exception => wrapper.exception, :application_trace => wrapper.application_trace, :framework_trace => wrapper.framework_trace, - :full_trace => wrapper.full_trace + :full_trace => wrapper.full_trace, + :routes => formatted_routes(exception) ) file = "rescues/#{wrapper.rescue_template}" @@ -78,5 +82,13 @@ module ActionDispatch def stderr_logger @stderr_logger ||= ActiveSupport::Logger.new($stderr) end + + def formatted_routes(exception) + return false unless @routes_app.respond_to?(:routes) + if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error) + inspector = ActionDispatch::Routing::RoutesInspector.new + inspector.format(@routes_app.routes.routes).join("\n") + end + end end end diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb deleted file mode 100644 index f1906a3ab3..0000000000 --- a/actionpack/lib/action_dispatch/middleware/head.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionDispatch - class Head - def initialize(app) - @app = app - end - - def call(env) - if env["REQUEST_METHOD"] == "HEAD" - env["REQUEST_METHOD"] = "GET" - env["rack.methodoverride.original_method"] = "HEAD" - status, headers, _ = @app.call(env) - [status, headers, []] - else - @app.call(env) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index 177d383e94..8c594c1523 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -10,8 +10,14 @@ </ol> </p> <% end %> +<%= render :template => "rescues/_trace" %> + +<h2> + Routes +</h2> + <p> - Try running <code>rake routes</code> for more information on available routes. + Routes match in priority from top to bottom </p> -<%= render :template => "rescues/_trace" %> +<p><pre><%= @routes %></pre></p> diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb new file mode 100644 index 0000000000..bc7229b6a1 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -0,0 +1,121 @@ +require 'delegate' + +module ActionDispatch + module Routing + class RouteWrapper < SimpleDelegator + def endpoint + rack_app ? rack_app.inspect : "#{controller}##{action}" + end + + def constraints + requirements.except(:controller, :action) + end + + def rack_app(app = self.app) + @rack_app ||= begin + class_name = app.class.name.to_s + if class_name == "ActionDispatch::Routing::Mapper::Constraints" + rack_app(app.app) + elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/ + app + end + end + end + + def verb + super.source.gsub(/[$^]/, '') + end + + def path + super.spec.to_s + end + + def name + super.to_s + end + + def reqs + @reqs ||= begin + reqs = endpoint + reqs += " #{constraints.inspect}" unless constraints.empty? + reqs + end + end + + def controller + requirements[:controller] || ':controller' + end + + def action + requirements[:action] || ':action' + end + + def internal? + path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}} + end + + def engine? + rack_app && rack_app.respond_to?(:routes) + end + end + + ## + # This class is just used for displaying route information when someone + # executes `rake routes`. People should not use this class. + class RoutesInspector # :nodoc: + def initialize + @engines = Hash.new + end + + def format(all_routes, filter = nil) + if filter + all_routes = all_routes.select{ |route| route.defaults[:controller] == filter } + end + + routes = collect_routes(all_routes) + + formatted_routes(routes) + + formatted_routes_for_engines + end + + def collect_routes(routes) + routes = routes.collect do |route| + RouteWrapper.new(route) + end.reject do |route| + route.internal? + end.collect do |route| + collect_engine_routes(route) + + {:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs } + end + end + + def collect_engine_routes(route) + name = route.endpoint + return unless route.engine? + return if @engines[name] + + routes = route.rack_app.routes + if routes.is_a?(ActionDispatch::Routing::RouteSet) + @engines[name] = collect_routes(routes.routes) + end + end + + def formatted_routes_for_engines + @engines.map do |name, routes| + ["\nRoutes for #{name}:"] + formatted_routes(routes) + end.flatten + end + + def formatted_routes(routes) + name_width = routes.map{ |r| r[:name].length }.max + verb_width = routes.map{ |r| r[:verb].length }.max + path_width = routes.map{ |r| r[:path].length }.max + + routes.map do |r| + "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 53a4afecb3..0a65b4dbcc 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -262,7 +262,7 @@ module ActionDispatch # for root cases, where the latter is the correct one. def self.normalize_path(path) path = Journey::Router::Utils.normalize_path(path) - path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$} + path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$} path end @@ -430,6 +430,10 @@ module ActionDispatch if options path = options.delete(:at) else + unless Hash === app + raise ArgumentError, "must be called with mount point" + end + options = app app, path = options.find { |k, v| k.respond_to?(:call) } options.delete(app) if app @@ -913,7 +917,7 @@ module ActionDispatch @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @as = options[:as] - @param = options[:param] || :id + @param = (options[:param] || :id).to_sym @options = options end @@ -961,12 +965,18 @@ module ActionDispatch "#{path}/:#{param}" end + alias :shallow_scope :member_scope + def new_scope(new_path) "#{path}/#{new_path}" end + def nested_param + :"#{singular}_#{param}" + end + def nested_scope - "#{path}/:#{singular}_#{param}" + "#{path}/:#{nested_param}" end end @@ -1477,18 +1487,18 @@ module ActionDispatch def nested_options #:nodoc: options = { :as => parent_resource.member_name } options[:constraints] = { - :"#{parent_resource.singular}_id" => id_constraint - } if id_constraint? + parent_resource.nested_param => param_constraint + } if param_constraint? options end - def id_constraint? #:nodoc: - @scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp) + def param_constraint? #:nodoc: + @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end - def id_constraint #:nodoc: - @scope[:constraints][:id] + def param_constraint #:nodoc: + @scope[:constraints][parent_resource.param] end def canonical_action?(action, flag) #:nodoc: @@ -1501,7 +1511,7 @@ module ActionDispatch def path_for_action(action, path) #:nodoc: prefix = shallow_scoping? ? - "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path] + "#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path] if canonical_action?(action, path.blank?) prefix.to_s diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 1d6ca0c78d..0bbed6cbea 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -333,7 +333,7 @@ module ActionDispatch end end - MountedHelpers.class_eval <<-RUBY + MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{name} @#{name} ||= _#{name} end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index fd3bed7e8f..f4c708ea33 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -102,7 +102,7 @@ module ActionDispatch super end - # Hook overriden in controller to add request information + # Hook overridden in controller to add request information # with `default_url_options`. Application logic should not # go into url_options. def url_options diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 41fa3a4b95..9de545b3c5 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -127,16 +127,13 @@ module ActionDispatch # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block - # will create some routes using <tt>map.draw { map.connect ... }</tt>: + # will create some routes using <tt>set.draw { match ... }</tt>: # # with_routing do |set| - # set.draw do |map| - # map.connect ':controller/:action/:id' - # assert_equal( - # ['/content/10/show', {}], - # map.generate(:controller => 'content', :id => 10, :action => 'show') - # end + # set.draw do + # resources :users # end + # assert_equal "/users", users_path # end # def with_routing diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index a86b510719..639ae6f398 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -12,7 +12,7 @@ module ActionDispatch def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application - super(DEFAULT_ENV.merge(env)) + super(default_env.merge(env)) self.host = 'test.host' self.remote_addr = '0.0.0.0' @@ -69,5 +69,11 @@ module ActionDispatch def cookies @cookies ||= {}.with_indifferent_access end + + private + + def default_env + DEFAULT_ENV + end end end diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 245849d706..ee263df484 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -26,11 +26,11 @@ module ActionView # Encapsulates the interaction with the view flow so it # returns the correct buffer on +yield+. This is usually - # overwriten by helpers to add more behavior. + # overwritten by helpers to add more behavior. # :api: plugin def _layout_for(name=nil) name ||= :layout view_flow.get(name).html_safe end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 02c1250c76..68b0195700 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -95,7 +95,7 @@ module ActionView # have SSL certificates for each of the asset hosts this technique allows you # to avoid warnings in the client about mixed media. # - # ActionController::Base.asset_host = Proc.new { |source, request| + # config.action_controller.asset_host = Proc.new { |source, request| # if request.ssl? # "#{request.protocol}#{request.host_with_port}" # else diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 397738dd98..9186855319 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -134,7 +134,7 @@ module ActionView # # <%# Add some other content, or use a different template: %> # - # <% content_for :navigation, true do %> + # <% content_for :navigation, flush: true do %> # <li><%= link_to 'Login', :action => 'login' %></li> # <% end %> # @@ -148,14 +148,14 @@ module ActionView # # WARNING: content_for is ignored in caches. So you shouldn't use it # for elements that will be fragment cached. - def content_for(name, content = nil, flush = false, &block) + def content_for(name, content = nil, options = {}, &block) if content || block_given? if block_given? - flush = content if content + options = content if content content = capture(&block) end if content - flush ? @view_flow.set(name, content) : @view_flow.append(name, content) + options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) end nil else diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index eef426703d..c88af0355f 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -353,7 +353,7 @@ module ActionView html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled) html_attributes[:value] = value - content_tag(:option, text, html_attributes) + content_tag_string(:option, text, html_attributes) end.join("\n").html_safe end @@ -711,7 +711,7 @@ module ActionView def option_html_attributes(element) return {} unless Array === element - Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }] + Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, v] }] end def option_text_and_value(option) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 1a0019a48c..d7d9c45120 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -382,11 +382,18 @@ module ActionView # Creates a submit button with the text <tt>value</tt> as the caption. # # ==== Options + # * <tt>:data</tt> - This option can be used to add custom data attributes. + # * <tt>:disabled</tt> - If true, the user will not be able to use this input. + # * Any other key creates standard HTML options for the tag. + # + # ==== Data attributes + # # * <tt>:confirm => 'question?'</tt> - If present the unobtrusive JavaScript # drivers will provide a prompt with the question specified. If the user accepts, # the form is processed normally, otherwise no action is taken. - # * <tt>:disabled</tt> - If true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. + # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a + # disabled version of the submit button when the form is submitted. This feature is + # provided by the unobtrusive JavaScript driver. # # ==== Examples # submit_tag @@ -407,13 +414,21 @@ module ActionView # submit_tag "Edit", :class => "edit_button" # # => <input class="edit_button" name="commit" type="submit" value="Edit" /> # - # submit_tag "Save", :confirm => "Are you sure?" + # submit_tag "Save", :data => { :confirm => "Are you sure?" } # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" /> # def submit_tag(value = "Save changes", options = {}) options = options.stringify_keys + if disable_with = options.delete("disable_with") + ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" + + options["data-disable-with"] = disable_with + end + if confirm = options.delete("confirm") + ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'" + options["data-confirm"] = confirm end @@ -428,13 +443,21 @@ module ActionView # so this helper will also accept a block. # # ==== Options + # * <tt>:data</tt> - This option can be used to add custom data attributes. + # * <tt>:disabled</tt> - If true, the user will not be able to + # use this input. + # * Any other key creates standard HTML options for the tag. + # + # ==== Data attributes + # # * <tt>:confirm => 'question?'</tt> - If present, the # unobtrusive JavaScript drivers will provide a prompt with # the question specified. If the user accepts, the form is # processed normally, otherwise no action is taken. - # * <tt>:disabled</tt> - If true, the user will not be able to - # use this input. - # * Any other key creates standard HTML options for the tag. + # * <tt>:disable_with</tt> - Value of this parameter will be + # used as the value for a disabled version of the submit + # button when the form is submitted. This feature is provided + # by the unobtrusive JavaScript driver. # # ==== Examples # button_tag @@ -447,12 +470,23 @@ module ActionView # # <strong>Ask me!</strong> # # </button> # + # button_tag "Checkout", :data => { disable_with => "Please wait..." } + # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> + # def button_tag(content_or_options = nil, options = nil, &block) options = content_or_options if block_given? && content_or_options.is_a?(Hash) options ||= {} options = options.stringify_keys + if disable_with = options.delete("disable_with") + ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" + + options["data-disable-with"] = disable_with + end + if confirm = options.delete("confirm") + ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'" + options["data-confirm"] = confirm end @@ -466,11 +500,15 @@ module ActionView # <tt>source</tt> is passed to AssetTagHelper#path_to_image # # ==== Options + # * <tt>:data</tt> - This option can be used to add custom data attributes. + # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * Any other key creates standard HTML options for the tag. + # + # ==== Data attributes + # # * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the form is # processed normally, otherwise no action is taken. - # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. - # * Any other key creates standard HTML options for the tag. # # ==== Examples # image_submit_tag("login.png") @@ -485,12 +523,14 @@ module ActionView # image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button") # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> # - # image_submit_tag("save.png", :confirm => "Are you sure?") + # image_submit_tag("save.png", :data => { :confirm => "Are you sure?" }) # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" /> def image_submit_tag(source, options = {}) options = options.stringify_keys if confirm = options.delete("confirm") + ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead'" + options["data-confirm"] = confirm end diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index e077cd5b3c..192f5eebaa 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -137,10 +137,10 @@ module ActionView def add_options(option_tags, options, value = nil) if options[:include_blank] - option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags + option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags end if value.blank? && options[:prompt] - option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags + option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags end option_tags end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 736f9fa2f0..3d86790a8f 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -132,8 +132,7 @@ module ActionView # # posts_path # # link_to(body, url_options = {}, html_options = {}) - # # url_options, except :confirm or :method, - # # is passed to url_for + # # url_options, except :method, is passed to url_for # # link_to(options = {}, html_options = {}) do # # name @@ -144,9 +143,7 @@ module ActionView # end # # ==== Options - # * <tt>:confirm => 'question?'</tt> - This will allow the unobtrusive JavaScript - # driver to prompt with the question specified. If the user accepts, the link is - # processed normally, otherwise no action is taken. + # * <tt>:data</tt> - This option can be used to add custom data attributes. # * <tt>:method => symbol of HTTP verb</tt> - This modifier will dynamically # create an HTML form and immediately submit the form for processing using # the HTTP verb specified. Useful for having links perform a POST operation @@ -163,6 +160,16 @@ module ActionView # completion of the Ajax request and performing JavaScript operations once # they're complete # + # ==== Data attributes + # + # * <tt>:confirm => 'question?'</tt> - This will allow the unobtrusive JavaScript + # driver to prompt with the question specified. If the user accepts, the link is + # processed normally, otherwise no action is taken. + # * <tt>:disable_with</tt> - Value of this parameter will be + # used as the value for a disabled version of the submit + # button when the form is submitted. This feature is provided + # by the unobtrusive JavaScript driver. + # # ==== Examples # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base @@ -226,13 +233,15 @@ module ActionView # link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux") # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a> # - # The two options specific to +link_to+ (<tt>:confirm</tt> and <tt>:method</tt>) are used as follows: + # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows: # - # link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?" - # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> + # link_to("Destroy", "http://www.example.com", :method => :delete) + # # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a> # - # link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?") - # # => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a> + # You can also use custom data attributes using the <tt>:data</tt> option: + # + # link_to "Visit Other Site", "http://www.rubyonrails.org/", :data => { :confirm => "Are you sure?" } + # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} @@ -255,10 +264,9 @@ module ActionView # to allow styling of the form itself and its children. This can be changed # using the <tt>:form_class</tt> modifier within +html_options+. You can control # the form submission and input element behavior using +html_options+. - # This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers - # described in the +link_to+ documentation. If no <tt>:method</tt> modifier - # is given, it will default to performing a POST operation. You can also - # disable the button by passing <tt>:disabled => true</tt> in +html_options+. + # This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation. + # If no <tt>:method</tt> modifier is given, it will default to performing a POST operation. + # You can also disable the button by passing <tt>:disabled => true</tt> in +html_options+. # If you are using RESTful routes, you can pass the <tt>:method</tt> # to change the HTTP verb used to submit the form. # @@ -269,15 +277,23 @@ module ActionView # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>, # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>. # * <tt>:disabled</tt> - If set to true, it will generate a disabled button. - # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to - # prompt with the question specified. If the user accepts, the link is - # processed normally, otherwise no action is taken. + # * <tt>:data</tt> - This option can be used to add custom data attributes. # * <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. # * <tt>:form</tt> - This hash will be form attributes # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will # be placed # + # ==== Data attributes + # + # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to + # prompt with the question specified. If the user accepts, the link is + # processed normally, otherwise no action is taken. + # * <tt>:disable_with</tt> - Value of this parameter will be + # used as the value for a disabled version of the submit + # button when the form is submitted. This feature is provided + # by the unobtrusive JavaScript driver. + # # ==== Examples # <%= button_to "New", :action => "new" %> # # => "<form method="post" action="/controller/new" class="button_to"> @@ -311,7 +327,7 @@ module ActionView # # # <%= button_to "Delete Image", { :action => "delete", :id => @image.id }, - # :confirm => "Are you sure?", :method => :delete %> + # :method => :delete, :data => { :confirm => "Are you sure?" } %> # # => "<form method="post" action="/images/delete/1" class="button_to"> # # <div> # # <input type="hidden" name="_method" value="delete" /> @@ -321,12 +337,12 @@ module ActionView # # </form>" # # - # <%= button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?', - # :method => "delete", :remote => true) %> + # <%= button_to('Destroy', 'http://www.example.com', + # :method => "delete", :remote => true, :data => { :confirm' => 'Are you sure?', :disable_with => 'loading...' }) %> # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'> # # <div> # # <input name='_method' value='delete' type='hidden' /> - # # <input value='Destroy' type='submit' data-confirm='Are you sure?' /> + # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' /> # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/> # # </div> # # </form>" @@ -627,12 +643,24 @@ module ActionView html_options = html_options.stringify_keys html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) + disable_with = html_options.delete("disable_with") confirm = html_options.delete('confirm') method = html_options.delete('method') - html_options["data-confirm"] = confirm if confirm + if confirm + ActiveSupport::Deprecation.warn ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" + + html_options["data-confirm"] = confirm + end + add_method_to_attributes!(html_options, method) if method + if disable_with + ActiveSupport::Deprecation.warn ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" + + html_options["data-disable-with"] = disable_with + end + html_options else link_to_remote_options?(options) ? {'data-remote' => 'true'} : {} diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 00989ec405..47dd932c71 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -96,7 +96,7 @@ module ActionView # Helpers related to template lookup using the lookup context information. module ViewPaths - attr_reader :view_paths + attr_reader :view_paths, :html_fallback_for_js # Whenever setting view paths, makes a copy so we can manipulate then in # instance objects as we wish. @@ -184,7 +184,10 @@ module ActionView def formats=(values) if values values.concat(default_formats) if values.delete "*/*" - values << :html if values == [:js] + if values == [:js] + values << :html + @html_fallback_for_js = true + end end super(values) end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index e3d8e9d508..6fb8cbb46c 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -22,5 +22,11 @@ module ActionView def instrument(name, options={}) ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } end + + def prepend_formats(formats) + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js + @lookup_context.formats = formats | @lookup_context.formats + end end end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 9100545718..a08a566b35 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -320,6 +320,8 @@ module ActionView @block = block @details = extract_details(options) + prepend_formats(options[:formats]) + if String === partial @object = options[:object] @path = partial diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 3c1b11396a..156ad4e547 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -8,9 +8,10 @@ module ActionView template = determine_template(options) context = @lookup_context + prepend_formats(template.formats) + unless context.rendered_format - context.formats = template.formats unless template.formats.empty? - context.rendered_format = context.formats.first + context.rendered_format = template.formats.first || formats.last end render_template(template, options[:layout], options[:locals]) |