diff options
Diffstat (limited to 'actionpack/lib/action_view')
27 files changed, 586 insertions, 654 deletions
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 |