aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/routing_optimisation.rb
diff options
context:
space:
mode:
authorMichael Koziarski <michael@koziarski.com>2007-09-09 00:18:55 +0000
committerMichael Koziarski <michael@koziarski.com>2007-09-09 00:18:55 +0000
commit80ff0b9f1c07eae7668680fd12335ffa218e53ac (patch)
tree42307295c7d16ee3a778ec74b6f3becb0b5fb2ab /actionpack/lib/action_controller/routing_optimisation.rb
parent7ace0a654c46cf9ca57f42981c826eace21fa42c (diff)
downloadrails-80ff0b9f1c07eae7668680fd12335ffa218e53ac.tar.gz
rails-80ff0b9f1c07eae7668680fd12335ffa218e53ac.tar.bz2
rails-80ff0b9f1c07eae7668680fd12335ffa218e53ac.zip
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. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7421 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/lib/action_controller/routing_optimisation.rb')
-rw-r--r--actionpack/lib/action_controller/routing_optimisation.rb99
1 files changed, 99 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/routing_optimisation.rb b/actionpack/lib/action_controller/routing_optimisation.rb
new file mode 100644
index 0000000000..535f1bed63
--- /dev/null
+++ b/actionpack/lib/action_controller/routing_optimisation.rb
@@ -0,0 +1,99 @@
+module ActionController
+ module Routing
+ # Much of the slow performance from routes comes from the
+ # complexity of expiry, :requirements matching, defaults providing
+ # and figuring out which url pattern to use. With named routes
+ # we can avoid the expense of finding the right route. So if
+ # they've provided the right number of arguments, and have no
+ # :requirements, we can just build up a string and return it.
+ #
+ # To support building optimisations for other common cases, the
+ # generation code is seperated into several classes
+ module Optimisation
+ def generate_optimisation_block(route, kind)
+ return "" unless route.optimise?
+ OPTIMISERS.inject("") do |memo, klazz|
+ optimiser = klazz.new(route, kind)
+ memo << "return #{optimiser.generation_code} if #{optimiser.guard_condition}\n"
+ end
+ end
+
+ class Optimiser
+ attr_reader :route, :kind
+ def initialize(route, kind)
+ @route = route
+ @kind = kind
+ end
+
+ def guard_condition
+ 'false'
+ end
+
+ def generation_code
+ 'nil'
+ end
+ end
+
+ # Given a route:
+ # map.person '/people/:id'
+ #
+ # If the user calls person_url(@person), we can simply
+ # return a string like "/people/#{@person.to_param}"
+ # rather than triggering the expensive logic in url_for
+ class PositionalArguments < Optimiser
+ def guard_condition
+ number_of_arguments = route.segment_keys.size
+ # if they're using foo_url(:id=>2) it's one
+ # argument, but we don't want to generate /foos/id2
+ if number_of_arguments == 1
+ "args.size == 1 && !args.first.is_a?(Hash)"
+ else
+ "args.size == #{number_of_arguments}"
+ end
+ end
+
+ def generation_code
+ elements = []
+ idx = 0
+
+
+ if kind == :url
+ elements << '#{request.protocol}'
+ elements << '#{request.host_with_port}'
+ end
+
+ # The last entry in route.segments appears to
+ # *always* be a 'divider segment' for '/'
+ # but we have assertions to ensure that we don't
+ # include the trailing slashes, so skip them
+ route.segments[0..-2].each do |segment|
+ if segment.is_a?(DynamicSegment)
+ elements << "\#{URI.escape(args[#{idx}].to_param, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
+ idx += 1
+ else
+ elements << segment.to_s
+ end
+ end
+ %("#{elements * ''}")
+ end
+ end
+
+ # This case is mostly the same as the positional arguments case
+ # above, but it supports additional query parameters as the last
+ # argument
+ class PositionalArgumentsWithAdditionalParams < PositionalArguments
+ def guard_condition
+ "args.size == #{route.segment_keys.size + 1}"
+ end
+
+ # This case uses almost the Use the same code as positional arguments,
+ # but add an args.last.to_query on the end
+ def generation_code
+ super.insert(-2, '?#{args.last.to_query}')
+ end
+ end
+
+ OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
+ end
+ end
+end \ No newline at end of file