From 53c6984944b03b5de036167a418593dfcd12e886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 3 Jan 2010 23:33:34 +0100 Subject: Add notifications to ActionDispatch::ShowExceptions, this can be used as hooks for plugins like ExceptionNotifier. --- .../action_dispatch/middleware/show_exceptions.rb | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 4ebc8a2ab9..af356707c6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,7 +1,24 @@ require 'active_support/core_ext/exception' +require 'active_support/notifications' require 'action_dispatch/http/request' module ActionDispatch + # This middleware rescues any exception returned by the application and renders + # nice exception pages if it's being rescued locally. + # + # Every time an exception is caught, a notification is published, becoming a good API + # to deal with exceptions. So, if you want send an e-mail through ActionMailer + # everytime this notification is published, you just need to do the following: + # + # ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload| + # ExceptionNotifier.deliver_exception(start, payload) + # end + # + # The payload is a hash which has to pairs: + # + # * :env - Contains the rack env for the given request; + # * :exception - The exception raised; + # class ShowExceptions LOCALHOST = '127.0.0.1'.freeze @@ -44,8 +61,11 @@ module ActionDispatch def call(env) @app.call(env) rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) + ActiveSupport::Notifications.instrument 'action_dispatch.show_exception', + :env => env, :exception => exception do + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) + end end private -- cgit v1.2.3 From bd729344a7ac747cccaeed983d435fc36c905683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 4 Jan 2010 22:10:13 +0100 Subject: Remove deprecated formatted named routes --- actionpack/lib/action_dispatch/routing/route_set.rb | 8 -------- 1 file changed, 8 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index bd397432ce..792762ebd7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -189,14 +189,6 @@ module ActionDispatch url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) # end # end - #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL. - def formatted_#{selector}(*args) # def formatted_users_url(*args) - ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn( - "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " + - "Please pass format to the standard " + # "Please pass format to the standard " + - "#{selector} method instead.", caller) # "users_url method instead.", caller) - #{selector}(*args) # users_url(*args) - end # end protected :#{selector} # protected :users_url end_eval helpers << selector -- cgit v1.2.3 From 562a00ba16746bf36b7d8b327fabae3dabfdb122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 4 Jan 2010 22:11:35 +0100 Subject: @_formats initialization should be AbstractController::Base. --- actionpack/lib/action_dispatch/middleware/show_exceptions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index af356707c6..10f04dcdf6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -14,7 +14,7 @@ module ActionDispatch # ExceptionNotifier.deliver_exception(start, payload) # end # - # The payload is a hash which has to pairs: + # The payload is a hash which has two pairs: # # * :env - Contains the rack env for the given request; # * :exception - The exception raised; -- cgit v1.2.3 From 2601a16ede92566c651c06942294250ea653bd85 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 4 Jan 2010 16:22:39 -0600 Subject: Autoload AS test case --- actionpack/lib/action_dispatch/testing/integration.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2a5f5dcd5c..4ec47d146c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,6 +1,5 @@ require 'stringio' require 'uri' -require 'active_support/test_case' require 'active_support/core_ext/object/metaclass' require 'rack/test' -- cgit v1.2.3 From 3f28e0bda6386ed25d07182dd39b84f6a7d330da Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 4 Jan 2010 19:46:21 -0600 Subject: Trash string coercion rack hacks --- .../action_dispatch/middleware/string_coercion.rb | 29 ---------------------- 1 file changed, 29 deletions(-) delete mode 100644 actionpack/lib/action_dispatch/middleware/string_coercion.rb (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/string_coercion.rb b/actionpack/lib/action_dispatch/middleware/string_coercion.rb deleted file mode 100644 index 232e947835..0000000000 --- a/actionpack/lib/action_dispatch/middleware/string_coercion.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionDispatch - class StringCoercion - class UglyBody < ActiveSupport::BasicObject - def initialize(body) - @body = body - end - - def each - @body.each do |part| - yield part.to_s - end - end - - private - def method_missing(*args, &block) - @body.__send__(*args, &block) - end - end - - def initialize(app) - @app = app - end - - def call(env) - status, headers, body = @app.call(env) - [status, headers, UglyBody.new(body)] - end - end -end -- cgit v1.2.3 From 8ff4faf66a332b24d1a82a4e62daeabc06945dcf Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 5 Jan 2010 11:47:10 -0600 Subject: assert_template depends on AV::Template monkey patches in action_view/test_case --- actionpack/lib/action_dispatch/testing/assertions/response.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 5686bbdbde..c2486d3730 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -2,6 +2,15 @@ module ActionDispatch module Assertions # A small suite of assertions that test responses from Rails applications. module ResponseAssertions + extend ActiveSupport::Concern + + included do + # TODO: Need to pull in AV::Template monkey patches that track which + # templates are rendered. assert_template should probably be part + # of AV instead of AD. + require 'action_view/test_case' + end + # Asserts that the response is one of the following types: # # * :success - Status code was 200 -- cgit v1.2.3 From b3900a29eb89b6b4613966c03282997fcc0cd6ac Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 5 Jan 2010 12:00:38 -0600 Subject: All router redirect helper to accept a full URI [#3653 state:resolved] --- actionpack/lib/action_dispatch/routing/mapper.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8f33346a4f..8b46790fd2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -200,13 +200,21 @@ module ActionDispatch path = args.shift || block path_proc = path.is_a?(Proc) ? path : proc { |params| path % params } status = options[:status] || 301 + body = 'Moved Permanently' lambda do |env| - req = Rack::Request.new(env) - params = path_proc.call(env["action_dispatch.request.path_parameters"]) - url = req.scheme + '://' + req.host + params - - [ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ] + req = Request.new(env) + + uri = URI.parse(path_proc.call(req.params)) + uri.scheme ||= req.scheme + uri.host ||= req.host + + headers = { + 'Location' => uri.to_s, + 'Content-Type' => 'text/html', + 'Content-Length' => body.length.to_s + } + [ status, headers, [body] ] end end -- cgit v1.2.3 From e55d70a380a8d7408cc495086ff49af6c6e406d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 5 Jan 2010 23:40:56 +0100 Subject: redirect in routes takes port into account [#3653 status:resolved] --- actionpack/lib/action_dispatch/routing/mapper.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8b46790fd2..2a25a8c4ea 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -208,6 +208,7 @@ module ActionDispatch uri = URI.parse(path_proc.call(req.params)) uri.scheme ||= req.scheme uri.host ||= req.host + uri.port ||= req.port unless req.port == 80 headers = { 'Location' => uri.to_s, -- cgit v1.2.3 From 0cf190001e9b03f40f615736779eedb30a1f2b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 6 Jan 2010 00:33:17 +0100 Subject: Remove CGI.escape in function of Rack::Mount.escape --- actionpack/lib/action_dispatch/testing/assertions/selector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index c2dc591ff7..a6b1126e2b 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -524,7 +524,7 @@ module ActionDispatch fix_content = lambda do |node| # Gets around a bug in the Rails 1.1 HTML parser. - node.content.gsub(/)?/m) { CGI.escapeHTML($1) } + node.content.gsub(/)?/m) { Rack::Utils.escapeHTML($1) } end selected = elements.map do |element| -- cgit v1.2.3 From 10389a4c29c5b17083a7fa43b97334b4ea811ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 6 Jan 2010 00:42:58 +0100 Subject: Ruby 1.9.1 requires hash given to foormat to contain symbols. --- actionpack/lib/action_dispatch/routing/mapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 2a25a8c4ea..9c14305ba9 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -205,7 +205,7 @@ module ActionDispatch lambda do |env| req = Request.new(env) - uri = URI.parse(path_proc.call(req.params)) + uri = URI.parse(path_proc.call(req.params.symbolize_keys)) uri.scheme ||= req.scheme uri.host ||= req.host uri.port ||= req.port unless req.port == 80 -- cgit v1.2.3 From e4099c2ad3aa82061c8766dc3781fcd2923289e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 6 Jan 2010 09:32:29 +0100 Subject: Allow named routes to be debugged. --- .../lib/action_dispatch/routing/route_set.rb | 43 ++++++++++------------ 1 file changed, 20 insertions(+), 23 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 792762ebd7..0d2ffc6d69 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -138,41 +138,38 @@ module ActionDispatch end end - def named_helper_module_eval(code, *args) - @module.module_eval(code, *args) - end - def define_hash_access(route, name, kind, options) selector = hash_access_name(name, kind) - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks + + # We use module_eval to avoid leaks + @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(options = nil) # def hash_for_users_url(options = nil) options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false} end # end protected :#{selector} # protected :hash_for_users_url - end_eval + END_EVAL helpers << selector end + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(:bar => bar, :baz => baz, :bang => bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, :sort_by => 'baz') + # def define_url_helper(route, name, kind, options) selector = url_helper_name(name, kind) - # The segment keys used for positional parameters - hash_access_method = hash_access_name(name, kind) - # allow ordered parameters to be associated with corresponding - # dynamic segments, so you can do - # - # foo_url(bar, baz, bang) - # - # instead of - # - # foo_url(:bar => bar, :baz => baz, :bang => bang) - # - # Also allow options hash, so you can do - # - # foo_url(bar, baz, bang, :sort_by => 'baz') - # - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks + # We use module_eval to avoid leaks + @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(*args) # def users_url(*args) # opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first @@ -190,7 +187,7 @@ module ActionDispatch # end # end protected :#{selector} # protected :users_url - end_eval + END_EVAL helpers << selector end end -- cgit v1.2.3 From 0d5ce7c52555c859ca5d6fb10abb4f6424d785c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 6 Jan 2010 09:51:46 +0100 Subject: namespace in routes changes both the path and name prefix. --- actionpack/lib/action_dispatch/routing/mapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9c14305ba9..3e577c7f99 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -292,7 +292,7 @@ module ActionDispatch end def namespace(path) - scope("/#{path}") { yield } + scope("/#{path}", :name_prefix => path.to_s) { yield } end def constraints(constraints = {}) -- cgit v1.2.3 From f149eb19d4675becee164fee2881a562cdaa0551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 7 Jan 2010 15:26:31 +0100 Subject: From now on, parameters defined in default_url_options can be absent from named routes. This allows the following setup to work: # app/controllers/application_controller.rb class ApplicationController def default_url_options(options=nil) { :locale => I18n.locale } end end # From your views and controllers: I18n.locale #=> :en users_url #=> "/en/users" users_url(:pl) #=> "/pl/users" user_url(1) #=> "/en/users/1" user_url(:pl, 1) #=> "/pl/users/1" user_url(1, :locale => :pl) #=> "/pl/users/1" If you provide all expected parameters, it still works as previously. But if any parameter is missing, it tries to assign all possible ones with the hash returned in default_url_options or the one passed straight to the named route method. Beware that default_url_options in ApplicationController is not shared with ActionMailer, so you are required to always give the locale in your email views. --- .../lib/action_dispatch/routing/route_set.rb | 72 +++++++++++++++------- 1 file changed, 51 insertions(+), 21 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0d2ffc6d69..7752642a7b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -74,9 +74,8 @@ module ActionDispatch @routes = {} @helpers = [] - @module ||= Module.new - @module.instance_methods.each do |selector| - @module.class_eval { remove_method selector } + @module ||= Module.new do + instance_methods.each { |selector| remove_method(selector) } end end @@ -168,25 +167,56 @@ module ActionDispatch selector = url_helper_name(name, kind) hash_access_method = hash_access_name(name, kind) - # We use module_eval to avoid leaks + # We use module_eval to avoid leaks. + # + # def users_url(*args) + # if args.empty? || Hash === args.first + # options = hash_for_users_url(args.first || {}) + # else + # options = hash_for_users_url(args.extract_options!) + # default = default_url_options(options) if self.respond_to?(:default_url_options, true) + # options = (default ||= {}).merge(options) + # + # keys = [] + # keys -= options.keys unless keys.size == args.size + # + # args = args.zip(keys).inject({}) do |h, (v, k)| + # h[k] = v + # h + # end + # + # # Tell url_for to skip default_url_options + # options[:use_defaults] = false + # options.merge!(args) + # end + # + # url_for(options) + # end @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{selector}(*args) # def users_url(*args) - # - opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first - args.first || {} # args.first || {} - else # else - options = args.extract_options! # options = args.extract_options! - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)| - h[k] = v # h[k] = v - h # h - end # end - options.merge(args) # options.merge(args) - end # end - # - url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) - # - end # end - protected :#{selector} # protected :users_url + def #{selector}(*args) + if args.empty? || Hash === args.first + options = #{hash_access_method}(args.first || {}) + else + options = #{hash_access_method}(args.extract_options!) + default = default_url_options(options) if self.respond_to?(:default_url_options, true) + options = (default ||= {}).merge(options) + + keys = #{route.segment_keys.inspect} + keys -= options.keys unless keys.size == args.size + + args = args.zip(keys).inject({}) do |h, (v, k)| + h[k] = v + h + end + + # Tell url_for to skip default_url_options + options[:use_defaults] = false + options.merge!(args) + end + + url_for(options) + end + protected :#{selector} END_EVAL helpers << selector end -- cgit v1.2.3 From 3b631df101d911d57ac3fe97514c60ae412e3812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 7 Jan 2010 17:17:06 +0100 Subject: Ensure that segments in default_url_options also work with format specified. --- actionpack/lib/action_dispatch/routing/route_set.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 7752642a7b..90893aa0e6 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -178,7 +178,7 @@ module ActionDispatch # options = (default ||= {}).merge(options) # # keys = [] - # keys -= options.keys unless keys.size == args.size + # keys -= options.keys if args.size < keys.size - 1 # # args = args.zip(keys).inject({}) do |h, (v, k)| # h[k] = v @@ -202,7 +202,7 @@ module ActionDispatch options = (default ||= {}).merge(options) keys = #{route.segment_keys.inspect} - keys -= options.keys unless keys.size == args.size + keys -= options.keys if args.size < keys.size - 1 # take format into account args = args.zip(keys).inject({}) do |h, (v, k)| h[k] = v -- cgit v1.2.3 From 36969c6ecd69fc285bf0267805152319a4b71ceb Mon Sep 17 00:00:00 2001 From: Joao Carlos Date: Sat, 9 Jan 2010 03:51:31 +0200 Subject: Fixes namespaced routes [#3673 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_dispatch/routing/mapper.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3e577c7f99..00659c8bd4 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -258,10 +258,17 @@ module ActionDispatch else name_prefix_set = false end + + if namespace = options.delete(:namespace) + namespace_set = true + namespace, @scope[:namespace] = @scope[:namespace], namespace + else + namespace_set = false + end if controller = options.delete(:controller) controller_set = true - controller, @scope[:controller] = @scope[:controller], controller + controller, @scope[:controller] = @scope[:controller], @scope[:namespace] ? "#{@scope[:namespace]}/#{controller}" : controller else controller_set = false end @@ -281,6 +288,7 @@ module ActionDispatch ensure @scope[:path] = path if path_set @scope[:name_prefix] = name_prefix if name_prefix_set + @scope[:namespace] = namespace if namespace_set @scope[:controller] = controller if controller_set @scope[:options] = options @scope[:blocks] = blocks @@ -292,7 +300,7 @@ module ActionDispatch end def namespace(path) - scope("/#{path}", :name_prefix => path.to_s) { yield } + scope("/#{path}", :name_prefix => path.to_s, :namespace => path.to_s) { yield } end def constraints(constraints = {}) -- cgit v1.2.3 From 8d72ba51bac75e44ca86fafbcab54eab7a355d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 10 Jan 2010 18:42:45 +0100 Subject: Ensure nested namespaces work as expected. --- actionpack/lib/action_dispatch/routing/mapper.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 00659c8bd4..f7fb4ddd7a 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -77,7 +77,6 @@ module ActionDispatch path end - def app Constraints.new( to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults), @@ -123,7 +122,6 @@ module ActionDispatch end end - def blocks if @options[:constraints].present? && !@options[:constraints].is_a?(Hash) block = @options[:constraints] @@ -258,17 +256,17 @@ module ActionDispatch else name_prefix_set = false end - + if namespace = options.delete(:namespace) namespace_set = true - namespace, @scope[:namespace] = @scope[:namespace], namespace + namespace, @scope[:namespace] = @scope[:namespace], (@scope[:namespace] ? "#{@scope[:namespace]}/#{namespace}" : namespace) else namespace_set = false end if controller = options.delete(:controller) controller_set = true - controller, @scope[:controller] = @scope[:controller], @scope[:namespace] ? "#{@scope[:namespace]}/#{controller}" : controller + controller, @scope[:controller] = @scope[:controller], (@scope[:namespace] ? "#{@scope[:namespace]}/#{controller}" : controller) else controller_set = false end @@ -277,13 +275,12 @@ module ActionDispatch unless constraints.is_a?(Hash) block, constraints = constraints, {} end + constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] - options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) yield - self ensure @scope[:path] = path if path_set -- cgit v1.2.3 From 521ef3c40f34d61d42d092eb39348a1be52ac57d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 13 Jan 2010 11:45:27 -0600 Subject: Passing in a crud action overloads the default action instead of creating a new member action. --- actionpack/lib/action_dispatch/routing/mapper.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f7fb4ddd7a..e283cf0403 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -321,6 +321,8 @@ module ActionDispatch end module Resources + CRUD_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] + class Resource #:nodoc: attr_reader :plural, :singular @@ -489,8 +491,13 @@ module ActionDispatch end if args.first.is_a?(Symbol) - with_exclusive_name_prefix(args.first) do - return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym)) + action = args.first + if CRUD_ACTIONS.include?(action) + return match("(.:format)", options.merge(:to => action)) + else + with_exclusive_name_prefix(action) do + return match("/#{action}(.:format)", options.merge(:to => action)) + end end end -- cgit v1.2.3 From bf9b81e2cbfd4333f0b813ac07ea9d9c982e7779 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 13 Jan 2010 12:18:06 -0600 Subject: Pass :as to resources to change the resource name --- actionpack/lib/action_dispatch/routing/mapper.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index e283cf0403..48fb7edd67 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -328,13 +328,14 @@ module ActionDispatch def initialize(entities, options = {}) entities = entities.to_s + @options = options @plural = entities.pluralize @singular = entities.singularize end def name - plural + @options[:as] || plural end def controller @@ -360,7 +361,7 @@ module ActionDispatch end def name - singular + @options[:as] || singular end end @@ -373,7 +374,7 @@ module ActionDispatch return self end - resource = SingletonResource.new(resources.pop) + resource = SingletonResource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do @@ -407,7 +408,7 @@ module ActionDispatch return self end - resource = Resource.new(resources.pop) + resource = Resource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do -- cgit v1.2.3 From d01716731bd9c68de686d6ac85e3da5251305495 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 13 Jan 2010 17:23:14 -0600 Subject: Add router support for resources :only and :except actions --- actionpack/lib/action_dispatch/routing/mapper.rb | 54 +++++++++++++++++------- 1 file changed, 38 insertions(+), 16 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 48fb7edd67..5009ed001c 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -324,7 +324,11 @@ module ActionDispatch CRUD_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] class Resource #:nodoc: - attr_reader :plural, :singular + def self.default_actions + [:index, :create, :new, :show, :update, :destroy, :edit] + end + + attr_reader :plural, :singular, :options def initialize(entities, options = {}) entities = entities.to_s @@ -334,8 +338,22 @@ module ActionDispatch @singular = entities.singularize end + def default_actions + self.class.default_actions + end + + def actions + if only = options[:only] + only.map(&:to_sym) + elsif except = options[:except] + default_actions - except.map(&:to_sym) + else + default_actions + end + end + def name - @options[:as] || plural + options[:as] || plural end def controller @@ -356,12 +374,16 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: + def self.default_actions + [:show, :create, :update, :destroy, :new, :edit] + end + def initialize(entity, options = {}) super end def name - @options[:as] || singular + options[:as] || singular end end @@ -387,12 +409,12 @@ module ActionDispatch with_scope_level(:resource, resource) do yield if block_given? - get "(.:format)", :to => :show, :as => resource.member_name - post "(.:format)", :to => :create - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy - get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" - get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + get "(.:format)", :to => :show, :as => resource.member_name if resource.actions.include?(:show) + post "(.:format)", :to => :create if resource.actions.include?(:create) + put "(.:format)", :to => :update if resource.actions.include?(:update) + delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) + get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) + get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) end end @@ -422,22 +444,22 @@ module ActionDispatch yield if block_given? with_scope_level(:collection) do - get "(.:format)", :to => :index, :as => resource.collection_name - post "(.:format)", :to => :create + get "(.:format)", :to => :index, :as => resource.collection_name if resource.actions.include?(:index) + post "(.:format)", :to => :create if resource.actions.include?(:create) with_exclusive_name_prefix :new do - get "/new(.:format)", :to => :new, :as => resource.singular + get "/new(.:format)", :to => :new, :as => resource.singular if resource.actions.include?(:new) end end with_scope_level(:member) do scope("/:id") do - get "(.:format)", :to => :show, :as => resource.member_name - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy + get "(.:format)", :to => :show, :as => resource.member_name if resource.actions.include?(:show) + put "(.:format)", :to => :update if resource.actions.include?(:update) + delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) with_exclusive_name_prefix :edit do - get "/edit(.:format)", :to => :edit, :as => resource.singular + get "/edit(.:format)", :to => :edit, :as => resource.singular if resource.actions.include?(:edit) end end end -- cgit v1.2.3 From 35933822dec7be3f895c7a3f1440d72c982aebdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 14 Jan 2010 01:31:17 +0100 Subject: Ensure optional path scopes are properly handled. --- actionpack/lib/action_dispatch/routing/mapper.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 5009ed001c..bad90df2e3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -68,13 +68,9 @@ module ActionDispatch end def normalize_path(path) - path = nil if path == "" - path = "#{@scope[:path]}#{path}" if @scope[:path] - path = Rack::Mount::Utils.normalize_path(path) if path - - raise ArgumentError, "path is required" unless path - - path + path = "#{@scope[:path]}#{path}" + raise ArgumentError, "path is required" if path.empty? + Mapper.normalize_path(path) end def app @@ -160,6 +156,14 @@ module ActionDispatch end end + # Invokes Rack::Mount::Utils.normalize path and ensure that + # (:locale) becomes (/:locale) instead of /(:locale). + def self.normalize_path(path) + path = Rack::Mount::Utils.normalize_path(path) + path.sub!(/^\/\(+\/?:/, '(/:') + path + end + module Base def initialize(set) @set = set @@ -245,7 +249,7 @@ module ActionDispatch if path = options.delete(:path) path_set = true - path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s) + path, @scope[:path] = @scope[:path], Mapper.normalize_path(@scope[:path].to_s + path.to_s) else path_set = false end -- cgit v1.2.3 From be968ecd8b93f128a01427f76c888c291cbc239b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 13 Jan 2010 20:21:04 -0600 Subject: Respect resources_path_names and :path_names options in new dsl --- actionpack/lib/action_dispatch/routing/mapper.rb | 129 ++++++++++++++------- .../lib/action_dispatch/routing/route_set.rb | 11 +- 2 files changed, 96 insertions(+), 44 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index bad90df2e3..277cb48e50 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -247,53 +247,35 @@ module ActionDispatch options[:controller] = args.first end - if path = options.delete(:path) - path_set = true - path, @scope[:path] = @scope[:path], Mapper.normalize_path(@scope[:path].to_s + path.to_s) - else - path_set = false - end - - if name_prefix = options.delete(:name_prefix) - name_prefix_set = true - name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix) - else - name_prefix_set = false - end + recover = {} - if namespace = options.delete(:namespace) - namespace_set = true - namespace, @scope[:namespace] = @scope[:namespace], (@scope[:namespace] ? "#{@scope[:namespace]}/#{namespace}" : namespace) - else - namespace_set = false + options[:constraints] ||= {} + unless options[:constraints].is_a?(Hash) + block, options[:constraints] = options[:constraints], {} end - if controller = options.delete(:controller) - controller_set = true - controller, @scope[:controller] = @scope[:controller], (@scope[:namespace] ? "#{@scope[:namespace]}/#{controller}" : controller) - else - controller_set = false + scope_options.each do |option| + if value = options.delete(option) + recover[option] = @scope[option] + @scope[option] = send("merge_#{option}_scope", @scope[option], value) + end end - constraints = options.delete(:constraints) || {} - unless constraints.is_a?(Hash) - block, constraints = constraints, {} - end + recover[:block] = @scope[:blocks] + @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block) - constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) - blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] - options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) + recover[:options] = @scope[:options] + @scope[:options] = merge_options_scope(@scope[:options], options) yield self ensure - @scope[:path] = path if path_set - @scope[:name_prefix] = name_prefix if name_prefix_set - @scope[:namespace] = namespace if namespace_set - @scope[:controller] = controller if controller_set - @scope[:options] = options - @scope[:blocks] = blocks - @scope[:constraints] = constraints + scope_options.each do |option| + @scope[option] = recover[option] if recover.has_key?(option) + end + + @scope[:options] = recover[:options] + @scope[:blocks] = recover[:block] end def controller(controller) @@ -322,6 +304,43 @@ module ActionDispatch args.push(options) super(*args) end + + private + def scope_options + @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym } + end + + def merge_path_scope(parent, child) + Mapper.normalize_path(parent.to_s + child.to_s) + end + + def merge_name_prefix_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_namespace_scope(parent, child) + parent ? "#{parent}/#{child}" : child + end + + def merge_controller_scope(parent, child) + @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child + end + + def merge_resources_path_names_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_constraints_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_blocks_scope(parent, child) + (parent || []) + [child] + end + + def merge_options_scope(parent, child) + (parent || {}).merge(child) + end end module Resources @@ -391,6 +410,11 @@ module ActionDispatch end end + def initialize(*args) + super + @scope[:resources_path_names] = @set.resources_path_names + end + def resource(*resources, &block) options = resources.extract_options! @@ -400,6 +424,13 @@ module ActionDispatch return self end + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resource(resources, options) + end + return self + end + resource = SingletonResource.new(resources.pop, options) if @scope[:scope_level] == :resources @@ -417,8 +448,8 @@ module ActionDispatch post "(.:format)", :to => :create if resource.actions.include?(:create) put "(.:format)", :to => :update if resource.actions.include?(:update) delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) - get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) - get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) + get "/#{action_path(:new)}(.:format)", :to => :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) + get "/#{action_path(:edit)}(.:format)", :to => :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) end end @@ -434,6 +465,13 @@ module ActionDispatch return self end + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resources(resources, options) + end + return self + end + resource = Resource.new(resources.pop, options) if @scope[:scope_level] == :resources @@ -452,7 +490,7 @@ module ActionDispatch post "(.:format)", :to => :create if resource.actions.include?(:create) with_exclusive_name_prefix :new do - get "/new(.:format)", :to => :new, :as => resource.singular if resource.actions.include?(:new) + get "/#{action_path(:new)}(.:format)", :to => :new, :as => resource.singular if resource.actions.include?(:new) end end @@ -463,7 +501,7 @@ module ActionDispatch delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) with_exclusive_name_prefix :edit do - get "/edit(.:format)", :to => :edit, :as => resource.singular if resource.actions.include?(:edit) + get "/#{action_path(:edit)}(.:format)", :to => :edit, :as => resource.singular if resource.actions.include?(:edit) end end end @@ -517,13 +555,15 @@ module ActionDispatch return self end + resources_path_names = options.delete(:path_names) + if args.first.is_a?(Symbol) action = args.first if CRUD_ACTIONS.include?(action) return match("(.:format)", options.merge(:to => action)) else with_exclusive_name_prefix(action) do - return match("/#{action}(.:format)", options.merge(:to => action)) + return match("/#{action_path(action, resources_path_names)}(.:format)", options.merge(:to => action)) end end end @@ -550,6 +590,11 @@ module ActionDispatch end private + def action_path(name, path_names = nil) + path_names ||= @scope[:resources_path_names] + path_names[name.to_sym] || name.to_s + end + def with_exclusive_name_prefix(prefix) begin old_name_prefix = @scope[:name_prefix] diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 90893aa0e6..660d28dbec 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -189,12 +189,12 @@ module ActionDispatch # options[:use_defaults] = false # options.merge!(args) # end - # + # # url_for(options) # end @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(*args) - if args.empty? || Hash === args.first + if args.empty? || Hash === args.first options = #{hash_access_method}(args.first || {}) else options = #{hash_access_method}(args.extract_options!) @@ -225,9 +225,16 @@ module ActionDispatch attr_accessor :routes, :named_routes attr_accessor :disable_clear_and_finalize + def self.default_resources_path_names + { :new => 'new', :edit => 'edit' } + end + + attr_accessor :resources_path_names + def initialize self.routes = [] self.named_routes = NamedRouteCollection.new + self.resources_path_names = self.class.default_resources_path_names @disable_clear_and_finalize = false end -- cgit v1.2.3 From 8c8942ed4f2da52aa42ccd46560acb0b5fd37cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 14 Jan 2010 19:53:07 +0100 Subject: Move Dispatcher setup to Railties and add instrumentation hook. --- actionpack/lib/action_dispatch/middleware/callbacks.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 49bc20f11f..8098933e01 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,4 +1,10 @@ module ActionDispatch + # Provide callbacks to be executed before and after the request dispatch. + # + # It also provides a to_prepare callback, which is performed in all requests + # in development by only once in production and notification callback for async + # operations. + # class Callbacks include ActiveSupport::Callbacks @@ -29,12 +35,6 @@ module ActionDispatch set_callback(:call, :after, *args, &block) end - class << self - # DEPRECATED - alias_method :before_dispatch, :before - alias_method :after_dispatch, :after - end - def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request run_callbacks(:prepare) @@ -43,7 +43,10 @@ module ActionDispatch def call(env) run_callbacks(:call) do run_callbacks(:prepare) if @prepare_each_request - @app.call(env) + + ActiveSupport::Notifications.instrument "action_dispatch.callback" do + @app.call(env) + end end end end -- cgit v1.2.3 From 4598d8874948268e1162c1ef75d0bd565b1e0e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 15 Jan 2010 14:16:52 +0100 Subject: Ensure log is flushed and tailed on failures. --- actionpack/lib/action_dispatch/middleware/callbacks.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 8098933e01..5ec406e134 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -43,11 +43,10 @@ module ActionDispatch def call(env) run_callbacks(:call) do run_callbacks(:prepare) if @prepare_each_request - - ActiveSupport::Notifications.instrument "action_dispatch.callback" do - @app.call(env) - end + @app.call(env) end + ensure + ActiveSupport::Notifications.instrument "action_dispatch.callback" end end end -- cgit v1.2.3 From 3eaf525213ccef5c63c9e296fa643ad416a3f84c Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 12:35:18 -0600 Subject: Make HEAD method masquerade as GET so requests are routed correctly --- actionpack/lib/action_dispatch/http/request.rb | 12 +++++++----- actionpack/lib/action_dispatch/middleware/head.rb | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/head.rb (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 6e8a5dcb8a..3d0aab8a06 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -35,7 +35,8 @@ module ActionDispatch # :get. If the request \method is not listed in the HTTP_METHODS # constant above, an UnknownHttpMethod exception is raised. def request_method - HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") + method = env["rack.methodoverride.original_method"] || env["REQUEST_METHOD"] + HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") end # Returns the HTTP request \method used for action processing as a @@ -43,7 +44,8 @@ module ActionDispatch # method returns :get for a HEAD request because the two are # functionally equivalent from the application's perspective.) def method - request_method == :head ? :get : request_method + method = env["REQUEST_METHOD"] + HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") end # Is this a GET (or HEAD) request? Equivalent to request.method == :get. @@ -53,17 +55,17 @@ module ActionDispatch # Is this a POST request? Equivalent to request.method == :post. def post? - request_method == :post + method == :post end # Is this a PUT request? Equivalent to request.method == :put. def put? - request_method == :put + method == :put end # Is this a DELETE request? Equivalent to request.method == :delete. def delete? - request_method == :delete + method == :delete end # Is this a HEAD request? Since request.method sees HEAD as :get, diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb new file mode 100644 index 0000000000..56e2d2f2a8 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/head.rb @@ -0,0 +1,18 @@ +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, body = @app.call(env) + [status, headers, []] + else + @app.call(env) + end + end + end +end -- cgit v1.2.3 From ead93c5be5b0f1945b7d0302f1aae4685ee3f2fb Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 14:44:27 -0600 Subject: Move Flash into middleware --- actionpack/lib/action_dispatch/http/request.rb | 4 - actionpack/lib/action_dispatch/middleware/flash.rb | 174 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/flash.rb (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 3d0aab8a06..22a08ec10d 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -463,10 +463,6 @@ EOM @env['rack.session.options'] = options end - def flash - session['flash'] || {} - end - # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb new file mode 100644 index 0000000000..99b36366d6 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -0,0 +1,174 @@ +module ActionDispatch + class Request + # Access the contents of the flash. Use flash["notice"] to + # read a notice you put there or flash["notice"] = "hello" + # to put a new one. + def flash + session['flash'] ||= Flash::FlashHash.new + end + end + + # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed + # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create + # action that sets flash[:notice] = "Successfully created" before redirecting to a display action that can + # then expose the flash to its template. Actually, that exposure is automatically done. Example: + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Successfully created post" + # redirect_to posts_path(@post) + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # show.html.erb + # <% if flash[:notice] %> + #
<%= flash[:notice] %>
+ # <% end %> + # + # This example just places a string in the flash, but you can put any object in there. And of course, you can put as + # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed. + # + # See docs on the FlashHash class for more details about the flash. + class Flash + class FlashNow #:nodoc: + def initialize(flash) + @flash = flash + end + + def []=(k, v) + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k] + end + end + + class FlashHash < Hash + def initialize #:nodoc: + super + @used = Set.new + end + + def []=(k, v) #:nodoc: + keep(k) + super + end + + def update(h) #:nodoc: + h.keys.each { |k| keep(k) } + super + end + + alias :merge! :update + + def replace(h) #:nodoc: + @used = Set.new + super + end + + # Sets a flash that will not be available to the next action, only to the current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your app. + # When you need to pass an object to the next action, you use the standard flash assign ([]=). + # When you need to pass an object to the current action, you use now, and your object will + # vanish when the current action is done. + # + # Entries set via now are accessed the same way as standard entries: flash['my-key']. + def now + FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + use(k, false) + end + + # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + def discard(k = nil) + use(k) + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to care about it. + def sweep #:nodoc: + keys.each do |k| + unless @used.include?(k) + @used << k + else + delete(k) + @used.delete(k) + end + end + + # clean up after keys that could have been left over by calling reject! or shift on the flash + (@used - keys).each{ |k| @used.delete(k) } + end + + # Convenience accessor for flash[:alert] + def alert + self[:alert] + end + + # Convenience accessor for flash[:alert]= + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for flash[:notice] + def notice + self[:notice] + end + + # Convenience accessor for flash[:notice]= + def notice=(message) + self[:notice] = message + end + + private + # Used internally by the keep and discard methods + # use() # marks the entire flash as used + # use('msg') # marks the "msg" entry as used + # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) + # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) + # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself + # if no key is passed. + def use(key = nil, used = true) + Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } + return key ? self[key] : self + end + end + + def initialize(app) + @app = app + end + + def call(env) + if (session = env['rack.session']) && (flash = session['flash']) + flash.sweep + end + + @app.call(env) + ensure + if (session = env['rack.session']) && (flash = session['flash']) && flash.empty? + session.delete('flash') + end + end + end +end -- cgit v1.2.3 From 184ef28f55bb576e1eaebf915f8ce64aa8823e90 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 14:53:54 -0600 Subject: Routing method shorthand shouldn't clobber :to options --- actionpack/lib/action_dispatch/routing/mapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 277cb48e50..6ff573443b 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -560,10 +560,10 @@ module ActionDispatch if args.first.is_a?(Symbol) action = args.first if CRUD_ACTIONS.include?(action) - return match("(.:format)", options.merge(:to => action)) + return match("(.:format)", options.reverse_merge(:to => action)) else with_exclusive_name_prefix(action) do - return match("/#{action_path(action, resources_path_names)}(.:format)", options.merge(:to => action)) + return match("/#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action)) end end end -- cgit v1.2.3 From 576b8dda52b0d0d099f88241ef6f4ec1e1248c3b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 15:59:07 -0600 Subject: Cleanup internal resource macro to use method helper shorthand --- actionpack/lib/action_dispatch/routing/mapper.rb | 34 ++++++++++-------------- 1 file changed, 14 insertions(+), 20 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6ff573443b..3c13cf88f4 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -344,7 +344,7 @@ module ActionDispatch end module Resources - CRUD_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] + CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] class Resource #:nodoc: def self.default_actions @@ -444,12 +444,12 @@ module ActionDispatch with_scope_level(:resource, resource) do yield if block_given? - get "(.:format)", :to => :show, :as => resource.member_name if resource.actions.include?(:show) - post "(.:format)", :to => :create if resource.actions.include?(:create) - put "(.:format)", :to => :update if resource.actions.include?(:update) - delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) - get "/#{action_path(:new)}(.:format)", :to => :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) - get "/#{action_path(:edit)}(.:format)", :to => :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) + get :show, :as => resource.member_name if resource.actions.include?(:show) + post :create if resource.actions.include?(:create) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) + get :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) end end @@ -486,23 +486,17 @@ module ActionDispatch yield if block_given? with_scope_level(:collection) do - get "(.:format)", :to => :index, :as => resource.collection_name if resource.actions.include?(:index) - post "(.:format)", :to => :create if resource.actions.include?(:create) - - with_exclusive_name_prefix :new do - get "/#{action_path(:new)}(.:format)", :to => :new, :as => resource.singular if resource.actions.include?(:new) - end + get :index, :as => resource.collection_name if resource.actions.include?(:index) + post :create if resource.actions.include?(:create) + get :new, :as => resource.singular if resource.actions.include?(:new) end with_scope_level(:member) do scope("/:id") do - get "(.:format)", :to => :show, :as => resource.member_name if resource.actions.include?(:show) - put "(.:format)", :to => :update if resource.actions.include?(:update) - delete "(.:format)", :to => :destroy if resource.actions.include?(:destroy) - - with_exclusive_name_prefix :edit do - get "/#{action_path(:edit)}(.:format)", :to => :edit, :as => resource.singular if resource.actions.include?(:edit) - end + get :show, :as => resource.member_name if resource.actions.include?(:show) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end end -- cgit v1.2.3 From 6437eb9f351f379d1d32aaabf5d8da9f21d53b0f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 16:12:28 -0600 Subject: Always join scoped paths with slashes --- actionpack/lib/action_dispatch/routing/mapper.rb | 28 ++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3c13cf88f4..9b8660d22b 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -68,7 +68,7 @@ module ActionDispatch end def normalize_path(path) - path = "#{@scope[:path]}#{path}" + path = "#{@scope[:path]}/#{path}" raise ArgumentError, "path is required" if path.empty? Mapper.normalize_path(path) end @@ -160,7 +160,7 @@ module ActionDispatch # (:locale) becomes (/:locale) instead of /(:locale). def self.normalize_path(path) path = Rack::Mount::Utils.normalize_path(path) - path.sub!(/^\/\(+\/?:/, '(/:') + path.sub!(%r{/\(+/?:}, '(/:') path end @@ -283,7 +283,7 @@ module ActionDispatch end def namespace(path) - scope("/#{path}", :name_prefix => path.to_s, :namespace => path.to_s) { yield } + scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield } end def constraints(constraints = {}) @@ -311,7 +311,7 @@ module ActionDispatch end def merge_path_scope(parent, child) - Mapper.normalize_path(parent.to_s + child.to_s) + Mapper.normalize_path("#{parent}/#{child}") end def merge_name_prefix_scope(parent, child) @@ -440,7 +440,7 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do yield if block_given? @@ -481,7 +481,7 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resources, resource) do yield if block_given? @@ -492,7 +492,7 @@ module ActionDispatch end with_scope_level(:member) do - scope("/:id") do + scope(':id') do get :show, :as => resource.member_name if resource.actions.include?(:show) put :update if resource.actions.include?(:update) delete :destroy if resource.actions.include?(:destroy) @@ -523,7 +523,7 @@ module ActionDispatch end with_scope_level(:member) do - scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do + scope(':id', :name_prefix => parent_resource.member_name, :as => "") do yield end end @@ -535,7 +535,7 @@ module ActionDispatch end with_scope_level(:nested) do - scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do + scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do yield end end @@ -554,10 +554,16 @@ module ActionDispatch if args.first.is_a?(Symbol) action = args.first if CRUD_ACTIONS.include?(action) - return match("(.:format)", options.reverse_merge(:to => action)) + begin + old_path = @scope[:path] + @scope[:path] = "#{@scope[:path]}(.:format)" + return match(options.reverse_merge(:to => action)) + ensure + @scope[:path] = old_path + end else with_exclusive_name_prefix(action) do - return match("/#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action)) + return match("#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action)) end end end -- cgit v1.2.3 From 89082004b0381ec67b4a2782ef4d4f63853e586f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 16:20:12 -0600 Subject: Fix const reference for SessionRestoreError --- actionpack/lib/action_dispatch/middleware/session/abstract_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 7d4f0998ce..311880cabc 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -102,7 +102,7 @@ module ActionDispatch # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => const_error - raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" + raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" end retry -- cgit v1.2.3 From b2578a148cb78b4f238ad92864c9d9e509e5e451 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 15 Jan 2010 16:30:11 -0600 Subject: Fix singleton resource named routes --- actionpack/lib/action_dispatch/routing/mapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9b8660d22b..ce5c56ae1c 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -448,8 +448,8 @@ module ActionDispatch post :create if resource.actions.include?(:create) put :update if resource.actions.include?(:update) delete :destroy if resource.actions.include?(:destroy) - get :new, :as => "new_#{resource.singular}" if resource.actions.include?(:new) - get :edit, :as => "edit_#{resource.singular}" if resource.actions.include?(:edit) + get :new, :as => resource.singular if resource.actions.include?(:new) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end -- cgit v1.2.3 From 92f49b5f1ebf42514c58e1fda87c0b8a1b33d08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 16 Jan 2010 13:17:03 +0100 Subject: Split ActionDispatch http in smaller chunks. --- actionpack/lib/action_dispatch/http/cache.rb | 123 +++++++ .../lib/action_dispatch/http/mime_negotiation.rb | 101 ++++++ actionpack/lib/action_dispatch/http/parameters.rb | 50 +++ actionpack/lib/action_dispatch/http/request.rb | 379 ++------------------- actionpack/lib/action_dispatch/http/response.rb | 80 +---- actionpack/lib/action_dispatch/http/upload.rb | 48 +++ actionpack/lib/action_dispatch/http/url.rb | 129 +++++++ 7 files changed, 480 insertions(+), 430 deletions(-) create mode 100644 actionpack/lib/action_dispatch/http/cache.rb create mode 100644 actionpack/lib/action_dispatch/http/mime_negotiation.rb create mode 100644 actionpack/lib/action_dispatch/http/parameters.rb create mode 100644 actionpack/lib/action_dispatch/http/upload.rb create mode 100644 actionpack/lib/action_dispatch/http/url.rb (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb new file mode 100644 index 0000000000..428e62dc6b --- /dev/null +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -0,0 +1,123 @@ +module ActionDispatch + module Http + module Cache + module Request + def if_modified_since + if since = env['HTTP_IF_MODIFIED_SINCE'] + Time.rfc2822(since) rescue nil + end + end + + def if_none_match + env['HTTP_IF_NONE_MATCH'] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if_none_match && if_none_match == etag + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. If both headers are + # supplied, both must match, or the request is not considered fresh. + def fresh?(response) + last_modified = if_modified_since + etag = if_none_match + + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end + end + + module Response + def cache_control + @cache_control ||= {} + end + + def last_modified + if last = headers['Last-Modified'] + Time.httpdate(last) + end + end + + def last_modified? + headers.include?('Last-Modified') + end + + def last_modified=(utc_time) + headers['Last-Modified'] = utc_time.httpdate + end + + def etag + @etag + end + + def etag? + @etag + end + + def etag=(etag) + key = ActiveSupport::Cache.expand_cache_key(etag) + @etag = %("#{Digest::MD5.hexdigest(key)}") + end + + private + + def handle_conditional_get! + if etag? || last_modified? || !@cache_control.empty? + set_conditional_cache_control! + elsif nonempty_ok_response? + self.etag = @body + + if request && request.etag_matches?(etag) + self.status = 304 + self.body = [] + end + + set_conditional_cache_control! + else + headers["Cache-Control"] = "no-cache" + end + end + + def nonempty_ok_response? + @status == 200 && string_body? + end + + def string_body? + !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } + end + + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + + def set_conditional_cache_control! + control = @cache_control + + if control.empty? + headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + elsif @cache_control[:no_cache] + headers["Cache-Control"] = "no-cache" + else + extras = control[:extras] + max_age = control[:max_age] + + options = [] + options << "max-age=#{max_age.to_i}" if max_age + options << (control[:public] ? "public" : "private") + options << "must-revalidate" if control[:must_revalidate] + options.concat(extras) if extras + + headers["Cache-Control"] = options.join(", ") + end + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb new file mode 100644 index 0000000000..40617e239a --- /dev/null +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -0,0 +1,101 @@ +module ActionDispatch + module Http + module MimeNegotiation + # The MIME type of the HTTP request, such as Mime::XML. + # + # For backward compatibility, the post \format is extracted from the + # X-Post-Data-Format HTTP header if present. + def content_type + @env["action_dispatch.request.content_type"] ||= begin + if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ + Mime::Type.lookup($1.strip.downcase) + else + nil + end + end + end + + # Returns the accepted MIME type for the request. + def accepts + @env["action_dispatch.request.accepts"] ||= begin + header = @env['HTTP_ACCEPT'].to_s.strip + + if header.empty? + [content_type] + else + Mime::Type.parse(header) + end + end + end + + # Returns the Mime type for the \format used in the request. + # + # GET /posts/5.xml | request.format => Mime::XML + # GET /posts/5.xhtml | request.format => Mime::HTML + # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of ActionController::Base.use_accept_header + # + def format(view_path = []) + formats.first + end + + def formats + accept = @env['HTTP_ACCEPT'] + + @env["action_dispatch.request.formats"] ||= + if parameters[:format] + Array(Mime[parameters[:format]]) + elsif xhr? || (accept && !accept.include?(?,)) + accepts + else + [Mime::HTML] + end + end + + # Sets the \format by string extension, which can be used to force custom formats + # that are not controlled by the extension. + # + # class ApplicationController < ActionController::Base + # before_filter :adjust_format_for_iphone + # + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def format=(extension) + parameters[:format] = extension.to_s + @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] + end + + # Returns a symbolized version of the :format parameter of the request. + # If no \format is given it returns :jsfor Ajax requests and :html + # otherwise. + def template_format + parameter_format = parameters[:format] + + if parameter_format + parameter_format + elsif xhr? + :js + else + :html + end + end + + # Receives an array of mimes and return the first user sent mime that + # matches the order array. + # + def negotiate_mime(order) + formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? formats.first : nil + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb new file mode 100644 index 0000000000..97546d5f93 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -0,0 +1,50 @@ +require 'active_support/core_ext/hash/keys' + +module ActionDispatch + module Http + module Parameters + # Returns both GET and POST \parameters in a single hash. + def parameters + @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access + end + alias :params :parameters + + def path_parameters=(parameters) #:nodoc: + @env.delete("action_dispatch.request.symbolized_path_parameters") + @env.delete("action_dispatch.request.parameters") + @env["action_dispatch.request.path_parameters"] = parameters + end + + # The same as path_parameters with explicitly symbolized keys. + def symbolized_path_parameters + @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys + end + + # Returns a hash with the \parameters used to form the \path of the request. + # Returned hash keys are strings: + # + # {'action' => 'my_action', 'controller' => 'my_controller'} + # + # See symbolized_path_parameters for symbolized keys. + def path_parameters + @env["action_dispatch.request.path_parameters"] ||= {} + end + + private + + # Convert nested Hashs to HashWithIndifferentAccess + def normalize_parameters(value) + case value + when Hash + h = {} + value.each { |k, v| h[k] = normalize_parameters(v) } + h.with_indifferent_access + when Array + value.map { |e| normalize_parameters(e) } + else + value + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 22a08ec10d..187ce7c15d 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -2,14 +2,17 @@ require 'tempfile' require 'stringio' require 'strscan' -require 'active_support/memoizable' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'action_dispatch/http/headers' module ActionDispatch class Request < Rack::Request + include ActionDispatch::Http::Cache::Request + include ActionDispatch::Http::MimeNegotiation + include ActionDispatch::Http::Parameters + include ActionDispatch::Http::Upload + include ActionDispatch::Http::URL %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST @@ -19,9 +22,11 @@ module ActionDispatch HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| - define_method(env.sub(/^HTTP_/n, '').downcase) do - @env[env] - end + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{env.sub(/^HTTP_/n, '').downcase} + @env["#{env}"] + end + METHOD end def key?(key) @@ -81,25 +86,6 @@ module ActionDispatch Http::Headers.new(@env) end - # Returns the content length of the request as an integer. - def content_length - super.to_i - end - - # The MIME type of the HTTP request, such as Mime::XML. - # - # For backward compatibility, the post \format is extracted from the - # X-Post-Data-Format HTTP header if present. - def content_type - @env["action_dispatch.request.content_type"] ||= begin - if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ - Mime::Type.lookup($1.strip.downcase) - else - nil - end - end - end - def forgery_whitelisted? method == :get || xhr? || content_type.nil? || !content_type.verify_request? end @@ -108,104 +94,9 @@ module ActionDispatch content_type.to_s end - # Returns the accepted MIME type for the request. - def accepts - @env["action_dispatch.request.accepts"] ||= begin - header = @env['HTTP_ACCEPT'].to_s.strip - - if header.empty? - [content_type] - else - Mime::Type.parse(header) - end - end - end - - def if_modified_since - if since = env['HTTP_IF_MODIFIED_SINCE'] - Time.rfc2822(since) rescue nil - end - end - - def if_none_match - env['HTTP_IF_NONE_MATCH'] - end - - def not_modified?(modified_at) - if_modified_since && modified_at && if_modified_since >= modified_at - end - - def etag_matches?(etag) - if_none_match && if_none_match == etag - end - - # Check response freshness (Last-Modified and ETag) against request - # If-Modified-Since and If-None-Match conditions. If both headers are - # supplied, both must match, or the request is not considered fresh. - def fresh?(response) - last_modified = if_modified_since - etag = if_none_match - - return false unless last_modified || etag - - success = true - success &&= not_modified?(response.last_modified) if last_modified - success &&= etag_matches?(response.etag) if etag - success - end - - # Returns the Mime type for the \format used in the request. - # - # GET /posts/5.xml | request.format => Mime::XML - # GET /posts/5.xhtml | request.format => Mime::HTML - # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of ActionController::Base.use_accept_header - # - def format(view_path = []) - formats.first - end - - def formats - accept = @env['HTTP_ACCEPT'] - - @env["action_dispatch.request.formats"] ||= - if parameters[:format] - Array.wrap(Mime[parameters[:format]]) - elsif xhr? || (accept && !accept.include?(?,)) - accepts - else - [Mime::HTML] - end - end - - # Sets the \format by string extension, which can be used to force custom formats - # that are not controlled by the extension. - # - # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone - # - # private - # def adjust_format_for_iphone - # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] - # end - # end - def format=(extension) - parameters[:format] = extension.to_s - @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] - end - - # Returns a symbolized version of the :format parameter of the request. - # If no \format is given it returns :jsfor Ajax requests and :html - # otherwise. - def template_format - parameter_format = parameters[:format] - - if parameter_format - parameter_format - elsif xhr? - :js - else - :html - end + # Returns the content length of the request as an integer. + def content_length + super.to_i end # Returns true if the request's "X-Requested-With" header contains @@ -238,7 +129,7 @@ module ActionDispatch if @env.include? 'HTTP_CLIENT_IP' if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) # We don't know which came from the proxy, and which from the user - raise ActionController::ActionControllerError.new(<tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". - def domain(tld_length = 1) - return nil unless named_host?(host) - - host.split('.').last(1 + tld_length).join('.') - end - - # Returns all the \subdomains as an array, so ["dev", "www"] would be - # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, - # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] - # in "www.rubyonrails.co.uk". - def subdomains(tld_length = 1) - return [] unless named_host?(host) - parts = host.split('.') - parts[0..-(tld_length+2)] - end - - # Returns the query string, accounting for server idiosyncrasies. - def query_string - @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') - end - - # Returns the request URI, accounting for server idiosyncrasies. - # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. - def request_uri - if uri = @env['REQUEST_URI'] - # Remove domain, which webrick puts into the request_uri. - (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri - else - # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. - uri = @env['PATH_INFO'].to_s - - if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = uri.sub(/#{script_filename}\//, '') - end - - env_qs = @env['QUERY_STRING'].to_s - uri += "?#{env_qs}" unless env_qs.empty? - - if uri.blank? - @env.delete('REQUEST_URI') - else - @env['REQUEST_URI'] = uri - end - end - end - - # Returns the interpreted \path to requested resource after all the installation - # directory of this application was taken into account. - def path - path = request_uri.to_s[/\A[^\?]*/] - path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') - path - end - # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post @@ -392,33 +165,6 @@ EOM @env['RAW_POST_DATA'] end - # Returns both GET and POST \parameters in a single hash. - def parameters - @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access - end - alias_method :params, :parameters - - def path_parameters=(parameters) #:nodoc: - @env.delete("action_dispatch.request.symbolized_path_parameters") - @env.delete("action_dispatch.request.parameters") - @env["action_dispatch.request.path_parameters"] = parameters - end - - # The same as path_parameters with explicitly symbolized keys. - def symbolized_path_parameters - @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys - end - - # Returns a hash with the \parameters used to form the \path of the request. - # Returned hash keys are strings: - # - # {'action' => 'my_action', 'controller' => 'my_controller'} - # - # See symbolized_path_parameters for symbolized keys. - def path_parameters - @env["action_dispatch.request.path_parameters"] ||= {} - end - # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body @@ -434,18 +180,6 @@ EOM FORM_DATA_MEDIA_TYPES.include?(content_type.to_s) end - # Override Rack's GET method to support indifferent access - def GET - @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) - end - alias_method :query_parameters, :GET - - # Override Rack's POST method to support indifferent access - def POST - @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) - end - alias_method :request_parameters, :POST - def body_stream #:nodoc: @env['rack.input'] end @@ -463,6 +197,19 @@ EOM @env['rack.session.options'] = options end + # Override Rack's GET method to support indifferent access + def GET + @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) + end + alias :query_parameters :GET + + # Override Rack's POST method to support indifferent access + def POST + @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) + end + alias :request_parameters :POST + + # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization @@ -471,77 +218,5 @@ EOM @env['X_HTTP_AUTHORIZATION'] || @env['REDIRECT_X_HTTP_AUTHORIZATION'] end - - # Receives an array of mimes and return the first user sent mime that - # matches the order array. - # - def negotiate_mime(order) - formats.each do |priority| - if priority == Mime::ALL - return order.first - elsif order.include?(priority) - return priority - end - end - - order.include?(Mime::ALL) ? formats.first : nil - end - - private - - def named_host?(host) - !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) - end - - module UploadedFile - def self.extended(object) - object.class_eval do - attr_accessor :original_path, :content_type - alias_method :local_path, :path if method_defined?(:path) - end - end - - # Take the basename of the upload's original filename. - # This handles the full Windows paths given by Internet Explorer - # (and perhaps other broken user agents) without affecting - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - unless defined? @original_filename - @original_filename = - unless original_path.blank? - if original_path =~ /^(?:.*[:\\\/])?(.*)/m - $1 - else - File.basename original_path - end - end - end - @original_filename - end - end - - # Convert nested Hashs to HashWithIndifferentAccess and replace - # file upload hashs with UploadedFile objects - def normalize_parameters(value) - case value - when Hash - if value.has_key?(:tempfile) - upload = value[:tempfile] - upload.extend(UploadedFile) - upload.original_path = value[:filename] - upload.content_type = value[:type] - upload - else - h = {} - value.each { |k, v| h[k] = normalize_parameters(v) } - h.with_indifferent_access - end - when Array - value.map { |e| normalize_parameters(e) } - else - value - end - end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 8524bbd993..65df9b1f03 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,6 +32,8 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response + include ActionDispatch::Http::Cache::Response + attr_accessor :request, :blank attr_writer :header, :sending_file @@ -55,10 +57,6 @@ module ActionDispatch # :nodoc: yield self if block_given? end - def cache_control - @cache_control ||= {} - end - def status=(status) @status = Rack::Utils.status_code(status) end @@ -114,33 +112,6 @@ module ActionDispatch # :nodoc: # information. attr_accessor :charset, :content_type - def last_modified - if last = headers['Last-Modified'] - Time.httpdate(last) - end - end - - def last_modified? - headers.include?('Last-Modified') - end - - def last_modified=(utc_time) - headers['Last-Modified'] = utc_time.httpdate - end - - def etag - @etag - end - - def etag? - @etag - end - - def etag=(etag) - key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = %("#{Digest::MD5.hexdigest(key)}") - end - CONTENT_TYPE = "Content-Type" cattr_accessor(:default_charset) { "utf-8" } @@ -222,31 +193,6 @@ module ActionDispatch # :nodoc: end private - def handle_conditional_get! - if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control! - elsif nonempty_ok_response? - self.etag = @body - - if request && request.etag_matches?(etag) - self.status = 304 - self.body = [] - end - - set_conditional_cache_control! - else - headers["Cache-Control"] = "no-cache" - end - end - - def nonempty_ok_response? - @status == 200 && string_body? - end - - def string_body? - !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } - end - def assign_default_content_type_and_charset! return if headers[CONTENT_TYPE].present? @@ -259,27 +205,5 @@ module ActionDispatch # :nodoc: headers[CONTENT_TYPE] = type end - DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" - - def set_conditional_cache_control! - control = @cache_control - - if control.empty? - headers["Cache-Control"] = DEFAULT_CACHE_CONTROL - elsif @cache_control[:no_cache] - headers["Cache-Control"] = "no-cache" - else - extras = control[:extras] - max_age = control[:max_age] - - options = [] - options << "max-age=#{max_age.to_i}" if max_age - options << (control[:public] ? "public" : "private") - options << "must-revalidate" if control[:must_revalidate] - options.concat(extras) if extras - - headers["Cache-Control"] = options.join(", ") - end - end end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb new file mode 100644 index 0000000000..dc6121b911 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -0,0 +1,48 @@ +module ActionDispatch + module Http + module UploadedFile + def self.extended(object) + object.class_eval do + attr_accessor :original_path, :content_type + alias_method :local_path, :path if method_defined?(:path) + end + end + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + module Upload + # Convert nested Hashs to HashWithIndifferentAccess and replace + # file upload hashs with UploadedFile objects + def normalize_parameters(value) + if Hash === value && value.has_key?(:tempfile) + upload = value[:tempfile] + upload.extend(UploadedFile) + upload.original_path = value[:filename] + upload.content_type = value[:type] + upload + else + super + end + end + private :normalize_parameters + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb new file mode 100644 index 0000000000..40ceb5a9b6 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -0,0 +1,129 @@ +module ActionDispatch + module Http + module URL + # Returns the complete URL used for this request. + def url + protocol + host_with_port + request_uri + end + + # Returns 'https://' if this is an SSL request and 'http://' otherwise. + def protocol + ssl? ? 'https://' : 'http://' + end + + # Is this an SSL request? + def ssl? + @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' + end + + # Returns the \host for this request, such as "example.com". + def raw_host_with_port + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + + # Returns the host for this request, such as example.com. + def host + raw_host_with_port.sub(/:\d+$/, '') + end + + # Returns a \host:\port string for this request, such as "example.com" or + # "example.com:8080". + def host_with_port + "#{host}#{port_string}" + end + + # Returns the port number of this request as an integer. + def port + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + + # Returns the standard \port number for this request's protocol. + def standard_port + case protocol + when 'https://' then 443 + else 80 + end + end + + # Returns a \port suffix like ":8080" if the \port number of this request + # is not the default HTTP \port 80 or HTTPS \port 443. + def port_string + port == standard_port ? '' : ":#{port}" + end + + def server_port + @env['SERVER_PORT'].to_i + end + + # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify + # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + def domain(tld_length = 1) + return nil unless named_host?(host) + + host.split('.').last(1 + tld_length).join('.') + end + + # Returns all the \subdomains as an array, so ["dev", "www"] would be + # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, + # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] + # in "www.rubyonrails.co.uk". + def subdomains(tld_length = 1) + return [] unless named_host?(host) + parts = host.split('.') + parts[0..-(tld_length+2)] + end + + # Returns the query string, accounting for server idiosyncrasies. + def query_string + @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') + end + + # Returns the request URI, accounting for server idiosyncrasies. + # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. + def request_uri + if uri = @env['REQUEST_URI'] + # Remove domain, which webrick puts into the request_uri. + (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri + else + # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. + uri = @env['PATH_INFO'].to_s + + if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) + uri = uri.sub(/#{script_filename}\//, '') + end + + env_qs = @env['QUERY_STRING'].to_s + uri += "?#{env_qs}" unless env_qs.empty? + + if uri.blank? + @env.delete('REQUEST_URI') + else + @env['REQUEST_URI'] = uri + end + end + end + + # Returns the interpreted \path to requested resource after all the installation + # directory of this application was taken into account. + def path + path = request_uri.to_s[/\A[^\?]*/] + path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') + path + end + + private + + def named_host?(host) + !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From e9a1dbe79a6610793a71af227aaf64ff55554cad Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 15:16:22 -0600 Subject: Allow custom controller for resource(s) [#3703 state:resolved] --- actionpack/lib/action_dispatch/routing/mapper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionpack/lib/action_dispatch') diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ce5c56ae1c..9aaa4355f2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -380,7 +380,7 @@ module ActionDispatch end def controller - plural + options[:controller] || plural end def member_name -- cgit v1.2.3