diff options
Diffstat (limited to 'actionpack/lib')
54 files changed, 784 insertions, 364 deletions
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index f169ab7c3a..95992c2698 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -13,7 +13,7 @@ module AbstractController # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. - def process_action(method_name) + def process_action(method_name, *args) run_callbacks(:process_action, method_name) do super end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 606f7eedec..4ee54474cc 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -265,11 +265,11 @@ module AbstractController raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" when nil if name - _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/ + _prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def _layout - if template_exists?("#{_implied_layout_name}", #{_prefix.inspect}) + if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect}) "#{_implied_layout_name}" else super diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 91b75273fa..691310d5d2 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -13,14 +13,15 @@ module AbstractController # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, # it will trigger the lookup_context and consequently expire the cache. class I18nProxy < ::I18n::Config #:nodoc: - attr_reader :i18n_config, :lookup_context + attr_reader :original_config, :lookup_context - def initialize(i18n_config, lookup_context) - @i18n_config, @lookup_context = i18n_config, lookup_context + def initialize(original_config, lookup_context) + original_config = original_config.original_config if original_config.respond_to?(:original_config) + @original_config, @lookup_context = original_config, lookup_context end def locale - @i18n_config.locale + @original_config.locale end def locale=(value) @@ -60,6 +61,20 @@ module AbstractController end end end + + def parent_prefixes + @parent_prefixes ||= begin + parent_controller = superclass + prefixes = [] + + until parent_controller.abstract? + prefixes << parent_controller.controller_path + parent_controller = parent_controller.superclass + end + + prefixes + end + end end attr_writer :view_context_class @@ -98,7 +113,7 @@ module AbstractController def render_to_string(*args, &block) options = _normalize_args(*args, &block) _normalize_options(options) - render_to_body(options) + render_to_body(options).tap { self.response_body = nil } end # Raw rendering of a template to a Rack-compatible body. @@ -114,9 +129,12 @@ module AbstractController view_context.render(options) end - # The prefix used in render "foo" shortcuts. - def _prefix - controller_path + # The prefixes used in render "foo" shortcuts. + def _prefixes + @_prefixes ||= begin + parent_prefixes = self.class.parent_prefixes + parent_prefixes.dup.unshift(controller_path) + end end private @@ -156,7 +174,7 @@ module AbstractController end if (options.keys & [:partial, :file, :template, :once]).empty? - options[:prefix] ||= _prefix + options[:prefixes] ||= _prefixes end options[:template] ||= (options[:action] || action_name).to_s diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 48308cbb60..81c0698fb8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -24,7 +24,7 @@ module ActionController # # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action # after executing code in the action. For example, the +index+ action of the PostsController would render the - # template <tt>app/views/posts/index.erb</tt> by default after populating the <tt>@posts</tt> instance variable. + # template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable. # # Unlike index, the create action will not render a template. After performing its main purpose (creating a # new post), it initiates a redirect instead. This redirect works by returning an external diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 37c155b9cd..0be04b70a1 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -1,52 +1,72 @@ module ActionController #:nodoc: module Caching - # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when - # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple - # parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like: + # Fragment caching is used for caching various blocks within + # views without caching the entire action as a whole. This is + # useful when certain elements of an action change frequently or + # depend on complicated state while other parts rarely change or + # can be shared amongst multiple parties. The caching is done using + # the <tt>cache</tt> helper available in the Action View. A + # template with fragment caching might look like: # # <b>Hello <%= @name %></b> + # # <% cache do %> # All the topics in the system: # <%= render :partial => "topic", :collection => Topic.find(:all) %> # <% end %> # - # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would - # be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>. + # This cache will bind the name of the action that called it, so if + # this code was part of the view for the topics/list action, you + # would be able to invalidate it using: + # + # expire_fragment(:controller => "topics", :action => "list") # - # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using - # <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like: + # This default behavior is limited if you need to cache multiple + # fragments per action or if the action itself is cached using + # <tt>caches_action</tt>. To remedy this, there is an option to + # qualify the name of the cached fragment by using the + # <tt>:action_suffix</tt> option: # # <% cache(:action => "list", :action_suffix => "all_topics") do %> # - # That would result in a name such as <tt>/topics/list/all_topics</tt>, avoiding conflicts with the action cache and with any fragments that use a - # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique - # cache names that we can refer to when we need to expire the cache. + # That would result in a name such as + # <tt>/topics/list/all_topics</tt>, avoiding conflicts with the + # action cache and with any fragments that use a different suffix. + # Note that the URL doesn't have to really exist or be callable + # - the url_for system is just used to generate unique cache names + # that we can refer to when we need to expire the cache. # # The expiration call for this example is: # - # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") + # expire_fragment(:controller => "topics", + # :action => "list", + # :action_suffix => "all_topics") module Fragments - # Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading, - # writing, or expiring a cached fragment. If the key is a hash, the generated key is the return - # value of url_for on that hash (without the protocol). All keys are prefixed with <tt>views/</tt> and uses + # Given a key (as described in <tt>expire_fragment</tt>), returns + # a key suitable for use in reading, writing, or expiring a + # cached fragment. If the key is a hash, the generated key is the + # return value of url_for on that hash (without the protocol). + # All keys are prefixed with <tt>views/</tt> and uses # ActiveSupport::Cache.expand_cache_key for the expansion. def fragment_cache_key(key) ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) end - # Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) + # Writes <tt>content</tt> to the location signified by + # <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats). def write_fragment(key, content, options = nil) return content unless cache_configured? key = fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do - content = content.html_safe.to_str if content.respond_to?(:html_safe) + content = content.to_str cache_store.write(key, content, options) end content end - # Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) + # Reads a cached fragment from the location signified by <tt>key</tt> + # (see <tt>expire_fragment</tt> for acceptable formats). def read_fragment(key, options = nil) return unless cache_configured? @@ -57,7 +77,8 @@ module ActionController #:nodoc: end end - # Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats) + # Check if a cached fragment from the location signified by + # <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats) def fragment_exist?(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) @@ -70,6 +91,7 @@ module ActionController #:nodoc: # Removes fragments from the cache. # # +key+ can take one of three forms: + # # * String - This would normally take the form of a path, like # <tt>pages/45/notes</tt>. # * Hash - Treated as an implicit call to +url_for+, like diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 3e57d2c236..8c583c7ce0 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -106,7 +106,7 @@ module ActionController #:nodoc: end def page_cache_path(path, extension = nil) - page_cache_directory + page_cache_file(path, extension) + page_cache_directory.to_s + page_cache_file(path, extension) end def instrument_page_cache(name, path) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 3b19310a69..3fae697cc3 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -16,7 +16,11 @@ module ActionController payload = event.payload additions = ActionController::Base.log_process_action(payload) - message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration + status = payload[:status] + if status.nil? && payload[:exception].present? + status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil + end + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? info(message) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 329798e84f..b2c8053584 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -43,12 +43,61 @@ module ActionController end end - # Provides a way to get a valid Rack application from a controller. + # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a + # valid Rack interface without the additional niceties provided by + # <tt>ActionController::Base</tt>. + # + # A sample metal controller might look like this: + # + # class HelloController < ActionController::Metal + # def index + # self.response_body = "Hello World!" + # end + # end + # + # And then to route requests to your metal controller, you would add + # something like this to <tt>config/routes.rb</tt>: + # + # match 'hello', :to => HelloController.action(:index) + # + # The +action+ method returns a valid Rack application for the \Rails + # router to dispatch to. + # + # == Rendering Helpers + # + # <tt>ActionController::Metal</tt> by default provides no utilities for rendering + # views, partials, or other responses aside from explicitly calling of + # <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To + # add the render helpers you're used to having in a normal controller, you + # can do the following: + # + # class HelloController < ActionController::Metal + # include ActionController::Rendering + # append_view_path "#{Rails.root}/app/views" + # + # def index + # render "hello/index" + # end + # end + # + # == Redirection Helpers + # + # To add redirection helpers to your metal controller, do the following: + # + # class HelloController < ActionController::Metal + # include ActionController::Redirecting + # include Rails.application.routes.url_helpers + # + # def index + # redirect_to root_url + # end + # end + # + # == Other Helpers + # + # You can refer to the modules included in <tt>ActionController::Base</tt> to see + # other features you can bring into your metal controller. # - # In AbstractController, dispatching is triggered directly by calling #process on a new controller. - # <tt>ActionController::Metal</tt> provides an <tt>action</tt> method that returns a valid Rack application for a - # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the \Rails router, - # can dispatch directly to actions returned by controllers in your application. class Metal < AbstractController::Base abstract! @@ -133,7 +182,7 @@ module ActionController end def response_body=(val) - body = val.respond_to?(:each) ? val : [val] + body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val]) super body end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 282dcf66b3..cfa7004048 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -12,10 +12,10 @@ module ActionController def method_for_action(action_name) super || begin - if template_exists?(action_name.to_s, _prefix) + if template_exists?(action_name.to_s, _prefixes) "default_render" end end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 9ba37134b8..a2e06fe0a6 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -63,13 +63,13 @@ module ActionController #:nodoc: # might look something like this: # # def index - # @people = Person.find(:all) + # @people = Person.all # end # # Here's the same action, with web-service support baked in: # # def index - # @people = Person.find(:all) + # @people = Person.all # # respond_to do |format| # format.html @@ -155,7 +155,7 @@ module ActionController #:nodoc: # Respond to also allows you to specify a common block for different formats by using any: # # def index - # @people = Person.find(:all) + # @people = Person.all # # respond_to do |format| # format.html @@ -178,7 +178,7 @@ module ActionController #:nodoc: # respond_to :html, :xml, :json # # def index - # @people = Person.find(:all) + # @people = Person.all # respond_with(@person) # end # end @@ -208,8 +208,8 @@ module ActionController #:nodoc: # It also accepts a block to be given. It's used to overwrite a default # response: # - # def destroy - # @user = User.find(params[:id]) + # def create + # @user = User.new(params[:user]) # flash[:notice] = "User was successfully created." if @user.save # # respond_with(@user) do |format| diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 14cc547dd0..32d52c84c4 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -6,7 +6,7 @@ module ActionController # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: - self.formats = request.formats.map { |x| x.to_sym } + self.formats = request.formats.map { |x| x.ref } super end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 148efbb081..b89e03bfb6 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -71,25 +71,24 @@ module ActionController #:nodoc: end protected - - def protect_from_forgery(options = {}) - self.request_forgery_protection_token ||= :authenticity_token - before_filter :verify_authenticity_token, options - end - # The actual before_filter that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token - verified_request? || raise(ActionController::InvalidAuthenticityToken) + verified_request? || handle_unverified_request + end + + def handle_unverified_request + reset_session end # Returns true or false if a request is verified. Checks: # - # * is the format restricted? By default, only HTML requests are checked. # * is it a GET request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? + # * Does the X-CSRF-Token header match the form_authenticity_token def verified_request? - !protect_against_forgery? || request.forgery_whitelisted? || - form_authenticity_token == params[request_forgery_protection_token] + !protect_against_forgery? || request.get? || + form_authenticity_token == params[request_forgery_protection_token] || + form_authenticity_token == request.headers['X-CSRF-Token'] end # Sets the token value for the current session. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 38d32211cc..4b45413cf8 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -77,8 +77,6 @@ module ActionController #:nodoc: # # respond_with(@project, :manager, @task) # - # Check <code>polymorphic_url</code> documentation for more examples. - # class Responder attr_reader :controller, :request, :format, :resource, :resources, :options @@ -115,7 +113,7 @@ module ActionController #:nodoc: # Main entry point for responder responsible to dispatch to the proper format. # def respond - method = :"to_#{format}" + method = "to_#{format}" respond_to?(method) ? send(method) : to_format end @@ -171,7 +169,7 @@ module ActionController #:nodoc: # Checks whether the resource responds to the current format or not. # def resourceful? - resource.respond_to?(:"to_#{format}") + resource.respond_to?("to_#{format}") end # Returns the resource location by retrieving it from the options or diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 3e5d23b5c1..09dd08898c 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -170,7 +170,7 @@ module HTML def contains_bad_protocols?(attr_name, value) uri_attributes.include?(attr_name) && - (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first)) + (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase)) end end end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 1d2f7e4f19..4f4cb96a74 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -43,7 +43,7 @@ module ActionDispatch alias :etag? :etag def initialize(*) - status, header, body = super + super @cache_control = {} @etag = self["ETag"] diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 5b87a80c1b..7c9ebe7c7b 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -216,7 +216,11 @@ module Mime end def to_sym - @symbol || @string.to_sym + @symbol + end + + def ref + to_sym || to_s end def ===(list) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 08f30e068d..f07ac44f7a 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -2,6 +2,7 @@ require 'tempfile' require 'stringio' require 'strscan' +require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'active_support/inflector' @@ -133,8 +134,9 @@ module ActionDispatch end def forgery_whitelisted? - get? || xhr? || content_mime_type.nil? || !content_mime_type.verify_request? + get? end + deprecate :forgery_whitelisted? => "it is just an alias for 'get?' now, update your code" def media_type content_mime_type.to_s diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 796cd8c09b..535ff42b90 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -28,8 +28,11 @@ module ActionDispatch rewritten_url = "" unless options[:only_path] - rewritten_url << (options[:protocol] || "http") - rewritten_url << "://" unless rewritten_url.match("://") + unless options[:protocol] == false + rewritten_url << (options[:protocol] || "http") + rewritten_url << ":" unless rewritten_url.match(%r{:|//}) + end + rewritten_url << "//" unless rewritten_url.match("//") rewritten_url << rewrite_authentication(options) rewritten_url << host_or_subdomain_and_domain(options) rewritten_url << ":#{options.delete(:port)}" if options[:port] diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 5776a7bb27..1bb2ad7f67 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/delegation' + module ActionDispatch # Provide callbacks to be executed before and after the request dispatch. class Callbacks @@ -5,10 +7,8 @@ module ActionDispatch define_callbacks :call, :rescuable => true - def self.to_prepare(*args, &block) - ActiveSupport::Deprecation.warn "ActionDispatch::Callbacks.to_prepare is deprecated. " << - "Please use ActionDispatch::Reloader.to_prepare instead." - ActionDispatch::Reloader.to_prepare(*args, &block) + class << self + delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" end def self.before(*args, &block) @@ -25,7 +25,7 @@ module ActionDispatch end def call(env) - _run_call_callbacks do + run_callbacks :call do @app.call(env) end end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index f369d2d3c2..7ac608f0a8 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -90,17 +90,14 @@ module ActionDispatch # **.**, ***.** style TLDs like co.uk or com.au # # www.example.co.uk gives: - # $1 => example - # $2 => co.uk + # $& => example.co.uk # # example.com gives: - # $1 => example - # $2 => com + # $& => example.com # # lots.of.subdomains.example.local gives: - # $1 => example - # $2 => local - DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...)$/ + # $& => example.local + DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ def self.build(request) secret = request.env[TOKEN_KEY] @@ -131,11 +128,17 @@ module ActionDispatch options[:path] ||= "/" if options[:domain] == :all + # if there is a provided tld length then we use it otherwise default domain regexp + domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP + # if host is not ip and matches domain regexp # (ip confirms to domain regexp so we explicitly check for ip) - options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ DOMAIN_REGEXP) - ".#{$1}.#{$2}" + options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp) + ".#{$&}" end + elsif options[:domain].is_a? Array + # if host matches one of the supplied domains without a dot in front of it + options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] } end end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 579b5d8a02..29289a76b4 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,9 +1,12 @@ module ActionDispatch - # ActionDispatch::Reloader provides to_prepare and to_cleanup callbacks. - # These are analogs of ActionDispatch::Callback's before and after - # callbacks, with the difference that to_cleanup is not called until the + # ActionDispatch::Reloader provides prepare and cleanup callbacks, + # intended to assist with code reloading during development. + # + # Prepare callbacks are run before each request, and cleanup callbacks + # after each request. In this respect they are analogs of ActionDispatch::Callback's + # before and after callbacks. However, cleanup callbacks are not called until the # request is fully complete -- that is, after #close has been called on - # the request body. This is important for streaming responses such as the + # the response body. This is important for streaming responses such as the # following: # # self.response_body = lambda { |response, output| @@ -15,7 +18,10 @@ module ActionDispatch # classes before they are unloaded. # # By default, ActionDispatch::Reloader is included in the middleware stack - # only in the development environment. + # only in the development environment; specifically, when config.cache_classes + # is false. Callbacks may be registered even when it is not included in the + # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+ + # or +ActionDispatch::Reloader.cleanup!+ are called manually. # class Reloader include ActiveSupport::Callbacks @@ -23,8 +29,8 @@ module ActionDispatch define_callbacks :prepare, :scope => :name define_callbacks :cleanup, :scope => :name - # Add a preparation callback. Preparation callbacks are run before each - # request. + # Add a prepare callback. Prepare callbacks are run before each request, prior + # to ActionDispatch::Callback's before callbacks. def self.to_prepare(*args, &block) set_callback(:prepare, *args, &block) end @@ -35,12 +41,14 @@ module ActionDispatch set_callback(:cleanup, *args, &block) end + # Execute all prepare callbacks. def self.prepare! - new(nil).send(:_run_prepare_callbacks) + new(nil).run_callbacks :prepare end + # Execute all cleanup callbacks. def self.cleanup! - new(nil).send(:_run_cleanup_callbacks) + new(nil).run_callbacks :cleanup end def initialize(app) @@ -56,10 +64,13 @@ module ActionDispatch end def call(env) - _run_prepare_callbacks + run_callbacks :prepare response = @app.call(env) response[2].extend(CleanupOnClose) response + rescue Exception + run_callbacks :cleanup + raise end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 71e736ce9f..dbe3206808 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -43,20 +43,20 @@ module ActionDispatch end def call(env) - status, headers, body = @app.call(env) - - # Only this middleware cares about RoutingError. So, let's just raise - # it here. - # TODO: refactor this middleware to handle the X-Cascade scenario without - # having to raise an exception. - if headers['X-Cascade'] == 'pass' - raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" + begin + status, headers, body = @app.call(env) + exception = nil + + # Only this middleware cares about RoutingError. So, let's just raise + # it here. + if headers['X-Cascade'] == 'pass' + raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" + end + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false end - [status, headers, body] - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) + exception ? render_exception(env, exception) : [status, headers, body] end private diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index bd6ffbab5d..50d8ca9484 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -6,5 +6,5 @@ </h1> <pre><%=h @exception.message %></pre> -<%= render :file => "rescues/_trace.erb" %> -<%= render :file => "rescues/_request_and_response.erb" %> +<%= render :template => "rescues/_trace" %> +<%= render :template => "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb index 02fa18211d..c658559be9 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -13,9 +13,5 @@ <p><%=h @exception.sub_template_message %></p> -<% @real_exception = @exception - @exception = @exception.original_exception || @exception %> -<%= render :file => "rescues/_trace.erb" %> -<% @exception = @real_exception %> - -<%= render :file => "rescues/_request_and_response.erb" %> +<%= render :template => "rescues/_trace" %> +<%= render :template => "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 8810227a59..43fd93adf6 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -56,6 +56,18 @@ module ActionDispatch # resources :posts, :comments # end # + # Alternately, you can add prefixes to your path without using a separate + # directory by using +scope+. +scope+ takes additional options which + # apply to all enclosed routes. + # + # scope :path => "/cpanel", :as => 'admin' do + # resources :posts, :comments + # end + # + # For more, see <tt>Routing::Mapper::Resources#resources</tt>, + # <tt>Routing::Mapper::Scoping#namespace</tt>, and + # <tt>Routing::Mapper::Scoping#scope</tt>. + # # == Named routes # # Routes can be named by passing an <tt>:as</tt> option, diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f65a294eca..589df218a8 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -22,18 +22,22 @@ module ActionDispatch @app, @constraints, @request = app, constraints, request end - def call(env) + def matches?(env) req = @request.new(env) @constraints.each { |constraint| if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return [ 404, {'X-Cascade' => 'pass'}, [] ] + return false elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)) - return [ 404, {'X-Cascade' => 'pass'}, [] ] + return false end } - @app.call(env) + return true + end + + def call(env) + matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ] end private @@ -247,7 +251,7 @@ module ActionDispatch # # root :to => 'pages#main' # - # For options, see the +match+ method's documentation, as +root+ uses it internally. + # For options, see +match+, as +root+ uses it internally. # # You should put the root route at the top of <tt>config/routes.rb</tt>, # because this means it will be matched first. As this is the most popular route @@ -256,15 +260,114 @@ module ActionDispatch match '/', options.reverse_merge(:as => :root) end - # When you set up a regular route, you supply a series of symbols that - # Rails maps to parts of an incoming HTTP request. + # Matches a url pattern to one or more routes. Any symbols in a pattern + # are interpreted as url query parameters and thus available as +params+ + # in an action: + # + # # sets :controller, :action and :id in params + # match ':controller/:action/:id' + # + # Two of these symbols are special, +:controller+ maps to the controller + # and +:action+ to the controller's action. A pattern can also map + # wildcard segments (globs) to params: + # + # match 'songs/*category/:title' => 'songs#show' + # + # # 'songs/rock/classic/stairway-to-heaven' sets + # # params[:category] = 'rock/classic' + # # params[:title] = 'stairway-to-heaven' + # + # When a pattern points to an internal route, the route's +:action+ and + # +:controller+ should be set in options or hash shorthand. Examples: + # + # match 'photos/:id' => 'photos#show' + # match 'photos/:id', :to => 'photos#show' + # match 'photos/:id', :controller => 'photos', :action => 'show' + # + # A pattern can also point to a +Rack+ endpoint i.e. anything that + # responds to +call+: + # + # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" } + # match 'photos/:id' => PhotoRackApp + # # Yes, controller actions are just rack endpoints + # match 'photos/:id' => PhotosController.action(:show) + # + # === Options + # + # Any options not seen here are passed on as params with the url. + # + # [:controller] + # The route's controller. + # + # [:action] + # The route's action. + # + # [:path] + # The path prefix for the routes. + # + # [:module] + # The namespace for :controller. + # + # match 'path' => 'c#a', :module => 'sekret', :controller => 'posts' + # #=> Sekret::PostsController + # + # See <tt>Scoping#namespace</tt> for its scope equivalent. + # + # [:as] + # The name used to generate routing helpers. + # + # [:via] + # Allowed HTTP verb(s) for route. + # + # match 'path' => 'c#a', :via => :get + # match 'path' => 'c#a', :via => [:get, :post] + # + # [:to] + # Points to a +Rack+ endpoint. Can be an object that responds to + # +call+ or a string representing a controller's action. + # + # match 'path', :to => 'controller#action' + # match 'path', :to => lambda { [200, {}, "Success!"] } + # match 'path', :to => RackApp + # + # [:on] + # Shorthand for wrapping routes in a specific RESTful context. Valid + # values are :member, :collection, and :new. Only use within + # <tt>resource(s)</tt> block. For example: + # + # resource :bar do + # match 'foo' => 'c#a', :on => :member, :via => [:get, :post] + # end + # + # Is equivalent to: # - # match ':controller/:action/:id/:user_id' + # resource :bar do + # member do + # match 'foo' => 'c#a', :via => [:get, :post] + # end + # end + # + # [:constraints] + # Constrains parameters with a hash of regular expressions or an + # object that responds to #matches? + # + # match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ } + # + # class Blacklist + # def matches?(request) request.remote_ip == '1.2.3.4' end + # end + # match 'path' => 'c#a', :constraints => Blacklist.new # - # Two of these symbols are special: :controller maps to the name of a - # controller in your application, and :action maps to the name of an - # action within that controller. Anything other than :controller or - # :action will be available to the action as part of params. + # See <tt>Scoping#constraints</tt> for more examples with its scope + # equivalent. + # + # [:defaults] + # Sets defaults for parameters + # + # # Sets params[:format] to 'jpg' by default + # match 'path' => 'c#a', :defaults => { :format => 'jpg' } + # + # See <tt>Scoping#defaults</tt> for its scope equivalent. def match(path, options=nil) mapping = Mapping.new(@set, @scope, path, options || {}).to_route @set.add_route(*mapping) @@ -279,6 +382,8 @@ module ActionDispatch # # mount(SomeRackApp => "some_route") # + # For options, see +match+, as +mount+ uses it internally. + # # All mounted applications come with routing helpers to access them. # These are named after the class specified, so for the above example # the helper is either +some_rack_app_path+ or +some_rack_app_url+. @@ -349,7 +454,7 @@ module ActionDispatch module HttpHelpers # Define a route that only recognizes HTTP GET. - # For supported arguments, see +match+. + # For supported arguments, see <tt>Base#match</tt>. # # Example: # @@ -359,7 +464,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP POST. - # For supported arguments, see +match+. + # For supported arguments, see <tt>Base#match</tt>. # # Example: # @@ -369,7 +474,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP PUT. - # For supported arguments, see +match+. + # For supported arguments, see <tt>Base#match</tt>. # # Example: # @@ -379,7 +484,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP PUT. - # For supported arguments, see +match+. + # For supported arguments, see <tt>Base#match</tt>. # # Example: # @@ -458,7 +563,7 @@ module ActionDispatch super end - # Used to scope a set of routes to particular constraints. + # Scopes a set of routes to the given default options. # # Take the following route definition as an example: # @@ -470,51 +575,26 @@ module ActionDispatch # The difference here being that the routes generated are like /rails/projects/2, # rather than /accounts/rails/projects/2. # - # === Supported options - # [:module] - # If you want to route /posts (without the prefix /admin) to - # Admin::PostsController, you could use - # - # scope :module => "admin" do - # resources :posts - # end - # - # [:path] - # If you want to prefix the route, you could use - # - # scope :path => "/admin" do - # resources :posts - # end + # === Options # - # This will prefix all of the +posts+ resource's requests with '/admin' + # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>. # - # [:as] - # Prefixes the routing helpers in this scope with the specified label. + # === Examples # - # scope :as => "sekret" do - # resources :posts - # end - # - # Helpers such as +posts_path+ will now be +sekret_posts_path+ - # - # [:shallow_path] - # - # Prefixes nested shallow routes with the specified path. - # - # scope :shallow_path => "sekret" do - # resources :posts do - # resources :comments, :shallow => true - # end + # # route /posts (without the prefix /admin) to Admin::PostsController + # scope :module => "admin" do + # resources :posts + # end # - # The +comments+ resource here will have the following routes generated for it: + # # prefix the posts resource's requests with '/admin' + # scope :path => "/admin" do + # resources :posts + # end # - # post_comments GET /sekret/posts/:post_id/comments(.:format) - # post_comments POST /sekret/posts/:post_id/comments(.:format) - # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format) - # edit_comment GET /sekret/comments/:id/edit(.:format) - # comment GET /sekret/comments/:id(.:format) - # comment PUT /sekret/comments/:id(.:format) - # comment DELETE /sekret/comments/:id(.:format) + # # prefix the routing helper name: sekret_posts_path instead of posts_path + # scope :as => "sekret" do + # resources :posts + # end def scope(*args) options = args.extract_options! options = options.dup @@ -577,43 +657,31 @@ module ActionDispatch # admin_post GET /admin/posts/:id(.:format) {:action=>"show", :controller=>"admin/posts"} # admin_post PUT /admin/posts/:id(.:format) {:action=>"update", :controller=>"admin/posts"} # admin_post DELETE /admin/posts/:id(.:format) {:action=>"destroy", :controller=>"admin/posts"} - # === Supported options # - # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ options all default to the name of the namespace. + # === Options # - # [:path] - # The path prefix for the routes. - # - # namespace :admin, :path => "sekret" do - # resources :posts - # end + # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ + # options all default to the name of the namespace. # - # All routes for the above +resources+ will be accessible through +/sekret/posts+, rather than +/admin/posts+ - # - # [:module] - # The namespace for the controllers. - # - # namespace :admin, :module => "sekret" do - # resources :posts - # end + # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see + # <tt>Resources#resources</tt>. # - # The +PostsController+ here should go in the +Sekret+ namespace and so it should be defined like this: + # === Examples # - # class Sekret::PostsController < ApplicationController - # # code go here - # end - # - # [:as] - # Changes the name used in routing helpers for this namespace. - # - # namespace :admin, :as => "sekret" do - # resources :posts - # end + # # accessible through /sekret/posts rather than /admin/posts + # namespace :admin, :path => "sekret" do + # resources :posts + # end # - # Routing helpers such as +admin_posts_path+ will now be +sekret_posts_path+. + # # maps to Sekret::PostsController rather than Admin::PostsController + # namespace :admin, :module => "sekret" do + # resources :posts + # end # - # [:shallow_path] - # See the +scope+ method. + # # generates sekret_posts_path rather than admin_posts_path + # namespace :admin, :as => "sekret" do + # resources :posts + # end def namespace(path, options = {}) path = path.to_s options = { :path => path, :as => path, :module => path, @@ -680,9 +748,9 @@ module ActionDispatch end # Allows you to set default parameters for a route, such as this: - # defaults :id => 'home' do - # match 'scoped_pages/(:id)', :to => 'pages#show' - # end + # defaults :id => 'home' do + # match 'scoped_pages/(:id)', :to => 'pages#show' + # end # Using this, the +:id+ parameter here will default to 'home'. def defaults(defaults = {}) scope(:defaults => defaults) { yield } @@ -779,6 +847,14 @@ module ActionDispatch # resources :posts, :comments # end # + # By default the :id parameter doesn't accept dots. If you need to + # use dots as part of the :id parameter add a constraint which + # overrides this restriction, e.g: + # + # resources :articles, :id => /[^\/]+/ + # + # This allows any character other than a slash as part of your :id. + # module Resources # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. @@ -827,7 +903,8 @@ module ActionDispatch alias :member_name :singular - # Checks for uncountable plurals, and appends "_index" if they're. + # Checks for uncountable plurals, and appends "_index" if the plural + # and singular form are the same. def collection_name singular == plural ? "#{plural}_index" : plural end @@ -906,6 +983,9 @@ module ActionDispatch # GET /geocoder/edit # PUT /geocoder # DELETE /geocoder + # + # === Options + # Takes same options as +resources+. def resource(*resources, &block) options = resources.extract_options! @@ -967,7 +1047,9 @@ module ActionDispatch # PUT /photos/:id/comments/:id # DELETE /photos/:id/comments/:id # - # === Supported options + # === Options + # Takes same options as <tt>Base#match</tt> as well as: + # # [:path_names] # Allows you to change the paths of the seven default actions. # Paths not specified are not changed. @@ -976,20 +1058,59 @@ module ActionDispatch # # The above example will now change /posts/new to /posts/brand_new # - # [:module] - # Set the module where the controller can be found. Defaults to nothing. + # [:only] + # Only generate routes for the given actions. # - # resources :posts, :module => "admin" + # resources :cows, :only => :show + # resources :cows, :only => [:show, :index] # - # All requests to the posts resources will now go to +Admin::PostsController+. + # [:except] + # Generate all routes except for the given actions. # - # [:path] + # resources :cows, :except => :show + # resources :cows, :except => [:show, :index] + # + # [:shallow] + # Generates shallow routes for nested resource(s). When placed on a parent resource, + # generates shallow routes for all nested resources. + # + # resources :posts, :shallow => true do + # resources :comments + # end + # + # Is the same as: + # + # resources :posts do + # resources :comments + # end + # resources :comments + # + # [:shallow_path] + # Prefixes nested shallow routes with the specified path. + # + # scope :shallow_path => "sekret" do + # resources :posts do + # resources :comments, :shallow => true + # end + # end + # + # The +comments+ resource here will have the following routes generated for it: + # + # post_comments GET /sekret/posts/:post_id/comments(.:format) + # post_comments POST /sekret/posts/:post_id/comments(.:format) + # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format) + # edit_comment GET /sekret/comments/:id/edit(.:format) + # comment GET /sekret/comments/:id(.:format) + # comment PUT /sekret/comments/:id(.:format) + # comment DELETE /sekret/comments/:id(.:format) # - # Set a path prefix for this resource. + # === Examples # - # resources :posts, :path => "admin" + # # routes call Admin::PostsController + # resources :posts, :module => "admin" # - # All actions for this resource will now be at +/admin/posts+. + # # resource actions are at /admin/posts. + # resources :posts, :path => "admin" def resources(*resources, &block) options = resources.extract_options! @@ -1111,7 +1232,7 @@ module ActionDispatch end def shallow - scope(:shallow => true) do + scope(:shallow => true, :shallow_path => @scope[:path]) do yield end end @@ -1321,7 +1442,7 @@ module ActionDispatch name = case @scope[:scope_level] when :nested - [member_name, prefix] + [name_prefix, prefix] when :collection [prefix, name_prefix, collection_name] when :new diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 03bfe178e5..4b4e9da173 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -450,7 +450,7 @@ module ActionDispatch end def raise_routing_error - raise ActionController::RoutingError.new("No route matches #{options.inspect}") + raise ActionController::RoutingError, "No route matches #{options.inspect}" end def different_controller? @@ -540,7 +540,9 @@ module ActionDispatch end dispatcher = route.app - dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints) + while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do + dispatcher = dispatcher.app + end if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false) dispatcher.prepare_params!(params) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 1558c3ae05..77a15f3e97 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -20,7 +20,7 @@ module ActionDispatch # # You can also pass an explicit status number like <tt>assert_response(501)</tt> # or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>. - # See ActionDispatch::StatusCodes for a full list. + # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list. # # ==== Examples # diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 1390b74a95..11e8c63fa0 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -37,9 +37,6 @@ module ActionDispatch # # # Test a custom route # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1') - # - # # Check a Simply RESTful generated route - # assert_recognizes list_items_url, 'items/list' def assert_recognizes(expected_options, path, extras={}, message=nil) request = recognized_request_for(path) @@ -124,7 +121,8 @@ module ActionDispatch options[:controller] = "/#{controller}" end - assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message) + generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) } + assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end # A helper to make it easier to test different route configurations. diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 92ff3380b0..ab8c6259c5 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -18,7 +18,7 @@ module ActionView #:nodoc: # following loop for names: # # <b>Names of all the people</b> - # <% for person in @people %> + # <% @people.each do |person| %> # Name: <%= person.name %><br/> # <% end %> # @@ -232,8 +232,8 @@ module ActionView #:nodoc: @controller_path ||= controller && controller.controller_path end - def controller_prefix - @controller_prefix ||= controller && controller._prefix + def controller_prefixes + @controller_prefixes ||= controller && controller._prefixes end ActiveSupport.run_load_hooks(:action_view, self) diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 41013c800c..d338ce616a 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -18,7 +18,7 @@ module ActionView #:nodoc: autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" autoload :NumberHelper autoload :PrototypeHelper - autoload :RawOutputHelper + autoload :OutputSafetyHelper autoload :RecordTagHelper autoload :SanitizeHelper autoload :ScriptaculousHelper @@ -48,7 +48,7 @@ module ActionView #:nodoc: include JavaScriptHelper include NumberHelper include PrototypeHelper - include RawOutputHelper + include OutputSafetyHelper include RecordTagHelper include SanitizeHelper include ScriptaculousHelper diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index e52797042f..52eb43a1cd 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -11,7 +11,9 @@ module ActionView attr_reader :config, :asset_paths class_attribute :expansions - self.expansions = { } + def self.inherited(base) + base.expansions = { } + end def initialize(config, asset_paths) @config = config @@ -69,9 +71,21 @@ module ActionView if sources.first == :all collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") else - sources.collect do |source| - determine_source(source, expansions) - end.flatten + sources.inject([]) do |list, source| + determined_source = determine_source(source, expansions) + update_source_list(list, determined_source) + end + end + end + + def update_source_list(list, source) + case source + when String + list.delete(source) + list << source + when Array + updated_sources = source - list + list.concat(updated_sources) end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 6581e1d6f2..b9126af944 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -33,13 +33,21 @@ module ActionView all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application' ((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq else - expanded_sources = sources.collect do |source| - determine_source(source, expansions) - end.flatten - expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}")) + expanded_sources = sources.inject([]) do |list, source| + determined_source = determine_source(source, expansions) + update_source_list(list, determined_source) + end + add_application_js(expanded_sources, sources) expanded_sources end end + + def add_application_js(expanded_sources, sources) + if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}")) + expanded_sources.delete('application') + expanded_sources << "application" + end + end end @@ -59,7 +67,10 @@ module ActionView # <script type="text/javascript" src="/javascripts/body.js"></script> # <script type="text/javascript" src="/javascripts/tail.js"></script> def register_javascript_expansion(expansions) - JavascriptIncludeTag.expansions.merge!(expansions) + js_expansions = JavascriptIncludeTag.expansions + expansions.each do |key, values| + js_expansions[key] = (js_expansions[key] || []) | Array(values) if values + end end end @@ -170,4 +181,4 @@ module ActionView end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index d02b28d7f6..f3e041de95 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -44,7 +44,10 @@ module ActionView # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" /> def register_stylesheet_expansion(expansions) - StylesheetIncludeTag.expansions.merge!(expansions) + style_expansions = StylesheetIncludeTag.expansions + expansions.each do |key, values| + style_expansions[key] = (style_expansions[key] || []) | Array(values) if values + end end end @@ -141,4 +144,4 @@ module ActionView end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index f544a9d147..385378ea29 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -2,31 +2,29 @@ module ActionView # = Action View Cache Helper module Helpers module CacheHelper - # This helper to exposes a method for caching of view fragments. - # See ActionController::Caching::Fragments for usage instructions. + # This helper exposes a method for caching fragments of a view + # rather than an entire action or page. This technique is useful + # caching pieces like menus, lists of newstopics, static HTML + # fragments, and so on. This method takes a block that contains + # the content you wish to cache. # - # A method for caching fragments of a view rather than an entire - # action or page. This technique is useful caching pieces like - # menus, lists of news topics, static HTML fragments, and so on. - # This method takes a block that contains the content you wish - # to cache. See ActionController::Caching::Fragments for more - # information. + # See ActionController::Caching::Fragments for usage instructions. # # ==== Examples - # If you wanted to cache a navigation menu, you could do the - # following. + # If you want to cache a navigation menu, you can do following: # # <% cache do %> # <%= render :partial => "menu" %> # <% end %> # - # You can also cache static content... + # You can also cache static content: # # <% cache do %> # <p>Hello users! Welcome to our website!</p> # <% end %> # - # ...and static content mixed with RHTML content. + # Static content with embedded ruby content can be cached as + # well: # # <% cache do %> # Topics: @@ -46,8 +44,8 @@ module ActionView private # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: - if controller.fragment_exist?(name, options) - controller.read_fragment(name, options) + if fragment = controller.read_fragment(name, options) + fragment else # VIEW TODO: Make #capture usable outside of ERB # This dance is needed because Builder can't use capture diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 875ec9b77b..dc8e4bc316 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@ require 'date' require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/with_options' @@ -566,6 +567,27 @@ module ActionView def select_year(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_year end + + # Returns an html time tag for the given date or time. + # + # ==== Examples + # time_tag Date.today # => + # <time datetime="2010-11-04">November 04, 2010</time> + # time_tag Time.now # => + # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time> + # time_tag Date.yesterday, 'Yesterday' # => + # <time datetime="2010-11-03">Yesterday</time> + # time_tag Date.today, :pubdate => true # => + # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> + # + def time_tag(date_or_time, *args) + options = args.extract_options! + format = options.delete(:format) || :long + content = args.first || I18n.l(date_or_time, :format => format) + datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339 + + content_tag(:time, content, options.reverse_merge(:datetime => datetime)) + end end class DateTimeSelector #:nodoc: diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 6f0e2c99ba..befaa3e8d9 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -298,6 +298,23 @@ module ActionView # # If you don't need to attach a form to a model instance, then check out # FormTagHelper#form_tag. + # + # === Form to external resources + # + # When you build forms to external resources sometimes you need to set an authenticity token or just render a form + # without it, for example when you submit data to a payment gateway number and types of fields could be limited. + # + # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter + # + # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f| + # ... + # <% end %> + # + # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: + # + # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| + # ... + # <% end %> def form_for(record, options = {}, &proc) raise ArgumentError, "Missing block" unless block_given? @@ -314,6 +331,8 @@ module ActionView end options[:html][:remote] = options.delete(:remote) + options[:html][:authenticity_token] = options.delete(:authenticity_token) + builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc) fields_for = fields_for(object_name, object, options, &proc) default_options = builder.multipart? ? { :multipart => true } : {} @@ -530,8 +549,11 @@ module ActionView # <% end %> # ... # <% end %> - def fields_for(record, record_object = nil, options = nil, &block) - capture(instantiate_builder(record, record_object, options, &block), &block) + def fields_for(record, record_object = nil, options = {}, &block) + builder = instantiate_builder(record, record_object, options, &block) + output = capture(builder, &block) + output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id? + output end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -858,8 +880,7 @@ module ActionView end end - module InstanceTagMethods #:nodoc: - extend ActiveSupport::Concern + class InstanceTag include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper attr_reader :object, :method_name, :object_name @@ -1025,7 +1046,7 @@ module ActionView self.class.value_before_type_cast(object, @method_name) end - module ClassMethods + class << self def value(object, method_name) object.send method_name if object end @@ -1111,10 +1132,6 @@ module ActionView end end - class InstanceTag - include InstanceTagMethods - end - class FormBuilder # The methods which wrap a form helper call. class_attribute :field_helpers @@ -1248,7 +1265,7 @@ module ActionView def submit(value=nil, options={}) value, options = nil, value if value.is_a?(Hash) value ||= submit_default_value - @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) + @template.submit_tag(value, options) end def emitted_hidden_id? @@ -1309,14 +1326,8 @@ module ActionView def fields_for_nested_model(name, object, options, block) object = convert_to_model(object) - if object.persisted? - @template.fields_for(name, object, options) do |builder| - block.call(builder) - @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? - end - else - @template.fields_for(name, object, options, &block) - end + options[:hidden_field_id] = object.persisted? + @template.fields_for(name, object, options, &block) end def nested_child_index(name) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 6ac8577785..7698602022 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -533,7 +533,7 @@ module ActionView else selected = Array.wrap(selected) options = selected.extract_options!.symbolize_keys - [ options[:selected] || selected , options[:disabled] ] + [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ] end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 9500e85e8b..71f8534cbf 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -25,6 +25,9 @@ module ActionView # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post". # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> # is added to simulate the verb over post. + # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to + # pass custom authenticity token string, or to not add authenticity_token field at all + # (by passing <tt>false</tt>). # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behaviour. By default this behaviour is an ajax submit. @@ -47,6 +50,12 @@ module ActionView # <%= form_tag('/posts', :remote => true) %> # # => <form action="/posts" method="post" data-remote="true"> # + # form_tag('http://far.away.com/form', :authenticity_token => false) + # # form without authenticity token + # + # form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae") + # # form with custom authenticity token + # def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block) html_options = html_options_for_form(url_for_options, options, *parameters_for_url) if block_given? @@ -401,6 +410,59 @@ module ActionView tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) end + # Creates a button element that defines a <tt>submit</tt> button, + # <tt>reset</tt>button or a generic button which can be used in + # JavaScript, for example. You can use the button tag as a regular + # submit tag but it isn't supported in legacy browsers. However, + # the button tag allows richer labels such as images and emphasis, + # so this helper will also accept a block. + # + # ==== Options + # * <tt>:confirm => 'question?'</tt> - If present, the + # unobtrusive JavaScript drivers will provide a prompt with + # the question specified. If the user accepts, the form is + # processed normally, otherwise no action is taken. + # * <tt>:disabled</tt> - If true, the user will not be able to + # use this input. + # * <tt>:disable_with</tt> - Value of this parameter will be + # used as the value for a disabled version of the submit + # button when the form is submitted. This feature is provided + # by the unobtrusive JavaScript driver. + # * Any other key creates standard HTML options for the tag. + # + # ==== Examples + # button_tag + # # => <button name="button" type="submit">Button</button> + # + # button_tag(:type => 'button') do + # content_tag(:strong, 'Ask me!') + # end + # # => <button name="button" type="button"> + # <strong>Ask me!</strong> + # </button> + # + # button_tag "Checkout", :disable_with => "Please wait..." + # # => <button data-disable-with="Please wait..." name="button" + # type="submit">Checkout</button> + # + def button_tag(content_or_options = nil, options = nil, &block) + options = content_or_options if block_given? && content_or_options.is_a?(Hash) + options ||= {} + options.stringify_keys! + + if disable_with = options.delete("disable_with") + options["data-disable-with"] = disable_with + end + + if confirm = options.delete("confirm") + options["data-confirm"] = confirm + end + + options.reverse_merge! 'name' => 'button', 'type' => 'submit' + + content_tag :button, content_or_options || 'Button', options, &block + end + # Displays an image which when clicked will submit the form. # # <tt>source</tt> is passed to AssetTagHelper#path_to_image @@ -534,6 +596,7 @@ module ActionView html_options["action"] = url_for(url_for_options, *parameters_for_url) html_options["accept-charset"] = "UTF-8" html_options["data-remote"] = true if html_options.delete("remote") + html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token") end end @@ -541,6 +604,7 @@ module ActionView snowman_tag = tag(:input, :type => "hidden", :name => "utf8", :value => "✓".html_safe) + authenticity_token = html_options.delete("authenticity_token") method = html_options.delete("method").to_s method_tag = case method @@ -549,10 +613,10 @@ module ActionView '' when /^post$/i, "", nil html_options["method"] = "post" - token_tag + token_tag(authenticity_token) else html_options["method"] = "post" - tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag + tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token) end tags = snowman_tag << method_tag @@ -572,11 +636,12 @@ module ActionView output.safe_concat("</form>") end - def token_tag - unless protect_against_forgery? + def token_tag(token) + if token == false || !protect_against_forgery? '' else - tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) + token = form_authenticity_token if token.nil? + tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token) end end diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb new file mode 100644 index 0000000000..a035dd70ad --- /dev/null +++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb @@ -0,0 +1,38 @@ +require 'active_support/core_ext/string/output_safety' + +module ActionView #:nodoc: + # = Action View Raw Output Helper + module Helpers #:nodoc: + module OutputSafetyHelper + # This method outputs without escaping a string. Since escaping tags is + # now default, this can be used when you don't want Rails to automatically + # escape tags. This is not recommended if the data is coming from the user's + # input. + # + # For example: + # + # <%=raw @user.name %> + def raw(stringish) + stringish.to_s.html_safe + end + + # This method returns a html safe string similar to what <tt>Array#join</tt> + # would return. All items in the array, including the supplied separator, are + # html escaped unless they are html safe, and the returned string is marked + # as html safe. + # + # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />") + # # => "<p>foo</p><br /><p>bar</p>" + # + # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe) + # # => "<p>foo</p><br /><p>bar</p>" + # + def safe_join(array, sep=$,) + sep ||= "".html_safe + sep = ERB::Util.html_escape(sep) + + array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/raw_output_helper.rb b/actionpack/lib/action_view/helpers/raw_output_helper.rb deleted file mode 100644 index 216683a2e0..0000000000 --- a/actionpack/lib/action_view/helpers/raw_output_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView #:nodoc: - # = Action View Raw Output Helper - module Helpers #:nodoc: - module RawOutputHelper - # This method outputs without escaping a string. Since escaping tags is - # now default, this can be used when you don't want Rails to automatically - # escape tags. This is not recommended if the data is coming from the user's - # input. - # - # For example: - # - # <%=raw @user.name %> - def raw(stringish) - stringish.to_s.html_safe - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ee51617a2b..786af5ca58 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -14,7 +14,7 @@ module ActionView BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seemless muted required - autofocus novalidate formnovalidate open).to_set + autofocus novalidate formnovalidate open pubdate).to_set BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) # Returns an empty HTML tag of type +name+ which by default is XHTML diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 3d276000a1..4f7f5c454f 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -459,7 +459,7 @@ module ActionView end AUTO_LINK_RE = %r{ - (?: ([\w+.:-]+:)// | www\. ) + (?: ([0-9A-Za-z+.:-]+:)// | www\. ) [^\s<]+ }x diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index c23315b344..2cd2dca711 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -253,8 +253,9 @@ module ActionView # using the +link_to+ method with the <tt>:method</tt> modifier as described in # the +link_to+ documentation. # - # The generated form element has a class name of <tt>button_to</tt> - # to allow styling of the form itself and its children. You can control + # By default, the generated form element has a class name of <tt>button_to</tt> + # to allow styling of the form itself and its children. This can be changed + # using the <tt>:form_class</tt> modifier within +html_options+. You can control # the form submission and input element behavior using +html_options+. # This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers # described in the +link_to+ documentation. If no <tt>:method</tt> modifier @@ -275,6 +276,8 @@ module ActionView # processed normally, otherwise no action is taken. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behaviour. By default this behaviour is an ajax submit. + # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will + # be placed # # ==== Examples # <%= button_to "New", :action => "new" %> @@ -283,6 +286,12 @@ module ActionView # # </form>" # # + # <%= button_to "New", :action => "new", :form_class => "new-thing" %> + # # => "<form method="post" action="/controller/new" class="new-thing"> + # # <div><input value="New" type="submit" /></div> + # # </form>" + # + # # <%= button_to "Delete Image", { :action => "delete", :id => @image.id }, # :confirm => "Are you sure?", :method => :delete %> # # => "<form method="post" action="/images/delete/1" class="button_to"> @@ -312,6 +321,7 @@ module ActionView end form_method = method.to_s == 'get' ? 'get' : 'post' + form_class = html_options.delete('form_class') || 'button_to' remote = html_options.delete('remote') @@ -327,7 +337,7 @@ module ActionView html_options.merge!("type" => "submit", "value" => name) - ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" + + ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"#{ERB::Util.html_escape(form_class)}\"><div>" + method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe end @@ -487,13 +497,14 @@ module ActionView email_address_obfuscated = email_address.dup email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot") - case encode when "javascript" - string = - "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".unpack('C*').map { |c| - sprintf("%%%x", c) - }.join + string = '' + html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)) + html = escape_javascript(html) + "document.write('#{html}');".each_byte do |c| + string << sprintf("%%%x", c) + end "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe when "hex" email_address_encoded = email_address_obfuscated.unpack('C*').map {|c| diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index d524c68450..06c607931d 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -78,17 +78,17 @@ module ActionView @view_paths = ActionView::Base.process_view_paths(paths) end - def find(name, prefix = nil, partial = false, keys = []) - @view_paths.find(*args_for_lookup(name, prefix, partial, keys)) + def find(name, prefixes = [], partial = false, keys = []) + @view_paths.find(*args_for_lookup(name, prefixes, partial, keys)) end alias :find_template :find - def find_all(name, prefix = nil, partial = false, keys = []) - @view_paths.find_all(*args_for_lookup(name, prefix, partial, keys)) + def find_all(name, prefixes = [], partial = false, keys = []) + @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys)) end - def exists?(name, prefix = nil, partial = false, keys = []) - @view_paths.exists?(*args_for_lookup(name, prefix, partial, keys)) + def exists?(name, prefixes = [], partial = false, keys = []) + @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys)) end alias :template_exists? :exists? @@ -107,18 +107,26 @@ module ActionView protected - def args_for_lookup(name, prefix, partial, keys) #:nodoc: - name, prefix = normalize_name(name, prefix) - [name, prefix, partial || false, @details, details_key, keys] + def args_for_lookup(name, prefixes, partial, keys) #:nodoc: + name, prefixes = normalize_name(name, prefixes) + [name, prefixes, partial || false, @details, details_key, keys] end # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. - def normalize_name(name, prefix) #:nodoc: + def normalize_name(name, prefixes) #:nodoc: name = name.to_s.gsub(handlers_regexp, '') parts = name.split('/') - return parts.pop, [prefix, *parts].compact.join("/") + name = parts.pop + + prefixes = if prefixes.blank? + [parts.join('/')] + else + prefixes.map { |prefix| [prefix, *parts].compact.join('/') } + end + + return name, prefixes end def default_handlers #:nodoc: @@ -156,11 +164,11 @@ module ActionView @frozen_formats = true end - # Overload formats= to reject [:"*/*"] values. + # Overload formats= to reject ["*/*"] values. def formats=(values) if values && values.size == 1 value = values.first - values = nil if value == :"*/*" + values = nil if value == "*/*" values << :html if value == :js end super(values) @@ -178,11 +186,11 @@ module ActionView end # Overload locale= to also set the I18n.locale. If the current I18n.config object responds - # to i18n_config, it means that it's has a copy of the original I18n configuration and it's + # to original_config, it means that it's has a copy of the original I18n configuration and it's # acting as proxy, which we need to skip. def locale=(value) if value - config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config + config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config config.locale = value end @@ -237,4 +245,4 @@ module ActionView include Details include ViewPaths end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index cd3f130dc4..c181689e62 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -12,19 +12,19 @@ module ActionView # # <%= render :partial => "account" %> # - # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable + # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable # +account+ to the template for display. # # In another template for Advertiser#buy, we could have: # # <%= render :partial => "account", :locals => { :account => @buyer } %> # - # <% for ad in @advertisements %> + # <% @advertisements.each do |ad| %> # <%= render :partial => "ad", :locals => { :ad => ad } %> # <% end %> # - # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then - # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. + # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then + # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. # # == The :as and :object options # @@ -40,16 +40,16 @@ module ActionView # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we # wanted it to be +agreement+ instead of +contract+ we'd do: # - # <%= render :partial => "contract", :as => :agreement %> + # <%= render :partial => "contract", :as => 'agreement' %> # # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial; # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. - # + # # Revisiting a previous example we could have written this code: - # + # # <%= render :partial => "account", :object => @buyer %> # - # <% for ad in @advertisements %> + # <% @advertisements.each do |ad| %> # <%= render :partial => "ad", :object => ad %> # <% end %> # @@ -64,12 +64,12 @@ module ActionView # # <%= render :partial => "ad", :collection => @advertisements %> # - # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An + # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An # iteration counter will automatically be made available to the template with a name of the form # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. # # The <tt>:as</tt> option may be used when rendering partials. - # + # # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option. # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial: # @@ -89,7 +89,7 @@ module ActionView # # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> # - # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from. + # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. # # == Rendering objects with the RecordIdentifier # diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb index fa35120a0d..e3de3e1eac 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -11,13 +11,15 @@ module ActionView #:nodoc: end def find(*args) - find_all(*args).first || raise(MissingTemplate.new(self, "#{args[1]}/#{args[0]}", args[3], args[2])) + find_all(*args).first || raise(MissingTemplate.new(self, *args)) end - def find_all(*args) - each do |resolver| - templates = resolver.find_all(*args) - return templates unless templates.empty? + def find_all(path, prefixes = [], *args) + prefixes.each do |prefix| + each do |resolver| + templates = resolver.find_all(path, prefix, *args) + return templates unless templates.empty? + end end [] end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 317479ad4c..94c0a8a8fb 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -108,11 +108,11 @@ module ActionView locals << @variable_counter if @collection find_template(path, locals) end - end + end def find_template(path=@path, locals=@locals.keys) - prefix = @view.controller_prefix unless path.include?(?/) - @lookup_context.find_template(path, prefix, true, locals) + prefixes = path.include?(?/) ? [] : @view.controller_prefixes + @lookup_context.find_template(path, prefixes, true, locals) end def collection_with_template @@ -152,14 +152,14 @@ module ActionView object = object.to_model if object.respond_to?(:to_model) object.class.model_name.partial_path.dup.tap do |partial| - path = @view.controller_prefix + path = @view.controller_prefixes.first partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) end end end def retrieve_variable(path) - variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym + variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym variable_counter = :"#{variable}_counter" if @collection [variable, variable_counter] end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index ece3f621b6..9ae1636131 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -22,14 +22,14 @@ module ActionView def render_once(options) paths, locals = options[:once], options[:locals] || {} layout, keys = options[:layout], locals.keys - prefix = options.fetch(:prefix, @view.controller_prefix) + prefixes = options.fetch(:prefixes, @view.controller_prefixes) raise "render :once expects a String or an Array to be given" unless paths render_with_layout(layout, locals) do contents = [] Array.wrap(paths).each do |path| - template = find_template(path, prefix, false, keys) + template = find_template(path, prefixes, false, keys) contents << render_template(template, nil, locals) if @rendered.add?(template) end contents.join("\n") @@ -43,13 +43,13 @@ module ActionView if options.key?(:text) Template::Text.new(options[:text], formats.try(:first)) elsif options.key?(:file) - with_fallbacks { find_template(options[:file], options[:prefix], false, keys) } + with_fallbacks { find_template(options[:file], options[:prefixes], false, keys) } elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") Template.new(options[:inline], "inline template", handler, :locals => keys) elsif options.key?(:template) options[:template].respond_to?(:render) ? - options[:template] : find_template(options[:template], options[:prefix], false, keys) + options[:template] : find_template(options[:template], options[:prefixes], false, keys) end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 0d8373ef14..96d506fac5 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -123,7 +123,7 @@ module ActionView @locals = details[:locals] || [] @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now - @formats = Array.wrap(format).map(&:to_sym) + @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f } end # Render a template. If the template was not compiled yet, it is done @@ -163,7 +163,7 @@ module ActionView name = pieces.pop partial = !!name.sub!(/^_/, "") lookup.disable_cache do - lookup.find_template(name, pieces.join('/'), partial, @locals) + lookup.find_template(name, [ pieces.join('/') ], partial, @locals) end end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index ff256738a9..e246646963 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -27,7 +27,7 @@ module ActionView class MissingTemplate < ActionViewError #:nodoc: attr_reader :path - def initialize(paths, path, details, partial) + def initialize(paths, path, prefixes, partial, details, *) @path = path display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial @@ -38,7 +38,11 @@ module ActionView 'template' end - super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}") + searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } + + out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" + out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join + super out end end @@ -52,6 +56,7 @@ module ActionView attr_reader :original_exception, :backtrace def initialize(template, assigns, original_exception) + super(original_exception.message) @template, @assigns, @original_exception = template, assigns.dup, original_exception @sub_templates = nil @backtrace = original_exception.backtrace @@ -61,10 +66,6 @@ module ActionView @template.identifier end - def message - ActiveSupport::Deprecation.silence { original_exception.message } - end - def sub_template_message if @sub_templates "Trace of template inclusion: " + diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 0dccc99d14..4d999fb3b2 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -47,7 +47,7 @@ module ActionView path end - # Hnadles templates caching. If a key is given and caching is on + # Handles templates caching. If a key is given and caching is on # always check the cache before hitting the resolver. Otherwise, # it always hits the resolver but check if the resolver is fresher # before returning it. @@ -109,18 +109,27 @@ module ActionView def query(path, exts, formats) query = File.join(@path, path) - exts.each do |ext| - query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}' - end + query << exts.map { |ext| + "{#{ext.compact.map { |e| ".#{e}" }.join(',')},}" + }.join - Dir[query].reject { |p| File.directory?(p) }.map do |p| - handler, format = extract_handler_and_format(p, formats) + query.gsub!(/\{\.html,/, "{.html,.text.html,") + query.gsub!(/\{\.text,/, "{.text,.text.plain,") + + templates = [] + sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] } + Dir[query].each do |p| + next if File.directory?(p) || !sanitizer[p].include?(p) + + handler, format = extract_handler_and_format(p, formats) contents = File.open(p, "rb") {|io| io.read } - Template.new(contents, File.expand_path(p), handler, + templates << Template.new(contents, File.expand_path(p), handler, :virtual_path => path, :format => format, :updated_at => mtime(p)) end + + templates end # Returns the file mtime from the filesystem. diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 4026f7a40e..2ce109ea99 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -156,10 +156,8 @@ module ActionView # The instance of ActionView::Base that is used by +render+. def view @view ||= begin - view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) + view = @controller.view_context view.singleton_class.send :include, _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 view.output_buffer = self.output_buffer @@ -171,6 +169,7 @@ module ActionView INTERNAL_IVARS = %w{ @__name__ + @__io__ @_assertion_wrapped @_assertions @_result diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb index 55583096e0..5c5cab7c7d 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionpack/lib/action_view/testing/resolvers.rb @@ -13,7 +13,11 @@ module ActionView #:nodoc: @hash = hash end - private + def to_s + @hash.keys.join(', ') + end + + private def query(path, exts, formats) query = "" |