aboutsummaryrefslogtreecommitdiffstats
path: root/actionview
diff options
context:
space:
mode:
Diffstat (limited to 'actionview')
-rw-r--r--actionview/CHANGELOG.md17
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb1
-rw-r--r--actionview/lib/action_view/log_subscriber.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb5
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb78
-rw-r--r--actionview/test/actionpack/controller/render_test.rb22
-rw-r--r--actionview/test/fixtures/actionpack/test/_customer_iteration.erb1
-rw-r--r--actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb8
-rw-r--r--actionview/test/template/partial_iteration_test.rb33
-rw-r--r--actionview/test/template/render_test.rb5
-rw-r--r--actionview/test/template/test_case_test.rb3
17 files changed, 163 insertions, 45 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 185f420472..f4a12360d7 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Add `PartialIteration` object used when rendering collections.
+
+ The iteration object is available as the local variable
+ `#{template_name}_iteration` when rendering partials with collections.
+
+ It gives access to the `size` of the collection being iterated over,
+ the current `index` and two convenience methods `first?` and `last?`.
+
+ *Joel Junström*, *Lucas Uyezu*
+
+* Return an absolute instead of relative path from an asset url in the case
+ of the `asset_host` proc returning nil
+
+ *Jolyon Pawlyn*
+
* Fix `html_escape_once` to properly handle hex escape sequences (e.g. ᨫ)
*John F. Douthat*
@@ -96,7 +111,7 @@
* Remove wrapping div with inline styles for hidden form fields.
We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
- inside a form are valid HTML5, and the absense of inline styles help in validating
+ inside a form are valid HTML5, and the absence of inline styles help in validating
for Content Security Policy.
*Joost Baaij*
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 72d79735ae..1f103786cb 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -60,7 +60,7 @@ module ActionView
def digest
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :info, " Cache digest for #{template.inspect}: #{digest}"
+ logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
end
rescue ActionView::MissingTemplate
logger.try :error, " Couldn't find template for digesting: #{name}"
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 469f7c16bd..9e8d005ec7 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -203,7 +203,6 @@ module ActionView
request = self.request if respond_to?(:request)
host = options[:host]
host ||= config.asset_host if defined? config.asset_host
- host ||= request.base_url if request && options[:protocol] == :request
if host.respond_to?(:call)
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
@@ -214,6 +213,7 @@ module ActionView
host = host % (Zlib.crc32(source) % 4)
end
+ host ||= request.base_url if request && options[:protocol] == :request
return unless host
if host =~ URI_REGEXP
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 48f42947db..528e2828a1 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -152,11 +152,9 @@ module ActionView
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
- # This way, the client either sends only the hidden field (representing
- # the deselected multiple select box), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # form, and parameters extraction gets the last occurrence of any repeated
- # key in the query string, that works for ordinary forms.
+ # <b>Note:</b> The client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. This means that the resulting array
+ # always contains a blank string.
#
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index b0d9c7c7f9..f03362d0f5 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -17,7 +17,7 @@ module ActionView #:nodoc:
stringish.to_s.html_safe
end
- # This method returns a html safe string similar to what <tt>Array#join</tt>
+ # This method returns an html safe string similar to what <tt>Array#join</tt>
# would return. The array is flattened, and all items, including
# the supplied separator, are html escaped unless they are html
# safe, and the returned string is marked as html safe.
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index ebfc35a4c7..6cd6e858dd 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -13,13 +13,13 @@ module ActionView
# * <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.
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
- # type as <tt>text/plain</tt>.
+ # type as <tt>text/plain</tt>.
# * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
- # performs html escape on the string first. Setting the content type as
- # <tt>text/html</tt>.
+ # performs html escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
- # object.
+ # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # object.
#
# 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.
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 35444bcfb4..268558669e 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -9,6 +9,7 @@ module ActionView
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
+ include OutputSafetyHelper
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index 6c8d9cb5bf..9047dbdd85 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -13,11 +13,11 @@ module ActionView
end
def render_template(event)
- return unless logger.info?
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
- info(message)
+ info do
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << " (#{event.duration.round(1)}ms)"
+ end
end
alias :render_partial :render_template
alias :render_collection :render_template
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 5fff6b0771..ea687d9cca 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -66,10 +66,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- syms = Set.new Mime::SET.symbols
- details[:formats] = details[:formats].select { |v|
- syms.include? v
- }
+ details[:formats] &= Mime::SET.symbols
end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 36f17f01fd..a4f6573601 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,6 +1,33 @@
require 'thread_safe'
module ActionView
+ class PartialIteration
+ # The number of iterations that will be done by the partial.
+ attr_reader :size
+
+ # The current iteration of the partial.
+ attr_reader :index
+
+ def initialize(size)
+ @size = size
+ @index = 0
+ end
+
+ # Check if this is the first iteration of the partial.
+ def first?
+ index == 0
+ end
+
+ # Check if this is the last iteration of the partial.
+ def last?
+ index == size - 1
+ end
+
+ def iterate! # :nodoc:
+ @index += 1
+ end
+ end
+
# = Action View Partials
#
# There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -56,8 +83,12 @@ module ActionView
# <%= 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+.
+ # iteration object will automatically be made available to the template with a name of the form
+ # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
+ # the collection and the total size of the collection. The iteration object also has two convenience methods,
+ # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+.
+ # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's
+ # +index+ method.
#
# The <tt>:as</tt> option may be used when rendering partials.
#
@@ -352,7 +383,7 @@ module ActionView
end
if @path
- @variable, @variable_counter = retrieve_variable(@path, as)
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
@template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
@@ -385,19 +416,22 @@ module ActionView
def collection_with_template
view, locals, template = @view, @locals, @template
- as, counter = @variable, @variable_counter
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
if layout = @options[:layout]
layout = find_template(layout, @template_keys)
end
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+ locals[iteration] = partial_iteration
+
@collection.map do |object|
- locals[as] = object
- locals[counter] = (index += 1)
+ locals[as] = object
+ locals[counter] = partial_iteration.index
content = template.render(view, locals)
content = layout.render(view, locals) { content } if layout
+ partial_iteration.iterate!
content
end
end
@@ -407,16 +441,20 @@ module ActionView
cache = {}
keys = @locals.keys
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+
@collection.map do |object|
- index += 1
- path, as, counter = collection_data[index]
+ index = partial_iteration.index
+ path, as, counter, iteration = collection_data[index]
- locals[as] = object
- locals[counter] = index
+ locals[as] = object
+ locals[counter] = index
+ locals[iteration] = partial_iteration
template = (cache[path] ||= find_template(path, keys + [as, counter]))
- template.render(view, locals)
+ content = template.render(view, locals)
+ partial_iteration.iterate!
+ content
end
end
@@ -466,8 +504,11 @@ module ActionView
def retrieve_template_keys
keys = @locals.keys
- keys << @variable if @object || @collection
- keys << @variable_counter if @collection
+ keys << @variable if @object || @collection
+ if @collection
+ keys << @variable_counter
+ keys << @variable_iteration
+ end
keys
end
@@ -477,8 +518,11 @@ module ActionView
raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
$1.to_sym
end
- variable_counter = :"#{variable}_counter" if @collection
- [variable, variable_counter]
+ if @collection
+ variable_counter = :"#{variable}_counter"
+ variable_iteration = :"#{variable}_iteration"
+ end
+ [variable, variable_counter, variable_iteration]
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index ab7b961ed2..b3b51ae583 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -536,6 +536,14 @@ class TestController < ApplicationController
render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer
end
+ def partial_collection_with_iteration
+ render partial: "customer_iteration", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ]
+ end
+
+ def partial_collection_with_as_and_iteration
+ render partial: "customer_iteration_with_as", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ], as: :client
+ end
+
def partial_collection_with_counter
render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
@@ -839,7 +847,7 @@ class RenderTest < ActionController::TestCase
def test_render_text_with_nil
get :render_text_with_nil
assert_response 200
- assert_equal ' ', @response.body
+ assert_equal '', @response.body
end
# :ported:
@@ -1027,7 +1035,7 @@ class RenderTest < ActionController::TestCase
def test_rendering_nothing_on_layout
get :rendering_nothing_on_layout
- assert_equal " ", @response.body
+ assert_equal '', @response.body
end
def test_render_to_string_doesnt_break_assigns
@@ -1237,6 +1245,16 @@ class RenderTest < ActionController::TestCase
assert_equal "david david davidmary mary mary", @response.body
end
+ def test_partial_collection_with_iteration
+ get :partial_collection_with_iteration
+ assert_equal "3-0: david-first3-1: mary3-2: christine-last", @response.body
+ end
+
+ def test_partial_collection_with_as_and_iteration
+ get :partial_collection_with_as_and_iteration
+ assert_equal "3-0: david-first3-1: mary3-2: christine-last", @response.body
+ end
+
def test_partial_collection_with_counter
get :partial_collection_with_counter
assert_equal "david0mary1", @response.body
diff --git a/actionview/test/fixtures/actionpack/test/_customer_iteration.erb b/actionview/test/fixtures/actionpack/test/_customer_iteration.erb
new file mode 100644
index 0000000000..fb530b04a7
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/test/_customer_iteration.erb
@@ -0,0 +1 @@
+<%= customer_iteration_iteration.size %>-<%= customer_iteration_iteration.index %>: <%= customer_iteration.name %><%= '-first' if customer_iteration_iteration.first? %><%= '-last' if customer_iteration_iteration.last? %> \ No newline at end of file
diff --git a/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb b/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb
new file mode 100644
index 0000000000..57297d0564
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb
@@ -0,0 +1 @@
+<%= client_iteration.size %>-<%= client_iteration.index %>: <%= client.name %><%= '-first' if client_iteration.first? %><%= '-last' if client_iteration.last? %> \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 343681b5a9..d789a5ca27 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -546,6 +546,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal "http://cdn.example.com/images/file.png", image_path("file.png")
end
+ def test_image_url_with_asset_host_proc_returning_nil
+ @controller.config.asset_host = Proc.new { nil }
+ @controller.request = Struct.new(:base_url, :script_name).new("http://www.example.com", nil)
+
+ assert_equal "/images/rails.png", image_path("rails.png")
+ assert_equal "http://www.example.com/images/rails.png", image_url("rails.png")
+ end
+
def test_caching_image_path_with_caching_and_proc_asset_host_using_request
@controller.config.asset_host = Proc.new do |source, request|
if request.ssl?
diff --git a/actionview/test/template/partial_iteration_test.rb b/actionview/test/template/partial_iteration_test.rb
new file mode 100644
index 0000000000..695f9f1bef
--- /dev/null
+++ b/actionview/test/template/partial_iteration_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'action_view/renderer/partial_renderer'
+
+class PartialIterationTest < ActiveSupport::TestCase
+ def test_has_size_and_index
+ iteration = ActionView::PartialIteration.new 3
+ assert_equal 0, iteration.index, "should be at the first index"
+ assert_equal 3, iteration.size, "should have the size"
+ end
+
+ def test_first_is_true_when_current_is_at_the_first_index
+ iteration = ActionView::PartialIteration.new 3
+ assert iteration.first?, "first when current is 0"
+ end
+
+ def test_first_is_false_unless_current_is_at_the_first_index
+ iteration = ActionView::PartialIteration.new 3
+ iteration.iterate!
+ assert !iteration.first?, "not first when current is 1"
+ end
+
+ def test_last_is_true_when_current_is_at_the_last_index
+ iteration = ActionView::PartialIteration.new 3
+ iteration.iterate!
+ iteration.iterate!
+ assert iteration.last?, "last when current is 2"
+ end
+
+ def test_last_is_false_unless_current_is_at_the_last_index
+ iteration = ActionView::PartialIteration.new 3
+ assert !iteration.last?, "not last when current is 0"
+ end
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 67f1aabbd2..c13e59d82b 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -256,7 +256,7 @@ module RenderTestCases
end
def test_render_partial_collection_without_as
- assert_equal "local_inspector,local_inspector_counter",
+ assert_equal "local_inspector,local_inspector_counter,local_inspector_iteration",
@view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ])
end
@@ -328,7 +328,8 @@ module RenderTestCases
exception = assert_raises ActionView::Template::Error do
@controller_view.render("partial_name_local_variable")
end
- assert_match "undefined local variable or method `partial_name_local_variable'", exception.message
+ assert_instance_of NameError, exception.original_exception
+ assert_equal :partial_name_local_variable, exception.original_exception.name
end
# TODO: The reason for this test is unclear, improve documentation
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 4ee0930341..4582fa13ee 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'rails/engine'
module ActionView
@@ -223,7 +224,7 @@ module ActionView
test "is able to use mounted routes" do
with_routing do |set|
- app = Class.new do
+ app = Class.new(Rails::Engine) do
def self.routes
@routes ||= ActionDispatch::Routing::RouteSet.new
end