diff options
Diffstat (limited to 'actionpack')
35 files changed, 841 insertions, 520 deletions
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f771737779..0d667a76a7 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| 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-mount', '~> 0.7.1') + s.add_dependency('rack-mount', '~> 0.7.2') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 306bd41e2d..db6ff41f55 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -99,7 +99,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, self) end # Normalize arguments, options and then delegates render_to_body and @@ -127,7 +132,11 @@ module AbstractController # Find and renders a template based on the options given. # :api: private def _render_template(options) #:nodoc: - view_context.render(options) + if options.key?(:partial) + view_renderer.render_partial(view_context, options) + else + view_renderer.render_template(view_context, options) + end end # The prefixes used in render "foo" shortcuts. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index b9bd49f670..0dd847f967 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,7 +2,207 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Methods for sending streaming templates back to the client. + # Allow 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 first chunk of response is sent to the client + # just 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 at 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 + # @post = Post.scoped + # render :stream => true + # end + # end + # + # == When to use streaming + # + # Streaming may be considering an overkill for common actions like + # new or edit. The real benefit of streaming is on expensive actions + # that, for example, does 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 + # most of streaming, you would want to rewrite it as: + # + # def dashboard + # # Allow lazily execution of the query + # @posts = Post.scoped + # @pages = Page.scoped + # @articles = Article.scoped + # render :stream => true + # end + # + # == Communication between layout and template + # + # When streaming, the layout is rendered first than the template. + # This means that, if your application currently rely on 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 if 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 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 below, using +provide+: + # + # <%= provide :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # Has as final result: + # + # <html> + # <head><title>Main</title></head> + # <body>Hello</body> + # </html> + # + # That said, when streaming, you need to properly check your templates + # and chose 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 start rendering will not propagate + # to the client. + # + # If you try to modify cookies, session or flash, a 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. So, 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 :tcp_nodelay. 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 +251,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/test_case.rb b/actionpack/lib/action_controller/test_case.rb index bc4f8bb9ce..0085f542aa 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -147,7 +147,9 @@ module ActionController if value.is_a? Fixnum value = value.to_s elsif value.is_a? Array - value = Result.new(value) + value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v }) + elsif value.is_a? String + value = value.dup end if extra_keys.include?(key.to_sym) 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_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..ca0a89c8a5 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>) @@ -159,9 +158,10 @@ module ActionView #:nodoc: end end - attr_accessor :_template, :_view_flow - attr_internal :request, :controller, :config, :assigns, :lookup_context + attr_accessor :view_renderer + attr_internal :request, :controller, :config, :assigns + delegate :lookup_context, :to => :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, @@ -182,33 +182,35 @@ module ActionView #: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: + def initialize(context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: assign(assigns_for_first_render) self.helpers = Module.new unless self.class.helpers @_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) end - @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? - lookup_context : ActionView::LookupContext.new(lookup_context) - @_lookup_context.formats = formats if formats + # 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 + @view_renderer = ActionView::Renderer.new(lookup_context, controller) + end + + _prepare_context end + # TODO Is this needed anywhere? Maybe deprecate it? def controller_path @controller_path ||= controller && controller.controller_path end - def controller_prefixes - @controller_prefixes ||= controller && controller._prefixes - end - ActiveSupport.run_load_hooks(:action_view, self) end end 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..3ef9826d30 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -19,6 +19,7 @@ module ActionView #:nodoc: autoload :NumberHelper autoload :OutputSafetyHelper autoload :RecordTagHelper + autoload :RenderingHelper autoload :SanitizeHelper autoload :SprocketsHelper autoload :TagHelper @@ -48,6 +49,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_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/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 440acafa88..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. # @@ -947,7 +952,8 @@ module ActionView label_tag(name_and_id["id"], options, &block) else content = if text.blank? - I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence + method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name + I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence else text.to_s end @@ -1166,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/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index b545031fcc..63d13a0f0b 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -304,6 +304,7 @@ module ActionView # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to "."). # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) + # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary) # ==== Examples # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.21 KB @@ -341,15 +342,17 @@ module ActionView options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) + + base = options[:prefix] == :si ? 1000 : 1024 - if number.to_i < 1024 + if number.to_i < base unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true) storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe else max_exp = STORAGE_UNITS.size - 1 - exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024 + exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit - number /= 1024 ** exponent + number /= base ** exponent unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) 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..c91e03b7e8 --- /dev/null +++ b/actionpack/lib/action_view/helpers/rendering_helper.rb @@ -0,0 +1,92 @@ +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) + elsif options.key?(:partial) + view_renderer.render_partial(self, options) + else + view_renderer.render_template(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/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/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/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 4a52b3172e..d389105a7a 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -3,9 +3,9 @@ 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, controller) + @lookup_context = lookup_context + @controller = controller 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..70327b16c4 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[@controller.class.name] 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 + def render(context, options, block) + setup(context, options, block) - 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 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,42 @@ 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 controller_prefixes + @controller_prefixes ||= @controller && @controller._prefixes + end + def collection if @options.key?(:collection) collection = @options[:collection] @@ -109,7 +331,7 @@ module ActionView end def find_template(path=@path, locals=@locals.keys) - prefixes = path.include?(?/) ? [] : @view.controller_prefixes + prefixes = path.include?(?/) ? [] : controller_prefixes @lookup_context.find_template(path, prefixes, true, locals) end @@ -150,7 +372,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 = controller_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..3c0126f6bb --- /dev/null +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -0,0 +1,46 @@ +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, :controller + + def initialize(lookup_context, controller) + @lookup_context = lookup_context + @controller = controller + 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, @controller).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, @controller) + end + + def _partial_renderer #:nodoc: + @_partial_renderer ||= PartialRenderer.new(@lookup_context, @controller) + end + end +end
\ No newline at end of file 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..6c55a865a1 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) @@ -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..14e032790d 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -147,9 +147,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/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_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/test_test.rb b/actionpack/test/controller/test_test.rb index edda0d0a30..5896222a0a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -493,6 +493,18 @@ XML ) end + def test_params_passing_with_frozen_values + assert_nothing_raised do + get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze + end + parsed_params = eval(@response.body) + assert_equal( + {'controller' => 'test_test/test', 'action' => 'test_params', + 'frozen' => 'icy', 'frozens' => ['icy']}, + parsed_params + ) + end + def test_id_converted_to_string get :test_params, :id => 20, :foo => Object.new assert_kind_of String, @request.path_parameters['id'] 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/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 7afab3179c..c25c850eb3 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -24,7 +24,10 @@ class FormHelperTest < ActionView::TestCase :helpers => { :label => { :post => { - :body => "Write entire text here" + :body => "Write entire text here", + :color => { + :red => "Rojo" + } } } } @@ -141,6 +144,13 @@ class FormHelperTest < ActionView::TestCase I18n.locale = old_locale end + def test_label_with_locales_and_value + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, :value => "red")) + ensure + I18n.locale = old_locale + end + def test_label_with_for_attribute_as_symbol assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for")) end 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/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 23a7e17e65..63b92aadf4 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -172,6 +172,17 @@ class NumberHelperTest < ActionView::TestCase assert_equal '10 Bytes', number_to_human_size(10) end + def test_number_to_human_size_with_si_prefix + assert_equal '3 Bytes', number_to_human_size(3.14159265, :prefix => :si) + assert_equal '123 Bytes', number_to_human_size(123.0, :prefix => :si) + assert_equal '123 Bytes', number_to_human_size(123, :prefix => :si) + assert_equal '1.23 KB', number_to_human_size(1234, :prefix => :si) + assert_equal '12.3 KB', number_to_human_size(12345, :prefix => :si) + assert_equal '1.23 MB', number_to_human_size(1234567, :prefix => :si) + assert_equal '1.23 GB', number_to_human_size(1234567890, :prefix => :si) + assert_equal '1.23 TB', number_to_human_size(1234567890123, :prefix => :si) + end + def test_number_to_human_size_with_options_hash assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) 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/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 |