diff options
Diffstat (limited to 'actionpack')
25 files changed, 533 insertions, 191 deletions
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index b4311599a9..df0b5ac9a2 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |s| s.add_dependency('rack-test', '~> 0.5.4') #s.add_dependency('rack-mount', '~> 0.6.6') s.add_dependency('tzinfo', '~> 0.3.16') - s.add_dependency('erubis', '~> 2.6.5') + s.add_dependency('erubis', '~> 2.6.6') end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 6e3998aa21..b81d5954eb 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -50,8 +50,8 @@ module AbstractController if controller.respond_to?(:_helpers) include controller._helpers - if controller.respond_to?(:_router) - include controller._router.url_helpers + if controller.respond_to?(:_routes) + include controller._routes.url_helpers end # TODO: Fix RJS to not require this diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index a70ba0d2e3..1a2cbaab65 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -60,17 +60,11 @@ module ActionController include ActionController::Compatibility def self.inherited(klass) - ::ActionController::Base.subclasses << klass.to_s super klass.helper :all end - def self.subclasses - @subclasses ||= [] - end - config_accessor :asset_host, :asset_path - ActiveSupport.run_load_hooks(:action_controller, self) end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 89201fb5ee..e0bc47318a 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute' module ActionController @@ -64,7 +65,7 @@ module ActionController def helpers_dir=(value) ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead", caller - self.helpers_path = Array(value) + self.helpers_path = Array.wrap(value) end # Declares helper accessors for controller attributes. For example, the @@ -103,7 +104,7 @@ module ActionController # Extract helper names from files in app/helpers/**/*_helper.rb def all_application_helpers helpers = [] - helpers_path.each do |path| + Array.wrap(helpers_path).each do |path| extract = /^#{Regexp.quote(path.to_s)}\/?(.*)_helper.rb$/ helpers += Dir["#{path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index c465035ca1..a51fc5b8e4 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -12,17 +12,17 @@ module ActionController ).merge(:script_name => request.script_name) end - def _router - raise "In order to use #url_for, you must include the helpers of a particular " \ - "router. For instance, `include Rails.application.routes.url_helpers" + def _routes + raise "In order to use #url_for, you must include routing helpers explicitly. " \ + "For instance, `include Rails.application.routes.url_helpers" end module ClassMethods def action_methods @action_methods ||= begin - super - _router.named_routes.helper_names + super - _routes.named_routes.helper_names end end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 86395c4d93..9261422f0b 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -2,7 +2,6 @@ require "rails" require "action_controller" require "action_dispatch/railtie" require "action_view/railtie" -require "active_support/core_ext/class/subclasses" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 08bc80dbc2..64f4d1d532 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -64,6 +64,11 @@ module ActionDispatch super(key.to_s, value) end + def clear + load_for_write! + super + end + def to_hash load_for_read! h = {}.replace(self) diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 401d98b663..c664fb0bc2 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -31,7 +31,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # AppName::Applications.routes.draw do |map| + # AppName::Application.routes.draw do |map| # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... @@ -62,7 +62,7 @@ module ActionDispatch # # redirect_to show_item_path(:id => 25) # - # Use <tt>root</tt> as a shorthand to name a route for the root path "". + # Use <tt>root</tt> as a shorthand to name a route for the root path "/". # # # In routes.rb # root :to => 'blogs#index' @@ -72,7 +72,7 @@ module ActionDispatch # # # and provide these named routes # root_url # => 'http://www.example.com/' - # root_path # => '' + # root_path # => '/' # # Note: when using +controller+, the route is simply named after the # method you call on the block parameter rather than map. @@ -91,9 +91,7 @@ module ActionDispatch # # Routes can generate pretty URLs. For example: # - # match '/articles/:year/:month/:day', :constraints => { - # :controller => 'articles', - # :action => 'find_by_date', + # match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => { # :year => /\d{4}/, # :month => /\d{1,2}/, # :day => /\d{1,2}/ @@ -167,7 +165,7 @@ module ActionDispatch # # You can reload routes if you feel you must: # - # Rails::Application.reload_routes! + # Rails.application.reload_routes! # # This will clear all named routes and reload routes.rb if the file has been modified from # last load. To absolutely force reloading, use <tt>reload!</tt>. diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index bc3f62fb48..b3146a1c60 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -19,9 +19,9 @@ module ActionDispatch def in_memory_controller_namespaces namespaces = Set.new - ActionController::Base.subclasses.each do |klass| - controller_name = klass.underscore - namespaces << controller_name.split('/')[0...-1].join('/') + ActionController::Base.descendants.each do |klass| + next if klass.anonymous? + namespaces << klass.name.underscore.split('/')[0...-1].join('/') end namespaces.delete('') namespaces @@ -31,7 +31,7 @@ module ActionDispatch class DeprecatedMapper #:nodoc: def initialize(set) #:nodoc: ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " << - "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" + "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" @set = set end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0b4ba8c9f5..430f6fc5a3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -13,6 +13,8 @@ module ActionDispatch end end + attr_reader :app + def initialize(app, constraints, request) @app, @constraints, @request = app, constraints, request end @@ -63,6 +65,16 @@ module ActionDispatch end end + if path.match(':controller') + raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module] + + # Add a default constraint for :controller path segments that matches namespaced + # controllers with default routes like :controller/:action/:id(.:format), e.g: + # GET /admin/products/show/1 + # => { :controller => 'admin/products', :action => 'show', :id => '1' } + options.reverse_merge!(:controller => /.+?/) + end + path = normalize_path(path) path_without_format = path.sub(/\(\.:format\)$/, '') @@ -133,8 +145,11 @@ module ActionDispatch defaults[:controller] ||= default_controller defaults[:action] ||= default_action - defaults.delete(:controller) if defaults[:controller].blank? - defaults.delete(:action) if defaults[:action].blank? + defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp) + defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp) + + defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller) + defaults[:action] = defaults[:action].to_s if defaults.key?(:action) if defaults[:controller].blank? && segment_keys.exclude?("controller") raise ArgumentError, "missing :controller" @@ -183,15 +198,15 @@ module ActionDispatch def default_controller if @options[:controller] - @options[:controller].to_s + @options[:controller] elsif @scope[:controller] - @scope[:controller].to_s + @scope[:controller] end end def default_action if @options[:action] - @options[:action].to_s + @options[:action] end end end @@ -431,12 +446,16 @@ module ActionDispatch end def merge_options_scope(parent, child) - (parent || {}).merge(child) + (parent || {}).except(*override_keys(child)).merge(child) end def merge_shallow_scope(parent, child) child ? true : false end + + def override_keys(child) + child.key?(:only) || child.key?(:except) ? [:only, :except] : [] + end end module Resources @@ -444,7 +463,7 @@ module ActionDispatch # a path appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy] - MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except] class Resource #:nodoc: DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit] @@ -464,9 +483,9 @@ module ActionDispatch end def actions - if only = options[:only] + if only = @options[:only] Array(only).map(&:to_sym) - elsif except = options[:except] + elsif except = @options[:except] default_actions - Array(except).map(&:to_sym) else default_actions @@ -489,68 +508,31 @@ module ActionDispatch singular end - alias_method :nested_name, :member_name - # Checks for uncountable plurals, and appends "_index" if they're. def collection_name singular == plural ? "#{plural}_index" : plural end - def shallow? - options[:shallow] ? true : false - end - - def constraints - options[:constraints] || {} - end - - def id_constraint? - options[:id] && options[:id].is_a?(Regexp) || constraints[:id] && constraints[:id].is_a?(Regexp) - end - - def id_constraint - options[:id] || constraints[:id] - end - - def collection_options - (options || {}).dup.tap do |opts| - opts.delete(:id) - opts[:constraints] = options[:constraints].dup if options[:constraints] - opts[:constraints].delete(:id) if options[:constraints].is_a?(Hash) - end - end - - def nested_path - "#{path}/:#{singular}_id" - end - - def nested_options - {}.tap do |opts| - opts[:as] = member_name - opts["#{singular}_id".to_sym] = id_constraint if id_constraint? - opts[:options] = { :shallow => shallow? } unless options[:shallow].nil? - end - end - def resource_scope - [{ :controller => controller }] + { :controller => controller } end def collection_scope - [path, collection_options] + path end def member_scope - ["#{path}/:id", options] + "#{path}/:id" end def new_scope(new_path) - ["#{path}/#{new_path}"] + "#{path}/#{new_path}" end def nested_scope - [nested_path, nested_options] + "#{path}/:#{singular}_id" end + end class SingletonResource < Resource #:nodoc: @@ -559,7 +541,7 @@ module ActionDispatch def initialize(entities, options) @name = entities.to_s @path = options.delete(:path) || @name - @controller = (options.delete(:controller) || @name.to_s.pluralize).to_s + @controller = (options.delete(:controller) || plural).to_s @as = options.delete(:as) @options = options end @@ -569,24 +551,10 @@ module ActionDispatch end alias :collection_name :member_name - def nested_path - path - end - - def nested_options - {}.tap do |opts| - opts[:as] = member_name - opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil? - end - end - - def shallow? - false - end - def member_scope - [path, options] + path end + alias :nested_scope :member_scope end def initialize(*args) #:nodoc: @@ -693,18 +661,18 @@ module ActionDispatch end with_scope_level(:nested) do - if parent_resource.shallow? + if shallow? with_exclusive_scope do if @scope[:shallow_path].blank? - scope(*parent_resource.nested_scope) { yield } + scope(parent_resource.nested_scope, nested_options) { yield } else scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do - scope(*parent_resource.nested_scope) { yield } + scope(parent_resource.nested_scope, nested_options) { yield } end end end else - scope(*parent_resource.nested_scope) { yield } + scope(parent_resource.nested_scope, nested_options) { yield } end end end @@ -723,6 +691,10 @@ module ActionDispatch end end + def shallow? + parent_resource.instance_of?(Resource) && @scope[:shallow] + end + def match(*args) options = args.extract_options!.dup options[:anchor] = true unless options.key?(:anchor) @@ -800,16 +772,17 @@ module ActionDispatch return true end - if path_names = options.delete(:path_names) - scope(:path_names => path_names) do + scope_options = options.slice!(*RESOURCE_OPTIONS) + unless scope_options.empty? + scope(scope_options) do send(method, resources.pop, options, &block) end return true end - scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? } - options.reverse_merge!(scope_options) unless scope_options.empty? - options.reverse_merge!(@scope[:options]) unless @scope[:options].blank? + unless action_options?(options) + options.merge!(scope_action_options) if scope_action_options? + end if resource_scope? nested do @@ -821,6 +794,18 @@ module ActionDispatch false end + def action_options?(options) + options[:only] || options[:except] + end + + def scope_action_options? + @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except]) + end + + def scope_action_options + @scope[:options].slice(:only, :except) + end + def resource_scope? [:resource, :resources].include?(@scope[:scope_level]) end @@ -853,7 +838,7 @@ module ActionDispatch def resource_scope(resource) with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do - scope(*parent_resource.resource_scope) do + scope(parent_resource.resource_scope) do yield end end @@ -861,7 +846,7 @@ module ActionDispatch def new_scope with_scope_level(:new) do - scope(*parent_resource.new_scope(action_path(:new))) do + scope(parent_resource.new_scope(action_path(:new))) do yield end end @@ -869,7 +854,7 @@ module ActionDispatch def collection_scope with_scope_level(:collection) do - scope(*parent_resource.collection_scope) do + scope(parent_resource.collection_scope) do yield end end @@ -877,18 +862,33 @@ module ActionDispatch def member_scope with_scope_level(:member) do - scope(*parent_resource.member_scope) do + scope(parent_resource.member_scope) do yield end end end + def nested_options + {}.tap do |options| + options[:as] = parent_resource.member_name + options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint? + end + end + + def id_constraint? + @scope[:id].is_a?(Regexp) || (@scope[:constraints] && @scope[:constraints][:id].is_a?(Regexp)) + end + + def id_constraint + @scope[:id] || @scope[:constraints][:id] + end + def canonical_action?(action, flag) flag && CANONICAL_ACTIONS.include?(action) end def shallow_scoping? - parent_resource && parent_resource.shallow? && @scope[:scope_level] == :member + shallow? && @scope[:scope_level] == :member end def path_for_action(action, path) @@ -932,7 +932,7 @@ module ActionDispatch collection_name = parent_resource.collection_name member_name = parent_resource.member_name name_prefix = "#{name_prefix}_" if name_prefix.present? - end + end case @scope[:scope_level] when :collection diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5ecad6bc04..1b1a221c60 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -26,7 +26,7 @@ module ActionDispatch return [404, {'X-Cascade' => 'pass'}, []] end - controller.action(params[:action]).call(env) + dispatch(controller, params[:action], env) end def prepare_params!(params) @@ -34,29 +34,43 @@ module ActionDispatch split_glob_param!(params) if @glob_param end - def controller(params, raise_error=true) + # If this is a default_controller (i.e. a controller specified by the user) + # we should raise an error in case it's not found, because it usually means + # an user error. However, if the controller was retrieved through a dynamic + # segment, as in :controller(/:action), we should simply return nil and + # delegate the control back to Rack cascade. Besides, if this is not a default + # controller, it means we should respect the @scope[:module] parameter. + def controller(params, default_controller=true) if params && params.key?(:controller) controller_param = params[:controller] - unless controller = @controllers[controller_param] - controller_name = "#{controller_param.camelize}Controller" - controller = @controllers[controller_param] = - ActiveSupport::Dependencies.ref(controller_name) - end - - controller.get + controller_reference(controller_param) end rescue NameError => e - raise ActionController::RoutingError, e.message, e.backtrace if raise_error + raise ActionController::RoutingError, e.message, e.backtrace if default_controller end - private - def merge_default_action!(params) - params[:action] ||= 'index' - end + private - def split_glob_param!(params) - params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) } + def controller_reference(controller_param) + unless controller = @controllers[controller_param] + controller_name = "#{controller_param.camelize}Controller" + controller = @controllers[controller_param] = + ActiveSupport::Dependencies.ref(controller_name) end + controller.get + end + + def dispatch(controller, action, env) + controller.action(action).call(env) + end + + def merge_default_action!(params) + params[:action] ||= 'index' + end + + def split_glob_param!(params) + params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) } + end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -268,10 +282,10 @@ module ActionDispatch # Yes plz - JP included do routes.install_helpers(self) - singleton_class.send(:define_method, :_router) { routes } + singleton_class.send(:define_method, :_routes) { routes } end - define_method(:_router) { routes } + define_method(:_routes) { routes } end helpers @@ -302,7 +316,6 @@ module ActionDispatch @extras = extras normalize_options! - normalize_recall! normalize_controller_action_id! use_relative_controller! controller.sub!(%r{^/}, '') if controller @@ -319,7 +332,11 @@ module ActionDispatch def use_recall_for(key) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) - @options[key] = @recall.delete(key) + if named_route_exists? + @options[key] = @recall.delete(key) if segment_keys.include?(key) + else + @options[key] = @recall.delete(key) + end end end @@ -343,15 +360,6 @@ module ActionDispatch end end - def normalize_recall! - # If the target route is not a standard route then remove controller and action - # from the options otherwise they will appear in the url parameters - if block_or_proc_route_target? - recall.delete(:controller) unless segment_keys.include?(:controller) - recall.delete(:action) unless segment_keys.include?(:action) - end - end - # This pulls :controller, :action, and :id out of the recall. # The recall key is only used if there is no key in the options # or if the key in the options is identical. If any of @@ -424,12 +432,8 @@ module ActionDispatch named_route && set.named_routes[named_route] end - def block_or_proc_route_target? - named_route_exists? && !set.named_routes[named_route].app.is_a?(Dispatcher) - end - def segment_keys - named_route_exists? ? set.named_routes[named_route].segment_keys : [] + set.named_routes[named_route].segment_keys end end @@ -474,7 +478,7 @@ module ActionDispatch path_options = yield(path_options) if block_given? path = generate(path_options, path_segments || {}) - # ROUTES TODO: This can be called directly, so script_name should probably be set in the router + # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor] @@ -506,6 +510,8 @@ module ActionDispatch end dispatcher = route.app + dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints) + if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false) dispatcher.prepare_params!(params) return params diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index db17615d92..980abd44df 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -128,7 +128,7 @@ module ActionDispatch when String options when nil, Hash - _router.url_for(url_options.merge((options || {}).symbolize_keys)) + _routes.url_for(url_options.merge((options || {}).symbolize_keys)) else polymorphic_url(options) end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 956c88a553..20a2e7c1f0 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -173,7 +173,7 @@ module ActionView #:nodoc: @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } class_attribute :helpers - class_attribute :_router + class_attribute :_routes class << self delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index d1b10a9281..6302491c2a 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -213,12 +213,22 @@ module ActionView # ... # <% end %> # - # And for namespaced routes, like +admin_post_url+: + # For namespaced routes, like +admin_post_url+: # # <%= form_for([:admin, @post]) do |f| %> # ... # <% end %> # + # If your resource has associations defined, for example, you want to add comments + # to the post given that the routes are set correctly: + # + # <%= form_for([@document, @comment]) do |f| %> + # ... + # <% end %> + # + # Where +@document = Document.find(params[:id])+ and + # +@comment = Comment.new+. + # # === Unobtrusive JavaScript # # Specifying: diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index b8d6dc22f2..0e3cc54fd1 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -12,7 +12,7 @@ module ActionView # and controllers. module UrlHelper # This helper may be included in any class that includes the - # URL helpers of a router (router.url_helpers). Some methods + # URL helpers of a routes (routes.url_helpers). Some methods # provided here will only work in the4 context of a request # (link_to_unless_current, for instance), which must be provided # as a method called #request on the context. diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index e5614c9e3b..56aebca957 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -151,7 +151,7 @@ module ActionView @view ||= begin view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) view.singleton_class.send :include, _helpers - view.singleton_class.send :include, @controller._router.url_helpers + view.singleton_class.send :include, @controller._routes.url_helpers view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash" view.extend(Locals) view.locals = self.locals @@ -192,13 +192,13 @@ module ActionView end end - def _router - @controller._router if @controller.respond_to?(:_router) + def _routes + @controller._routes if @controller.respond_to?(:_routes) end def method_missing(selector, *args) - if @controller.respond_to?(:_router) && - @controller._router.named_routes.helpers.include?(selector) + if @controller.respond_to?(:_routes) && + @controller._routes.named_routes.helpers.include?(selector) @controller.__send__(selector, *args) else super diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index 0cdf5c2298..15c27501f2 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers'] +ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) module AbstractController module Testing diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index c8477fb83f..5be47f7c96 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -41,7 +41,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up module Rails end -# Monkey patch the old router initialization to be silenced. +# Monkey patch the old routes initialization to be silenced. class ActionDispatch::Routing::DeprecatedMapper def initialize_with_silencer(*args) ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) } @@ -182,13 +182,16 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase self.app = build_app - class StubDispatcher - def self.new(*args) - lambda { |env| - params = env['action_dispatch.request.path_parameters'] - controller, action = params[:controller], params[:action] - [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] - } + # Stub Rails dispatcher so it does not get controller references and + # simply return the controller#action as Rack::Body. + class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher + protected + def controller_reference(controller_param) + controller_param + end + + def dispatch(controller, action, env) + [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] end end @@ -275,9 +278,9 @@ end class ActionController::Base def self.test_routes(&block) - router = ActionDispatch::Routing::RouteSet.new - router.draw(&block) - include router.url_helpers + routes = ActionDispatch::Routing::RouteSet.new + routes.draw(&block) + include routes.url_helpers end end diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index c9a6e5dd45..9b9657929f 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' require 'active_support/core_ext/kernel/reporting' -ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers'] +ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) module Fun class GamesController < ActionController::Base @@ -106,7 +106,7 @@ class HelperTest < ActiveSupport::TestCase end def test_all_helpers_with_alternate_helper_dir - @controller_class.helpers_path = [File.dirname(__FILE__) + '/../fixtures/alternate_helpers'] + @controller_class.helpers_path = File.expand_path('../../fixtures/alternate_helpers', __FILE__) # Reload helpers @controller_class._helpers = Module.new @@ -143,7 +143,7 @@ class HelperTest < ActiveSupport::TestCase assert_equal ["some/foo/bar"], ActionController::Base.helpers_dir end ensure - ActionController::Base.helpers_path = [File.dirname(__FILE__) + '/../fixtures/helpers'] + ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) end private diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index d654338dba..b5ce391b61 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -36,6 +36,15 @@ class RespondToController < ActionController::Base type.all { render :text => "Nothing" } end end + + def json_xml_or_html + respond_to do |type| + type.json { render :text => 'JSON' } + type.xml { render :xml => 'XML' } + type.html { render :text => 'HTML' } + end + end + def forced_xml request.format = :xml @@ -364,6 +373,17 @@ class RespondToControllerTest < ActionController::TestCase get :handle_any_any assert_equal 'Whatever you ask for, I got it', @response.body end + + def test_browser_check_with_any_any + @request.accept = "application/json, application/xml" + get :json_xml_or_html + assert_equal 'JSON', @response.body + + @request.accept = "application/json, application/xml, */*" + get :json_xml_or_html + assert_equal 'HTML', @response.body + end + def test_rjs_type_skips_layout @request.accept = "text/javascript" diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 4b30b0af36..71a4a43477 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -120,7 +120,7 @@ module AbstractController def test_relative_url_root_is_respected # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This - # should probably come from the router. + # should probably come from routes. add_host! assert_equal('https://www.basecamphq.com/subdir/c/a/i', diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 26bd641cd6..2a014bf976 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -45,7 +45,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match 'account/logout' => redirect("/logout"), :as => :logout_redirect match 'account/login', :to => redirect("/login") - match 'account/overview' + constraints(lambda { |req| true }) do + match 'account/overview' + end + match '/account/nested/overview' match 'sign_in' => "sessions#new" @@ -298,8 +301,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ } + resource :token, :module => :api scope :module => :api do - resource :token resources :errors, :shallow => true do resources :notices end @@ -310,11 +313,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match '/' => 'mes#index' end - namespace :private do - root :to => redirect('/private/index') - match "index", :to => 'private#index' - end - get "(/:username)/followers" => "followers#index" get "/groups(/user/:username)" => "groups#index" get "(/user/:username)/photos" => "photos#index" @@ -325,7 +323,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - match "whatever/:controller(/:action(/:id))" + match "whatever/:controller(/:action(/:id))", :id => /\d+/ resource :profile do get :settings @@ -348,6 +346,63 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + namespace :private do + root :to => redirect('/private/index') + match "index", :to => 'private#index' + end + + scope :only => [:index, :show] do + namespace :only do + resources :clubs do + resources :players + resource :chairman + end + end + end + + scope :except => [:new, :create, :edit, :update, :destroy] do + namespace :except do + resources :clubs do + resources :players + resource :chairman + end + end + end + + scope :only => :show do + namespace :only do + resources :sectors, :only => :index do + resources :companies do + scope :only => :index do + resources :divisions + end + scope :except => [:show, :update, :destroy] do + resources :departments + end + end + resource :leader + resources :managers, :except => [:show, :update, :destroy] + end + end + end + + scope :except => :index do + namespace :except do + resources :sectors, :except => [:show, :update, :destroy] do + resources :companies do + scope :except => [:show, :update, :destroy] do + resources :divisions + end + scope :only => :index do + resources :departments + end + end + resource :leader + resources :managers, :only => :index + end + end + end + match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ end end @@ -470,6 +525,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_namespace_with_controller_segment + assert_raise(ArgumentError) do + self.class.stub_controllers do |routes| + routes.draw do + namespace :admin do + match '/:controller(/:action(/:id(.:format)))' + end + end + end + end + end + def test_session_singleton_resource with_test_routes do get '/session' @@ -1240,7 +1307,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'pass', @response.headers['X-Cascade'] get '/products/0001/images' assert_equal 'images#index', @response.body - get '/products/0001/images/1' + get '/products/0001/images/0001' assert_equal 'images#show', @response.body get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} @@ -1285,6 +1352,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_url_generator_for_namespaced_generic_route + with_test_routes do + get 'whatever/foo/bar/show' + assert_equal 'foo/bar#show', @response.body + + get 'whatever/foo/bar/show/1' + assert_equal 'foo/bar#show', @response.body + + assert_equal 'http://www.example.com/whatever/foo/bar/show', + url_for(:controller => "foo/bar", :action => "show") + + assert_equal 'http://www.example.com/whatever/foo/bar/show/1', + url_for(:controller => "foo/bar", :action => "show", :id => '1') + end + end + def test_assert_recognizes_account_overview with_test_routes do assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview") @@ -1628,6 +1711,182 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_only_should_be_read_from_scope + with_test_routes do + get '/only/clubs' + assert_equal 'only/clubs#index', @response.body + assert_equal '/only/clubs', only_clubs_path + + get '/only/clubs/1/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_path(:id => '1') } + + get '/only/clubs/1/players' + assert_equal 'only/players#index', @response.body + assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1') + + get '/only/clubs/1/players/2/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') } + + get '/only/clubs/1/chairman' + assert_equal 'only/chairmen#show', @response.body + assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1') + + get '/only/clubs/1/chairman/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') } + end + end + + def test_except_should_be_read_from_scope + with_test_routes do + get '/except/clubs' + assert_equal 'except/clubs#index', @response.body + assert_equal '/except/clubs', except_clubs_path + + get '/except/clubs/1/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_path(:id => '1') } + + get '/except/clubs/1/players' + assert_equal 'except/players#index', @response.body + assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1') + + get '/except/clubs/1/players/2/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') } + + get '/except/clubs/1/chairman' + assert_equal 'except/chairmen#show', @response.body + assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1') + + get '/except/clubs/1/chairman/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') } + end + end + + def test_only_option_should_override_scope + with_test_routes do + get '/only/sectors' + assert_equal 'only/sectors#index', @response.body + assert_equal '/only/sectors', only_sectors_path + + get '/only/sectors/1' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_path(:id => '1') } + end + end + + def test_only_option_should_not_inherit + with_test_routes do + get '/only/sectors/1/companies/2' + assert_equal 'only/companies#show', @response.body + assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2') + + get '/only/sectors/1/leader' + assert_equal 'only/leaders#show', @response.body + assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1') + end + end + + def test_except_option_should_override_scope + with_test_routes do + get '/except/sectors' + assert_equal 'except/sectors#index', @response.body + assert_equal '/except/sectors', except_sectors_path + + get '/except/sectors/1' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_path(:id => '1') } + end + end + + def test_except_option_should_not_inherit + with_test_routes do + get '/except/sectors/1/companies/2' + assert_equal 'except/companies#show', @response.body + assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2') + + get '/except/sectors/1/leader' + assert_equal 'except/leaders#show', @response.body + assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1') + end + end + + def test_except_option_should_override_scoped_only + with_test_routes do + get '/only/sectors/1/managers' + assert_equal 'only/managers#index', @response.body + assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1') + + get '/only/sectors/1/managers/2' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') } + end + end + + def test_only_option_should_override_scoped_except + with_test_routes do + get '/except/sectors/1/managers' + assert_equal 'except/managers#index', @response.body + assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1') + + get '/except/sectors/1/managers/2' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') } + end + end + + def test_only_scope_should_override_parent_scope + with_test_routes do + get '/only/sectors/1/companies/2/divisions' + assert_equal 'only/divisions#index', @response.body + assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + + get '/only/sectors/1/companies/2/divisions/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } + end + end + + def test_except_scope_should_override_parent_scope + with_test_routes do + get '/except/sectors/1/companies/2/divisions' + assert_equal 'except/divisions#index', @response.body + assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + + get '/except/sectors/1/companies/2/divisions/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } + end + end + + def test_except_scope_should_override_parent_only_scope + with_test_routes do + get '/only/sectors/1/companies/2/departments' + assert_equal 'only/departments#index', @response.body + assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2') + + get '/only/sectors/1/companies/2/departments/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } + end + end + + def test_only_scope_should_override_parent_except_scope + with_test_routes do + get '/except/sectors/1/companies/2/departments' + assert_equal 'except/departments#index', @response.body + assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2') + + get '/except/sectors/1/companies/2/departments/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } + end + end + private def with_test_routes yield diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index fd63f5ad5e..f0e01bfff0 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -30,6 +30,11 @@ class CookieStoreTest < ActionController::IntegrationTest render :text => "id: #{request.session_options[:id]}" end + def call_session_clear + session.clear + head :ok + end + def call_reset_session reset_session head :ok @@ -175,6 +180,23 @@ class CookieStoreTest < ActionController::IntegrationTest end end + def test_setting_session_value_after_session_clear + with_test_route_set do + get '/set_session_value' + assert_response :success + session_payload = response.body + assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly", + headers['Set-Cookie'] + + get '/call_session_clear' + assert_response :success + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + def test_persistent_session_id with_test_route_set do cookies[SessionKey] = SignedBar diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 326b336a1a..f83651d583 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -2,24 +2,24 @@ require 'abstract_unit' module TestUrlGeneration class WithMountPoint < ActionDispatch::IntegrationTest - Router = ActionDispatch::Routing::RouteSet.new - Router.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } class ::MyRouteGeneratingController < ActionController::Base - include Router.url_helpers + include Routes.url_helpers def index render :text => foo_path end end - include Router.url_helpers + include Routes.url_helpers - def _router - Router + def _routes + Routes end def app - Router + Routes end test "generating URLS normally" do @@ -30,7 +30,7 @@ module TestUrlGeneration assert_equal "/bar/foo", foo_path(:script_name => "/bar") end - test "the request's SCRIPT_NAME takes precedence over the router's" do + test "the request's SCRIPT_NAME takes precedence over the routes'" do get "/foo", {}, 'SCRIPT_NAME' => "/new" assert_equal "/new/foo", response.body end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 765beebfa3..048f96c9a9 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -406,6 +406,14 @@ end class UrlHelperControllerTest < ActionController::TestCase class UrlHelperController < ActionController::Base test_routes do |map| + match 'url_helper_controller_test/url_helper/show/:id', + :to => 'url_helper_controller_test/url_helper#show', + :as => :show + + match 'url_helper_controller_test/url_helper/profile/:name', + :to => 'url_helper_controller_test/url_helper#show', + :as => :profile + match 'url_helper_controller_test/url_helper/show_named_route', :to => 'url_helper_controller_test/url_helper#show_named_route', :as => :show_named_route @@ -418,6 +426,14 @@ class UrlHelperControllerTest < ActionController::TestCase :as => :normalize_recall_params end + def show + if params[:name] + render :inline => 'ok' + else + redirect_to profile_path(params[:id]) + end + end + def show_url_for render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>" end @@ -484,15 +500,24 @@ class UrlHelperControllerTest < ActionController::TestCase assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end - def test_recall_params_should_be_normalized_when_using_block_route + def test_recall_params_should_be_normalized get :normalize_recall_params assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body end - def test_recall_params_should_not_be_changed_when_using_normal_route + def test_recall_params_should_not_be_changed get :recall_params_not_changed assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body end + + def test_recall_params_should_normalize_id + get :show, :id => '123' + assert_equal 302, @response.status + assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location + + get :show, :name => '123' + assert_equal 'ok', @response.body + end end class TasksController < ActionController::Base |