aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2010-09-04 00:31:35 +0200
committerJosé Valim <jose.valim@gmail.com>2010-09-04 00:31:43 +0200
commit23a9455962f0362cf242ffa96db7a9e7fdb0804b (patch)
treec046d1285d078649db6833fa1b73a6c23f7f16ea /actionpack/lib/action_dispatch
parent63032c1162e96d3380168ca63ac42aa044dbebd6 (diff)
parentc3c1a1e14859e6716970283caeab0c4c3720862e (diff)
downloadrails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.tar.gz
rails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.tar.bz2
rails-23a9455962f0362cf242ffa96db7a9e7fdb0804b.zip
This commit merges most of the work done by Piotr Sarnacki in his Ruby Summer of Code project.
His work brings several capabilities from app to engines, as routes, middleware stack, asset handling and much more. Please check Rails::Engine documentation for more refenrences. Merge remote branch 'drogus/engines'
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb64
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb38
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb28
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb76
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb35
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb12
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
9 files changed, 214 insertions, 43 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 5a029a60d1..6f243574e4 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -1,4 +1,5 @@
require "active_support/inflector/methods"
+require "active_support/dependencies"
module ActionDispatch
class MiddlewareStack < Array
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index d7e88a54e4..581cadbeb4 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,12 +1,47 @@
require 'rack/utils'
module ActionDispatch
+ class FileHandler
+ def initialize(at, root)
+ @at, @root = at.chomp('/'), root.chomp('/')
+ @compiled_at = Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank?
+ @compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/)
+ @file_server = ::Rack::File.new(root)
+ end
+
+ def match?(path)
+ path = path.dup
+ if @compiled_at.blank? || path.sub!(@compiled_at, '')
+ full_path = File.join(@root, ::Rack::Utils.unescape(path))
+ paths = "#{full_path}#{ext}"
+
+ matches = Dir[paths]
+ match = matches.detect { |m| File.file?(m) }
+ if match
+ match.sub!(@compiled_root, '')
+ match
+ end
+ end
+ end
+
+ def call(env)
+ @file_server.call(env)
+ end
+
+ def ext
+ @ext ||= begin
+ ext = ::ActionController::Base.page_cache_extension
+ "{,#{ext},/index#{ext}}"
+ end
+ end
+ end
+
class Static
FILE_METHODS = %w(GET HEAD).freeze
- def initialize(app, root)
+ def initialize(app, roots)
@app = app
- @file_server = ::Rack::File.new(root)
+ @file_handlers = create_file_handlers(roots)
end
def call(env)
@@ -14,15 +49,10 @@ module ActionDispatch
method = env['REQUEST_METHOD']
if FILE_METHODS.include?(method)
- if file_exist?(path)
- return @file_server.call(env)
- else
- cached_path = directory_exist?(path) ? "#{path}/index" : path
- cached_path += ::ActionController::Base.page_cache_extension
-
- if file_exist?(cached_path)
- env['PATH_INFO'] = cached_path
- return @file_server.call(env)
+ @file_handlers.each do |file_handler|
+ if match = file_handler.match?(path)
+ env["PATH_INFO"] = match
+ return file_handler.call(env)
end
end
end
@@ -31,14 +61,12 @@ module ActionDispatch
end
private
- def file_exist?(path)
- full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
- File.file?(full_path) && File.readable?(full_path)
- end
+ def create_file_handlers(roots)
+ roots = { '' => roots } unless roots.is_a?(Hash)
- def directory_exist?(path)
- full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path))
- File.directory?(full_path) && File.readable?(full_path)
+ roots.map do |at, root|
+ FileHandler.new(at, root) if File.exist?(root)
+ end.compact
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 0b9689dc88..b2b0f4c08e 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -268,6 +268,7 @@ module ActionDispatch
autoload :Mapper, 'action_dispatch/routing/mapper'
autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
+ autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
autoload :UrlFor, 'action_dispatch/routing/url_for'
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index a2570cb877..900900ee24 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -261,7 +261,11 @@ module ActionDispatch
raise "A rack application must be specified" unless path
+ options[:as] ||= app_name(app)
+
match(path, options.merge(:to => app, :anchor => false, :format => false))
+
+ define_generate_prefix(app, options[:as])
self
end
@@ -269,6 +273,40 @@ module ActionDispatch
@set.default_url_options = options
end
alias_method :default_url_options, :default_url_options=
+
+ def with_default_scope(scope, &block)
+ scope(scope) do
+ instance_exec(&block)
+ end
+ end
+
+ private
+ def app_name(app)
+ return unless app.respond_to?(:routes)
+
+ if app.respond_to?(:railtie_name)
+ app.railtie_name
+ else
+ class_name = app.class.is_a?(Class) ? app.name : app.class.name
+ ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
+ end
+ end
+
+ def define_generate_prefix(app, name)
+ return unless app.respond_to?(:routes)
+
+ _route = @set.named_routes.routes[name.to_sym]
+ _routes = @set
+ app.routes.define_mounted_helper(name)
+ app.routes.class_eval do
+ define_method :_generate_prefix do |options|
+ prefix_options = options.slice(*_route.segment_keys)
+ # we must actually delete prefix segment keys to avoid passing them to next url_for
+ _route.segment_keys.each { |k| options.delete(k) }
+ _routes.url_helpers.send("#{name}_path", prefix_options)
+ end
+ end
+ end
end
module HttpHelpers
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index fb2118a8d7..02ba5236ee 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -42,6 +42,18 @@ module ActionDispatch
#
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
+ #
+ # == Using with mounted engines
+ #
+ # If you use mounted engine, there is a possibility that you will need to use
+ # polymorphic_url pointing at engine's routes. To do that, just pass proxy used
+ # to reach engine's routes as a first argument:
+ #
+ # For example:
+ #
+ # polymorphic_url([blog, @post]) # it will call blog.post_path(@post)
+ # form_for([blog, @post]) # => "/blog/posts/1
+ #
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
@@ -78,6 +90,9 @@ module ActionDispatch
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
+ if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
+ proxy = record_or_hash_or_array.shift
+ end
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
@@ -111,7 +126,14 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
- send(named_route, *args)
+ if proxy
+ proxy.send(named_route, *args)
+ else
+ # we need to use url_for, because polymorphic_url can be used in context of other than
+ # current routes (e.g. engine's routes). As named routes from engine are not included
+ # calling engine's named route directly would fail.
+ url_for _routes.url_helpers.__send__("hash_for_#{named_route}", *args)
+ end
end
# Returns the path component of a URL for the given record. It uses
@@ -155,7 +177,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.plural(parent).singularize
+ ActiveModel::Naming.route_key(parent).singularize
end
end
end
@@ -163,7 +185,7 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
else
- route << ActiveModel::Naming.plural(record)
+ route << ActiveModel::Naming.route_key(record)
route = [route.join("_").singularize] if inflection == :singular
route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b531cc1a8e..107e44287d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -158,10 +158,17 @@ module ActionDispatch
# 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
+ def #{selector}(*args)
+ options = args.extract_options!
+
+ if args.any?
+ options[:_positional_args] = args
+ options[:_positional_keys] = #{route.segment_keys.inspect}
+ end
+
+ options ? #{options.inspect}.merge(options) : #{options.inspect}
+ end
+ protected :#{selector}
END_EVAL
helpers << selector
end
@@ -185,21 +192,14 @@ module ActionDispatch
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(*args)
- options = #{hash_access_method}(args.extract_options!)
-
- if args.any?
- options[:_positional_args] = args
- options[:_positional_keys] = #{route.segment_keys.inspect}
- end
-
- url_for(options)
+ url_for(#{hash_access_method}(*args))
end
END_EVAL
helpers << selector
end
end
- attr_accessor :set, :routes, :named_routes
+ attr_accessor :set, :routes, :named_routes, :default_scope
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
@@ -230,7 +230,11 @@ module ActionDispatch
if block.arity == 1
mapper.instance_exec(DeprecatedMapper.new(self), &block)
else
- mapper.instance_exec(&block)
+ if default_scope
+ mapper.with_default_scope(default_scope, &block)
+ else
+ mapper.instance_exec(&block)
+ end
end
finalize! unless @disable_clear_and_finalize
@@ -261,6 +265,31 @@ module ActionDispatch
named_routes.install(destinations, regenerate_code)
end
+ module MountedHelpers
+ end
+
+ def mounted_helpers(name = nil)
+ define_mounted_helper(name) if name
+ MountedHelpers
+ end
+
+ def define_mounted_helper(name)
+ return if MountedHelpers.method_defined?(name)
+
+ routes = self
+ MountedHelpers.class_eval do
+ define_method "_#{name}" do
+ RoutesProxy.new(routes, self._routes_context)
+ end
+ end
+
+ MountedHelpers.class_eval <<-RUBY
+ def #{name}
+ @#{name} ||= _#{name}
+ end
+ RUBY
+ end
+
def url_helpers
@url_helpers ||= begin
routes = self
@@ -283,7 +312,7 @@ module ActionDispatch
singleton_class.send(:define_method, :_routes) { routes }
end
- define_method(:_routes) { routes }
+ define_method(:_routes) { @_routes || routes }
end
helpers
@@ -303,10 +332,9 @@ module ActionDispatch
end
class Generator #:nodoc:
- attr_reader :options, :recall, :set, :script_name, :named_route
+ attr_reader :options, :recall, :set, :named_route
def initialize(options, recall, set, extras = false)
- @script_name = options.delete(:script_name)
@named_route = options.delete(:use_route)
@options = options.dup
@recall = recall.dup
@@ -401,7 +429,7 @@ module ActionDispatch
return [path, params.keys] if @extras
path << "?#{params.to_query}" if params.any?
- "#{script_name}#{path}"
+ path
rescue Rack::Mount::RoutingError
raise_routing_error
end
@@ -453,7 +481,11 @@ module ActionDispatch
Generator.new(options, recall, self, extras).generate
end
- RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
+ RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name]
+
+ def _generate_prefix(options = {})
+ nil
+ end
def url_for(options)
finalize!
@@ -464,7 +496,6 @@ module ActionDispatch
rewritten_url = ""
path_segments = options.delete(:_path_segments)
-
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
@@ -476,9 +507,12 @@ module ActionDispatch
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
+ script_name = options.delete(:script_name)
+ path = (script_name.blank? ? _generate_prefix(options) : script_name).to_s
+
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
- path = generate(path_options, path_segments || {})
+ path << generate(path_options, path_segments || {})
# 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)
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
new file mode 100644
index 0000000000..f7d5f6397d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -0,0 +1,35 @@
+module ActionDispatch
+ module Routing
+ class RoutesProxy #:nodoc:
+ include ActionDispatch::Routing::UrlFor
+
+ attr_accessor :scope, :routes
+ alias :_routes :routes
+
+ def initialize(routes, scope)
+ @routes, @scope = routes, scope
+ end
+
+ def url_options
+ scope.send(:_with_routes, routes) do
+ scope.url_options
+ end
+ end
+
+ def method_missing(method, *args)
+ if routes.url_helpers.respond_to?(method)
+ self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args)
+ options = args.extract_options!
+ args << url_options.merge((options || {}).symbolize_keys)
+ routes.url_helpers.#{method}(*args)
+ end
+ RUBY
+ send(method, *args)
+ else
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 28ec830fe8..e836cf7c8e 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -133,6 +133,18 @@ module ActionDispatch
polymorphic_url(options)
end
end
+
+ protected
+ def _with_routes(routes)
+ old_routes, @_routes = @_routes, routes
+ yield
+ ensure
+ @_routes = old_routes
+ end
+
+ def _routes_context
+ self
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index b3e67f6e36..c587a36930 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -10,7 +10,7 @@ module ActionDispatch
end
def initialize(env = {})
- env = Rails.application.env_defaults.merge(env) if defined?(Rails.application)
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application)
super(DEFAULT_ENV.merge(env))
self.host = 'test.host'