diff options
Diffstat (limited to 'actionpack')
83 files changed, 1775 insertions, 1078 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 76dbfe7895..2bb1461ec9 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,10 +1,36 @@ *Rails 3.1.0 (unreleased)* +* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH] + +* auto_link has been removed with no replacement. If you still use auto_link + please install the rails_autolink gem: + http://github.com/tenderlove/rails_autolink + + [tenderlove] + +* Added streaming support, you can enable it with: [José Valim] + + class PostsController < ActionController::Base + stream :only => :index + end + + Please read the docs at `ActionController::Streaming` for more information. + +* Added `ActionDispatch::Request.ignore_accept_header` to ignore accept headers and only consider the format given as parameter [José Valim] + +* Created `ActionView::Renderer` and specified an API for `ActionView::Context`, check those objects for more information [José Valim] + +* Added `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist] + + This can be customized by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb` + * RJS has been extracted out to a gem. [fxn] -* Implicit actions named not_implemented can be rendered [Santiago Pastorino] +* Implicit actions named not_implemented can be rendered. [Santiago Pastorino] + +* Wildcard route will always match the optional format segment by default. [Prem Sichanugrist] -* Wildcard route will always matching the optional format segment by default. For example if you have this route: + For example if you have this route: map '*pages' => 'pages#show' @@ -88,8 +114,6 @@ tested. Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis] -* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim] - * Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim] * :rhtml and :rxml were finally removed as template handlers. [José Valim] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 0d667a76a7..fdd894fe63 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -19,12 +19,13 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('rack-cache', '~> 1.0.0') + s.add_dependency('rack-cache', '~> 1.0.1') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6.0beta1') - s.add_dependency('rack', '~> 1.2.1') - s.add_dependency('rack-test', '~> 0.5.7') + s.add_dependency('rack', '~> 1.3.0.beta') + s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.7.2') - s.add_dependency('tzinfo', '~> 0.3.23') + s.add_dependency('sprockets', '~> 2.0.0.beta.2') + s.add_dependency('tzinfo', '~> 0.3.27') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 0951267fea..f67d0e558e 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -130,27 +130,39 @@ module AbstractController self.class.action_methods end - # Returns true if the name can be considered an action. This can - # be overridden in subclasses to modify the semantics of what - # can be considered an action. + # Returns true if a method for the action is available and + # can be dispatched, false otherwise. # - # For instance, this is overriden by ActionController to add - # the implicit rendering feature. - # - # ==== Parameters - # * <tt>name</tt> - The name of an action to be tested - # - # ==== Returns - # * <tt>TrueClass</tt>, <tt>FalseClass</tt> - def action_method?(name) - self.class.action_methods.include?(name) + # Notice that <tt>action_methods.include?("foo")</tt> may return + # false and <tt>available_action?("foo")</tt> returns true because + # available action consider actions that are also available + # through other means, for example, implicit render ones. + def available_action?(action_name) + method_for_action(action_name).present? end private + # Returns true if the name can be considered an action because + # it has a method defined in the controller. + # + # ==== Parameters + # * <tt>name</tt> - The name of an action to be tested + # + # ==== Returns + # * <tt>TrueClass</tt>, <tt>FalseClass</tt> + # + # :api: private + def action_method?(name) + self.class.action_methods.include?(name) + end + # Call the action. Override this in a subclass to modify the # behavior around processing an action. This, and not #process, # is the intended way to override action dispatching. + # + # Notice that the first argument is the method to be dispatched + # which is *not* necessarily the same as the action name. def process_action(method_name, *args) send_action(method_name, *args) end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index f7b2b7ff53..e8426bc52b 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -13,8 +13,8 @@ module AbstractController # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. - def process_action(method_name, *args) - run_callbacks(:process_action, method_name) do + def process_action(*args) + run_callbacks(:process_action, action_name) do super end end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 0ff1c0491a..dc9778a416 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -4,8 +4,6 @@ module AbstractController module Helpers extend ActiveSupport::Concern - include Rendering - included do class_attribute :_helpers self._helpers = Module.new diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index d1b87b67ee..8f73e244d7 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -292,15 +292,15 @@ module AbstractController end end - attr_writer :action_has_layout + attr_internal_writer :action_has_layout def initialize(*) - @action_has_layout = true + @_action_has_layout = true super end def action_has_layout? - @action_has_layout + @_action_has_layout end private diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 306bd41e2d..ab2c532859 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -32,9 +32,13 @@ module AbstractController module Rendering extend ActiveSupport::Concern - include AbstractController::ViewPaths + included do + config_accessor :protected_instance_variables, :instance_reader => false + self.protected_instance_variables = [] + end + # Overwrite process to setup I18n proxy. def process(*) #:nodoc: old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) @@ -46,46 +50,27 @@ module AbstractController module ClassMethods def view_context_class @view_context_class ||= begin - controller = self - Class.new(ActionView::Base) do - if controller.respond_to?(:_routes) && controller._routes - include controller._routes.url_helpers - include controller._routes.mounted_helpers - end - - if controller.respond_to?(:_helpers) - include controller._helpers - - # TODO: Fix RJS to not require this - self.helpers = controller._helpers - end - end + routes = _routes if respond_to?(:_routes) + helpers = _helpers if respond_to?(:_helpers) + ActionView::Base.prepare(routes, helpers) 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 + attr_internal_writer :view_context_class - prefixes - end - end + # Explicitly define protected_instance_variables so it can be + # inherited and overwritten by other modules if needed. + def protected_instance_variables + config.protected_instance_variables end - attr_writer :view_context_class - def view_context_class - @view_context_class || self.class.view_context_class + @_view_context_class || self.class.view_context_class end def initialize(*) - @view_context_class = nil + @_view_context_class = nil super end @@ -99,7 +84,12 @@ module AbstractController # # Override this method in a module to change the default behavior. def view_context - view_context_class.new(lookup_context, view_assigns, self) + view_context_class.new(view_renderer, view_assigns, self) + end + + # Returns an object that is able to render templates. + def view_renderer + @_view_renderer ||= ActionView::Renderer.new(lookup_context) end # Normalize arguments, options and then delegates render_to_body and @@ -127,26 +117,24 @@ module AbstractController # Find and renders a template based on the options given. # :api: private def _render_template(options) #:nodoc: - view_context.render(options) - end - - # The prefixes used in render "foo" shortcuts. - def _prefixes - @_prefixes ||= begin - parent_prefixes = self.class.parent_prefixes - parent_prefixes.dup.unshift(controller_path) - end + view_renderer.render(view_context, options) end private + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + ) + # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns hash = {} variables = instance_variable_names - variables -= protected_instance_variables if respond_to?(:protected_instance_variables) + variables -= protected_instance_variables + variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) } hash end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb index cea0f5ad1e..6b7aae8c74 100644 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -11,11 +11,36 @@ module AbstractController delegate :find_template, :template_exists?, :view_paths, :formats, :formats=, :locale, :locale=, :to => :lookup_context + module ClassMethods + 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 + + # 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 + # LookupContext is the object responsible to hold all information required to lookup # templates, i.e. view paths and details. Check ActionView::LookupContext for more # information. def lookup_context - @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup) + @_lookup_context ||= + ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) end def details_for_lookup @@ -67,4 +92,4 @@ module AbstractController end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index aab2b9dc25..eba5e9377b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -23,6 +23,7 @@ module ActionController autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds + autoload :ParamsWrapper autoload :RackDelegation autoload :Redirecting autoload :Renderers diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index ca0dccf575..c03c77cb4a 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -206,13 +206,17 @@ module ActionController HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, + # Before callbacks should also be executed the earliest as possible, so + # also include them at the bottom. + AbstractController::Callbacks, + # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. Instrumentation, - # Before callbacks should also be executed the earliest as possible, so - # also include them at the bottom. - AbstractController::Callbacks, + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper, # The same with rescue, append it at the end to wrap as much as possible. Rescue diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 3fae697cc3..8d813a8e38 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -7,8 +7,10 @@ module ActionController def start_processing(event) payload = event.payload params = payload[:params].except(*INTERNAL_PARAMS) + format = payload[:format] + format = format.to_s.upcase if format.is_a?(Symbol) - info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}" + info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 006b9fd456..05dca445a4 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -18,13 +18,10 @@ module ActionController delegate :default_charset=, :to => "ActionDispatch::Response" end - # TODO: Update protected instance variables list - config_accessor :protected_instance_variables - self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render - @variables_added @request_origin @url - @parent_controller @action_name - @before_filter_chain_aborted @_headers @_params - @_response) + self.protected_instance_variables = %w( + @_status @_headers @_params @_env @_response @_request + @_view_runtime @_stream @_url_options @_action_has_layout + ) def rescue_action(env) raise env["action_dispatch.rescue.exception"] diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 3ec0c4c6a4..e8e465d3ba 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,21 +1,19 @@ module ActionController module ImplicitRender def send_action(method, *args) - if respond_to?(method, true) - ret = super - default_render unless response_body - ret - else - default_render - end + ret = super + default_render unless response_body + ret end def default_render(*args) render(*args) end - def action_method?(action_name) - super || template_exists?(action_name.to_s, _prefixes) + def method_for_action(action_name) + super || if template_exists?(action_name.to_s, _prefixes) + "default_render" + end end end end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index dc3ea939e6..16cbbce2fb 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -14,12 +14,12 @@ module ActionController attr_internal :view_runtime - def process_action(action, *args) + def process_action(*args) raw_payload = { :controller => self.class.name, :action => self.action_name, :params => request.filtered_parameters, - :formats => request.formats.map(&:to_sym), + :format => request.format.ref, :method => request.method, :path => (request.fullpath rescue "unknown") } diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb new file mode 100644 index 0000000000..881af74147 --- /dev/null +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -0,0 +1,224 @@ +require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/array/wrap' +require 'action_dispatch/http/mime_types' + +module ActionController + # Wraps parameters hash into nested hash. This will allow client to submit + # POST request without having to specify a root element in it. + # + # By default this functionality won't be enabled. You can enable + # it globally by setting +ActionController::Base.wrap_parameters+: + # + # ActionController::Base.wrap_parameters = [:json] + # + # You could also turn it on per controller by setting the format array to + # non-empty array: + # + # class UsersController < ApplicationController + # wrap_parameters :format => [:json, :xml] + # end + # + # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to + # send JSON parameters like this: + # + # {"user": {"name": "Konata"}} + # + # You can now just send a parameters like this: + # + # {"name": "Konata"} + # + # And it will be wrapped into a nested hash with the key name matching + # controller's name. For example, if you're posting to +UsersController+, + # your new +params+ hash will look like this: + # + # {"name" => "Konata", "user" => {"name" => "Konata"}} + # + # You can also specify the key in which the parameters should be wrapped to, + # and also the list of attributes it should wrap by using either +:only+ or + # +:except+ options like this: + # + # class UsersController < ApplicationController + # wrap_parameters :person, :only => [:username, :password] + # end + # + # If you're going to pass the parameters to an +ActiveModel+ object (such as + # +User.new(params[:user])+), you might consider passing the model class to + # the method instead. The +ParamsWrapper+ will actually try to determine the + # list of attribute names from the model and only wrap those attributes: + # + # class UsersController < ApplicationController + # wrap_parameters Person + # end + # + # You still could pass +:only+ and +:except+ to set the list of attributes + # you want to wrap. + # + # By default, if you don't specify the key in which the parameters would be + # wrapped to, +ParamsWrapper+ will actually try to determine if there's + # a model related to it or not. This controller, for example: + # + # class Admin::UsersController < ApplicationController + # end + # + # will try to check if +Admin::User+ or +User+ model exists, and use it to + # determine the wrapper key respectively. If both of the model doesn't exists, + # it will then fallback to use +user+ as the key. + module ParamsWrapper + extend ActiveSupport::Concern + + EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) + + included do + class_attribute :_wrapper_options + self._wrapper_options = {:format => []} + end + + module ClassMethods + # Sets the name of the wrapper key, or the model which +ParamsWrapper+ + # would use to determine the attribute names from. + # + # ==== Examples + # wrap_parameters :format => :xml + # # enables the parmeter wrapper for XML format + # + # wrap_parameters :person + # # wraps parameters into +params[:person]+ hash + # + # wrap_parameters Person + # # wraps parameters by determine the wrapper key from Person class + # (+person+, in this case) and the list of attribute names + # + # wrap_parameters :only => [:username, :title] + # # wraps only +:username+ and +:title+ attributes from parameters. + # + # wrap_parameters false + # # disable parameters wrapping for this controller altogether. + # + # ==== Options + # * <tt>:format</tt> - The list of formats in which the parameters wrapper + # will be enabled. + # * <tt>:only</tt> - The list of attribute names which parameters wrapper + # will wrap into a nested hash. + # * <tt>:except</tt> - The list of attribute names which parameters wrapper + # will exclude from a nested hash. + def wrap_parameters(name_or_model_or_options, options = {}) + model = nil + + case name_or_model_or_options + when Hash + options = name_or_model_or_options + when false + options = options.merge(:format => []) + when Symbol, String + options = options.merge(:name => name_or_model_or_options) + else + model = name_or_model_or_options + end + + _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model) + end + + # Sets the default wrapper key or model which will be used to determine + # wrapper key and attribute names. Will be called automatically when the + # module is inherited. + def inherited(klass) + if klass._wrapper_options[:format].present? + klass._set_wrapper_defaults(klass._wrapper_options.slice(:format)) + end + super + end + + protected + + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singularize name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + def _default_wrap_model + model_name = self.name.sub(/Controller$/, '').singularize + + begin + model_klass = model_name.constantize + rescue NameError => e + unscoped_model_name = model_name.split("::", 2).last + break if unscoped_model_name == model_name + model_name = unscoped_model_name + end until model_klass + + model_klass + end + + def _set_wrapper_defaults(options, model=nil) + options = options.dup + + unless options[:only] || options[:except] + model ||= _default_wrap_model + if model.respond_to?(:column_names) + options[:only] = model.column_names + end + end + + unless options[:name] + model ||= _default_wrap_model + options[:name] = model ? model.to_s.demodulize.underscore : + controller_name.singularize + end + + options[:only] = Array.wrap(options[:only]).collect(&:to_s) if options[:only] + options[:except] = Array.wrap(options[:except]).collect(&:to_s) if options[:except] + options[:format] = Array.wrap(options[:format]) + + self._wrapper_options = options + end + end + + # Performs parameters wrapping upon the request. Will be called automatically + # by the metal call stack. + def process_action(*args) + if _wrapper_enabled? + wrapped_hash = _wrap_parameters request.request_parameters + wrapped_filtered_hash = _wrap_parameters request.filtered_parameters + + # This will make the wrapped hash accessible from controller and view + request.parameters.merge! wrapped_hash + request.request_parameters.merge! wrapped_hash + + # This will make the wrapped hash displayed in the log file + request.filtered_parameters.merge! wrapped_filtered_hash + end + super + end + + private + + # Returns the wrapper key which will use to stored wrapped parameters. + def _wrapper_key + _wrapper_options[:name] + end + + # Returns the list of enabled formats. + def _wrapper_formats + _wrapper_options[:format] + end + + # Returns the list of parameters which will be selected for wrapped. + def _wrap_parameters(parameters) + value = if only = _wrapper_options[:only] + parameters.slice(*only) + else + except = _wrapper_options[:except] || [] + parameters.except(*(except + EXCLUDE_PARAMETERS)) + end + + { _wrapper_key => value } + end + + # Checks if we should perform parameters wrapping. + def _wrapper_enabled? + ref = request.content_mime_type.try(:ref) + _wrapper_formats.include?(ref) && !request.request_parameters[_wrapper_key] + end + end +end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 59a3621f72..ebadb29ea7 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -68,7 +68,7 @@ module ActionController #:nodoc: # respond_with(@project, @task) # end # - # Giving an array of resources, you ensure that the responder will redirect to + # Giving several resources ensures that the responder will redirect to # <code>project_task_url</code> instead of <code>task_url</code>. # # Namespaced and singleton resources require a symbol to be given, as in @@ -77,6 +77,11 @@ module ActionController #:nodoc: # # respond_with(@project, :manager, @task) # + # Note that if you give an array, it will be treated as a collection, + # so the following is not equivalent: + # + # respond_with [@project, :manager, @task] + # # === Custom options # # <code>respond_with</code> also allow you to pass options that are forwarded diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index b9bd49f670..3892a12407 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,7 +2,209 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Methods for sending streaming templates back to the client. + # Allows views to be streamed back to the client as they are rendered. + # + # The default way Rails renders views is by first rendering the template + # and then the layout. The response is sent to the client after the whole + # template is rendered, all queries are made, and the layout is processed. + # + # Streaming inverts the rendering flow by rendering the layout first and + # streaming each part of the layout as they are processed. This allows the + # header of the HTML (which is usually in the layout) to be streamed back + # to client very quickly, allowing JavaScripts and stylesheets to be loaded + # earlier than usual. + # + # This approach was introduced in Rails 3.1 and is still improving. Several + # Rack middlewares may not work and you need to be careful when streaming. + # Those points are going to be addressed soon. + # + # In order to use streaming, you will need to use a Ruby version that + # supports fibers (fibers are supported since version 1.9.2 of the main + # Ruby implementation). + # + # == Examples + # + # Streaming can be added to a controller easily, all you need to do is + # call +stream+ in the controller class: + # + # class PostsController + # stream + # end + # + # The +stream+ method accepts the same options as +before_filter+ and friends: + # + # class PostsController + # stream :only => :index + # end + # + # You can also selectively turn on streaming for specific actions: + # + # class PostsController + # def index + # @posts = Post.scoped + # render :stream => true + # end + # end + # + # == When to use streaming + # + # Streaming may be considered to be overkill for lightweight actions like + # +new+ or +edit+. The real benefit of streaming is on expensive actions + # that, for example, do a lot of queries on the database. + # + # In such actions, you want to delay queries execution as much as you can. + # For example, imagine the following +dashboard+ action: + # + # def dashboard + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all + # end + # + # Most of the queries here are happening in the controller. In order to benefit + # from streaming you would want to rewrite it as: + # + # def dashboard + # # Allow lazy execution of the queries + # @posts = Post.scoped + # @pages = Page.scoped + # @articles = Article.scoped + # render :stream => true + # end + # + # == Communication between layout and template + # + # When streaming, rendering happens top-down instead of inside-out. + # Rails starts with the layout, and the template is rendered later, + # when its +yield+ is reached. + # + # This means that, if your application currently relies on instance + # variables set in the template to be used in the layout, they won't + # work once you move to streaming. The proper way to communicate + # between layout and template, regardless of whether you use streaming + # or not, is by using +content_for+, +provide+ and +yield+. + # + # Take a simple example where the layout expects the template to tell + # which title to use: + # + # <html> + # <head><title><%= yield :title %></title></head> + # <body><%= yield %></body> + # </html> + # + # You would use +content_for+ in your template to specify the title: + # + # <%= content_for :title, "Main" %> + # Hello + # + # And the final result would be: + # + # <html> + # <head><title>Main</title></head> + # <body>Hello</body> + # </html> + # + # However, if +content_for+ is called several times, the final result + # would have all calls concatenated. For instance, if we have the following + # template: + # + # <%= content_for :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # The final result would be: + # + # <html> + # <head><title>Main page</title></head> + # <body>Hello</body> + # </html> + # + # This means that, if you have <code>yield :title</code> in your layout + # and you want to use streaming, you would have to render the whole template + # (and eventually trigger all queries) before streaming the title and all + # assets, which kills the purpose of streaming. For this reason Rails 3.1 + # introduces a new helper called +provide+ that does the same as +content_for+ + # but tells the layout to stop searching for other entries and continue rendering. + # + # For instance, the template above using +provide+ would be: + # + # <%= provide :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # Giving: + # + # <html> + # <head><title>Main</title></head> + # <body>Hello</body> + # </html> + # + # That said, when streaming, you need to properly check your templates + # and choose when to use +provide+ and +content_for+. + # + # == Headers, cookies, session and flash + # + # When streaming, the HTTP headers are sent to the client right before + # it renders the first line. This means that, modifying headers, cookies, + # session or flash after the template starts rendering will not propagate + # to the client. + # + # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # will be raised, showing those objects are closed for modification. + # + # == Middlewares + # + # Middlewares that need to manipulate the body won't work with streaming. + # You should disable those middlewares whenever streaming in development + # or production. For instance, +Rack::Bug+ won't work when streaming as it + # needs to inject contents in the HTML body. + # + # Also +Rack::Cache+ won't work with streaming as it does not support + # streaming bodies yet. Whenever streaming Cache-Control is automatically + # set to "no-cache". + # + # == Errors + # + # When it comes to streaming, exceptions get a bit more complicated. This + # happens because part of the template was already rendered and streamed to + # the client, making it impossible to render a whole exception page. + # + # Currently, when an exception happens in development or production, Rails + # will automatically stream to the client: + # + # "><script type="text/javascript">window.location = "/500.html"</script></html> + # + # The first two characters (">) are required in case the exception happens + # while rendering attributes for a given tag. You can check the real cause + # for the exception in your logger. + # + # == Web server support + # + # Not all web servers support streaming out-of-the-box. You need to check + # the instructions for each of them. + # + # ==== Unicorn + # + # Unicorn supports streaming but it needs to be configured. For this, you + # need to create a config file as follow: + # + # # unicorn.config.rb + # listen 3000, :tcp_nopush => false + # + # And use it on initialization: + # + # unicorn_rails --config-file unicorn.config.rb + # + # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>. + # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen + # + # If you are using Unicorn with Nginx, you may need to tweak Nginx. + # Streaming should work out of the box on Rainbows. + # + # ==== Passenger + # + # To be described. + # module Streaming extend ActiveSupport::Concern @@ -51,7 +253,7 @@ module ActionController #:nodoc: # Call render_to_body if we are streaming instead of usual +render+. def _render_template(options) #:nodoc: if options.delete(:stream) - Rack::Chunked::Body.new view_context.render_body(options) + Rack::Chunked::Body.new view_renderer.render_body(view_context, options) else super end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb index 0fe2e6d1a6..1eadfc0390 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -128,6 +128,8 @@ module HTML # (no parent element). # * <tt>:empty</tt> -- Match the element only if it has no child elements, # and no text content. + # * <tt>:content(string)</tt> -- Match the element only if it has <tt>string</tt> + # as its text content (ignoring leading and trailing whitespace). # * <tt>:only-child</tt> -- Match the element if it is the only child (element) # of its parent element. # * <tt>:only-of-type</tt> -- Match the element if it is the only child (element) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 68ba1a81b5..980c658ab7 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,6 +1,13 @@ module ActionDispatch module Http module MimeNegotiation + extend ActiveSupport::Concern + + included do + mattr_accessor :ignore_accept_header + self.ignore_accept_header = false + end + # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post \format is extracted from the @@ -42,16 +49,14 @@ module ActionDispatch formats.first end - BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ - def formats - accept = @env['HTTP_ACCEPT'] - @env["action_dispatch.request.formats"] ||= if parameters[:format] Array(Mime[parameters[:format]]) - elsif xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) + elsif use_accept_header && valid_accept_header accepts + elsif xhr? + [Mime::JS] else [Mime::HTML] end @@ -87,6 +92,18 @@ module ActionDispatch order.include?(Mime::ALL) ? formats.first : nil end + + protected + + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + + def valid_accept_header + xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) + end + + def use_accept_header + !self.class.ignore_accept_header + end end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index f07ac44f7a..ccb866f4f7 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -17,16 +17,17 @@ module ActionDispatch include ActionDispatch::Http::Upload include ActionDispatch::Http::URL - LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze - - %w[ AUTH_TYPE GATEWAY_INTERFACE + LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze + ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR SERVER_NAME SERVER_PROTOCOL HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM - HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| + HTTP_NEGOTIATE HTTP_PRAGMA ].freeze + + ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{env.sub(/^HTTP_/n, '').downcase} @env["#{env}"] diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index ac0fd9607d..d9c07d6ca3 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -165,7 +165,7 @@ module ActionDispatch # such as 2 to catch <tt>["www"]</tt> instead of <tt>"www.rubyonrails"</tt> # in "www.rubyonrails.co.uk". def subdomain(tld_length = @@tld_length) - subdomains(tld_length) + subdomains(tld_length).join(".") end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 9c9ccc62f5..8ebf870b95 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -59,7 +59,7 @@ module ActionDispatch end def set_session(env, sid, session_data, options) - persistent_session_id!(session_data, sid) + session_data.merge("session_id" => sid) end def set_cookie(env, session_id, cookie) diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index b1adf3d2d1..c17c746096 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -116,7 +116,7 @@ module ActionDispatch end def render(status, body) - [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]] + [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end def public_path diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 348f7b86b8..404943d720 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,10 +2,10 @@ require 'rack/utils' module ActionDispatch class FileHandler - def initialize(root) + def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ - @file_server = ::Rack::File.new(@root) + @file_server = ::Rack::File.new(@root, cache_control) end def match?(path) @@ -35,18 +35,15 @@ module ActionDispatch end class Static - FILE_METHODS = %w(GET HEAD).freeze - - def initialize(app, path) + def initialize(app, path, cache_control=nil) @app = app - @file_handler = FileHandler.new(path) + @file_handler = FileHandler.new(path, cache_control) end def call(env) - path = env['PATH_INFO'].chomp('/') - method = env['REQUEST_METHOD'] - - if FILE_METHODS.include?(method) + case env['REQUEST_METHOD'] + when 'GET', 'HEAD' + path = env['PATH_INFO'].chomp('/') if match = @file_handler.match?(path) env["PATH_INFO"] = match return @file_handler.call(env) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 97f7cf0bbe..0c5bafa666 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -24,7 +24,7 @@ <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> <p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p> -<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div> +<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> <h2 style="margin-top: 30px">Response</h2> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 2099fd069a..4b9d3141d5 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -1,7 +1,7 @@ <h1> <%=h @exception.class.to_s %> <% if @request.parameters['controller'] %> - in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> + in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> <% end %> </h1> <pre><%=h @exception.message %></pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 6c32fb17b8..6e71fd7ddc 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -1,11 +1,13 @@ -<html xmlns="http://www.w3.org/1999/xhtml"> +<!DOCTYPE html> +<html lang="en"> <head> + <meta charset="utf-8" /> <title>Action Controller: Exception caught</title> <style> body { background-color: #fff; color: #333; } body, p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; + font-family: helvetica, verdana, arial, sans-serif; font-size: 13px; line-height: 18px; } diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 0a3bd5fe40..f51cc3711b 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -9,10 +9,12 @@ module ActionDispatch config.action_dispatch.show_exceptions = true config.action_dispatch.best_standards_support = true config.action_dispatch.tld_length = 1 + config.action_dispatch.ignore_accept_header = false config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true} initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length + ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 1d09091dc7..97e8ccc9a5 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -3,6 +3,7 @@ require 'forwardable' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/module/remove_method' module ActionDispatch module Routing @@ -160,7 +161,7 @@ module ActionDispatch # We use module_eval to avoid leaks @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_method :#{selector} if method_defined?(:#{selector}) + remove_possible_method :#{selector} def #{selector}(*args) options = args.extract_options! @@ -194,7 +195,7 @@ module ActionDispatch hash_access_method = hash_access_name(name, kind) @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_method :#{selector} if method_defined?(:#{selector}) + remove_possible_method :#{selector} def #{selector}(*args) url_for(#{hash_access_method}(*args)) end @@ -240,6 +241,11 @@ module ActionDispatch end def eval_block(block) + if block.arity == 1 + raise "You are using the old router DSL which has been removed in Rails 3.1. " << + "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " << + "or add the rails_legacy_mapper gem to your Gemfile" + end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index e209978fb7..3335742d47 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -42,7 +42,7 @@ module ActionDispatch elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert(false, build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) + flunk(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) end end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 170ceb299a..584e5c3791 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -3,7 +3,7 @@ module ActionPack MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 4547aceb28..92b6f7c770 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -34,13 +34,12 @@ module ActionView autoload :Context autoload :Helpers autoload :LookupContext - autoload :Partials autoload :PathSet - autoload :Rendering autoload :Template autoload :TestCase autoload_under "renderer" do + autoload :Renderer autoload :AbstractRenderer autoload :PartialRenderer autoload :TemplateRenderer diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 87501d5b88..c98110353f 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -131,7 +131,7 @@ module ActionView #:nodoc: # # More builder documentation can be found at http://builder.rubyforge.org. class Base - include Helpers, Rendering, Partials, ::ERB::Util, Context + include Helpers, ::ERB::Util, Context # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc @@ -139,7 +139,6 @@ module ActionView #:nodoc: # How to complete the streaming when an exception occurs. # This is our best guess: first try to close the attribute, then the tag. - # Currently this is private API and may be changed at *any* time. cattr_accessor :streaming_completion_on_exception @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>) @@ -157,56 +156,61 @@ module ActionView #:nodoc: def cache_template_loading=(value) ActionView::Resolver.caching = value end - end - - attr_accessor :_template, :_view_flow - attr_internal :request, :controller, :config, :assigns, :lookup_context - - delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context - delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, - :flash, :action_name, :controller_name, :to => :controller + def process_view_paths(value) + value.is_a?(PathSet) ? + value.dup : ActionView::PathSet.new(Array.wrap(value)) + end - delegate :logger, :to => :controller, :allow_nil => true + def xss_safe? #:nodoc: + true + end - def self.xss_safe? #:nodoc: - true + # This method receives routes and helpers from the controller + # and return a subclass ready to be used as view context. + def prepare(routes, helpers) #:nodoc: + Class.new(self) do + if routes + include routes.url_helpers + include routes.mounted_helpers + end + + if helpers + include helpers + self.helpers = helpers + end + end + end end - def self.process_view_paths(value) - value.is_a?(PathSet) ? - value.dup : ActionView::PathSet.new(Array.wrap(value)) - end + attr_accessor :view_renderer + attr_internal :config, :assigns + + delegate :lookup_context, :to => :view_renderer + delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context def assign(new_assigns) # :nodoc: @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: - assign(assigns_for_first_render) - self.helpers = Module.new unless self.class.helpers - + def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: @_config = {} - @_virtual_path = nil - @_view_flow = OutputFlow.new - @output_buffer = nil - if @_controller = controller - @_request = controller.request if controller.respond_to?(:request) - @_config = controller.config.inheritable_copy if controller.respond_to?(:config) + # Handle all these for backwards compatibility. + # TODO Provide a new API for AV::Base and deprecate this one. + if context.is_a?(ActionView::Renderer) + @view_renderer = context + elsif + lookup_context = context.is_a?(ActionView::LookupContext) ? + context : ActionView::LookupContext.new(context) + lookup_context.formats = formats if formats + lookup_context.prefixes = controller._prefixes if controller + @view_renderer = ActionView::Renderer.new(lookup_context) end - @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? - lookup_context : ActionView::LookupContext.new(lookup_context) - @_lookup_context.formats = formats if formats - end - - def controller_path - @controller_path ||= controller && controller.controller_path - end - - def controller_prefixes - @controller_prefixes ||= controller && controller._prefixes + assign(assigns) + assign_controller(controller) + _prepare_context end ActiveSupport.run_load_hooks(:action_view, self) diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index a2a64de206..083856b2ca 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -8,33 +8,29 @@ module ActionView # Action View contexts are supplied to Action Controller to render template. # The default Action View context is ActionView::Base. # - # In order to work with ActionController, a Context must implement: - # - # Context#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options<Hash>:: see _render_partial in ActionView::Base - # Context#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template<ActionView::Template>:: The template to render - # layout<ActionView::Template>:: The layout to render around the template - # options<Hash>:: See _render_template_with_layout in ActionView::Base - # partial<Boolean>:: Whether or not the template to render is a partial - # - # An Action View context can also mix in Action View's helpers. In order to - # mix in helpers, a context must implement: - # - # Context#controller - # - Returns an instance of AbstractController - # - # In any case, a context must mix in ActionView::Context, which stores compiled - # template and provides the output buffer. + # In order to work with ActionController, a Context must just include this module. + # The initialization of the variables used by the context (@output_buffer, @view_flow, + # and @virtual_path) is responsibility of the object that includes this module + # (although you can call _prepare_context defined below). module Context include CompiledTemplates - attr_accessor :output_buffer + attr_accessor :output_buffer, :view_flow + + # Prepares the context by setting the appropriate instance variables. + # :api: plugin + def _prepare_context + @view_flow = OutputFlow.new + @output_buffer = nil + @virtual_path = nil + end - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object + # Encapsulates the interaction with the view flow so it + # returns the correct buffer on yield. This is usually + # overwriten by helpers to add more behavior. + # :api: plugin + def _layout_for(name=nil) + name ||= :layout + view_flow.get(name).html_safe end end end
\ No newline at end of file diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb index 386a06511f..a8f740713f 100644 --- a/actionpack/lib/action_view/flows.rb +++ b/actionpack/lib/action_view/flows.rb @@ -34,7 +34,7 @@ module ActionView @view = view @parent = nil @child = view.output_buffer - @content = view._view_flow.content + @content = view.view_flow.content @fiber = fiber @root = Fiber.current.object_id end diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 205116f610..78a68db282 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -9,6 +9,7 @@ module ActionView #:nodoc: autoload :AtomFeedHelper autoload :CacheHelper autoload :CaptureHelper + autoload :ControllerHelper autoload :CsrfHelper autoload :DateHelper autoload :DebugHelper @@ -19,6 +20,7 @@ module ActionView #:nodoc: autoload :NumberHelper autoload :OutputSafetyHelper autoload :RecordTagHelper + autoload :RenderingHelper autoload :SanitizeHelper autoload :SprocketsHelper autoload :TagHelper @@ -38,6 +40,7 @@ module ActionView #:nodoc: include AtomFeedHelper include CacheHelper include CaptureHelper + include ControllerHelper include CsrfHelper include DateHelper include DebugHelper @@ -48,6 +51,7 @@ module ActionView #:nodoc: include NumberHelper include OutputSafetyHelper include RecordTagHelper + include RenderingHelper include SanitizeHelper include SprocketsHelper include TagHelper diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index cb6737b94e..958f0e0a10 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -21,7 +21,6 @@ module ActionView return source if is_uri?(source) source = rewrite_extension(source, dir, ext) if ext - source = "/#{dir}/#{source}" unless source[0] == ?/ source = rewrite_asset_path(source, dir) if controller && include_host diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 38860431b4..cd0f8c8878 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -41,7 +41,8 @@ module ActionView # Break out the asset path rewrite in case plugins wish to put the asset id # someplace other than the query string. - def rewrite_asset_path(source, path = nil) + def rewrite_asset_path(source, dir) + source = "/#{dir}/#{source}" unless source[0] == ?/ path = config.asset_path if path && path.respond_to?(:call) 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 3d815b5e1f..e1ee0d0e1a 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 @@ -99,10 +99,9 @@ module ActionView # # When passing paths, the ".js" extension is optional. # - # To include the default JavaScript expansion pass <tt>:defaults</tt> as source. - # By default, <tt>:defaults</tt> loads jQuery. If the application was generated - # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead. - # In any case, the defaults can be overridden in <tt>config/application.rb</tt>: + # If the application is not using the asset pipeline, to include the default JavaScript + # expansion pass <tt>:defaults</tt> as source. By default, <tt>:defaults</tt> loads jQuery, + # and that can be overridden in <tt>config/application.rb</tt>: # # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) # diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 0139714240..ead7feb091 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -135,7 +135,7 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - result = @_view_flow.append(name, content) if content + result = @view_flow.append(name, content) if content result unless content end @@ -146,7 +146,7 @@ module ActionView # the layout to stop looking for more contents. def provide(name, content = nil, &block) content = capture(&block) if block_given? - result = @_view_flow.append!(name, content) if content + result = @view_flow.append!(name, content) if content result unless content end @@ -169,7 +169,7 @@ module ActionView # </body> # </html> def content_for?(name) - @_view_flow.get(name).present? + @view_flow.get(name).present? end # Use an alternate output buffer for the duration of the block. diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb new file mode 100644 index 0000000000..e22331cb3c --- /dev/null +++ b/actionpack/lib/action_view/helpers/controller_helper.rb @@ -0,0 +1,21 @@ +module ActionView + module Helpers + # This module keeps all methods and behavior in ActionView + # that simply delegates to the controller. + module ControllerHelper #:nodoc: + attr_internal :controller, :request + + delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, + :flash, :action_name, :controller_name, :controller_path, :to => :controller + + delegate :logger, :to => :controller, :allow_nil => true + + def assign_controller(controller) + if @_controller = controller + @_request = controller.request if controller.respond_to?(:request) + @_config = controller.config.inheritable_copy if controller.respond_to?(:config) + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 9277359d5c..eb8c96a6ae 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -94,9 +94,20 @@ module ActionView when 43200..86399 then locale.t :about_x_months, :count => 1 when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round else - distance_in_years = distance_in_minutes / 525600 - minute_offset_for_leap_year = (distance_in_years / 4) * 1440 - remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600) + fyear = from_time.year + fyear += 1 if from_time.month >= 3 + tyear = to_time.year + tyear -= 1 if to_time.month < 3 + leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # english it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + remainder = (minutes_with_offset % 525600) + distance_in_years = (minutes_with_offset / 525600) if remainder < 131400 locale.t(:about_x_years, :count => distance_in_years) elsif remainder < 394200 diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index efe30441b1..b6bb90a3ce 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -102,6 +102,11 @@ module ActionView include FormTagHelper include UrlHelper + # Converts the given object to an ActiveModel compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + # Creates a form and a scope around a specific model object that is used # as a base for questioning about values for the fields. # @@ -1167,7 +1172,7 @@ module ActionView class FormBuilder # The methods which wrap a form helper call. class_attribute :field_helpers - self.field_helpers = (FormHelper.instance_method_names - ['form_for']) + self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model) attr_accessor :object_name, :object, :options diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 49aa434020..9e0f8f32b7 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -74,6 +74,8 @@ module ActionView # ==== Options # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices. # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:include_blank</tt> - If set to true, an empty option will be create + # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something # * Any other key creates standard HTML attributes for the tag. # # ==== Examples @@ -99,18 +101,26 @@ module ActionView # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option> # # <option>Write</option></select> # + # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :include_blank => true + # # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select> + # + # select_tag "people", options_from_collection_for_select(@people, "id", "name"), :prompt => "Select something" + # # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select> + # # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option> # # <option>Paris</option><option>Rome</option></select> def select_tag(name, option_tags = nil, options = {}) html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name - if blank = options.delete(:include_blank) - if blank.kind_of?(String) - option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags - else - option_tags = "<option value=\"\"></option>".html_safe + option_tags - end + + if options.delete(:include_blank) + option_tags = "<option value=\"\"></option>".html_safe + option_tags + end + + if prompt = options.delete(:prompt) + option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags end + content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) end diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb new file mode 100644 index 0000000000..47efdded42 --- /dev/null +++ b/actionpack/lib/action_view/helpers/rendering_helper.rb @@ -0,0 +1,90 @@ +module ActionView + module Helpers + # = Action View Rendering + # + # Implements methods that allow rendering from a view context. + # In order to use this module, all you need is to implement + # view_renderer that returns an ActionView::Renderer object. + module RenderingHelper + # Returns the result of a render that's dictated by the options hash. The primary options are: + # + # * <tt>:partial</tt> - See ActionView::Partials. + # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. + # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. + # * <tt>:text</tt> - Renders the text passed in out. + # + # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter + # as the locals hash. + def render(options = {}, locals = {}, &block) + case options + when Hash + if block_given? + view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) + else + view_renderer.render(self, options) + end + else + view_renderer.render_partial(self, :partial => options, :locals => locals) + end + end + + # Overwrites _layout_for in the context object so it supports the case a block is + # passed to a partial. Returns the contents that are yielded to a layout, given a + # name or a block. + # + # You can think of a layout as a method that is called with a block. If the user calls + # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>. + # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>. + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render :layout => "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # <html> + # <%= yield %> + # </html> + # + # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>, + # this method returns the block that was passed in to <tt>render :layout</tt>, and the response + # would be + # + # <html> + # Content + # </html> + # + # Finally, the block can take block arguments, which can be passed in by +yield+: + # + # # The template + # <%= render :layout => "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # <html> + # <%= yield Struct.new(:name).new("David") %> + # </html> + # + # In this case, the layout would receive the block passed into <tt>render :layout</tt>, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # <html> + # Hello David + # </html> + # + def _layout_for(*args, &block) + name = args.first + + if block && !name.is_a?(Symbol) + capture(*args, &block) + else + super + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index b43b91178c..ab98da9624 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -40,10 +40,10 @@ module ActionView class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: def rewrite_asset_path(source, dir) - if source =~ /^\/#{dir}\/(.+)/ - assets.path($1, performing_caching?, dir) - else + if source[0] == ?/ source + else + assets.path(source, performing_caching?, dir) end end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 06e2b027da..ca09c77b5c 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -265,60 +265,6 @@ module ActionView text.html_safe.safe_concat("</p>") end - # Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option - # will limit what should be linked. You can add HTML attributes to the links using - # <tt>:html</tt>. Possible values for <tt>:link</tt> are <tt>:all</tt> (default), - # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and - # e-mail address is yielded and the result is used as the link text. - # - # ==== Examples - # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") - # # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and - # # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>" - # - # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :urls) - # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a> - # # or e-mail david@loudthinking.com" - # - # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :email_addresses) - # # => "Visit http://www.loudthinking.com/ or e-mail <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>" - # - # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :html => { :target => '_blank' }) do |text| - # truncate(text, :length => 15) - # end - # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. - # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." - # - # - # You can still use <tt>auto_link</tt> with the old API that accepts the - # +link+ as its optional second parameter and the +html_options+ hash - # as its optional third parameter: - # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :urls) - # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>. - # Please e-mail me at me@email.com." - # - # auto_link(post_body, :all, :target => "_blank") - # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. - # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." - def auto_link(text, *args, &block)#link = :all, html = {}, &block) - return '' if text.blank? - - options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter - unless args.empty? - options[:link] = args[0] || :all - options[:html] = args[1] || {} - end - options.reverse_merge!(:link => :all, :html => {}) - - case options[:link].to_sym - when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], options, &block), options[:html], &block) - when :email_addresses then auto_link_email_addresses(text, options[:html], &block) - when :urls then auto_link_urls(text, options[:html], options, &block) - end - end - # Creates a Cycle object whose _to_s_ method cycles through elements of an # array every time it is called. This can be used for example, to alternate # classes for table rows. You can use named cycles to allow nesting in loops. @@ -464,77 +410,6 @@ module ActionView @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object end - - AUTO_LINK_RE = %r{ - (?: ([0-9A-Za-z+.:-]+:)// | www\. ) - [^\s<]+ - }x - - # regexps for determining context, used high-volume - AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i] - - AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/ - - BRACKETS = { ']' => '[', ')' => '(', '}' => '{' } - - # Turns all urls into clickable links. If a block is given, each url - # is yielded and the result is used as the link text. - def auto_link_urls(text, html_options = {}, options = {}) - link_attributes = html_options.stringify_keys - text.gsub(AUTO_LINK_RE) do - scheme, href = $1, $& - punctuation = [] - - if auto_linked?($`, $') - # do not change string; URL is already linked - href - else - # don't include trailing punctuation character as part of the URL - while href.sub!(/[^\w\/-]$/, '') - punctuation.push $& - if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size - href << punctuation.pop - break - end - end - - link_text = block_given?? yield(href) : href - href = 'http://' + href unless scheme - - unless options[:sanitize] == false - link_text = sanitize(link_text) - href = sanitize(href) - end - content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('') - end - end - end - - # Turns all email addresses into clickable links. If a block is given, - # each email is yielded and the result is used as the link text. - def auto_link_email_addresses(text, html_options = {}, options = {}) - text.gsub(AUTO_EMAIL_RE) do - text = $& - - if auto_linked?($`, $') - text.html_safe - else - display_text = (block_given?) ? yield(text) : text - - unless options[:sanitize] == false - text = sanitize(text) - display_text = sanitize(display_text) unless text == display_text - end - mail_to text, display_text, html_options - end - end - end - - # Detects already linked context or position in the middle of a tag - def auto_linked?(left, right) - (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or - (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3]) - end end end end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 26ebae6546..ec9bdd5320 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -63,8 +63,8 @@ module ActionView private def scope_key_by_partial(key) if key.to_s.first == "." - if (path = @_template && @_template.virtual_path) - path.gsub(%r{/_?}, ".") + key.to_s + if @virtual_path + @virtual_path.gsub(%r{/_?}, ".") + key.to_s else raise "Cannot use t(#{key.inspect}) shortcut because path is not available" end diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 06c607931d..06975ffa2f 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -9,6 +9,8 @@ module ActionView # generate a key, given to view paths, used in the resolver cache lookup. Since # this key is generated just once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: + attr_accessor :prefixes + mattr_accessor :fallbacks @@fallbacks = FallbackFileSystemResolver.instances @@ -58,10 +60,11 @@ module ActionView end end - def initialize(view_paths, details = {}) + def initialize(view_paths, details = {}, prefixes = []) @details, @details_key = { :handlers => default_handlers }, nil @frozen_formats, @skip_default_locale = false, false @cache = true + @prefixes = prefixes self.view_paths = view_paths self.registered_detail_setters.each do |key, setter| diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb deleted file mode 100644 index c181689e62..0000000000 --- a/actionpack/lib/action_view/partials.rb +++ /dev/null @@ -1,226 +0,0 @@ -require 'active_support/core_ext/object/blank' - -module ActionView - # = Action View Partials - # - # There's also a convenience method for rendering sub templates within the current controller that depends on a - # single object (we call this kind of sub templates for partials). It relies on the fact that partials should - # follow the naming convention of being prefixed with an underscore -- as to separate them from regular - # templates that could be rendered on their own. - # - # In a template for Advertiser#account: - # - # <%= render :partial => "account" %> - # - # 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 } %> - # - # <% @advertisements.each do |ad| %> - # <%= render :partial => "ad", :locals => { :ad => ad } %> - # <% end %> - # - # 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 - # - # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same - # name as the template. So, given - # - # <%= render :partial => "contract" %> - # - # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written - # - # <%= render :partial => "contract", :locals => { :contract => @contract } %> - # - # 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' %> - # - # 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 %> - # - # <% @advertisements.each do |ad| %> - # <%= render :partial => "ad", :object => ad %> - # <% end %> - # - # The <tt>:object</tt> and <tt>:as</tt> options can be used together. - # - # == Rendering a collection of partials - # - # The example of partial use describes a familiar pattern where a template needs to iterate over an array and - # render a sub template for each of the elements. This pattern has been implemented as a single method that - # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined - # example in "Using partials" can be rewritten with a single line: - # - # <%= render :partial => "ad", :collection => @advertisements %> - # - # 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: - # - # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %> - # - # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you - # to specify a text which will displayed instead by using this form: - # - # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %> - # - # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also - # just keep domain objects, like Active Records, in there. - # - # == Rendering shared partials - # - # Two controllers can share a set of partials and render them like this: - # - # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> - # - # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. - # - # == Rendering objects with the RecordIdentifier - # - # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if - # you're following its conventions for RecordIdentifier#partial_path. Examples: - # - # # @account is an Account instance, so it uses the RecordIdentifier to replace - # # <%= render :partial => "accounts/account", :locals => { :account => @account} %> - # <%= render :partial => @account %> - # - # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace - # # <%= render :partial => "posts/post", :collection => @posts %> - # <%= render :partial => @posts %> - # - # == Rendering the default case - # - # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand - # defaults of render to render partials. Examples: - # - # # Instead of <%= render :partial => "account" %> - # <%= render "account" %> - # - # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %> - # <%= render "account", :account => @buyer %> - # - # # @account is an Account instance, so it uses the RecordIdentifier to replace - # # <%= render :partial => "accounts/account", :locals => { :account => @account } %> - # <%= render(@account) %> - # - # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace - # # <%= render :partial => "posts/post", :collection => @posts %> - # <%= render(@posts) %> - # - # == Rendering partials with layouts - # - # Partials can have their own layouts applied to them. These layouts are different than the ones that are - # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types - # of users: - # - # <%# app/views/users/index.html.erb &> - # Here's the administrator: - # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %> - # - # Here's the editor: - # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %> - # - # <%# app/views/users/_user.html.erb &> - # Name: <%= user.name %> - # - # <%# app/views/users/_administrator.html.erb &> - # <div id="administrator"> - # Budget: $<%= user.budget %> - # <%= yield %> - # </div> - # - # <%# app/views/users/_editor.html.erb &> - # <div id="editor"> - # Deadline: <%= user.deadline %> - # <%= yield %> - # </div> - # - # ...this will return: - # - # Here's the administrator: - # <div id="administrator"> - # Budget: $<%= user.budget %> - # Name: <%= user.name %> - # </div> - # - # Here's the editor: - # <div id="editor"> - # Deadline: <%= user.deadline %> - # Name: <%= user.name %> - # </div> - # - # You can also apply a layout to a block within any template: - # - # <%# app/views/users/_chief.html.erb &> - # <%= render(:layout => "administrator", :locals => { :user => chief }) do %> - # Title: <%= chief.title %> - # <% end %> - # - # ...this will return: - # - # <div id="administrator"> - # Budget: $<%= user.budget %> - # Title: <%= chief.name %> - # </div> - # - # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. - # - # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass - # an array to layout and treat it as an enumerable. - # - # <%# app/views/users/_user.html.erb &> - # <div class="user"> - # Budget: $<%= user.budget %> - # <%= yield user %> - # </div> - # - # <%# app/views/users/index.html.erb &> - # <%= render :layout => @users do |user| %> - # Title: <%= user.title %> - # <% end %> - # - # This will render the layout for each user and yield to the block, passing the user, each time. - # - # You can also yield multiple times in one layout and use block arguments to differentiate the sections. - # - # <%# app/views/users/_user.html.erb &> - # <div class="user"> - # <%= yield user, :header %> - # Budget: $<%= user.budget %> - # <%= yield user, :footer %> - # </div> - # - # <%# app/views/users/index.html.erb &> - # <%= render :layout => @users do |user, section| %> - # <%- case section when :header -%> - # Title: <%= user.title %> - # <%- when :footer -%> - # Deadline: <%= user.deadline %> - # <%- end -%> - # <% end %> - module Partials - def _render_partial(options, &block) #:nodoc: - _partial_renderer.setup(options, block).render - end - - def _partial_renderer #:nodoc: - @_partial_renderer ||= PartialRenderer.new(self) - end - end -end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index f20ba7e6d3..80391d72cc 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -6,7 +6,7 @@ module ActionView class Railtie < Rails::Railtie config.action_view = ActiveSupport::OrderedOptions.new config.action_view.stylesheet_expansions = {} - config.action_view.javascript_expansions = { :defaults => %w(jquery rails) } + config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } initializer "action_view.cache_asset_ids" do |app| unless app.config.cache_classes diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 4a52b3172e..60c527beeb 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -3,9 +3,8 @@ module ActionView delegate :find_template, :template_exists?, :with_fallbacks, :update_details, :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context - def initialize(view) - @view = view - @lookup_context = view.lookup_context + def initialize(lookup_context) + @lookup_context = lookup_context end def render diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 10cd37d56f..a351fbc04f 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,44 +1,230 @@ +require 'active_support/core_ext/object/blank' + module ActionView + # = Action View Partials + # + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. + # + # In a template for Advertiser#account: + # + # <%= render :partial => "account" %> + # + # 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 } %> + # + # <% @advertisements.each do |ad| %> + # <%= render :partial => "ad", :locals => { :ad => ad } %> + # <% end %> + # + # 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 + # + # By default <tt>ActionView::Partials::PartialRenderer</tt> has its object in a local variable with the same + # name as the template. So, given + # + # <%= render :partial => "contract" %> + # + # within contract we'll get <tt>@contract</tt> in the local variable +contract+, as if we had written + # + # <%= render :partial => "contract", :locals => { :contract => @contract } %> + # + # 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' %> + # + # 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 %> + # + # <% @advertisements.each do |ad| %> + # <%= render :partial => "ad", :object => ad %> + # <% end %> + # + # The <tt>:object</tt> and <tt>:as</tt> options can be used together. + # + # == Rendering a collection of partials + # + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: + # + # <%= render :partial => "ad", :collection => @advertisements %> + # + # 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: + # + # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %> + # + # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you + # to specify a text which will displayed instead by using this form: + # + # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %> + # + # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also + # just keep domain objects, like Active Records, in there. + # + # == Rendering shared partials + # + # Two controllers can share a set of partials and render them like this: + # + # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> + # + # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. + # + # == Rendering objects with the RecordIdentifier + # + # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if + # you're following its conventions for RecordIdentifier#partial_path. Examples: + # + # # @account is an Account instance, so it uses the RecordIdentifier to replace + # # <%= render :partial => "accounts/account", :locals => { :account => @account} %> + # <%= render :partial => @account %> + # + # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace + # # <%= render :partial => "posts/post", :collection => @posts %> + # <%= render :partial => @posts %> + # + # == Rendering the default case + # + # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand + # defaults of render to render partials. Examples: + # + # # Instead of <%= render :partial => "account" %> + # <%= render "account" %> + # + # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %> + # <%= render "account", :account => @buyer %> + # + # # @account is an Account instance, so it uses the RecordIdentifier to replace + # # <%= render :partial => "accounts/account", :locals => { :account => @account } %> + # <%= render(@account) %> + # + # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace + # # <%= render :partial => "posts/post", :collection => @posts %> + # <%= render(@posts) %> + # + # == Rendering partials with layouts + # + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: + # + # <%# app/views/users/index.html.erb &> + # Here's the administrator: + # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %> + # + # Here's the editor: + # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %> + # + # <%# app/views/users/_user.html.erb &> + # Name: <%= user.name %> + # + # <%# app/views/users/_administrator.html.erb &> + # <div id="administrator"> + # Budget: $<%= user.budget %> + # <%= yield %> + # </div> + # + # <%# app/views/users/_editor.html.erb &> + # <div id="editor"> + # Deadline: <%= user.deadline %> + # <%= yield %> + # </div> + # + # ...this will return: + # + # Here's the administrator: + # <div id="administrator"> + # Budget: $<%= user.budget %> + # Name: <%= user.name %> + # </div> + # + # Here's the editor: + # <div id="editor"> + # Deadline: <%= user.deadline %> + # Name: <%= user.name %> + # </div> + # + # You can also apply a layout to a block within any template: + # + # <%# app/views/users/_chief.html.erb &> + # <%= render(:layout => "administrator", :locals => { :user => chief }) do %> + # Title: <%= chief.title %> + # <% end %> + # + # ...this will return: + # + # <div id="administrator"> + # Budget: $<%= user.budget %> + # Title: <%= chief.name %> + # </div> + # + # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. + # + # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass + # an array to layout and treat it as an enumerable. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # Budget: $<%= user.budget %> + # <%= yield user %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <%= render :layout => @users do |user| %> + # Title: <%= user.title %> + # <% end %> + # + # This will render the layout for each user and yield to the block, passing the user, each time. + # + # You can also yield multiple times in one layout and use block arguments to differentiate the sections. + # + # <%# app/views/users/_user.html.erb &> + # <div class="user"> + # <%= yield user, :header %> + # Budget: $<%= user.budget %> + # <%= yield user, :footer %> + # </div> + # + # <%# app/views/users/index.html.erb &> + # <%= render :layout => @users do |user, section| %> + # <%- case section when :header -%> + # Title: <%= user.title %> + # <%- when :footer -%> + # Deadline: <%= user.deadline %> + # <%- end -%> + # <% end %> class PartialRenderer < AbstractRenderer #:nodoc: PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - def initialize(view) + def initialize(*) super - @partial_names = PARTIAL_NAMES[@view.controller.class.name] + @partial_names = PARTIAL_NAMES[@lookup_context.prefixes.first] end - def setup(options, block) - partial = options[:partial] - - @options = options - @locals = options[:locals] || {} - @block = block - - if String === partial - @object = options[:object] - @path = partial - @collection = collection - else - @object = partial - - if @collection = collection_from_object || collection - paths = @collection_data = @collection.map { |o| partial_path(o) } - @path = paths.uniq.size == 1 ? paths.first : nil - else - @path = partial_path - end - end - - if @path - @variable, @variable_counter = retrieve_variable(@path) - else - paths.map! { |path| retrieve_variable(path).unshift(path) } - end - - self - end + def render(context, options, block) + setup(context, options, block) - def render wrap_formats(@path) do identifier = ((@template = find_partial) ? @template.identifier : @path) @@ -77,7 +263,7 @@ module ActionView locals[as] = object content = @template.render(view, locals) do |*name| - view._block_layout_for(*name, &block) + view._layout_for(*name, &block) end content = layout.render(view, locals){ content } if layout @@ -86,6 +272,38 @@ module ActionView private + def setup(context, options, block) + @view = context + partial = options[:partial] + + @options = options + @locals = options[:locals] || {} + @block = block + + if String === partial + @object = options[:object] + @path = partial + @collection = collection + else + @object = partial + + if @collection = collection_from_object || collection + paths = @collection_data = @collection.map { |o| partial_path(o) } + @path = paths.uniq.size == 1 ? paths.first : nil + else + @path = partial_path + end + end + + if @path + @variable, @variable_counter = retrieve_variable(@path) + else + paths.map! { |path| retrieve_variable(path).unshift(path) } + end + + self + end + def collection if @options.key?(:collection) collection = @options[:collection] @@ -109,7 +327,7 @@ module ActionView end def find_template(path=@path, locals=@locals.keys) - prefixes = path.include?(?/) ? [] : @view.controller_prefixes + prefixes = path.include?(?/) ? [] : @lookup_context.prefixes @lookup_context.find_template(path, prefixes, true, locals) end @@ -150,7 +368,7 @@ 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_prefixes.first + path = @lookup_context.prefixes.first partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) end end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb new file mode 100644 index 0000000000..bf1b5a7d22 --- /dev/null +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -0,0 +1,54 @@ +module ActionView + # This is the main entry point for rendering. It basically delegates + # to other objects like TemplateRenderer and PartialRenderer which + # actually renders the template. + class Renderer + attr_accessor :lookup_context + + def initialize(lookup_context) + @lookup_context = lookup_context + end + + # Main render entry point shared by AV and AC. + def render(context, options) + if options.key?(:partial) + render_partial(context, options) + else + render_template(context, options) + end + end + + # Render but returns a valid Rack body. If fibers are defined, we return + # a streaming body that renders the template piece by piece. + # + # Note that partials are not supported to be rendered with streaming, + # so in such cases, we just wrap them in an array. + def render_body(context, options) + if options.key?(:partial) + [render_partial(context, options)] + else + StreamingTemplateRenderer.new(@lookup_context).render(context, options) + end + end + + # Direct accessor to template rendering. + def render_template(context, options) #:nodoc: + _template_renderer.render(context, options) + end + + # Direct access to partial rendering. + def render_partial(context, options, &block) #:nodoc: + _partial_renderer.render(context, options, block) + end + + private + + def _template_renderer #:nodoc: + @_template_renderer ||= TemplateRenderer.new(@lookup_context) + end + + def _partial_renderer #:nodoc: + @_partial_renderer ||= PartialRenderer.new(@lookup_context) + end + end +end diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index 03aab444f8..1ccf5a8ddb 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -4,50 +4,11 @@ require 'fiber' if defined?(Fiber) module ActionView - # Consider the following layout: - # - # <%= yield :header %> - # 2 - # <%= yield %> - # 5 - # <%= yield :footer %> - # - # And template: - # - # <%= provide :header, "1" %> - # 3 - # 4 - # <%= provide :footer, "6" %> - # - # It will stream: - # - # "1\n", "2\n", "3\n4\n", "5\n", "6\n" - # - # Notice that once you <%= yield %>, it will render the whole template - # before streaming again. In the future, we can also support streaming - # from the template and not only the layout. - # - # Also, notice we use +provide+ instead of +content_for+, as +provide+ - # gives the control back to the layout as soon as it is called. - # With +content_for+, it would render all the template to find all - # +content_for+ calls. For instance, consider this layout: - # - # <%= yield :header %> - # - # With this template: - # - # <%= content_for :header, "1" %> - # <%= provide :header, "2" %> - # <%= provide :header, "3" %> - # - # It will return "12\n" because +content_for+ continues rendering the - # template but it is returns back to the layout as soon as it sees the - # first +provide+. - # # == TODO # # * Support streaming from child templates, partials and so on. # * Integrate exceptions with exceptron + # * Rack::Cache needs to support streaming bodies class StreamingTemplateRenderer < TemplateRenderer #:nodoc: # A valid Rack::Body (i.e. it responds to each). # It is initialized with a block that, when called, starts @@ -60,11 +21,26 @@ module ActionView def each(&block) begin @start.call(block) - rescue + rescue Exception => exception + log_error(exception) block.call ActionView::Base.streaming_completion_on_exception end self end + + private + + # This is the same logging logic as in ShowExceptions middleware. + # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. + def log_error(exception) #:nodoc: + logger = ActionController::Base.logger + return unless logger + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end end # For streaming, instead of rendering a given a template, we return a Body @@ -106,7 +82,7 @@ module ActionView # Set the view flow to support streaming. It will be aware # when to stop rendering the layout because it needs to search # something in the template and vice-versa. - view._view_flow = StreamingFlow.new(view, fiber) + view.view_flow = StreamingFlow.new(view, fiber) # Yo! Start the fiber! fiber.resume @@ -118,7 +94,7 @@ module ActionView content = template.render(view, locals, &yielder) # Once rendering the template is done, sets its content in the :layout key. - view._view_flow.set(:layout, content) + view.view_flow.set(:layout, content) # In case the layout continues yielding, we need to resume # the fiber until all yields are handled. diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 6b5ead463f..a09cef8fef 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -3,7 +3,9 @@ require 'active_support/core_ext/array/wrap' module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: - def render(options) + def render(context, options) + @view = context + wrap_formats(options[:template] || options[:file]) do template = determine_template(options) freeze_formats(template.formats, true) @@ -18,7 +20,7 @@ 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[:prefixes], false, keys) } + with_fallbacks { find_template(options[:file], nil, false, keys) } elsif options.key?(:inline) handler = Template.handler_for_extension(options[:type] || "erb") Template.new(options[:inline], "inline template", handler, :locals => keys) @@ -46,7 +48,7 @@ module ActionView if layout view = @view - view._view_flow.set(:layout, content) + view.view_flow.set(:layout, content) layout.render(view, locals){ |*name| view._layout_for(*name) } else content diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb deleted file mode 100644 index 2bce2fb045..0000000000 --- a/actionpack/lib/action_view/rendering.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'active_support/core_ext/object/try' - -module ActionView - # = Action View Rendering - module Rendering - # Returns the result of a render that's dictated by the options hash. The primary options are: - # - # * <tt>:partial</tt> - See ActionView::Partials. - # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. - # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. - # * <tt>:text</tt> - Renders the text passed in out. - # - # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter - # as the locals hash. - def render(options = {}, locals = {}, &block) - case options - when Hash - if block_given? - _render_partial(options.merge(:partial => options[:layout]), &block) - elsif options.key?(:partial) - _render_partial(options) - else - _render_template(options) - end - else - _render_partial(:partial => options, :locals => locals) - end - end - - # Render but returns a valid Rack body. If fibers are defined, we return - # a streaming body that renders the template piece by piece. - # - # Note that partials are not supported to be rendered with streaming, - # so in such cases, we just wrap them in an array. - def render_body(options) - if options.key?(:partial) - [_render_partial(options)] - else - StreamingTemplateRenderer.new(self).render(options) - end - end - - # Returns the contents that are yielded to a layout, given a name or a block. - # - # You can think of a layout as a method that is called with a block. If the user calls - # <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>. - # If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>. - # - # The user can override this default by passing a block to the layout: - # - # # The template - # <%= render :layout => "my_layout" do %> - # Content - # <% end %> - # - # # The layout - # <html> - # <%= yield %> - # </html> - # - # In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>, - # this method returns the block that was passed in to <tt>render :layout</tt>, and the response - # would be - # - # <html> - # Content - # </html> - # - # Finally, the block can take block arguments, which can be passed in by +yield+: - # - # # The template - # <%= render :layout => "my_layout" do |customer| %> - # Hello <%= customer.name %> - # <% end %> - # - # # The layout - # <html> - # <%= yield Struct.new(:name).new("David") %> - # </html> - # - # In this case, the layout would receive the block passed into <tt>render :layout</tt>, - # and the struct specified would be passed into the block as an argument. The result - # would be - # - # <html> - # Hello David - # </html> - # - def _layout_for(*args) - name = args.first - name = :layout unless name.is_a?(Symbol) - @_view_flow.get(name).html_safe - end - - # Handle layout for calls from partials that supports blocks. - def _block_layout_for(*args, &block) - name = args.first - - if !name.is_a?(Symbol) && block - capture(*args, &block) - else - _layout_for(*args) - end - end - - def _render_template(options) #:nodoc: - _template_renderer.render(options) - end - - def _template_renderer #:nodoc: - @_template_renderer ||= TemplateRenderer.new(self) - end - end -end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 6dfc4f68ae..98ecd15aa0 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -139,15 +139,12 @@ module ActionView # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. def render(view, locals, buffer=nil, &block) - old_template, view._template = view._template, self ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do compile!(view) view.send(method_name, locals, buffer, &block) end rescue Exception => e handle_render_error(view, e) - ensure - view._template = old_template end def mime_type @@ -174,12 +171,7 @@ module ActionView end def inspect - @inspect ||= - if defined?(Rails.root) - identifier.sub("#{Rails.root}/", '') - else - identifier - end + @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier end protected @@ -264,9 +256,9 @@ module ActionView # encoding of the code source = <<-end_src def #{method_name}(local_assigns, output_buffer) - _old_output_buffer = @output_buffer;#{locals_code};#{code} + _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} ensure - @output_buffer = _old_output_buffer + @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 5c74bf843a..d0317a148b 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,4 +1,6 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/remove_method' require 'action_controller' require 'action_controller/test_case' require 'action_view' @@ -43,6 +45,7 @@ module ActionView include AbstractController::Helpers include ActionView::Helpers + delegate :lookup_context, :to => :controller attr_accessor :controller, :output_buffer, :rendered module ClassMethods @@ -127,7 +130,7 @@ module ActionView def say_no_to_protect_against_forgery! _helpers.module_eval do - remove_method :protect_against_forgery? if method_defined?(:protect_against_forgery?) + remove_possible_method :protect_against_forgery? def protect_against_forgery? false end @@ -147,9 +150,19 @@ module ActionView module Locals attr_accessor :locals - def _render_partial(options) - locals[options[:partial]] = options[:locals] - super(options) + def render(options = {}, local_assigns = {}) + case options + when Hash + if block_given? + locals[options[:layout]] = options[:locals] + elsif options.key?(:partial) + locals[options[:partial]] = options[:locals] + end + else + locals[options] = local_assigns + end + + super end end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index ddfa3df552..5d7a51e902 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -4,6 +4,8 @@ require "action_controller/log_subscriber" module Another class LogSubscribersController < ActionController::Base + wrap_parameters :person, :only => :name, :format => :json + def show render :nothing => true end @@ -95,6 +97,15 @@ class ACLogSubscriberTest < ActionController::TestCase assert_equal 'Parameters: {"id"=>"10"}', logs[1] end + def test_process_action_with_wrapped_parameters + @request.env['CONTENT_TYPE'] = 'application/json' + post :show, :id => '10', :name => 'jose' + wait + + assert_equal 3, logs.size + assert_match '"person"=>{"name"=>"jose"}', logs[1] + end + def test_process_action_with_view_runtime get :show wait diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb new file mode 100644 index 0000000000..f41b14d5d6 --- /dev/null +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -0,0 +1,54 @@ +require 'abstract_unit' + +# This is testing the decoupling of view renderer and view context +# by allowing the controller to be used as view context. This is +# similar to the way sinatra renders templates. +module RenderContext + class BasicController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>", + "layouts/basic.html.erb" => "?<%= yield %>?" + )] + + # 1) Include ActionView::Context to bring the required dependencies + include ActionView::Context + + # 2) Call _prepare_context that will do the required initialization + before_filter :_prepare_context + + def hello_world + @value = "Hello" + render :action => "hello_world", :layout => false + end + + def with_layout + @value = "Hello" + render :action => "hello_world", :layout => "basic" + end + + protected + + # 3) Set view_context to self + def view_context + self + end + + def __controller_method__ + "controller context!" + end + end + + class RenderContextTest < Rack::TestCase + test "rendering using the controller as context" do + get "/render_context/basic/hello_world" + assert_body "Hello from controller context!" + assert_status 200 + end + + test "rendering using the controller as context with layout" do + get "/render_context/basic/with_layout" + assert_body "?Hello from controller context!?" + assert_status 200 + end + end +end diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 3bb3016fdb..1e2191d417 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -33,10 +33,10 @@ module RenderImplicitAction assert_status 200 end - test "action_method? returns true for implicit actions" do - assert SimpleController.new.action_method?(:hello_world) - assert SimpleController.new.action_method?(:"hyphen-ated") - assert SimpleController.new.action_method?(:not_implemented) + test "available_action? returns true for implicit actions" do + assert SimpleController.new.available_action?(:hello_world) + assert SimpleController.new.available_action?(:"hyphen-ated") + assert SimpleController.new.available_action?(:not_implemented) end end end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index fed8d40b47..48cf0ab9cb 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -83,6 +83,19 @@ module RenderStreaming assert_streaming! end + test "rendering with template exception logs the exception" do + io = StringIO.new + _old, ActionController::Base.logger = ActionController::Base.logger, Logger.new(io) + + begin + get "/render_streaming/basic/template_exception" + io.rewind + assert_match "(undefined method `invalid!' for nil:NilClass)", io.read + ensure + ActionController::Base.logger = _old + end + end + test "do not stream on HTTP/1.0" do get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" assert_body "Hello world, I'm here!" diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb new file mode 100644 index 0000000000..548cd02dc0 --- /dev/null +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -0,0 +1,246 @@ +require 'abstract_unit' + +module Admin; class User; end; end + +class ParamsWrapperTest < ActionController::TestCase + class UsersController < ActionController::Base + class << self + attr_accessor :last_parameters + end + + def parse + self.class.last_parameters = request.params.except(:controller, :action) + head :ok + end + end + + class User; end + class Person; end + + tests UsersController + + def teardown + UsersController.last_parameters = nil + end + + def test_derived_name_from_controller + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_specify_wrapper_name + with_default_wrapper_options do + UsersController.wrap_parameters :person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) + end + end + + def test_specify_wrapper_model + with_default_wrapper_options do + UsersController.wrap_parameters Person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) + end + end + + def test_specify_only_option + with_default_wrapper_options do + UsersController.wrap_parameters :only => :username + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_specify_except_option + with_default_wrapper_options do + UsersController.wrap_parameters :except => :title + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_specify_both_wrapper_name_and_only_option + with_default_wrapper_options do + UsersController.wrap_parameters :person, :only => :username + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) + end + end + + def test_not_enabled_format + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/xml' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) + end + end + + def test_wrap_parameters_false + with_default_wrapper_options do + UsersController.wrap_parameters false + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) + end + end + + def test_specify_format + with_default_wrapper_options do + UsersController.wrap_parameters :format => :xml + + @request.env['CONTENT_TYPE'] = 'application/xml' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) + end + end + + def test_not_wrap_reserved_parameters + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } + assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_no_double_wrap_if_key_exists + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'user' => { 'username' => 'sikachu' }} + assert_parameters({ 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_nested_params + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'person' => { 'username' => 'sikachu' }} + assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}}) + end + end + + def test_derived_wrapped_keys_from_matching_model + User.expects(:respond_to?).with(:column_names).returns(true) + User.expects(:column_names).returns(["username"]) + + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_derived_wrapped_keys_from_specified_model + with_default_wrapper_options do + Person.expects(:respond_to?).with(:column_names).returns(true) + Person.expects(:column_names).returns(["username"]) + + UsersController.wrap_parameters Person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) + end + end + + private + def with_default_wrapper_options(&block) + @controller.class._wrapper_options = {:format => [:json]} + @controller.class.inherited(@controller.class) + yield + end + + def assert_parameters(expected) + assert_equal expected, UsersController.last_parameters + end +end + +class NamespacedParamsWrapperTest < ActionController::TestCase + module Admin + class UsersController < ActionController::Base + class << self + attr_accessor :last_parameters + end + + def parse + self.class.last_parameters = request.params.except(:controller, :action) + head :ok + end + end + end + + class Sample + def self.column_names + ["username"] + end + end + + tests Admin::UsersController + + def teardown + Admin::UsersController.last_parameters = nil + end + + def test_derived_name_from_controller + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu' } + assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) + end + end + + def test_namespace_lookup_from_model + Admin.const_set(:User, Class.new(Sample)) + begin + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + ensure + Admin.send :remove_const, :User + end + end + + def test_hierarchy_namespace_lookup_from_model + # Make sure that we cleanup ::Admin::User + admin_user_constant = ::Admin::User + ::Admin.send :remove_const, :User + + Object.const_set(:User, Class.new(Sample)) + begin + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + ensure + Object.send :remove_const, :User + ::Admin.const_set(:User, admin_user_constant) + end + end + + private + def with_default_wrapper_options(&block) + @controller.class._wrapper_options = {:format => [:json]} + @controller.class.inherited(@controller.class) + yield + end + + def assert_parameters(expected) + assert_equal expected, Admin::UsersController.last_parameters + end +end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 18cf944f46..aa9d193436 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -92,6 +92,12 @@ class LegacyRouteSetTests < Test::Unit::TestCase @rs.clear! end + def test_draw_with_block_arity_one_raises + assert_raise(RuntimeError) do + @rs.draw { |map| map.match '/:controller(/:action(/:id))' } + end + end + def test_default_setup @rs.draw { match '/:controller(/:action(/:id))' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 42356be1ea..3de1849db8 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -179,4 +179,8 @@ class ViewLoadPathsTest < ActionController::TestCase assert_nothing_raised { C.append_view_path 'c/path' } assert_paths C, "c/path" end + + def test_lookup_context_accessor + assert_equal ["test"], TestController.new.lookup_context.prefixes + end end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index 34db7a4c66..d854d55173 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -63,3 +63,56 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest end end end + +class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest + class UsersController < ActionController::Base + wrap_parameters :format => :json + + class << self + attr_accessor :last_request_parameters, :last_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + self.class.last_parameters = params + head :ok + end + end + + def teardown + UsersController.last_request_parameters = nil + end + + test "parses json params for application json" do + assert_parses( + {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, + "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + + test "parses json params for application jsonrequest" do + assert_parses( + {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, + "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/jsonrequest' } + ) + end + + private + def assert_parses(expected, actual, headers = {}) + with_test_routing(UsersController) do + post "/parse", actual, headers + assert_response :ok + assert_equal(expected, UsersController.last_request_parameters) + assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters) + end + end + + def with_test_routing(controller) + with_routing do |set| + set.draw do + match ':action', :to => controller + end + yield + end + end +end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 3ff558ec5a..560ea00923 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -82,21 +82,15 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest assert_equal 19512, file.size end - # Pending fix in Rack 1.2.2 - # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload test "parses mixed files" do - if Rack.release <= '1.2.1' - $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2' - else - params = parse_multipart('mixed_files') - assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] - - # Rack doesn't handle multipart/mixed for us. - files = params['files'] - files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) - assert_equal 19756, files.size - end + params = parse_multipart('mixed_files') + assert_equal %w(files foo), params.keys.sort + assert_equal 'bar', params['foo'] + + # Rack doesn't handle multipart/mixed for us. + files = params['files'] + files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + assert_equal 19756, files.size end test "does not create tempfile if no file has been selected" do diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index ad9de02eb4..38453dfe48 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -115,3 +115,41 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest {'HTTP_X_POST_DATA_FORMAT' => 'xml'} end end + +class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + wrap_parameters :person, :format => :xml + + class << self + attr_accessor :last_request_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + end + + def teardown + TestController.last_request_parameters = nil + end + + test "parses hash params" do + with_test_routing do + xml = "<name>David</name>" + post "/parse", xml, {'CONTENT_TYPE' => 'application/xml'} + assert_response :ok + assert_equal({"name" => "David", "person" => {"name" => "David"}}, TestController.last_request_parameters) + end + end + + private + def with_test_routing + with_routing do |set| + set.draw do + match ':action', :to => ::RootLessXmlParamsParsingTest::TestController + end + yield + end + end +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f03ae7f2b3..d128006404 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -137,30 +137,39 @@ class RequestTest < ActiveSupport::TestCase test "subdomains" do request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" assert_equal %w( www ), request.subdomains + assert_equal "www", request.subdomain request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" assert_equal %w( www ), request.subdomains(2) + assert_equal "www", request.subdomain(2) request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" assert_equal %w( dev www ), request.subdomains(2) + assert_equal "dev.www", request.subdomain(2) request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 assert_equal %w( dev www ), request.subdomains + assert_equal "dev.www", request.subdomain request = stub_request 'HTTP_HOST' => "foobar.foobar.com" assert_equal %w( foobar ), request.subdomains + assert_equal "foobar", request.subdomain request = stub_request 'HTTP_HOST' => "192.168.1.200" assert_equal [], request.subdomains + assert_equal "", request.subdomain request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" assert_equal [], request.subdomains + assert_equal "", request.subdomain request = stub_request 'HTTP_HOST' => "192.168.1.200.com" assert_equal %w( 192 168 1 ), request.subdomains + assert_equal "192.168.1", request.subdomain request = stub_request 'HTTP_HOST' => nil assert_equal [], request.subdomains + assert_equal "", request.subdomain end test "standard_port" do @@ -452,6 +461,40 @@ class RequestTest < ActiveSupport::TestCase assert request.formats.empty? end + test "ignore_accept_header" do + ActionDispatch::Request.ignore_accept_header = true + + begin + request = stub_request 'HTTP_ACCEPT' => 'application/xml' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/jxw' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/xml', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::JS ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/xml', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({:format => :json}) + assert_equal [ Mime::JSON ], request.formats + ensure + ActionDispatch::Request.ignore_accept_header = false + end + end + test "negotiate_mime" do request = stub_request 'HTTP_ACCEPT' => 'text/html', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 27f55fd7ab..b0efbcef4a 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -50,6 +50,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest get_session_id end + def renew_session_id + request.session_options[:renew] = true + head :ok + end + def rescue_action(e) raise end end @@ -102,6 +107,17 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end end + def test_properly_renew_cookies + with_test_route_set do + get '/set_session_value' + get '/persistent_session_id' + session_id = response.body + get '/renew_session_id' + get '/persistent_session_id' + assert_not_equal response.body, session_id + end + end + def test_does_set_secure_cookies_over_https with_test_route_set(:secure => true) do get '/set_session_value', nil, 'HTTPS' => 'on' diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index e453dd11ce..42f6c7f79f 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -131,10 +131,17 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest 'action_dispatch.request.parameters' => { 'action' => 'show', 'id' => 'unknown', - 'controller' => 'featured_tiles' + 'controller' => 'featured_tile' } }) assert_response 500 - assert_match(/RuntimeError\n in FeaturedTilesController/, body) + assert_match(/RuntimeError\n in FeaturedTileController/, body) + end + + test "sets the HTTP charset parameter" do + @app = DevelopmentApp + + get "/", {}, {'action_dispatch.show_exceptions' => true} + assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] end end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 2ebbed4414..9f3cbd19ef 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -5,6 +5,12 @@ module StaticTests assert_equal "Hello, World!", get("/nofile").body end + def test_sets_cache_control + response = get("/index.html") + assert_html "/index.html", response + assert_equal "public, max-age=60", response.headers["Cache-Control"] + end + def test_serves_static_index_at_root assert_html "/index.html", get("/index.html") assert_html "/index.html", get("/index") @@ -40,7 +46,7 @@ class StaticTest < ActiveSupport::TestCase DummyApp = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] } - App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public") + App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60") def setup @app = App diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index a9a36e6e6b..592c7da060 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase def setup super @av = ActionView::Base.new - @_view_flow = ActionView::OutputFlow.new + @view_flow = ActionView::OutputFlow.new end def test_capture_captures_the_temporary_output_buffer_in_its_block @@ -49,14 +49,14 @@ class CaptureHelperTest < ActionView::TestCase assert !content_for?(:title) provide :title, "hi" assert content_for?(:title) - assert_equal "hi", @_view_flow.get(:title) + assert_equal "hi", @view_flow.get(:title) provide :title, "<p>title</p>" - assert_equal "hi<p>title</p>", @_view_flow.get(:title) + assert_equal "hi<p>title</p>", @view_flow.get(:title) - @_view_flow = ActionView::OutputFlow.new + @view_flow = ActionView::OutputFlow.new provide :title, "hi" provide :title, "<p>title</p>".html_safe - assert_equal "hi<p>title</p>", @_view_flow.get(:title) + assert_equal "hi<p>title</p>", @view_flow.get(:title) end def test_with_output_buffer_swaps_the_output_buffer_given_no_argument diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 12d2410f49..3dd400026c 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -121,6 +121,10 @@ class DateHelperTest < ActionView::TestCase start_date = Date.new 1975, 1, 31 end_date = Date.new 1977, 1, 31 assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date)) + + start_date = Date.new 1982, 12, 3 + end_date = Date.new 2010, 11, 30 + assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date)) end def test_distance_in_words_with_integers diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 656fa0356b..f95308b847 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -200,12 +200,18 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end - def test_select_tag_with_include_blank_with_string - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => "string" + def test_select_tag_with_prompt + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end + def test_select_tag_with_prompt_and_include_blank + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string", :include_blank => true + expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>) + assert_dom_equal expected, actual + end + def test_text_area_tag_size_string actual = text_area_tag "body", "hello world", "size" => "20x40" expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>) diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb index 8b8b005a1d..50e1cccd3b 100644 --- a/actionpack/test/template/log_subscriber_test.rb +++ b/actionpack/test/template/log_subscriber_test.rb @@ -9,7 +9,9 @@ class AVLogSubscriberTest < ActiveSupport::TestCase def setup super @old_logger = ActionController::Base.logger - @view = ActionView::Base.new(ActionController::Base.view_paths, {}) + @controller = Object.new + @controller.stubs(:_prefixes).returns(%w(test)) + @view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) ActionView::LogSubscriber.attach_to :action_view end @@ -57,7 +59,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_partial_with_implicit_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render(Customer.new("david"), :greeting => "hi") wait @@ -74,7 +75,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_collection_with_implicit_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") wait @@ -83,7 +83,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_collection_template_without_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") wait diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index ff94cba59f..5fb1fdc044 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -180,6 +180,12 @@ class LookupContextTest < ActiveSupport::TestCase assert_not_equal template, old_template end + + test "responds to #prefixes" do + assert_equal [], @lookup_context.prefixes + @lookup_context.prefixes = ["foo"] + assert_equal ["foo"], @lookup_context.prefixes + end end class LookupContextWithFalseCaching < ActiveSupport::TestCase diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb index 4d69081570..b2df8efee3 100644 --- a/actionpack/test/template/streaming_render_test.rb +++ b/actionpack/test/template/streaming_render_test.rb @@ -13,8 +13,12 @@ class FiberedTest < ActiveSupport::TestCase @controller_view = TestController.new.view_context end + def render_body(options) + @view.view_renderer.render_body(@view, options) + end + def buffered_render(options) - body = @view.render_body(options) + body = render_body(options) string = "" body.each do |piece| string << piece @@ -24,7 +28,7 @@ class FiberedTest < ActiveSupport::TestCase def test_streaming_works content = [] - body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield") + body = render_body(:template => "test/hello_world.erb", :layout => "layouts/yield") body.each do |piece| content << piece diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 5c655d5b69..81fb34b80f 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -11,11 +11,9 @@ class TestERBTemplate < ActiveSupport::TestCase end class Context - attr_accessor :_template - def initialize @output_buffer = "original" - @_virtual_path = nil + @virtual_path = nil end def hello @@ -24,7 +22,7 @@ class TestERBTemplate < ActiveSupport::TestCase def partial ActionView::Template.new( - "<%= @_template.virtual_path %>", + "<%= @virtual_path %>", "partial", ERBHandler, :virtual_path => "partial" @@ -86,9 +84,9 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_virtual_path - @template = new_template("<%= @_template.virtual_path %>" \ + @template = new_template("<%= @virtual_path %>" \ "<%= partial.render(self, {}) %>" \ - "<%= @_template.virtual_path %>") + "<%= @virtual_path %>") assert_equal "hellopartialhello", render end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 11c355dc6d..cd4618a505 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -73,6 +73,10 @@ module ActionView view.request.flash.expects(:alert).with("this message") view.alert("this message") end + + test "uses controller lookup context" do + assert_equal self.lookup_context, @controller.lookup_context + end end class ClassMethodsTest < ActionView::TestCase diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index a4fcff5167..740f577a6e 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -278,260 +278,6 @@ class TextHelperTest < ActionView::TestCase assert_equal("12 berries", pluralize(12, "berry")) end - def test_auto_link_parsing - urls = %w( - http://www.rubyonrails.com - http://www.rubyonrails.com:80 - http://www.rubyonrails.com/~minam - https://www.rubyonrails.com/~minam - http://www.rubyonrails.com/~minam/url%20with%20spaces - http://www.rubyonrails.com/foo.cgi?something=here - http://www.rubyonrails.com/foo.cgi?something=here&and=here - http://www.rubyonrails.com/contact;new - http://www.rubyonrails.com/contact;new%20with%20spaces - http://www.rubyonrails.com/contact;new?with=query&string=params - http://www.rubyonrails.com/~minam/contact;new?with=query&string=params - http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007 - http://www.mail-archive.com/rails@lists.rubyonrails.org/ - http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1 - http://en.wikipedia.org/wiki/Texas_hold'em - https://www.google.com/doku.php?id=gps:resource:scs:start - http://connect.oraclecorp.com/search?search[q]=green+france&search[type]=Group - http://of.openfoundry.org/projects/492/download#4th.Release.3 - http://maps.google.co.uk/maps?f=q&q=the+london+eye&ie=UTF8&ll=51.503373,-0.11939&spn=0.007052,0.012767&z=16&iwloc=A - ) - - urls.each do |url| - assert_equal generate_result(url), auto_link(url) - end - end - - def generate_result(link_text, href = nil, escape = false) - href ||= link_text - if escape - %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>} - else - %{<a href="#{href}">#{link_text}</a>} - end - end - - def test_auto_link_should_not_be_html_safe - email_raw = 'santiago@wyeworks.com' - link_raw = 'http://www.rubyonrails.org' - - assert !auto_link(nil).html_safe?, 'should not be html safe' - assert !auto_link('').html_safe?, 'should not be html safe' - assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe' - assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe' - end - - def test_auto_link_email_address - email_raw = 'aaron@tenderlovemaking.com' - email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>} - assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe' - end - - def test_auto_link - email_raw = 'david@loudthinking.com' - email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>} - link_raw = 'http://www.rubyonrails.com' - link_result = generate_result(link_raw) - link_result_with_options = %{<a href="#{link_raw}" target="_blank">#{link_raw}</a>} - - assert_equal '', auto_link(nil) - assert_equal '', auto_link('') - assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}") - - assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses) - assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls) - assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses) - assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}") - assert_equal %(<p>Link #{link_result}</p>), auto_link("<p>Link #{link_raw}</p>") - assert_equal %(<p>#{link_result} Link</p>), auto_link("<p>#{link_raw} Link</p>") - assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"}) - assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.)) - assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>)) - assert_equal %(#{link_result} #{link_result}), auto_link(%(#{link_result} #{link_raw})) - - email2_raw = '+david@loudthinking.com' - email2_result = %{<a href="mailto:#{email2_raw}">#{email2_raw}</a>} - assert_equal email2_result, auto_link(email2_raw) - - email3_raw = '+david@loudthinking.com' - email3_result = %{<a href="mailto:+%64%61%76%69%64@%6c%6f%75%64%74%68%69%6e%6b%69%6e%67.%63%6f%6d">#{email3_raw}</a>} - assert_equal email3_result, auto_link(email3_raw, :all, :encode => :hex) - assert_equal email3_result, auto_link(email3_raw, :email_addresses, :encode => :hex) - - link2_raw = 'www.rubyonrails.com' - link2_result = generate_result(link2_raw, "http://#{link2_raw}") - assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls) - assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses) - assert_equal %(<p>Link #{link2_result}</p>), auto_link("<p>Link #{link2_raw}</p>") - assert_equal %(<p>#{link2_result} Link</p>), auto_link("<p>#{link2_raw} Link</p>") - assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.)) - assert_equal %(<p>Say hello to #{email_result}, then go to #{link2_result}.</p>), auto_link(%(<p>Say hello to #{email_raw}, then go to #{link2_raw}.</p>)) - - link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281' - link3_result = generate_result(link3_raw) - assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls) - assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses) - assert_equal %(<p>Link #{link3_result}</p>), auto_link("<p>Link #{link3_raw}</p>") - assert_equal %(<p>#{link3_result} Link</p>), auto_link("<p>#{link3_raw} Link</p>") - assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.)) - assert_equal %(<p>Go to #{link3_result}. Seriously, #{link3_result}? I think I'll say hello to #{email_result}. Instead.</p>), - auto_link(%(<p>Go to #{link3_raw}. Seriously, #{link3_raw}? I think I'll say hello to #{email_raw}. Instead.</p>)) - - link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123' - link4_result = generate_result(link4_raw) - assert_equal %(<p>Link #{link4_result}</p>), auto_link("<p>Link #{link4_raw}</p>") - assert_equal %(<p>#{link4_result} Link</p>), auto_link("<p>#{link4_raw} Link</p>") - - link5_raw = 'http://foo.example.com:3000/controller/action' - link5_result = generate_result(link5_raw) - assert_equal %(<p>#{link5_result} Link</p>), auto_link("<p>#{link5_raw} Link</p>") - - link6_raw = 'http://foo.example.com:3000/controller/action+pack' - link6_result = generate_result(link6_raw) - assert_equal %(<p>#{link6_result} Link</p>), auto_link("<p>#{link6_raw} Link</p>") - - link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123' - link7_result = generate_result(link7_raw) - assert_equal %(<p>#{link7_result} Link</p>), auto_link("<p>#{link7_raw} Link</p>") - - link8_raw = 'http://foo.example.com:3000/controller/action.html' - link8_result = generate_result(link8_raw) - assert_equal %(Go to #{link8_result}), auto_link("Go to #{link8_raw}", :urls) - assert_equal %(Go to #{link8_raw}), auto_link("Go to #{link8_raw}", :email_addresses) - assert_equal %(<p>Link #{link8_result}</p>), auto_link("<p>Link #{link8_raw}</p>") - assert_equal %(<p>#{link8_result} Link</p>), auto_link("<p>#{link8_raw} Link</p>") - assert_equal %(Go to #{link8_result}.), auto_link(%(Go to #{link8_raw}.)) - assert_equal %(<p>Go to #{link8_result}. Seriously, #{link8_result}? I think I'll say hello to #{email_result}. Instead.</p>), - auto_link(%(<p>Go to #{link8_raw}. Seriously, #{link8_raw}? I think I'll say hello to #{email_raw}. Instead.</p>)) - - link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html' - link9_result = generate_result(link9_raw) - assert_equal %(Go to #{link9_result}), auto_link("Go to #{link9_raw}", :urls) - assert_equal %(Go to #{link9_raw}), auto_link("Go to #{link9_raw}", :email_addresses) - assert_equal %(<p>Link #{link9_result}</p>), auto_link("<p>Link #{link9_raw}</p>") - assert_equal %(<p>#{link9_result} Link</p>), auto_link("<p>#{link9_raw} Link</p>") - assert_equal %(Go to #{link9_result}.), auto_link(%(Go to #{link9_raw}.)) - assert_equal %(<p>Go to #{link9_result}. Seriously, #{link9_result}? I think I'll say hello to #{email_result}. Instead.</p>), - auto_link(%(<p>Go to #{link9_raw}. Seriously, #{link9_raw}? I think I'll say hello to #{email_raw}. Instead.</p>)) - - link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/' - link10_result = generate_result(link10_raw) - assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>") - - link11_raw = 'http://asakusa.rubyist.net/' - link11_result = generate_result(link11_raw) - with_kcode 'u' do - assert_equal %(浅草.rbの公式サイトはこちら#{link11_result}), auto_link("浅草.rbの公式サイトはこちら#{link11_raw}") - end - end - - def test_auto_link_should_sanitize_input_when_sanitize_option_is_not_false - link_raw = %{http://www.rubyonrails.com?id=1&num=2} - assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw) - end - - def test_auto_link_should_not_sanitize_input_when_sanitize_option_is_false - link_raw = %{http://www.rubyonrails.com?id=1&num=2} - assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :sanitize => false) - end - - def test_auto_link_other_protocols - ftp_raw = 'ftp://example.com/file.txt' - assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}") - - file_scheme = 'file:///home/username/RomeoAndJuliet.pdf' - z39_scheme = 'z39.50r://host:696/db' - chrome_scheme = 'chrome://package/section/path' - view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme' - assert_equal generate_result(file_scheme), auto_link(file_scheme) - assert_equal generate_result(z39_scheme), auto_link(z39_scheme) - assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme) - assert_equal generate_result(view_source), auto_link(view_source) - end - - def test_auto_link_already_linked - linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com') - linked2 = %('<a href="http://www.example.com">www.example.com</a>') - linked3 = %('<a href="http://www.example.com" rel="nofollow">www.example.com</a>') - linked4 = %('<a href="http://www.example.com"><b>www.example.com</b></a>') - linked5 = %('<a href="#close">close</a> <a href="http://www.example.com"><b>www.example.com</b></a>') - assert_equal linked1, auto_link(linked1) - assert_equal linked2, auto_link(linked2) - assert_equal linked3, auto_link(linked3) - assert_equal linked4, auto_link(linked4) - assert_equal linked5, auto_link(linked5) - - linked_email = %Q(<a href="mailto:david@loudthinking.com">Mail me</a>) - assert_equal linked_email, auto_link(linked_email) - end - - def test_auto_link_within_tags - link_raw = 'http://www.rubyonrails.org/images/rails.png' - link_result = %Q(<img src="#{link_raw}" />) - assert_equal link_result, auto_link(link_result) - end - - def test_auto_link_with_brackets - link1_raw = 'http://en.wikipedia.org/wiki/Sprite_(computer_graphics)' - link1_result = generate_result(link1_raw) - assert_equal link1_result, auto_link(link1_raw) - assert_equal "(link: #{link1_result})", auto_link("(link: #{link1_raw})") - - link2_raw = 'http://en.wikipedia.org/wiki/Sprite_[computer_graphics]' - link2_result = generate_result(link2_raw) - assert_equal link2_result, auto_link(link2_raw) - assert_equal "[link: #{link2_result}]", auto_link("[link: #{link2_raw}]") - - link3_raw = 'http://en.wikipedia.org/wiki/Sprite_{computer_graphics}' - link3_result = generate_result(link3_raw) - assert_equal link3_result, auto_link(link3_raw) - assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") - end - - def test_auto_link_at_eol - url1 = "http://api.rubyonrails.com/Foo.html" - url2 = "http://www.ruby-doc.org/core/Bar.html" - - assert_equal %(<p><a href="#{url1}">#{url1}</a><br /><a href="#{url2}">#{url2}</a><br /></p>), auto_link("<p>#{url1}<br />#{url2}<br /></p>") - end - - def test_auto_link_with_block - url = "http://api.rubyonrails.com/Foo.html" - email = "fantabulous@shiznadel.ic" - - assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |_url| truncate(_url, :length => 10) } - end - - def test_auto_link_with_block_with_html - pic = "http://example.com/pic.png" - url = "http://example.com/album?a&b=c" - - assert_equal %(My pic: <a href="#{pic}"><img src="#{pic}" width="160px"></a> -- full album here #{generate_result(url)}), auto_link("My pic: #{pic} -- full album here #{url}") { |link| - if link =~ /\.(jpg|gif|png|bmp|tif)$/i - raw %(<img src="#{link}" width="160px">) - else - link - end - } - end - - def test_auto_link_with_options_hash - assert_dom_equal 'Welcome to my new blog at <a href="http://www.myblog.com/" class="menu" target="_blank">http://www.myblog.com/</a>. Please e-mail me at <a href="mailto:me@email.com" class="menu" target="_blank">me@email.com</a>.', - auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.", - :link => :all, :html => { :class => "menu", :target => "_blank" }) - end - - def test_auto_link_with_multiple_trailing_punctuations - url = "http://youtube.com" - url_result = generate_result(url) - assert_equal url_result, auto_link(url) - assert_equal "(link: #{url_result}).", auto_link("(link: #{url}).") - end - def test_cycle_class value = Cycle.new("one", 2, "3") assert_equal("one", value.to_s) diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index fc330f7a73..8d0f0124c2 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -9,7 +9,7 @@ class UrlHelperTest < ActiveSupport::TestCase # or request. # # In those cases, we'll set up a simple mock - attr_accessor :controller, :request, :_template + attr_accessor :controller, :request routes = ActionDispatch::Routing::RouteSet.new routes.draw do |