Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9450: 9450.diff

File 9450.diff, 17.5 kB (added by nzkoz, 3 years ago)

Another version of the patch

  • a/actionpack/lib/action_controller/integration.rb

    old new  
    7474 
    7575        unless defined? @named_routes_configured 
    7676          # install the named routes in this session instance. 
     77          # But we have to disable the optimisation code so that we can 
     78          # generate routes without @request being initialized 
     79          Routing.optimise_named_routes=false 
     80          Routing::Routes.reload! 
    7781          klass = class<<self; self; end 
    7882          Routing::Routes.install_helpers(klass) 
    7983 
  • a/actionpack/lib/action_controller/routing.rb

    old new  
    11require 'cgi' 
    22require 'uri' 
    33require 'action_controller/polymorphic_routes' 
     4require 'action_controller/routing_optimisation' 
    45 
    56class Object 
    67  def to_param 
     
    255256    mattr_accessor :controller_paths 
    256257    self.controller_paths = [] 
    257258     
     259    # Indicates whether or not optimise the generated named 
     260    # route helper methods 
     261    mattr_accessor :optimise_named_routes 
     262    self.optimise_named_routes = true 
     263     
    258264    # A helper module to hold URL related helpers. 
    259265    module Helpers 
    260266      include PolymorphicRoutes 
     
    325331    end 
    326332   
    327333    class Route #:nodoc: 
    328       attr_accessor :segments, :requirements, :conditions 
     334      attr_accessor :segments, :requirements, :conditions, :optimise 
    329335       
    330336      def initialize 
    331337        @segments = [] 
    332338        @requirements = {} 
    333339        @conditions = {} 
    334340      end 
     341       
     342      # Indicates whether the routes should be optimised with the string interpolation 
     343      # version of the named routes methods. 
     344      def optimise? 
     345        @optimise 
     346      end 
     347       
     348      def segment_keys 
     349        segments.collect do |segment| 
     350          segment.key if segment.respond_to? :key 
     351        end.compact 
     352      end 
    335353   
    336354      # Write and compile a +generate+ method for this Route. 
    337355      def write_generation 
     
    381399        end 
    382400        requirement_conditions * ' && ' unless requirement_conditions.empty? 
    383401      end 
     402       
    384403      def generation_structure 
    385404        segments.last.string_structure segments[0..-2] 
    386405      end 
     
    977996        requirements = assign_route_options(segments, defaults, requirements) 
    978997 
    979998        route = Route.new 
     999 
    9801000        route.segments = segments 
    9811001        route.requirements = requirements 
    9821002        route.conditions = conditions 
     1003         
     1004        # Routes cannot use the current string interpolation method 
     1005        # if there are user-supplied :requirements as the interpolation 
     1006        # code won't raise RoutingErrors when generating 
     1007        route.optimise = !options.key?(:requirements) && ActionController::Routing.optimise_named_routes 
    9831008 
    9841009        if !route.significant_keys.include?(:action) && !route.requirements[:action] 
    9851010          route.requirements[:action] = "index" 
     
    10561081      # named routes. 
    10571082      class NamedRouteCollection #:nodoc: 
    10581083        include Enumerable 
    1059  
     1084        include ActionController::Routing::Optimisation 
    10601085        attr_reader :routes, :helpers, :deprecated_named_routes 
    10611086 
    10621087        def initialize 
     
    11401165           
    11411166          def define_url_helper(route, name, kind, options) 
    11421167            selector = url_helper_name(name, kind) 
    1143              
    11441168            # The segment keys used for positional paramters 
    1145             segment_keys = route.segments.collect do |segment| 
    1146               segment.key if segment.respond_to? :key 
    1147             end.compact 
     1169             
    11481170            hash_access_method = hash_access_name(name, kind) 
    11491171             
    11501172            @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks 
    11511173              def #{selector}(*args) 
     1174 
     1175                #{generate_optimisation_block(route, kind)} 
     1176                 
    11521177                opts = if args.empty? || Hash === args.first 
    11531178                  args.first || {} 
    11541179                else 
     
    11661191                  #   foo_url(bar, baz, bang, :sort_by => 'baz') 
    11671192                  # 
    11681193                  options = args.last.is_a?(Hash) ? args.pop : {} 
    1169                   args = args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)| 
     1194                  args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| 
    11701195                    h[k] = v 
    11711196                    h 
    11721197                  end 
     
    11791204            @module.send(:protected, selector) 
    11801205            helpers << selector 
    11811206          end 
    1182            
    11831207      end 
    11841208   
    11851209      attr_accessor :routes, :named_routes 
  • /dev/null

    old new  
     1module ActionController 
     2  module Routing 
     3    # Much of the slow performance from routes comes from the  
     4    # complexity of expiry, :requirements matching, defaults providing 
     5    # and figuring out which url pattern to use.  With named routes  
     6    # we can avoid the expense of finding the right route.  So if  
     7    # they've provided the right number of arguments, and have no 
     8    # :requirements, we can just build up a string and return it 
     9    # 
     10    # This means that we end up generating code that looks like this: 
     11    # map.person_friend '/people/:person_id/friend/:friend_id',... 
     12    #  
     13    #     def person_friend_path(*args) 
     14    #       if args.size == 2 
     15    #         return "/people/#{URI.escape(args[0].to_param)}/friend/#{URI.escape(args[1].to_param)}" 
     16    #       elsif args.size == 3 && args[3].is_a?(Hash) 
     17    #         return "/people/#{URI.escape(args[0].to_param)}/friend/#{URI.escape(args[1].to_param)}?#{args[2].to_query_string}" 
     18    #       end 
     19    #  
     20    #       SLOW_CODE 
     21    #     end     
     22    module Optimisation 
     23      def generate_optimisation_block(route, kind) 
     24        return "" unless route.optimise? 
     25        OPTIMISERS.inject("") do |memo, klazz| 
     26          optimiser = klazz.new(route, kind) 
     27          memo << "return #{optimiser.generation_code} if #{optimiser.guard_condition}\n" 
     28        end 
     29      end 
     30 
     31      class Optimiser 
     32        attr_reader :route, :kind 
     33        def initialize(route, kind) 
     34          @route = route 
     35          @kind  = kind 
     36        end 
     37      end 
     38 
     39      class PositionalArguments < Optimiser 
     40        def guard_condition 
     41          number_of_arguments = route.segment_keys.size 
     42          # if they're using foo_url(:id=>2) it's one  
     43          # argument, but we don't want to generate /foos/id2 
     44          if number_of_arguments == 1 
     45            "args.size == 1 && !args.first.is_a?(Hash)" 
     46          else 
     47            "args.size == #{number_of_arguments}" 
     48          end 
     49        end 
     50 
     51        def generation_code 
     52          elements = [] 
     53          idx = 0 
     54 
     55          if kind == :url 
     56            elements << '#{request.protocol}' 
     57            elements << '#{request.host_with_port}' 
     58          end 
     59 
     60          # The last entry in route.segments appears to 
     61          # *always* be a 'divider segment' for '/' 
     62          # but we have assertions to ensure that we don't 
     63          # include the trailing slashes, so skip them 
     64          route.segments[0..-2].each do |segment| 
     65            if segment.is_a?(DynamicSegment) 
     66              elements << "\#{URI.escape(args[#{idx}].to_param, ActionController::Routing::Segment::UNSAFE_PCHAR)}" 
     67              idx += 1 
     68            else 
     69              elements << segment.to_s 
     70            end 
     71          end 
     72          %("#{elements * ''}") 
     73        end 
     74      end 
     75       
     76       
     77      # supports additional arguments like 
     78      # - person_url(@person, :q=>"some search term") 
     79      class PositionalArgumentsWithAdditionalParams < PositionalArguments 
     80        def guard_condition 
     81          number_of_arguments = route.segment_keys.size 
     82          if number_of_arguments == 1 
     83            "args.size == 2 && !args.first.is_a?(Hash)" 
     84          else 
     85            "args.size == #{number_of_arguments + 1}" 
     86          end 
     87        end 
     88         
     89        # Use the same code as without the arguments, but add an 
     90        # args.last.to_query on the end 
     91        def generation_code 
     92          orig_code = super 
     93          orig_code[-1] = '?#{args.last.to_query}"' 
     94          orig_code 
     95        end 
     96      end 
     97       
     98      OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams] 
     99    end 
     100  end 
     101end 
  • a/actionpack/test/controller/resources_test.rb

    old new  
    2626end 
    2727 
    2828class ResourcesTest < Test::Unit::TestCase 
     29   
     30   
     31  # The assertions in these tests are incompatible with the hash method 
     32  # optimisation.  This could indicate user level problems 
     33  def setup 
     34    ActionController::Routing.optimise_named_routes = false 
     35  end 
     36   
     37  def tear_down 
     38    ActionController::Routing.optimise_named_routes = true 
     39  end 
     40   
    2941  def test_should_arrange_actions 
    3042    resource = ActionController::Resources::Resource.new(:messages, 
    3143      :collection => { :rss => :get, :reorder => :post, :csv => :post }, 
  • a/actionpack/test/controller/routing_test.rb

    old new  
    4747class LegacyRouteSetTests < Test::Unit::TestCase 
    4848  attr_reader :rs 
    4949  def setup 
     50    # These tests assume optimisation is on, so re-enable it. 
     51    ActionController::Routing.optimise_named_routes = true 
    5052    @rs = ::ActionController::Routing::RouteSet.new 
    5153    @rs.draw {|m| m.connect ':controller/:action/:id' } 
    5254    ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) 
     
    166168   
    167169  def test_basic_named_route 
    168170    rs.add_named_route :home, '', :controller => 'content', :action => 'list'  
    169     x = setup_for_named_route.new 
    170     assert_equal({:controller => 'content', :action => 'list', :use_route => :home, :only_path => false}
     171    x = setup_for_named_route 
     172    assert_equal("http://named.route.test"
    171173                 x.send(:home_url)) 
    172174  end 
    173175 
    174176  def test_named_route_with_option 
    175177    rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page' 
    176     x = setup_for_named_route.new 
    177     assert_equal({:controller => 'content', :action => 'show_page', :title => 'new stuff', :use_route => :page, :only_path => false}
     178    x = setup_for_named_route 
     179    assert_equal("http://named.route.test/page/new%20stuff"
    178180                 x.send(:page_url, :title => 'new stuff')) 
    179181  end 
    180182 
    181183  def test_named_route_with_default 
    182184    rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' 
    183     x = setup_for_named_route.new 
    184     assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutPage', :use_route => :page, :only_path => false}, 
    185                  x.send(:page_url)) 
    186     assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutRails', :use_route => :page, :only_path => false}, 
     185    x = setup_for_named_route 
     186    assert_equal("http://named.route.test/page/AboutRails", 
    187187                 x.send(:page_url, :title => "AboutRails")) 
    188188 
    189189  end 
    190190 
    191191  def test_named_route_with_nested_controller 
    192192    rs.add_named_route :users, 'admin/user', :controller => '/admin/user', :action => 'index' 
    193     x = setup_for_named_route.new 
    194     assert_equal({:controller => '/admin/user', :action => 'index', :use_route => :users, :only_path => false}
     193    x = setup_for_named_route 
     194    assert_equal("http://named.route.test/admin/user"
    195195                 x.send(:users_url)) 
    196196  end 
    197197 
    198198  def setup_for_named_route 
    199     x = Class.new 
    200     x.send(:define_method, :url_for) {|x| x} 
    201     rs.install_helpers(x) 
    202     x 
     199    klass = Class.new(MockController) 
     200    rs.install_helpers(klass) 
     201    klass.new(rs) 
    203202  end 
    204203 
    205204  def test_named_route_without_hash 
     
    214213        :year => /\d+/, :month => /\d+/, :day => /\d+/ 
    215214      map.connect ':controller/:action/:id' 
    216215    end 
    217     x = setup_for_named_route.new 
    218     assert_equal( 
    219       {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, 
    220       x.send(:article_url, :title => 'hi') 
    221    
     216    x = setup_for_named_route 
     217    # assert_equal( 
     218    #   {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, 
     219    #   x.send(:article_url, :title => 'hi') 
     220    #
    222221    assert_equal( 
    223       {:controller => 'page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6, :use_route => :article, :only_path => false}
     222      "http://named.route.test/page/2005/6/10/hi"
    224223      x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) 
    225224    ) 
    226225  end 
     
    408407    assert_equal '/test', rs.generate(:controller => 'post', :action => 'show') 
    409408    assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) 
    410409     
    411     x = setup_for_named_route.new 
    412     assert_equal({:controller => 'post', :action => 'show', :use_route => :blog, :only_path => false}
     410    x = setup_for_named_route 
     411    assert_equal("http://named.route.test/test"
    413412                 x.send(:blog_url)) 
    414413  end 
    415414   
     
    455454    assert_equal '/', rs.generate(:controller => 'content', :action => 'index') 
    456455    assert_equal '/', rs.generate(:controller => 'content') 
    457456     
    458     x = setup_for_named_route.new 
    459     assert_equal({:controller => 'content', :action => 'index', :use_route => :home, :only_path => false}
     457    x = setup_for_named_route 
     458    assert_equal("http://named.route.test"
    460459                 x.send(:home_url)) 
    461460  end 
    462461   
     
    571570  ensure 
    572571    Object.send(:remove_const, :SubpathBooksController) rescue nil 
    573572  end 
     573   
     574  def test_failed_requirements_raises_exception_with_violated_requirements 
     575    rs.draw do |r| 
     576      r.foo_with_requirement 'foos/:id', :controller=>'foos', :requirements=>{:id=>/\d+/} 
     577    end 
     578     
     579    x = setup_for_named_route 
     580    assert_raises(ActionController::RoutingError) do  
     581      x.send(:foo_with_requirement_url, "I am Against the requirements") 
     582    end 
     583  end 
    574584end 
    575585 
    576586class SegmentTest < Test::Unit::TestCase 
     
    840850end 
    841851 
    842852uses_mocha 'RouteTest' do 
    843    
     853 
     854  class MockController 
     855    attr_accessor :routes 
     856 
     857    def initialize(routes) 
     858      self.routes = routes 
     859    end 
     860 
     861    def url_for(options) 
     862      only_path = options.delete(:only_path) 
     863      path = routes.generate(options) 
     864      only_path ? path : "http://named.route.test#{path}" 
     865    end 
     866     
     867    def request 
     868      @request ||= MockRequest.new(:host => "named.route.test", :method => :get) 
     869    end 
     870  end 
     871 
     872  class MockRequest 
     873    attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method 
     874 
     875    def initialize(values={}) 
     876      values.each { |key, value| send("#{key}=", value) } 
     877      if values[:host] 
     878        subdomain, self.domain = values[:host].split(/\./, 2) 
     879        self.subdomains = [subdomain] 
     880      end 
     881    end 
     882     
     883    def protocol 
     884      "http://" 
     885    end 
     886     
     887    def host_with_port 
     888      (subdomains * '.') + '.' +  domain 
     889    end 
     890     
     891  end 
     892 
    844893class RouteTest < Test::Unit::TestCase 
    845894 
    846895  def setup 
     
    13541403   
    13551404end 
    13561405 
    1357 class RouteSetTest < Test::Unit::TestCase 
    1358   class MockController 
    1359     attr_accessor :routes 
    1360  
    1361     def initialize(routes) 
    1362       self.routes = routes 
    1363     end 
    13641406 
    1365     def url_for(options) 
    1366       only_path = options.delete(:only_path) 
    1367       path = routes.generate(options) 
    1368       only_path ? path : "http://named.route.test#{path}" 
    1369     end 
    1370   end 
    13711407 
    1372   class MockRequest 
    1373     attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method 
    13741408 
    1375     def initialize(values={}) 
    1376       values.each { |key, value| send("#{key}=", value) } 
    1377       if values[:host] 
    1378         subdomain, self.domain = values[:host].split(/\./, 2) 
    1379         self.subdomains = [subdomain] 
    1380       end 
    1381     end 
    1382   end 
     1409class RouteSetTest < Test::Unit::TestCase 
    13831410 
    13841411  def set 
    13851412    @set ||= ROUTING::RouteSet.new 
     
    15101537      controller.send(:multi_url, 7, "hello", 5, :baz => "bar") 
    15111538  end 
    15121539   
    1513   def test_named_route_url_method_with_ordered_parameters_and_hash_ordered_parameters_override_hash 
     1540  def test_named_route_url_method_with_no_positional_arguments 
    15141541    controller = setup_named_route_test 
    1515     assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar", 
    1516       controller.send(:multi_url, 7, "hello", 5, :foo => 666, :baz => "bar") 
     1542    assert_equal "http://named.route.test/people?baz=bar", 
     1543      controller.send(:index_url, :baz => "bar") 
    15171544  end 
    15181545   
    15191546  def test_draw_default_route