diff options
Diffstat (limited to 'actionview')
27 files changed, 330 insertions, 217 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 534124d208..101f1263d9 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,38 @@ +* Fixed the translation helper method to accept different default values types + besides String. + + *Ulisses Almeida* + +* Collection rendering automatically caches and fetches multiple partials. + + Collections rendered as: + + ```ruby + <%= render @notifications %> + <%= render partial: 'notifications/notification', collection: @notifications, as: :notification %> + ``` + + will now read several partials from cache at once, if the template starts with a cache call: + + ```ruby + # notifications/_notification.html.erb + <% cache notification do %> + <%# ... %> + <% end %> + ``` + + *Kasper Timm Hansen* + +* Fixed a dependency tracker bug that caused template dependencies not + count layouts as dependencies for partials. + + *Juho Leinonen* + +* Extracted `ActionView::Helpers::RecordTagHelper` to external gem + (`record_tag_helper`) and added removal notices. + + *Todd Bealmear* + * Allow to pass a string value to `size` option in `image_tag` and `video_tag`. This makes the behavior more consistent with `width` or `height` options. 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/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/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 8d78ba13d5..b0793d0b91 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' @@ -110,6 +111,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. @@ -1224,11 +1226,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 +1246,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 +1267,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_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/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/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 342361217c..24b633c5bb 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 + has_default = options.has_key?(:default) remaining_defaults = Array(options.delete(:default)) - options[:default] = remaining_defaults.shift if remaining_defaults.first.kind_of? String + + 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 33882063f9..3dbce0738e 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -459,7 +459,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('&') diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index ee3dce24b7..9a26cba574 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -36,6 +36,12 @@ 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.include(ActionDispatch::Routing::UrlFor) diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 5ff15411cf..56b8ab1e2d 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 @@ -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 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/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/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index fe4cf3688a..8b47536a18 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -31,6 +31,10 @@ class Customer < Struct.new(:name, :id) def persisted? id.present? end + + def cache_key + name.to_s + end end module Quiz @@ -861,12 +865,12 @@ class RenderTest < ActionController::TestCase # :ported: def test_attempt_to_access_object_method - assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone } + assert_raise(AbstractController::ActionNotFound) { get :clone } end # :ported: def test_private_methods - assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout } + assert_raise(AbstractController::ActionNotFound) { get :determine_layout } end # :ported: diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 8e1ed2776d..34b2698c7f 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -113,7 +113,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_passing_routes_proxy with_namespaced_routes(:blog) do - proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self) + proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self, _routes.url_helpers) @blog_post.save assert_url "http://example.com/posts/#{@blog_post.id}", [proxy, @blog_post] end @@ -208,7 +208,7 @@ class PolymorphicRoutesTest < ActionController::TestCase @series.save polymorphic_url([nil, @series]) end - assert_match(/undefined method `series_url' for/, exception.message) + assert_match(/undefined method `series_url'/, exception.message) end end diff --git a/actionview/test/fixtures/test/_cached_customer.erb b/actionview/test/fixtures/test/_cached_customer.erb new file mode 100644 index 0000000000..52f35a3497 --- /dev/null +++ b/actionview/test/fixtures/test/_cached_customer.erb @@ -0,0 +1,3 @@ +<% cache cached_customer do %> + Hello: <%= cached_customer.name %> +<% end %>
\ No newline at end of file diff --git a/actionview/test/fixtures/test/_cached_customer_as.erb b/actionview/test/fixtures/test/_cached_customer_as.erb new file mode 100644 index 0000000000..fca8d19e34 --- /dev/null +++ b/actionview/test/fixtures/test/_cached_customer_as.erb @@ -0,0 +1,3 @@ +<% cache buyer do %> + <%= greeting %>: <%= customer.name %> +<% end %>
\ No newline at end of file diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index d74da4c318..672b4747ec 100644 --- a/actionview/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -60,7 +60,6 @@ class ERBTrackerTest < Minitest::Test end def test_dependency_of_template_partial_with_layout - skip # FIXME: Needs to be fixed properly, right now we can only match one dependency per line. Need multiple! template = FakeTemplate.new("<%# render partial: 'messages/show', layout: 'messages/layout' %>", :erb) tracker = make_tracker("multiple/_dependencies", template) diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb index ab84bccb56..4b7b653916 100644 --- a/actionview/test/template/record_tag_helper_test.rb +++ b/actionview/test/template/record_tag_helper_test.rb @@ -24,94 +24,10 @@ class RecordTagHelperTest < ActionView::TestCase end def test_content_tag_for - expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>) - actual = content_tag_for(:li, @post) - assert_dom_equal expected, actual + assert_raises(NoMethodError) { content_tag_for(:li, @post) } end - def test_content_tag_for_prefix - expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>) - actual = content_tag_for(:ul, @post, :archived) - assert_dom_equal expected, actual - end - - def test_content_tag_for_with_extra_html_options - expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>) - actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0") - assert_dom_equal expected, actual - end - - def test_content_tag_for_with_array_css_class - expected = %(<tr class="record_tag_post special odd" id="record_tag_post_45"></tr>) - actual = content_tag_for(:tr, @post, class: ["special", "odd"]) - assert_dom_equal expected, actual - end - - def test_content_tag_for_with_prefix_and_extra_html_options - expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>) - actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0") - assert_dom_equal expected, actual - end - - def test_block_not_in_erb_multiple_calls - expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = div_for(@post, class: "special") { @post.body } - assert_dom_equal expected, actual - actual = div_for(@post, class: "special") { @post.body } - assert_dom_equal expected, actual - end - - def test_block_works_with_content_tag_for_in_erb - expected = %(<tr class="record_tag_post" id="record_tag_post_45">What a wonderful world!</tr>) - actual = render_erb("<%= content_tag_for(:tr, @post) do %><%= @post.body %><% end %>") - assert_dom_equal expected, actual - end - - def test_div_for_in_erb - expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>") - assert_dom_equal expected, actual - end - - def test_content_tag_for_collection - post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" } - post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" } - expected = %(<li class="record_tag_post" id="record_tag_post_101">Hello!</li>\n<li class="record_tag_post" id="record_tag_post_102">World!</li>) - actual = content_tag_for(:li, [post_1, post_2]) { |post| post.body } - assert_dom_equal expected, actual - end - - def test_content_tag_for_collection_without_given_block - post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" } - post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" } - expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>) - actual = content_tag_for(:li, [post_1, post_2]) - assert_dom_equal expected, actual - end - - def test_div_for_collection - post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" } - post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" } - expected = %(<div class="record_tag_post" id="record_tag_post_101">Hello!</div>\n<div class="record_tag_post" id="record_tag_post_102">World!</div>) - actual = div_for([post_1, post_2]) { |post| post.body } - assert_dom_equal expected, actual - end - - def test_content_tag_for_single_record_is_html_safe - result = div_for(@post, class: "special") { @post.body } - assert result.html_safe? - end - - def test_content_tag_for_collection_is_html_safe - post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" } - post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" } - result = content_tag_for(:li, [post_1, post_2]) { |post| post.body } - assert result.html_safe? - end - - def test_content_tag_for_does_not_change_options_hash - options = { class: "important" } - content_tag_for(:li, @post, options) - assert_equal({ class: "important" }, options) + def test_div_for + assert_raises(NoMethodError) { div_for(@post, class: "special") } end end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index f77b81f0ee..22665b6844 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -61,9 +61,10 @@ module RenderTestCases def test_render_template_with_a_missing_partial_of_another_format @view.lookup_context.formats = [:html] - assert_raise ActionView::Template::Error, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do + e = assert_raise ActionView::Template::Error do @view.render(:template => "with_format", :formats => [:json]) end + assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :builder, :ruby]}.") end def test_render_file_with_locale @@ -597,3 +598,41 @@ class LazyViewRenderTest < ActiveSupport::TestCase silence_warnings { Encoding.default_external = old } end end + +class CachedCollectionViewRenderTest < CachedViewRenderTest + class CachedCustomer < Customer; end + + teardown do + ActionView::PartialRenderer.collection_cache.clear + end + + test "with custom key" do + customer = Customer.new("david") + key = ActionController::Base.new.fragment_cache_key([customer, 'key']) + + ActionView::PartialRenderer.collection_cache.write(key, 'Hello') + + assert_equal "Hello", + @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] }) + end + + test "automatic caching with inferred cache name" do + customer = CachedCustomer.new("david") + key = ActionController::Base.new.fragment_cache_key(customer) + + ActionView::PartialRenderer.collection_cache.write(key, 'Cached') + + assert_equal "Cached", + @view.render(partial: "test/cached_customer", collection: [customer]) + end + + test "automatic caching with as name" do + customer = CachedCustomer.new("david") + key = ActionController::Base.new.fragment_cache_key(customer) + + ActionView::PartialRenderer.collection_cache.write(key, 'Cached') + + assert_equal "Cached", + @view.render(partial: "test/cached_customer_as", collection: [customer], as: :buyer) + end +end diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb index c94508d678..aae6a9aa09 100644 --- a/actionview/test/template/template_test.rb +++ b/actionview/test/template/template_test.rb @@ -183,10 +183,11 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_error_when_template_isnt_valid_utf8 - assert_raises(ActionView::Template::Error, /\xFC/) do + e = assert_raises ActionView::Template::Error do @template = new_template("hello \xFCmlat", :virtual_path => nil) render end + assert_match(/\xFC/, e.message) end def with_external_encoding(encoding) diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index 5ad1938b61..c6cc47fb4f 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -328,9 +328,10 @@ module ActionView test "supports specifying locals (failing)" do controller.controller_path = "test" render(:template => "test/calling_partial_with_layout") - assert_raise ActiveSupport::TestCase::Assertion, /Somebody else.*David/m do + e = assert_raise ActiveSupport::TestCase::Assertion do assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "Somebody Else" } end + assert_match(/Somebody Else.*David/m, e.message) end test 'supports different locals on the same partial' do diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 8fde478ac9..ef4d13efa7 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -180,6 +180,11 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal 'A Generic String', translation end + def test_translate_with_object_default + translation = translate(:'translations.missing', default: 123) + assert_equal 123, translation + end + def test_translate_with_array_of_string_defaults translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string']) assert_equal 'A Generic String', translation diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index ee3313489f..ef4df0407a 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -495,6 +495,11 @@ class UrlHelperTest < ActiveSupport::TestCase %{<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email&reply-to=foo%40bar.com">My email</a>}, mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.", reply_to: "foo@bar.com") ) + + assert_dom_equal( + %{<a href="mailto:me@example.com?body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>}, + mail_to("me@example.com", "My email", cc: '', bcc: '', subject: "This is an example email", body: "This is the body of the message.") + ) end def test_mail_to_with_img |