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

Changeset 7421

Show
Ignore:
Timestamp:
09/09/07 00:18:55 (1 year ago)
Author:
nzkoz
Message:

Optimise named route generation when using positional arguments. Closes #9450 [Koz]

This change delivers significant performance benefits for the most
common usage scenarios for modern rails applications by avoiding the
costly trip through url_for. Initial benchmarks indicate this is
between 6 and 20 times as fast.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r7419 r7421  
    11*SVN* 
     2 
     3* Optimise named route generation when using positional arguments.  [Koz] 
     4 
     5  This change delivers significant performance benefits for the most 
     6  common usage scenarios for modern rails applications by avoiding the 
     7  costly trip through url_for.  Initial benchmarks indicate this is 
     8  between 6 and 20 times as fast. 
    29 
    310* Explicitly require active_record/query_cache before using it.  [Jeremy Kemper] 
  • trunk/actionpack/lib/action_controller/integration.rb

    r7383 r7421  
    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) 
  • trunk/actionpack/lib/action_controller/routing.rb

    r7415 r7421  
    22require 'uri' 
    33require 'action_controller/polymorphic_routes' 
     4require 'action_controller/routing_optimisation' 
    45 
    56class Object 
     
    255256    mattr_accessor :controller_paths 
    256257    self.controller_paths = [] 
     258     
     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 
    257263     
    258264    # A helper module to hold URL related helpers. 
     
    326332   
    327333    class Route #:nodoc: 
    328       attr_accessor :segments, :requirements, :conditions 
     334      attr_accessor :segments, :requirements, :conditions, :optimise 
    329335       
    330336      def initialize 
     
    332338        @requirements = {} 
    333339        @conditions = {} 
     340      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 
    334352      end 
    335353   
     
    382400        requirement_conditions * ' && ' unless requirement_conditions.empty? 
    383401      end 
     402       
    384403      def generation_structure 
    385404        segments.last.string_structure segments[0..-2] 
     
    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] 
     
    10521077      class NamedRouteCollection #:nodoc: 
    10531078        include Enumerable 
    1054  
     1079        include ActionController::Routing::Optimisation 
    10551080        attr_reader :routes, :helpers 
    10561081 
     
    11291154          def define_url_helper(route, name, kind, options) 
    11301155            selector = url_helper_name(name, kind) 
     1156            # The segment keys used for positional paramters 
    11311157             
    1132             # The segment keys used for positional paramters 
    1133             segment_keys = route.segments.collect do |segment| 
    1134               segment.key if segment.respond_to? :key 
    1135             end.compact 
    11361158            hash_access_method = hash_access_name(name, kind) 
    11371159             
    11381160            @module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks 
    11391161              def #{selector}(*args) 
     1162 
     1163                #{generate_optimisation_block(route, kind)} 
     1164                 
    11401165                opts = if args.empty? || Hash === args.first 
    11411166                  args.first || {} 
     
    11551180                  # 
    11561181                  options = args.last.is_a?(Hash) ? args.pop : {} 
    1157                   args = args.zip(#{segment_keys.inspect}).inject({}) do |h, (v, k)| 
     1182                  args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| 
    11581183                    h[k] = v 
    11591184                    h 
     
    11681193            helpers << selector 
    11691194          end 
    1170            
    11711195      end 
    11721196   
  • trunk/actionpack/test/controller/resources_test.rb

    r7415 r7421  
    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, 
  • trunk/actionpack/test/controller/routing_test.rb

    r7415 r7421  
    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' } 
     
    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 
     
    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 
     
    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 
     
    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 
     197   
     198  uses_mocha "named route optimisation" do 
     199    def test_optimised_named_route_call_never_uses_url_for 
     200      rs.add_named_route :users, 'admin/user', :controller => '/admin/user', :action => 'index' 
     201      x = setup_for_named_route 
     202      x.expects(:url_for).never 
     203      x.send(:users_url) 
     204      x.send(:users_path) 
     205      x.send(:users_url, :some_arg=>"some_value") 
     206      x.send(:users_path, :some_arg=>"some_value") 
     207    end 
     208  end 
    197209 
    198210  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 
     211    klass = Class.new(MockController) 
     212    rs.install_helpers(klass) 
     213    klass.new(rs) 
    203214  end 
    204215 
     
    215226      map.connect ':controller/:action/:id' 
    216227    end 
    217     x = setup_for_named_route.new 
     228    x = setup_for_named_route 
     229    # assert_equal( 
     230    #   {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, 
     231    #   x.send(:article_url, :title => 'hi') 
     232    # ) 
    218233    assert_equal( 
    219       {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article, :only_path => false}, 
    220       x.send(:article_url, :title => 'hi') 
    221     ) 
    222     assert_equal( 
    223       {:controller => 'page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6, :use_route => :article, :only_path => false}, 
     234      "http://named.route.test/page/2005/6/10/hi", 
    224235      x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) 
    225236    ) 
     
    409420    assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) 
    410421     
    411     x = setup_for_named_route.new 
    412     assert_equal({:controller => 'post', :action => 'show', :use_route => :blog, :only_path => false}
     422    x = setup_for_named_route 
     423    assert_equal("http://named.route.test/test"
    413424                 x.send(:blog_url)) 
    414425  end 
     
    456467    assert_equal '/', rs.generate(:controller => 'content') 
    457468     
    458     x = setup_for_named_route.new 
    459     assert_equal({:controller => 'content', :action => 'index', :use_route => :home, :only_path => false}
     469    x = setup_for_named_route 
     470    assert_equal("http://named.route.test"
    460471                 x.send(:home_url)) 
    461472  end 
     
    571582  ensure 
    572583    Object.send(:remove_const, :SubpathBooksController) rescue nil 
     584  end 
     585   
     586  def test_failed_requirements_raises_exception_with_violated_requirements 
     587    rs.draw do |r| 
     588      r.foo_with_requirement 'foos/:id', :controller=>'foos', :requirements=>{:id=>/\d+/} 
     589    end 
     590     
     591    x = setup_for_named_route 
     592    assert_raises(ActionController::RoutingError) do  
     593      x.send(:foo_with_requirement_url, "I am Against the requirements") 
     594    end 
    573595  end 
    574596end 
     
    841863 
    842864uses_mocha 'RouteTest' do 
    843    
     865 
     866  class MockController 
     867    attr_accessor :routes 
     868 
     869    def initialize(routes) 
     870      self.routes = routes 
     871    end 
     872 
     873    def url_for(options) 
     874      only_path = options.delete(:only_path) 
     875      path = routes.generate(options) 
     876      only_path ? path : "http://named.route.test#{path}" 
     877    end 
     878     
     879    def request 
     880      @request ||= MockRequest.new(:host => "named.route.test", :method => :get) 
     881    end 
     882  end 
     883 
     884  class MockRequest 
     885    attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method 
     886 
     887    def initialize(values={}) 
     888      values.each { |key, value| send("#{key}=", value) } 
     889      if values[:host] 
     890        subdomain, self.domain = values[:host].split(/\./, 2) 
     891        self.subdomains = [subdomain] 
     892      end 
     893    end 
     894     
     895    def protocol 
     896      "http://" 
     897    end 
     898     
     899    def host_with_port 
     900      (subdomains * '.') + '.' +  domain 
     901    end 
     902     
     903  end 
     904 
    844905class RouteTest < Test::Unit::TestCase 
    845906 
     
    13031364end 
    13041365 
     1366 
     1367 
     1368 
    13051369class RouteSetTest < Test::Unit::TestCase 
    1306   class MockController 
    1307     attr_accessor :routes 
    1308  
    1309     def initialize(routes) 
    1310       self.routes = routes 
    1311     end 
    1312  
    1313     def url_for(options) 
    1314       only_path = options.delete(:only_path) 
    1315       path = routes.generate(options) 
    1316       only_path ? path : "http://named.route.test#{path}" 
    1317     end 
    1318   end 
    1319  
    1320   class MockRequest 
    1321     attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method 
    1322  
    1323     def initialize(values={}) 
    1324       values.each { |key, value| send("#{key}=", value) } 
    1325       if values[:host] 
    1326         subdomain, self.domain = values[:host].split(/\./, 2) 
    1327         self.subdomains = [subdomain] 
    1328       end 
    1329     end 
    1330   end 
    13311370 
    13321371  def set 
     
    14591498  end 
    14601499   
    1461   def test_named_route_url_method_with_ordered_parameters_and_hash_ordered_parameters_override_hash 
     1500  def test_named_route_url_method_with_no_positional_arguments 
    14621501    controller = setup_named_route_test 
    1463     assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar", 
    1464       controller.send(:multi_url, 7, "hello", 5, :foo => 666, :baz => "bar") 
     1502    assert_equal "http://named.route.test/people?baz=bar", 
     1503      controller.send(:index_url, :baz => "bar") 
    14651504  end 
    14661505