diff options
Diffstat (limited to 'actionview/lib/action_view')
34 files changed, 370 insertions, 272 deletions
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 1feafc1094..43124bb904 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -70,6 +70,14 @@ module ActionView #:nodoc: # Headline: <%= headline %> # First name: <%= person.first_name %> # + # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the + # variables as: + # + # Headline: <%= local_assigns[:headline] %> + # + # This is useful in cases where you aren't sure if the local variable has been assigned. Alternately, you could also use + # <tt>defined? headline</tt> to first check if the variable has been assigned before using it. + # # === Template caching # # By default, Rails will compile each template to a method in order to render it. When you alter a template, diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index e34bdd4a46..7a7e116dbb 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -76,6 +76,12 @@ module ActionView (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest /xm + LAYOUT_DEPENDENCY = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm + def self.call(name, template) new(name, template).dependencies end @@ -106,15 +112,20 @@ module ActionView render_calls = source.split(/\brender\b/).drop(1) render_calls.each do |arguments| - arguments.scan(RENDER_ARGUMENTS) do - add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) - add_static_dependency(render_dependencies, Regexp.last_match[:static]) - end + add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY) + add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS) end render_dependencies.uniq end + def add_dependencies(render_dependencies, arguments, pattern) + arguments.scan(pattern) do + add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) + add_static_dependency(render_dependencies, Regexp.last_match[:static]) + end + end + def add_dynamic_dependency(dependencies, dependency) if dependency dependencies << "#{dependency.pluralize}/#{dependency.singularize}" diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index b7fdc16a9d..5c28043f8a 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -318,6 +318,7 @@ module ActionView end def extract_dimensions(size) + size = size.to_s if size =~ %r{\A\d+x\d+\z} size.split('x') elsif size =~ %r{\A\d+\z} diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 4db8930a26..0e2a5f90f4 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -110,6 +110,29 @@ module ActionView # <%= some_helper_method(person) %> # # Now all you'll have to do is change that timestamp when the helper method changes. + # + # === Automatic Collection Caching + # + # When rendering collections such as: + # + # <%= render @notifications %> + # <%= render partial: 'notifications/notification', collection: @notifications %> + # + # If the notifications/_notification partial starts with a cache call like so: + # + # <% cache notification do %> + # <%= notification.name %> + # <% end %> + # + # The collection can then automatically use any cached renders for that + # template by reading them at once instead of one by one. + # + # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for more + # information on what cache calls make a template eligible for this collection caching. + # + # The automatic cache multi read can be turned off like so: + # + # <%= render @notifications, cache: false %> def cache(name = {}, options = nil, &block) if controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) @@ -122,7 +145,7 @@ module ActionView # Cache fragments of a view if +condition+ is true # - # <%= cache_if admin?, project do %> + # <% cache_if admin?, project do %> # <b>All the topics on this project</b> # <%= render project.topics %> # <% end %> @@ -138,7 +161,7 @@ module ActionView # Cache fragments of a view unless +condition+ is true # - # <%= cache_unless admin?, project do %> + # <% cache_unless admin?, project do %> # <b>All the topics on this project</b> # <%= render project.topics %> # <% end %> @@ -161,6 +184,14 @@ module ActionView end end + # Given a key (as described in ActionController::Caching::Fragments.expire_fragment), + # returns a key suitable for use in reading, writing, or expiring a + # cached fragment. All keys are prefixed with <tt>views/</tt> and uses + # ActiveSupport::Cache.expand_cache_key for the expansion. + def fragment_cache_key(key) + ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) + end + private def fragment_name_with_digest(name) #:nodoc: diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index 5a3223968f..a67ba580f1 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -195,7 +195,9 @@ module ActionView def with_output_buffer(buf = nil) #:nodoc: unless buf buf = ActionView::OutputBuffer.new - buf.force_encoding(output_buffer.encoding) if output_buffer + if output_buffer && output_buffer.respond_to?(:encoding) + buf.force_encoding(output_buffer.encoding) + end end self.output_buffer, old_buffer = buf, output_buffer yield diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb index ba47eee9ba..e9dccbad1c 100644 --- a/actionview/lib/action_view/helpers/debug_helper.rb +++ b/actionview/lib/action_view/helpers/debug_helper.rb @@ -26,7 +26,7 @@ module ActionView Marshal::dump(object) object = ERB::Util.html_escape(object.to_yaml) content_tag(:pre, object, :class => "debug_dump") - rescue Exception # errors from Marshal or YAML + rescue # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback content_tag(:code, object.inspect, :class => "debug_dump") end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 8d78ba13d5..891cc53765 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' require 'action_view/model_naming' +require 'action_view/record_identifier' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' @@ -66,9 +67,10 @@ module ActionView # # In particular, thanks to the conventions followed in the generated field names, the # controller gets a nested hash <tt>params[:person]</tt> with the person attributes - # set in the form. That hash is ready to be passed to <tt>Person.create</tt>: + # set in the form. That hash is ready to be passed to <tt>Person.new</tt>: # - # if @person = Person.create(params[:person]) + # @person = Person.new(params[:person]) + # if @person.save # # success # else # # error handling @@ -110,6 +112,7 @@ module ActionView include FormTagHelper include UrlHelper include ModelNaming + include RecordIdentifier # Creates a form that allows the user to create or update the attributes # of a specific model object. @@ -138,6 +141,7 @@ module ActionView # will get expanded to # # <%= text_field :person, :first_name %> + # # which results in an HTML <tt><input></tt> tag whose +name+ attribute is # <tt>person[first_name]</tt>. This means that when the form is submitted, # the value entered by the user will be available in the controller as @@ -1224,11 +1228,11 @@ module ActionView object_name = model_name_from_record_or_class(object).param_key end - builder = options[:builder] || default_form_builder + builder = options[:builder] || default_form_builder_class builder.new(object_name, object, self, options) end - def default_form_builder + def default_form_builder_class builder = ActionView::Base.default_form_builder builder.respond_to?(:constantize) ? builder.constantize : builder end @@ -1244,7 +1248,7 @@ module ActionView # Admin: <%= person_form.check_box :admin %> # <% end %> # - # In the above block, the a +FormBuilder+ object is yielded as the + # In the above block, a +FormBuilder+ object is yielded as the # +person_form+ variable. This allows you to generate the +text_field+ # and +check_box+ fields by specifying their eponymous methods, which # modify the underlying template and associates the +@person+ model object @@ -1265,6 +1269,7 @@ module ActionView # ) # ) # end + # end # # The above code creates a new method +div_radio_button+ which wraps a div # around the new radio button. Note that when options are passed in, you diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index bbfbf482a4..8a5928477f 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -18,10 +18,10 @@ module ActionView # # could become: # - # <select name="post[category]"> - # <option></option> - # <option>joke</option> - # <option>poem</option> + # <select name="post[category]" id="post_category"> + # <option value=""></option> + # <option value="joke">joke</option> + # <option value="poem">poem</option> # </select> # # Another common case is a select tag for a <tt>belongs_to</tt>-associated object. @@ -32,7 +32,7 @@ module ActionView # # could become: # - # <select name="post[person_id]"> + # <select name="post[person_id]" id="post_person_id"> # <option value="">None</option> # <option value="1">David</option> # <option value="2" selected="selected">Sam</option> @@ -45,7 +45,7 @@ module ActionView # # could become: # - # <select name="post[person_id]"> + # <select name="post[person_id]" id="post_person_id"> # <option value="">Select Person</option> # <option value="1">David</option> # <option value="2">Sam</option> @@ -71,11 +71,11 @@ module ActionView # # could become: # - # <select name="post[category]"> - # <option></option> - # <option>joke</option> - # <option>poem</option> - # <option disabled="disabled">restricted</option> + # <select name="post[category]" id="post_category"> + # <option value=""></option> + # <option value="joke">joke</option> + # <option value="poem">poem</option> + # <option disabled="disabled" value="restricted">restricted</option> # </select> # # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled. @@ -83,7 +83,7 @@ module ActionView # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }}) # # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return: - # <select name="post[category_id]"> + # <select name="post[category_id]" id="post_category_id"> # <option value="1" disabled="disabled">2008 stuff</option> # <option value="2" disabled="disabled">Christmas</option> # <option value="3">Jokes</option> @@ -109,7 +109,7 @@ module ActionView # # would become: # - # <select name="post[person_id]"> + # <select name="post[person_id]" id="post_person_id"> # <option value=""></option> # <option value="1" selected="selected">David</option> # <option value="2">Sam</option> @@ -192,7 +192,7 @@ module ActionView # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true) # # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: - # <select name="post[author_id]"> + # <select name="post[author_id]" id="post_author_id"> # <option value="">Please select</option> # <option value="1" selected="selected">D. Heinemeier Hansson</option> # <option value="2">D. Thomas</option> @@ -243,7 +243,7 @@ module ActionView # # Possible output: # - # <select name="city[country_id]"> + # <select name="city[country_id]" id="city_country_id"> # <optgroup label="Africa"> # <option value="1">South Africa</option> # <option value="3">Somalia</option> @@ -302,17 +302,17 @@ module ActionView # # => <option value="DKK">Kroner</option> # # options_for_select([ "VISA", "MasterCard" ], "MasterCard") - # # => <option>VISA</option> - # # => <option selected="selected">MasterCard</option> + # # => <option value="VISA">VISA</option> + # # => <option selected="selected" value="MasterCard">MasterCard</option> # # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") # # => <option value="$20">Basic</option> # # => <option value="$40" selected="selected">Plus</option> # # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) - # # => <option selected="selected">VISA</option> - # # => <option>MasterCard</option> - # # => <option selected="selected">Discover</option> + # # => <option selected="selected" value="VISA">VISA</option> + # # => <option value="MasterCard">MasterCard</option> + # # => <option selected="selected" value="Discover">Discover</option> # # You can optionally provide HTML attributes as the last element of the array. # diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 8bb243b8b7..65a0548ffb 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -84,14 +84,13 @@ module ActionView # * <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 created. If set to a string, the string will be used as the option's content and the value will be empty. # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something. - # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # select_tag "people", options_from_collection_for_select(@people, "id", "name") # # <select id="people" name="people"><option value="1">David</option></select> # - # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"] + # select_tag "people", options_from_collection_for_select(@people, "id", "name", "1") # # <select id="people" name="people"><option value="1" selected="selected">David</option></select> # # select_tag "people", "<option>David</option>".html_safe diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb index 629c447f3f..e237a32cb7 100644 --- a/actionview/lib/action_view/helpers/javascript_helper.rb +++ b/actionview/lib/action_view/helpers/javascript_helper.rb @@ -21,7 +21,7 @@ module ActionView # Also available through the alias j(). This is particularly helpful in JavaScript # responses, like: # - # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); + # $('some_element').replaceWith('<%= j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index f66dbfe7d3..ca8d30e4ef 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' @@ -117,8 +116,8 @@ module ActionView # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -192,8 +191,8 @@ module ActionView # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -240,8 +239,8 @@ module ActionView # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -292,8 +291,8 @@ module ActionView # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb index 77c3e6d394..f7ee573035 100644 --- a/actionview/lib/action_view/helpers/record_tag_helper.rb +++ b/actionview/lib/action_view/helpers/record_tag_helper.rb @@ -1,108 +1,21 @@ -require 'action_view/record_identifier' - module ActionView - # = Action View Record Tag Helpers module Helpers module RecordTagHelper - include ActionView::RecordIdentifier - - # Produces a wrapper DIV element with id and class parameters that - # relate to the specified Active Record object. Usage example: - # - # <%= div_for(@person, class: "foo") do %> - # <%= @person.name %> - # <% end %> - # - # produces: - # - # <div id="person_123" class="person foo"> Joe Bloggs </div> - # - # You can also pass an array of Active Record objects, which will then - # get iterated over and yield each record as an argument for the block. - # For example: - # - # <%= div_for(@people, class: "foo") do |person| %> - # <%= person.name %> - # <% end %> - # - # produces: - # - # <div id="person_123" class="person foo"> Joe Bloggs </div> - # <div id="person_124" class="person foo"> Jane Bloggs </div> - # - def div_for(record, *args, &block) - content_tag_for(:div, record, *args, &block) + def div_for(*) + raise NoMethodError, "The `div_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." end - # content_tag_for creates an HTML element with id and class parameters - # that relate to the specified Active Record object. For example: - # - # <%= content_tag_for(:tr, @person) do %> - # <td><%= @person.first_name %></td> - # <td><%= @person.last_name %></td> - # <% end %> - # - # would produce the following HTML (assuming @person is an instance of - # a Person object, with an id value of 123): - # - # <tr id="person_123" class="person">....</tr> - # - # If you require the HTML id attribute to have a prefix, you can specify it: - # - # <%= content_tag_for(:tr, @person, :foo) do %> ... - # - # produces: - # - # <tr id="foo_person_123" class="person">... - # - # You can also pass an array of objects which this method will loop through - # and yield the current object to the supplied block, reducing the need for - # having to iterate through the object (using <tt>each</tt>) beforehand. - # For example (assuming @people is an array of Person objects): - # - # <%= content_tag_for(:tr, @people) do |person| %> - # <td><%= person.first_name %></td> - # <td><%= person.last_name %></td> - # <% end %> - # - # produces: - # - # <tr id="person_123" class="person">...</tr> - # <tr id="person_124" class="person">...</tr> - # - # content_tag_for also accepts a hash of options, which will be converted to - # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined - # with the default class name for your object. For example: - # - # <%= content_tag_for(:li, @person, class: "bar") %>... - # - # produces: - # - # <li id="person_123" class="person bar">... - # - def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block) - options, prefix = prefix, nil if prefix.is_a?(Hash) - - Array(single_or_multiple_records).map do |single_record| - content_tag_for_single_record(tag_name, single_record, prefix, options, &block) - end.join("\n").html_safe + def content_tag_for(*) + raise NoMethodError, "The `content_tag_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." end - - private - - # Called by <tt>content_tag_for</tt> internally to render a content tag - # for each record. - def content_tag_for_single_record(tag_name, record, prefix, options, &block) - options = options ? options.dup : {} - options[:class] = [ dom_class(record, prefix), options[:class] ].compact - options[:id] = dom_id(record, prefix) - - if block_given? - content_tag(tag_name, capture(record, &block), options) - else - content_tag(tag_name, "", options) - end - end end end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 463a4e9f60..a2e9f37453 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -99,7 +99,7 @@ module ActionView # strip_tags("<div id='top-bar'>Welcome to my website!</div>") # # => Welcome to my website! def strip_tags(html) - self.class.full_sanitizer.sanitize(html) + self.class.full_sanitizer.sanitize(html, encode_special_chars: false) end # Strips all link tags from +html+ leaving just the link text. diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index 45c75d10c0..a4f6eb0150 100644 --- a/actionview/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb @@ -5,6 +5,7 @@ module ActionView eager_autoload do autoload :Base + autoload :Translator autoload :CheckBox autoload :CollectionCheckBoxes autoload :CollectionRadioButtons diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 7740c60eac..acc6443a96 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -14,7 +14,7 @@ module ActionView @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]") @object = retrieve_object(options.delete(:object)) @options = options - @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match + @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil end # This is what child classes implement. @@ -79,35 +79,30 @@ module ActionView end def add_default_name_and_id(options) - if options.has_key?("index") - options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } - options.delete("index") - elsif defined?(@auto_index) - options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } - else - options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) } - options["id"] = options.fetch("id"){ tag_id } + index = name_and_id_index(options) + options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) } + options["id"] = options.fetch("id"){ tag_id(index) } + if namespace = options.delete("namespace") + options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace end - - options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence - end - - def tag_name(multiple = false) - "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" - end - - def tag_name_with_index(index, multiple = false) - "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" end - def tag_id - "#{sanitized_object_name}_#{sanitized_method_name}" + def tag_name(multiple = false, index = nil) + # a little duplication to construct less strings + if index + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" + else + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + end end - def tag_id_with_index(index) - "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + def tag_id(index = nil) + # a little duplication to construct less strings + if index + "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + else + "#{sanitized_object_name}_#{sanitized_method_name}" + end end def sanitized_object_name @@ -149,6 +144,10 @@ module ActionView end option_tags end + + def name_and_id_index(options) + options.key?("index") ? options.delete("index") || "" : @auto_index + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 6242a2a085..1765fa6558 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -41,14 +41,7 @@ module ActionView end def hidden_field - hidden_name = @html_options[:name] - - hidden_name ||= if @options.has_key?(:index) - "#{tag_name_with_index(@options[:index])}[]" - else - "#{tag_name}[]" - end - + hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]" @template_object.hidden_field_tag(hidden_name, "", id: nil) end end diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 08a23e497e..b31d5fda66 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -15,20 +15,10 @@ module ActionView def translation method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name - @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') - - if object.respond_to?(:to_model) - key = object.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence - - content ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_and_value) - end + content ||= Translator + .new(object, @object_name, method_and_value, scope: "helpers.label") + .translate content ||= @method_name.humanize content diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb index ae67bc13af..cf7b117614 100644 --- a/actionview/lib/action_view/helpers/tags/placeholderable.rb +++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb @@ -7,24 +7,12 @@ module ActionView if tag_value = @options[:placeholder] placeholder = tag_value if tag_value.is_a?(String) - - object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}" - if object.respond_to?(:to_model) - key = object.class.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence - - placeholder ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_and_value) - end - + placeholder ||= Tags::Translator + .new(object, @object_name, method_and_value, scope: "helpers.placeholder") + .translate placeholder ||= @method_name.humanize - @options[:placeholder] = placeholder end end diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb new file mode 100644 index 0000000000..8b6655481d --- /dev/null +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -0,0 +1,40 @@ +module ActionView + module Helpers + module Tags # :nodoc: + class Translator # :nodoc: + def initialize(object, object_name, method_and_value, scope:) + @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') + @method_and_value = method_and_value + @scope = scope + @model = object.respond_to?(:to_model) ? object.to_model : nil + end + + def translate + translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence + translated_attribute || human_attribute_name + end + + protected + + attr_reader :object_name, :method_and_value, :scope, :model + + private + + def i18n_default + if model + key = model.model_name.i18n_key + ["#{key}.#{method_and_value}".to_sym, ""] + else + "" + end + end + + def human_attribute_name + if model && model.class.respond_to?(:human_attribute_name) + model.class.human_attribute_name(method_and_value) + end + end + end + end + end +end diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 2c40ed1832..c216d4401f 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -103,7 +103,9 @@ module ActionView # Highlights one or more +phrases+ everywhere in +text+ by inserting it into # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt> # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to - # '<mark>\1</mark>') or passing a block that receives each matched term. + # '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+ + # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false + # for <tt>:sanitize</tt> will turn sanitizing off. # # highlight('You searched for: rails', 'rails') # # => You searched for: <mark>rails</mark> @@ -122,6 +124,9 @@ module ActionView # # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) } # # => You searched for: <a href="search?q=rails">rails</a> + # + # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false) + # # => "<a>ruby</a> on <mark>rails</mark>" def highlight(text, phrases, options = {}) text = sanitize(text) if options.fetch(:sanitize, true) diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 342361217c..29a0860c00 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -37,8 +37,12 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options = options.dup - remaining_defaults = Array(options.delete(:default)) - options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String + has_default = options.has_key?(:default) + remaining_defaults = Array(options.delete(:default)).compact + + if has_default && !remaining_defaults.first.kind_of?(Symbol) + options[:default] = remaining_defaults.shift + end # If the user has explicitly decided to NOT raise errors, pass that option to I18n. # Otherwise, tell I18n to raise an exception, which we rescue further in this method. diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 8c2d5705f1..49da5eba5d 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -46,9 +46,9 @@ module ActionView end protected :_back_url - # Creates a link tag of the given +name+ using a URL created by the set of +options+. + # Creates an anchor element of the given +name+ using a URL created by the set of +options+. # See the valid options in the documentation for +url_for+. It's also possible to - # pass a String instead of an options hash, which generates a link tag that uses the + # pass a String instead of an options hash, which generates an anchor element that uses the # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead # of an options hash will generate a link to the referrer (a JavaScript back link # will be used in place of a referrer if none exists). If +nil+ is passed as the name @@ -172,6 +172,12 @@ module ActionView # # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> + # + # Also you can set target attribute with <tt>target</tt> options: + # + # link_to "External link", "http://www.rubyonrails.org/", target: "_blank" + # # => <a href="http://www.rubyonrails.org/" target="_blank">External link</a> + # def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options, name = options, name, block if block_given? options ||= {} @@ -280,9 +286,7 @@ module ActionView html_options, options = options, name if block_given? options ||= {} html_options ||= {} - html_options = html_options.stringify_keys - convert_boolean_attributes!(html_options, %w(disabled)) url = options.is_a?(String) ? options : url_for(options) remote = html_options.delete('remote') @@ -294,8 +298,9 @@ module ActionView form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} form_options[:class] ||= html_options.delete('form_class') || 'button_to' - form_options.merge!(method: form_method, action: url) - form_options.merge!("data-remote" => "true") if remote + form_options[:method] = form_method + form_options[:action] = url + form_options[:'data-remote'] = true if remote request_token_tag = form_method == 'post' ? token_tag : '' @@ -459,7 +464,7 @@ module ActionView html_options = (html_options || {}).stringify_keys extras = %w{ cc bcc body subject reply_to }.map! { |item| - option = html_options.delete(item) || next + option = html_options.delete(item).presence || next "#{item.dasherize}=#{Rack::Utils.escape_path(option)}" }.compact extras = extras.empty? ? '' : '?' + extras.join('&') @@ -576,34 +581,6 @@ module ActionView html_options["data-method"] = method end - # Processes the +html_options+ hash, converting the boolean - # attributes from true/false form into the form required by - # HTML/XHTML. (An attribute is considered to be boolean if - # its name is listed in the given +bool_attrs+ array.) - # - # More specifically, for each boolean attribute in +html_options+ - # given as: - # - # "attr" => bool_value - # - # if the associated +bool_value+ evaluates to true, it is - # replaced with the attribute's name; otherwise the attribute is - # removed from the +html_options+ hash. (See the XHTML 1.0 spec, - # section 4.5 "Attribute Minimization" for more: - # http://www.w3.org/TR/xhtml1/#h-4.5) - # - # Returns the updated +html_options+ hash, which is also modified - # in place. - # - # Example: - # - # convert_boolean_attributes!( html_options, - # %w( checked disabled readonly ) ) - def convert_boolean_attributes!(html_options, bool_attrs) - bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) } - html_options - end - def token_tag(token=nil) if token != false && protect_against_forgery? token ||= form_authenticity_token diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 0b5c0b9991..9d636c8c9e 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -228,7 +228,7 @@ module ActionView # set by the <tt>layout</tt> method. # # ==== Returns - # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise. + # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise. def _conditional_layout? return unless super diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index 81f9c40b85..9a26cba574 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -36,9 +36,15 @@ module ActionView end end + initializer "action_view.collection_caching" do |app| + ActiveSupport.on_load(:action_controller) do + PartialRenderer.collection_cache = app.config.action_controller.cache_store + end + end + initializer "action_view.setup_action_pack" do |app| ActiveSupport.on_load(:action_controller) do - ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) + ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor) end end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 6c3015180a..cd151c0189 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,3 +1,4 @@ +require 'action_view/renderer/partial_renderer/collection_caching' require 'thread_safe' module ActionView @@ -153,23 +154,23 @@ module ActionView # 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 &> + # <%# 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 &> + # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> # - # <%# app/views/users/_administrator.html.erb &> + # <%# app/views/users/_administrator.html.erb %> # <div id="administrator"> # Budget: $<%= user.budget %> # <%= yield %> # </div> # - # <%# app/views/users/_editor.html.erb &> + # <%# app/views/users/_editor.html.erb %> # <div id="editor"> # Deadline: <%= user.deadline %> # <%= yield %> @@ -232,7 +233,7 @@ module ActionView # # You can also apply a layout to a block within any template: # - # <%# app/views/users/_chief.html.erb &> + # <%# app/views/users/_chief.html.erb %> # <%= render(layout: "administrator", locals: { user: chief }) do %> # Title: <%= chief.title %> # <% end %> @@ -249,13 +250,13 @@ module ActionView # 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 &> + # <%# app/views/users/_user.html.erb %> # <div class="user"> # Budget: $<%= user.budget %> # <%= yield user %> # </div> # - # <%# app/views/users/index.html.erb &> + # <%# app/views/users/index.html.erb %> # <%= render layout: @users do |user| %> # Title: <%= user.title %> # <% end %> @@ -264,14 +265,14 @@ module ActionView # # You can also yield multiple times in one layout and use block arguments to differentiate the sections. # - # <%# app/views/users/_user.html.erb &> + # <%# 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 &> + # <%# app/views/users/index.html.erb %> # <%= render layout: @users do |user, section| %> # <%- case section when :header -%> # Title: <%= user.title %> @@ -280,6 +281,8 @@ module ActionView # <%- end -%> # <% end %> class PartialRenderer < AbstractRenderer + include CollectionCaching + PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| h[k] = ThreadSafe::Cache.new end @@ -321,8 +324,9 @@ module ActionView spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) end - result = @template ? collection_with_template : collection_without_template - result.join(spacer).html_safe + cache_collection_render do + @template ? collection_with_template : collection_without_template + end.join(spacer).html_safe end def render_partial @@ -519,7 +523,7 @@ module ActionView def retrieve_variable(path, as) variable = as || begin base = path[-1] == "/" ? "" : File.basename(path) - raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/ + raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/ $1.to_sym end if @collection @@ -530,8 +534,7 @@ module ActionView end IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + - "make sure your partial name starts with underscore, " + - "and is followed by any combination of letters, numbers and underscores." + "make sure your partial name starts with underscore." OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " + "make sure it starts with lowercase letter, " + diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb new file mode 100644 index 0000000000..b77c884e66 --- /dev/null +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -0,0 +1,70 @@ +require 'active_support/core_ext/object/try' + +module ActionView + module CollectionCaching # :nodoc: + extend ActiveSupport::Concern + + included do + # Fallback cache store if Action View is used without Rails. + # Otherwise overriden in Railtie to use Rails.cache. + mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new } + end + + private + def cache_collection_render + return yield unless cache_collection? + + keyed_collection = collection_by_cache_keys + partial_cache = collection_cache.read_multi(*keyed_collection.keys) + + @collection = keyed_collection.reject { |key, _| partial_cache.key?(key) }.values + rendered_partials = @collection.any? ? yield.dup : [] + + fetch_or_cache_partial(partial_cache, order_by: keyed_collection.each_key) do + rendered_partials.shift + end + end + + def cache_collection? + @options.fetch(:cache, automatic_cache_eligible?) + end + + def automatic_cache_eligible? + single_template_render? && !callable_cache_key? && + @template.eligible_for_collection_caching?(as: @options[:as]) + end + + def single_template_render? + @template # Template is only set when a collection renders one template. + end + + def callable_cache_key? + @options[:cache].respond_to?(:call) + end + + def collection_by_cache_keys + seed = callable_cache_key? ? @options[:cache] : ->(i) { i } + + @collection.each_with_object({}) do |item, hash| + hash[expanded_cache_key(seed.call(item))] = item + end + end + + def expanded_cache_key(key) + key = @view.fragment_cache_key(@view.cache_fragment_name(key)) + key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. + end + + def fetch_or_cache_partial(cached_partials, order_by:) + cache_options = @options[:cache_options] || @locals[:cache_options] || {} + + order_by.map do |key| + cached_partials.fetch(key) do + yield.tap do |rendered_partial| + collection_cache.write(key, rendered_partial, cache_options) + end + end + end + end + end +end diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb index 964b18337e..1bee35d80d 100644 --- a/actionview/lib/action_view/renderer/renderer.rb +++ b/actionview/lib/action_view/renderer/renderer.rb @@ -37,7 +37,7 @@ module ActionView end end - # Direct accessor to template rendering. + # Direct access to template rendering. def render_template(context, options) #:nodoc: TemplateRenderer.new(@lookup_context).render(context, options) end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index cd21d7ab47..dbb4855e39 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -40,7 +40,7 @@ module ActionView find_template(options[:template], options[:prefixes], false, keys, @details) end else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option." + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option." end end diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f281333a41..0371db07dc 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -130,5 +130,11 @@ module ActionView controller.optimize_routes_generation? : super end protected :optimize_routes_generation? + + private + + def _generate_paths_by_default + true + end end end diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake index b39f7d583b..f394c319c1 100644 --- a/actionview/lib/action_view/tasks/dependencies.rake +++ b/actionview/lib/action_view/tasks/dependencies.rake @@ -2,20 +2,22 @@ namespace :cache_digests do desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)' task :nested_dependencies => :environment do abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? - puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).nested_dependencies + puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).nested_dependencies end desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)' task :dependencies => :environment do abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present? - puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).dependencies + puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).dependencies end - def template_name - ENV['TEMPLATE'].split('.', 2).first - end + class CacheDigests + def self.template_name + ENV['TEMPLATE'].split('.', 2).first + end - def finder - ApplicationController.new.lookup_context + def self.finder + ApplicationController.new.lookup_context + end end end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 6b61378a1f..377ceb534a 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -87,6 +87,19 @@ module ActionView # expected_encoding # ) + ## + # :method: local_assigns + # + # Returns a hash with the defined local variables. + # + # Given this sub template rendering: + # + # <%= render "shared/header", { headline: "Welcome", person: person } %> + # + # You can use +local_assigns+ in the sub templates to access the local variables: + # + # local_assigns[:headline] # => "Welcome" + eager_autoload do autoload :Error autoload :Handlers @@ -103,7 +116,7 @@ module ActionView # This finalizer is needed (and exactly with a proc inside another proc) # otherwise templates leak in development. - Finalizer = proc do |method_name, mod| + Finalizer = proc do |method_name, mod| # :nodoc: proc do mod.module_eval do remove_possible_method method_name @@ -117,6 +130,7 @@ module ActionView @source = source @identifier = identifier @handler = handler + @cache_name = extract_resource_cache_call_name @compiled = false @original_encoding = nil @locals = details[:locals] || [] @@ -152,6 +166,10 @@ module ActionView @type ||= Types[@formats.first] if @formats.first end + def eligible_for_collection_caching?(as: nil) + @cache_name == (as || inferred_cache_name).to_s + end + # Receives a view object and return a template similar to self by using @virtual_path. # # This method is useful if you have a template object but it does not contain its source @@ -332,5 +350,14 @@ module ActionView payload = { virtual_path: @virtual_path, identifier: @identifier } ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block) end + + def extract_resource_cache_call_name + $1 if @handler.respond_to?(:resource_cache_call_pattern) && + @source =~ @handler.resource_cache_call_pattern + end + + def inferred_cache_name + @inferred_cache_name ||= @virtual_path.split('/').last.sub('_', '') + end end end diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 85a100ed4c..88a8570706 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -123,6 +123,24 @@ module ActionView ).src end + # Returns Regexp to extract a cached resource's name from a cache call at the + # first line of a template. + # The extracted cache name is expected in $1. + # + # <% cache notification do %> # => notification + # + # The pattern should support templates with a beginning comment: + # + # <%# Still extractable even though there's a comment %> + # <% cache notification do %> # => notification + # + # But fail to extract a name if a resource association is cached. + # + # <% cache notification.event do %> # => nil + def resource_cache_call_pattern + /\A(?:<%#.*%>\n?)?<% cache\(?\s*(\w+\.?)/ + end + private def valid_encoding(string, encoding) diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb index 397c86014a..b08fb0870f 100644 --- a/actionview/lib/action_view/template/handlers/raw.rb +++ b/actionview/lib/action_view/template/handlers/raw.rb @@ -2,7 +2,7 @@ module ActionView module Template::Handlers class Raw def call(template) - escaped = template.source.gsub(/:/, '\:') + escaped = template.source.gsub(':'.freeze, '\:'.freeze) '%q:' + escaped + ':;' end diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 812b011bd7..06810ad14d 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -204,7 +204,7 @@ module ActionView def view @view ||= begin view = @controller.view_context - view.singleton_class.send :include, _helpers + view.singleton_class.include(_helpers) view.extend(Locals) view.rendered_views = self.rendered_views view.output_buffer = self.output_buffer |