aboutsummaryrefslogtreecommitdiffstats
path: root/actionview
diff options
context:
space:
mode:
Diffstat (limited to 'actionview')
-rw-r--r--actionview/CHANGELOG.md88
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/README.rdoc4
-rw-r--r--actionview/Rakefile27
-rw-r--r--actionview/actionview.gemspec4
-rw-r--r--actionview/app/assets/javascripts/MIT-LICENSE2
-rw-r--r--actionview/app/assets/javascripts/README.md15
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/disable.coffee2
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/remote.coffee7
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee10
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee2
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee12
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee2
-rw-r--r--actionview/lib/action_view.rb4
-rw-r--r--actionview/lib/action_view/base.rb117
-rw-r--r--actionview/lib/action_view/context.rb5
-rw-r--r--actionview/lib/action_view/digestor.rb12
-rw-r--r--actionview/lib/action_view/file_template.rb33
-rw-r--r--actionview/lib/action_view/gem_version.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/csp_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/csrf_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb225
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-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/tags/base.rb2
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionview/lib/action_view/layouts.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb93
-rw-r--r--actionview/lib/action_view/railtie.rb11
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb59
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb118
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb28
-rw-r--r--actionview/lib/action_view/renderer/renderer.rb20
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb6
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb36
-rw-r--r--actionview/lib/action_view/rendering.rb82
-rw-r--r--actionview/lib/action_view/template.rb111
-rw-r--r--actionview/lib/action_view/template/handlers.rb28
-rw-r--r--actionview/lib/action_view/template/handlers/builder.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb14
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubi.rb10
-rw-r--r--actionview/lib/action_view/template/handlers/html.rb2
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb4
-rw-r--r--actionview/lib/action_view/template/html.rb19
-rw-r--r--actionview/lib/action_view/template/inline.rb22
-rw-r--r--actionview/lib/action_view/template/resolver.rb164
-rw-r--r--actionview/lib/action_view/template/text.rb8
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb17
-rw-r--r--actionview/lib/action_view/view_paths.rb26
-rw-r--r--actionview/package.json4
-rw-r--r--actionview/test/abstract_unit.rb10
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb10
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb4
-rw-r--r--actionview/test/actionpack/controller/render_test.rb21
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb9
-rw-r--r--actionview/test/activerecord/multifetch_cache_test.rb6
-rw-r--r--actionview/test/activerecord/relation_cache_test.rb3
-rw-r--r--actionview/test/fixtures/actionpack/test/hello.builder2
-rw-r--r--actionview/test/fixtures/test/_first.html.erb1
-rw-r--r--actionview/test/fixtures/test/_first.xml.erb1
-rw-r--r--actionview/test/fixtures/test/_first_layer.html.erb4
-rw-r--r--actionview/test/fixtures/test/_first_layer.xml.erb4
-rw-r--r--actionview/test/fixtures/test/_second.html.erb1
-rw-r--r--actionview/test/fixtures/test/_second.xml.erb1
-rw-r--r--actionview/test/fixtures/test/_second_layer.html.erb4
-rw-r--r--actionview/test/fixtures/test/_second_layer.xml.erb4
-rw-r--r--actionview/test/fixtures/test/hello.builder2
-rw-r--r--actionview/test/fixtures/test/layout_render_file.erb2
-rw-r--r--actionview/test/fixtures/test/mixing_formats.html.erb5
-rw-r--r--actionview/test/fixtures/test/mixing_formats_deep.html.erb5
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb20
-rw-r--r--actionview/test/template/capture_helper_test.rb2
-rw-r--r--actionview/test/template/compiled_templates_test.rb43
-rw-r--r--actionview/test/template/csp_helper_test.rb35
-rw-r--r--actionview/test/template/csrf_helper_test.rb46
-rw-r--r--actionview/test/template/dependency_tracker_test.rb4
-rw-r--r--actionview/test/template/digestor_test.rb29
-rw-r--r--actionview/test/template/fallback_file_system_resolver_test.rb16
-rw-r--r--actionview/test/template/form_collections_helper_test.rb24
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb17
-rw-r--r--actionview/test/template/form_helper_test.rb72
-rw-r--r--actionview/test/template/form_options_helper_test.rb1
-rw-r--r--actionview/test/template/html_test.rb8
-rw-r--r--actionview/test/template/log_subscriber_test.rb8
-rw-r--r--actionview/test/template/lookup_context_test.rb123
-rw-r--r--actionview/test/template/render_test.rb144
-rw-r--r--actionview/test/template/resolver_cache_test.rb1
-rw-r--r--actionview/test/template/resolver_patterns_test.rb7
-rw-r--r--actionview/test/template/streaming_render_test.rb5
-rw-r--r--actionview/test/template/template_test.rb36
-rw-r--r--actionview/test/template/test_case_test.rb11
-rw-r--r--actionview/test/template/testing/fixture_resolver_test.rb2
-rw-r--r--actionview/test/template/testing/null_resolver_test.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb4
-rw-r--r--actionview/test/template/text_test.rb4
-rw-r--r--actionview/test/template/translation_helper_test.rb13
-rw-r--r--actionview/test/template/url_helper_test.rb10
-rw-r--r--actionview/test/ujs/public/test/call-remote.js4
-rw-r--r--actionview/test/ujs/public/test/data-disable-with.js42
-rw-r--r--actionview/test/ujs/public/test/data-disable.js19
-rw-r--r--actionview/test/ujs/public/test/data-remote.js38
-rw-r--r--actionview/test/ujs/public/test/settings.js2
-rw-r--r--actionview/test/ujs/server.rb1
108 files changed, 1712 insertions, 728 deletions
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 1f537e725a..43688fc8a7 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,87 @@
+## Rails 6.0.0.beta3 (March 11, 2019) ##
+
+* Only accept formats from registered mime types
+
+ A lack of filtering on mime types could allow an attacker to read
+ arbitrary files on the target server or to perform a denial of service
+ attack.
+
+ Fixes CVE-2019-5418
+ Fixes CVE-2019-5419
+
+ *John Hawthorn*, *Eileen M. Uchitelle*, *Aaron Patterson*
+
+
+## Rails 6.0.0.beta2 (February 25, 2019) ##
+
+* ActionView::Template.finalize_compiled_template_methods is deprecated with
+ no replacement.
+
+ *tenderlove*
+
+* config.action_view.finalize_compiled_template_methods is deprecated with
+ no replacement.
+
+ *tenderlove*
+
+* Ensure unique DOM IDs for collection inputs with float values.
+ Fixes #34974
+
+ *Mark Edmondson*
+
+
+## Rails 6.0.0.beta1 (January 18, 2019) ##
+
+* [Rename npm package](https://github.com/rails/rails/pull/34905) from
+ [`rails-ujs`](https://www.npmjs.com/package/rails-ujs) to
+ [`@rails/ujs`](https://www.npmjs.com/package/@rails/ujs).
+
+ *Javan Makhmali*
+
+* Remove deprecated `image_alt` helper.
+
+ *Rafael Mendonça França*
+
+* Fix the need of `#protect_against_forgery?` method defined in
+ `ActionView::Base` subclasses. This prevents the use of forms and buttons.
+
+ *Genadi Samokovarov*
+
+* Fix UJS permanently showing disabled text in a[data-remote][data-disable-with] elements within forms.
+
+ Fixes #33889.
+
+ *Wolfgang Hobmaier*
+
+* Prevent non-primary mouse keys from triggering Rails UJS click handlers.
+ Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks.
+ For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur.
+
+ ```
+ <%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %>
+ ```
+
+ Fixes #34541.
+
+ *Wolfgang Hobmaier*
+
+* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
+
+ For example, given input like this:
+
+ ```
+ This is a paragraph with an initial indent,
+ followed by additional lines that are not indented,
+ and finally terminated with a blockquote:
+ "A pithy saying"
+ ```
+
+ Calling `word_wrap` should not trim the indents on the first and last lines.
+
+ Fixes #34487.
+
+ *Lyle Mullican*
+
* Add allocations to template rendering instrumentation.
Adds the allocations for template and partial rendering to the server output on render.
@@ -138,9 +222,9 @@
*Rui Onodera*
-* Rails 6 requires Ruby 2.4.1 or newer.
+* Rails 6 requires Ruby 2.5.0 or newer.
- *Jeremy Daer*
+ *Jeremy Daer*, *Kasper Timm Hansen*
Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index 03a0723564..ba50c67024 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -5,6 +5,8 @@ view helpers that assist when building HTML forms, Atom feeds and more.
Template formats that Action View handles are ERB (embedded Ruby, typically
used to inline short Ruby snippets inside HTML), and XML Builder.
+You can read more about Action View in the {Action View Overview}[https://edgeguides.rubyonrails.org/action_view_overview.html] guide.
+
== Download and installation
The latest version of Action View can be installed with RubyGems:
@@ -27,7 +29,7 @@ Action View is released under the MIT license:
API documentation is at
-* http://api.rubyonrails.org
+* https://api.rubyonrails.org
Bug reports for the Ruby on Rails project can be filed here:
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 7851a2b6bf..237e458b6f 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -31,9 +31,26 @@ namespace :test do
desc "Run tests for rails-ujs"
task :ujs do
+ system("npm run lint")
+ exit $?.exitstatus unless $?.success?
+
begin
+ listen_host = "localhost"
+ listen_port = "4567"
+
+ runner_command = %w(ruby ../ci/qunit-selenium-runner.rb)
+ if ENV["SELENIUM_DRIVER_URL"]
+ require "socket"
+ runner_command += %W(http://#{Socket.gethostname}:#{listen_port}/ #{ENV["SELENIUM_DRIVER_URL"]})
+ listen_host = "0.0.0.0"
+ else
+ runner_command += %W(http://localhost:#{listen_port}/)
+ end
+
Dir.mkdir("log")
- pid = spawn("bundle exec rackup test/ujs/config.ru -p 4567 -s puma > log/test.log 2>&1", pgroup: true)
+ pid = File.open("log/test.log", "w") do |f|
+ spawn(*%W(rackup test/ujs/config.ru -o #{listen_host} -p #{listen_port} -s puma), out: f, err: f, pgroup: true)
+ end
start_time = Time.now
@@ -41,12 +58,16 @@ namespace :test do
break if system("lsof -i :4567", 1 => File::NULL)
if Time.now - start_time > 5
- puts "Timed out after 5 seconds"
+ puts "Failed to start puma after 5 seconds"
+ puts
+ puts File.read("log/test.log")
exit 1
end
+
+ sleep 0.2
end
- system("npm run lint && bundle exec ruby ../ci/qunit-selenium-runner.rb http://localhost:4567/")
+ system(*runner_command)
status = $?.exitstatus
ensure
Process.kill("KILL", -pid) if pid
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 5f1e746421..200c775dcc 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -9,13 +9,13 @@ Gem::Specification.new do |s|
s.summary = "Rendering framework putting the V in MVC (part of Rails)."
s.description = "Simple, battle-tested conventions and helpers for building web pages."
- s.required_ruby_version = ">= 2.4.1"
+ s.required_ruby_version = ">= 2.5.0"
s.license = "MIT"
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
- s.homepage = "http://rubyonrails.org"
+ s.homepage = "https://rubyonrails.org"
s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"]
s.require_path = "lib"
diff --git a/actionview/app/assets/javascripts/MIT-LICENSE b/actionview/app/assets/javascripts/MIT-LICENSE
index 28e1b12496..03319ea365 100644
--- a/actionview/app/assets/javascripts/MIT-LICENSE
+++ b/actionview/app/assets/javascripts/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2018 Rails Core team
+Copyright (c) 2007-2019 Rails Core team
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index b74fa1afad..aa167004b6 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -17,11 +17,11 @@ Note that the `data` attributes this library adds are a feature of HTML5. If you
### NPM
- npm install rails-ujs --save
-
+ npm install @rails/ujs --save
+
### Yarn
-
- yarn add rails-ujs
+
+ yarn add @rails/ujs
Ensure that `.yarnclean` does not include `assets` if you use [yarn autoclean](https://yarnpkg.com/lang/en/docs/cli/autoclean/).
@@ -40,8 +40,7 @@ In a conventional Rails application that uses the asset pipeline, require `rails
If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file:
```javascript
-import Rails from 'rails-ujs';
-Rails.start()
+require("@rails/ujs").start()
```
## How to run tests
@@ -53,5 +52,5 @@ Run `bundle exec rake ujs:server` first, and then run the web tests by visiting
rails-ujs is released under the [MIT License](MIT-LICENSE).
[data]: https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes"
-[validator]: http://validator.w3.org/
-[csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
+[validator]: https://validator.w3.org/
+[csrf]: https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
index a8c692ee62..4cfaead078 100644
--- a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
@@ -34,6 +34,7 @@ Rails.disableElement = (e) ->
# Replace element's html with the 'data-disable-with' after storing original html
# and prevent clicking on it
disableLinkElement = (element) ->
+ return if getData(element, 'ujs:disabled')
replacement = element.getAttribute('data-disable-with')
if replacement?
setData(element, 'ujs:enable-with', element.innerHTML) # store enabled state
@@ -58,6 +59,7 @@ disableFormElements = (form) ->
formElements(form, Rails.formDisableSelector).forEach(disableFormElement)
disableFormElement = (element) ->
+ return if getData(element, 'ujs:disabled')
replacement = element.getAttribute('data-disable-with')
if replacement?
if matches(element, 'button')
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
index b3448dabac..a5b61220bb 100644
--- a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
@@ -82,9 +82,12 @@ Rails.formSubmitButtonClick = (e) ->
setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'))
setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'))
-Rails.handleMetaClick = (e) ->
+Rails.preventInsignificantClick = (e) ->
link = this
method = (link.getAttribute('data-method') or 'GET').toUpperCase()
data = link.getAttribute('data-params')
metaClick = e.metaKey or e.ctrlKey
- e.stopImmediatePropagation() if metaClick and method is 'GET' and not data
+ insignificantMetaClick = metaClick and method is 'GET' and not data
+ primaryMouseKey = e.button is 0
+ e.stopImmediatePropagation() if not primaryMouseKey or insignificantMetaClick
+
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
index 32a915ac0b..0347058195 100644
--- a/actionview/app/assets/javascripts/rails-ujs/start.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -2,9 +2,10 @@
fire, delegate
getData, $
refreshCSRFTokens, CSRFProtection
+ loadCSPNonce
enableElement, disableElement, handleDisabledElement
- handleConfirm
- handleRemote, formSubmitButtonClick, handleMetaClick
+ handleConfirm, preventInsignificantClick
+ handleRemote, formSubmitButtonClick,
handleMethod
} = Rails
@@ -35,13 +36,14 @@ Rails.start = ->
delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+ delegate document, Rails.linkClickSelector, 'click', preventInsignificantClick
delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
delegate document, Rails.linkClickSelector, 'click', handleConfirm
- delegate document, Rails.linkClickSelector, 'click', handleMetaClick
delegate document, Rails.linkClickSelector, 'click', disableElement
delegate document, Rails.linkClickSelector, 'click', handleRemote
delegate document, Rails.linkClickSelector, 'click', handleMethod
+ delegate document, Rails.buttonClickSelector, 'click', preventInsignificantClick
delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
delegate document, Rails.buttonClickSelector, 'click', handleConfirm
delegate document, Rails.buttonClickSelector, 'click', disableElement
@@ -60,11 +62,13 @@ Rails.start = ->
delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+ delegate document, Rails.formInputClickSelector, 'click', preventInsignificantClick
delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
delegate document, Rails.formInputClickSelector, 'click', handleConfirm
delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
+ document.addEventListener('DOMContentLoaded', loadCSPNonce)
window._rails_loaded = true
if window.Rails is Rails and fire(document, 'rails:attachBindings')
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index 019bda635a..5b223d50f6 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -69,7 +69,7 @@ processResponse = (response, type) ->
script.setAttribute('nonce', cspNonce())
script.text = response
document.head.appendChild(script).parentNode.removeChild(script)
- else if type.match(/\bxml\b/)
+ else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
try response = parser.parseFromString(response, type)
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
index 8d2d6ce447..a33f531375 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
@@ -1,4 +1,8 @@
-# Content-Security-Policy nonce for inline scripts
-cspNonce = Rails.cspNonce = ->
- meta = document.querySelector('meta[name=csp-nonce]')
- meta and meta.content
+nonce = null
+
+Rails.loadCSPNonce = ->
+ nonce = document.querySelector("meta[name=csp-nonce]")?.content
+
+# Returns the Content-Security-Policy nonce for inline scripts.
+Rails.cspNonce = ->
+ nonce ? Rails.loadCSPNonce()
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
index a7eee52060..768d9683d4 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
@@ -27,7 +27,7 @@ if typeof CustomEvent isnt 'function'
# obj::
# a native DOM element
# name::
-# string that corrspends to the event you want to trigger
+# string that corresponds to the event you want to trigger
# e.g. 'click', 'submit'
# data::
# data you want to pass when you dispatch an event
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index c1eeda75f5..8cb4648a67 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -35,7 +35,6 @@ module ActionView
eager_autoload do
autoload :Base
autoload :Context
- autoload :CompiledTemplates, "action_view/context"
autoload :Digestor
autoload :Helpers
autoload :LookupContext
@@ -45,6 +44,7 @@ module ActionView
autoload :Rendering
autoload :RoutingUrlFor
autoload :Template
+ autoload :FileTemplate
autoload :ViewPaths
autoload_under "renderer" do
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index d41fe2a608..5253ef7b0c 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -3,6 +3,7 @@
require "active_support/core_ext/module/attr_internal"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/ordered_options"
+require "active_support/deprecation"
require "action_view/log_subscriber"
require "action_view/helpers"
require "action_view/context"
@@ -179,37 +180,133 @@ module ActionView #:nodoc:
def xss_safe? #:nodoc:
true
end
+
+ def with_empty_template_cache # :nodoc:
+ subclass = Class.new(self) {
+ # We can't implement these as self.class because subclasses will
+ # share the same template cache as superclasses, so "changed?" won't work
+ # correctly.
+ define_method(:compiled_method_container) { subclass }
+ define_singleton_method(:compiled_method_container) { subclass }
+ }
+ end
+
+ def changed?(other) # :nodoc:
+ compiled_method_container != other.compiled_method_container
+ end
end
- attr_accessor :view_renderer
+ attr_reader :view_renderer, :lookup_context
attr_internal :config, :assigns
- delegate :lookup_context, to: :view_renderer
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
def assign(new_assigns) # :nodoc:
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
+ # :stopdoc:
+
+ def self.build_lookup_context(context)
+ case context
+ when ActionView::Renderer
+ context.lookup_context
+ when Array
+ ActionView::LookupContext.new(context)
+ when ActionView::PathSet
+ ActionView::LookupContext.new(context)
+ when nil
+ ActionView::LookupContext.new([])
+ else
+ raise NotImplementedError, context.class.name
+ end
+ end
+
+ def self.empty
+ with_view_paths([])
+ end
+
+ def self.with_view_paths(view_paths, assigns = {}, controller = nil)
+ with_context ActionView::LookupContext.new(view_paths), assigns, controller
+ end
+
+ def self.with_context(context, assigns = {}, controller = nil)
+ new context, assigns, controller
+ end
+
+ NULL = Object.new
+
+ # :startdoc:
+
+ def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc:
@_config = ActiveSupport::InheritableOptions.new
- if context.is_a?(ActionView::Renderer)
- @view_renderer = context
+ unless formats == NULL
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
+ Passing formats to ActionView::Base.new is deprecated
+ eowarn
+ end
+
+ case lookup_context
+ when ActionView::LookupContext
+ @lookup_context = lookup_context
else
- lookup_context = context.is_a?(ActionView::LookupContext) ?
- context : ActionView::LookupContext.new(context)
- lookup_context.formats = formats if formats
- lookup_context.prefixes = controller._prefixes if controller
- @view_renderer = ActionView::Renderer.new(lookup_context)
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
+ ActionView::Base instances should be constructed with a lookup context,
+ assignments, and a controller.
+ eowarn
+ @lookup_context = self.class.build_lookup_context(lookup_context)
end
+ @view_renderer = ActionView::Renderer.new @lookup_context
+ @current_template = nil
+
@cache_hit = {}
assign(assigns)
assign_controller(controller)
_prepare_context
end
+ def _run(method, template, locals, buffer, &block)
+ _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
+ @current_template = template
+ @output_buffer = buffer
+ send(method, locals, buffer, &block)
+ ensure
+ @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
+ end
+
+ def compiled_method_container
+ if self.class == ActionView::Base
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
+ ActionView::Base instances must implement `compiled_method_container`
+ or use the class method `with_empty_template_cache` for constructing
+ an ActionView::Base instances that has an empty cache.
+ eowarn
+ end
+
+ self.class
+ end
+
+ def in_rendering_context(options)
+ old_view_renderer = @view_renderer
+ old_lookup_context = @lookup_context
+
+ if !lookup_context.html_fallback_for_js && options[:formats]
+ formats = Array(options[:formats])
+ if formats == [:js]
+ formats << :html
+ end
+ @lookup_context = lookup_context.with_prepended_formats(formats)
+ @view_renderer = ActionView::Renderer.new @lookup_context
+ end
+
+ yield @view_renderer
+ ensure
+ @view_renderer = old_view_renderer
+ @lookup_context = old_lookup_context
+ end
+
ActiveSupport.run_load_hooks(:action_view, self)
end
end
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index 3c605c3ee3..2b22c30a3a 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
module ActionView
- module CompiledTemplates #:nodoc:
- # holds compiled template code
- end
-
# = Action View Context
#
# Action View contexts are supplied to Action Controller to render a template.
@@ -16,7 +12,6 @@ module ActionView
# object that includes this module (although you can call _prepare_context
# defined below).
module Context
- include CompiledTemplates
attr_accessor :output_buffer, :view_flow
# Prepares the context by setting the appropriate instance variables.
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 6d2e471a44..9fa8d7eab1 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -18,11 +18,11 @@ module ActionView
# * <tt>name</tt> - Template name
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
# * <tt>dependencies</tt> - An array of dependent views
- def digest(name:, finder:, dependencies: nil)
+ def digest(name:, format:, finder:, dependencies: nil)
if dependencies.nil? || dependencies.empty?
- cache_key = "#{name}.#{finder.rendered_format}"
+ cache_key = "#{name}.#{format}"
else
- cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
+ cache_key = [ name, format, dependencies ].flatten.compact.join(".")
end
# this is a correctly done double-checked locking idiom
@@ -48,8 +48,6 @@ module ActionView
logical_name = name.gsub(%r|/_|, "/")
if template = find_template(finder, logical_name, [], partial, [])
- finder.rendered_format ||= template.formats.first
-
if node = seen[template.identifier] # handle cycles in the tree
node
else
@@ -73,9 +71,7 @@ module ActionView
private
def find_template(finder, name, prefixes, partial, keys)
finder.disable_cache do
- format = finder.rendered_format
- result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
- result || finder.find_all(name, prefixes, partial, keys).first
+ finder.find_all(name, prefixes, partial, keys).first
end
end
end
diff --git a/actionview/lib/action_view/file_template.rb b/actionview/lib/action_view/file_template.rb
new file mode 100644
index 0000000000..dea02176eb
--- /dev/null
+++ b/actionview/lib/action_view/file_template.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require "action_view/template"
+
+module ActionView
+ class FileTemplate < Template
+ def initialize(filename, handler, details)
+ @filename = filename
+
+ super(nil, filename, handler, details)
+ end
+
+ def source
+ File.binread @filename
+ end
+
+ def refresh(_)
+ self
+ end
+
+ # Exceptions are marshalled when using the parallel test runner with DRb, so we need
+ # to ensure that references to the template object can be marshalled as well. This means forgoing
+ # the marshalling of the compiler mutex and instantiating that again on unmarshalling.
+ def marshal_dump # :nodoc:
+ [ @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ]
+ end
+
+ def marshal_load(array) # :nodoc:
+ @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array
+ @compile_mutex = Mutex.new
+ end
+ end
+end
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 77ae444a58..5bed37583e 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -10,7 +10,7 @@ module ActionView
MAJOR = 6
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta3"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 3d7c8dae75..59d70a1dc4 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -329,14 +329,14 @@ module ActionView
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
#
- # Active Storage (images that are uploaded by the users of your app):
+ # Active Storage blobs (images that are uploaded by the users of your app):
#
# image_tag(user.avatar)
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]))
- # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
- # image_tag(user.avatar.variant(resize_to_fit: [100, 100]), size: '100')
- # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
def image_tag(source, options = {})
options = options.symbolize_keys
check_for_image_tag_errors(options)
@@ -355,29 +355,6 @@ module ActionView
tag("img", options)
end
- # Returns a string suitable for an HTML image tag alt attribute.
- # The +src+ argument is meant to be an image file path.
- # The method removes the basename of the file path and the digest,
- # if any. It also removes hyphens and underscores from file names and
- # replaces them with spaces, returning a space-separated, titleized
- # string.
- #
- # ==== Examples
- #
- # image_alt('rails.png')
- # # => Rails
- #
- # image_alt('hyphenated-file-name.png')
- # # => Hyphenated file name
- #
- # image_alt('underscored_file_name.png')
- # # => Underscored file name
- def image_alt(src)
- ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
-
- File.basename(src, ".*").sub(/-[[:xdigit:]]{32,64}\z/, "").tr("-_", " ").capitalize
- end
-
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index b1a14250c3..020aebeea3 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -216,13 +216,13 @@ module ActionView
end
end
- def digest_path_from_virtual(virtual_path) # :nodoc:
- digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies)
+ def digest_path_from_template(template) # :nodoc:
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
if digest.present?
- "#{virtual_path}:#{digest}"
+ "#{template.virtual_path}:#{digest}"
else
- virtual_path
+ template.virtual_path
end
end
@@ -234,7 +234,7 @@ module ActionView
if virtual_path || digest_path
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
- digest_path ||= digest_path_from_virtual(virtual_path)
+ digest_path ||= digest_path_from_template(@current_template)
[ digest_path, name ]
else
diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb
index e2e065c218..4415018845 100644
--- a/actionview/lib/action_view/helpers/csp_helper.rb
+++ b/actionview/lib/action_view/helpers/csp_helper.rb
@@ -14,9 +14,11 @@ module ActionView
# This is used by the Rails UJS helper to create dynamically
# loaded inline <script> elements.
#
- def csp_meta_tag
+ def csp_meta_tag(**options)
if content_security_policy?
- tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
+ options[:name] = "csp-nonce"
+ options[:content] = content_security_policy_nonce
+ tag("meta", options)
end
end
end
diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb
index 69c59844a6..c0422c6ff5 100644
--- a/actionview/lib/action_view/helpers/csrf_helper.rb
+++ b/actionview/lib/action_view/helpers/csrf_helper.rb
@@ -20,7 +20,7 @@ module ActionView
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
#
def csrf_meta_tags
- if protect_against_forgery?
+ if defined?(protect_against_forgery?) && protect_against_forgery?
[
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
tag("meta", name: "csrf-token", content: form_authenticity_token)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index c2caa77afb..5533cef249 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -739,7 +739,7 @@ module ActionView
# def labelled_form_with(**options, &block)
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
# end
- def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
options[:allow_method_names_outside_object] = true
options[:skip_default_ids] = !form_with_generates_ids
@@ -752,7 +752,7 @@ module ActionView
if block_given?
builder = instantiate_builder(scope, model, options)
- output = capture(builder, &Proc.new)
+ output = capture(builder, &block)
options[:multipart] ||= builder.multipart?
html_options = html_options_for_form_with(url, model, options)
@@ -1680,6 +1680,227 @@ module ActionView
@index = options[:index] || options[:child_index]
end
+ ##
+ # :method: text_field
+ #
+ # :call-seq: text_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.text_field :name %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: password_field
+ #
+ # :call-seq: password_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.password_field :password %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: text_area
+ #
+ # :call-seq: text_area(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.text_area :detail %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: color_field
+ #
+ # :call-seq: color_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.color_field :favorite_color %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: search_field
+ #
+ # :call-seq: search_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.search_field :name %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: telephone_field
+ #
+ # :call-seq: telephone_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.telephone_field :phone %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: phone_field
+ #
+ # :call-seq: phone_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.phone_field :phone %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: date_field
+ #
+ # :call-seq: date_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.date_field :born_on %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: time_field
+ #
+ # :call-seq: time_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.time_field :borned_at %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: datetime_field
+ #
+ # :call-seq: datetime_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.datetime_field :graduation_day %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: datetime_local_field
+ #
+ # :call-seq: datetime_local_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.datetime_local_field :graduation_day %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: month_field
+ #
+ # :call-seq: month_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.month_field :birthday_month %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: week_field
+ #
+ # :call-seq: week_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.week_field :birthday_week %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: url_field
+ #
+ # :call-seq: url_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.url_field :homepage %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: email_field
+ #
+ # :call-seq: email_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.email_field :address %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: number_field
+ #
+ # :call-seq: number_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.number_field :age %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
+ ##
+ # :method: range_field
+ #
+ # :call-seq: range_field(method, options = {})
+ #
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
+ #
+ # <%= form_with model: @user do |f| %>
+ # <%= f.range_field :age %>
+ # <% end %>
+ #
+ # Please refer to the documentation of the base helper for details.
+
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index ebdd96f570..a7747456a4 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -654,7 +654,7 @@ module ActionView
#
# ==== Gotcha
#
- # The HTML specification says when nothing is select on a collection of radio buttons
+ # The HTML specification says when nothing is selected on a collection of radio buttons
# web browsers do not send any value to server.
# Unfortunately this introduces a gotcha:
# if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 279cde5e76..52a951b2ca 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -38,7 +38,7 @@ module ActionView #:nodoc:
# Converts the array to a comma-separated sentence where the last element is
# joined by the connector word. This is the html_safe-aware version of
- # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
#
def to_sentence(array, options = {})
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 1e12aa2736..7ead691113 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -27,10 +27,12 @@ module ActionView
def render(options = {}, locals = {}, &block)
case options
when Hash
- if block_given?
- view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
- else
- view_renderer.render(self, options)
+ in_rendering_context(options) do |renderer|
+ if block_given?
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
+ else
+ view_renderer.render(self, options)
+ end
end
else
view_renderer.render_partial(self, partial: options, locals: locals, &block)
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index eef527d36f..b58e1a6680 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -138,7 +138,7 @@ module ActionView
end
def sanitized_value(value)
- value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s
+ value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
end
def select_content_tag(option_tags, options, html_options)
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a338d076e4..c282505e13 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -228,7 +228,7 @@ module ActionView
# pluralize(2, 'Person', locale: :de)
# # => 2 Personen
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
- word = if count == 1 || count =~ /^1(\.0+)?$/
+ word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
singular
else
plural || singular.pluralize(locale)
@@ -259,7 +259,7 @@ module ActionView
# # => Once\r\nupon\r\na\r\ntime
def word_wrap(text, line_width: 80, break_sequence: "\n")
text.split("\n").collect! do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
end * break_sequence
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index ae1c93e12f..d5b0a9263f 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -114,7 +114,7 @@ module ActionView
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
#
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
# for more information.
def localize(*args)
I18n.localize(*args)
@@ -138,7 +138,7 @@ module ActionView
end
def html_safe_translation_key?(key)
- /(\b|_|\.)html$/.match?(key.to_s)
+ /(?:_|\b)html\z/.match?(key.to_s)
end
end
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 948dd1551f..d63ada3890 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -618,7 +618,7 @@ module ActionView
end
def token_tag(token = nil, form_options: {})
- if token != false && protect_against_forgery?
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 3e6d352c15..08f66bf435 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -322,7 +322,7 @@ module ActionView
end
class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout(formats)
+ def _layout(lookup_context, formats)
if _conditional_layout?
#{layout_definition}
else
@@ -388,8 +388,8 @@ module ActionView
case name
when String then _normalize_layout(name)
when Proc then name
- when true then Proc.new { |formats| _default_layout(formats, true) }
- when :default then Proc.new { |formats| _default_layout(formats, false) }
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
when false, nil then nil
else
raise ArgumentError,
@@ -411,9 +411,9 @@ module ActionView
#
# ==== Returns
# * <tt>template</tt> - The template object for the default layout (or +nil+)
- def _default_layout(formats, require_layout = false)
+ def _default_layout(lookup_context, formats, require_layout = false)
begin
- value = _layout(formats) if action_has_layout?
+ value = _layout(lookup_context, formats) if action_has_layout?
rescue NameError => e
raise e, "Could not render layout: #{e.message}"
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index af67ffa12d..fd3d025cbf 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -3,6 +3,7 @@
require "concurrent/map"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/attribute_accessors"
+require "active_support/deprecation"
require "action_view/template/resolver"
module ActionView
@@ -15,6 +16,8 @@ module ActionView
# only once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
attr_accessor :prefixes, :rendered_format
+ deprecate :rendered_format
+ deprecate :rendered_format=
mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
@@ -24,7 +27,7 @@ module ActionView
registered_details << name
Accessors::DEFAULT_PROCS[name] = block
- Accessors.send :define_method, :"default_#{name}", &block
+ Accessors.define_method(:"default_#{name}", &block)
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{name}
@details.fetch(:#{name}, [])
@@ -57,21 +60,36 @@ module ActionView
alias :eql? :equal?
@details_keys = Concurrent::Map.new
+ @digest_cache = Concurrent::Map.new
- def self.get(details)
+ def self.digest_cache(details)
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
+ end
+
+ def self.details_cache_key(details)
if details[:formats]
details = details.dup
details[:formats] &= Template::Types.symbols
end
- @details_keys[details] ||= Concurrent::Map.new
+ @details_keys[details] ||= Object.new
end
def self.clear
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
+ path_set.each(&:clear_cache)
+ end
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
+ @view_context_class = nil
@details_keys.clear
+ @digest_cache.clear
end
def self.digest_caches
- @details_keys.values
+ @digest_cache.values
+ end
+
+ def self.view_context_class(klass)
+ @view_context_class ||= klass.with_empty_template_cache
end
end
@@ -82,7 +100,7 @@ module ActionView
# Calculate the details key. Remove the handlers from calculation to improve performance
# since the user cannot modify it explicitly.
def details_key #:nodoc:
- @details_key ||= DetailsKey.get(@details) if @cache
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
end
# Temporary skip passing the details_key forward.
@@ -96,7 +114,8 @@ module ActionView
private
def _set_detail(key, value) # :doc:
- @details = @details.dup if @details_key
+ @details = @details.dup if @digest_cache || @details_key
+ @digest_cache = nil
@details_key = nil
@details[key] = value
end
@@ -106,12 +125,6 @@ module ActionView
module ViewPaths
attr_reader :view_paths, :html_fallback_for_js
- # Whenever setting view paths, makes a copy so that we can manipulate them in
- # instance objects as we wish.
- def view_paths=(paths)
- @view_paths = ActionView::PathSet.new(Array(paths))
- end
-
def find(name, prefixes = [], partial = false, keys = [], options = {})
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
end
@@ -138,19 +151,34 @@ module ActionView
# Adds fallbacks to the view paths. Useful in cases when you are rendering
# a :file.
def with_fallbacks
- added_resolvers = 0
- self.class.fallbacks.each do |resolver|
- next if view_paths.include?(resolver)
- view_paths.push(resolver)
- added_resolvers += 1
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
+
+ if block_given?
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
+ the lookup context returned by `with_fallbacks` instead.
+ eowarn
+
+ begin
+ _view_paths = @view_paths
+ @view_paths = view_paths
+ yield
+ ensure
+ @view_paths = _view_paths
+ end
+ else
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
end
- yield
- ensure
- added_resolvers.times { view_paths.pop }
end
private
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
+ # instance objects as we wish.
+ def build_view_paths(paths)
+ ActionView::PathSet.new(Array(paths))
+ end
+
def args_for_lookup(name, prefixes, partial, keys, details_options)
name, prefixes = normalize_name(name, prefixes)
details, details_key = detail_args_for(details_options)
@@ -163,7 +191,7 @@ module ActionView
user_details = @details.merge(options)
if @cache
- details_key = DetailsKey.get(user_details)
+ details_key = DetailsKey.details_cache_key(user_details)
else
details_key = nil
end
@@ -190,7 +218,7 @@ module ActionView
end
if @cache
- [details, DetailsKey.get(details)]
+ [details, DetailsKey.details_cache_key(details)]
else
[details, nil]
end
@@ -221,16 +249,23 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details_key = nil
+ @digest_cache = nil
@cache = true
@prefixes = prefixes
- @rendered_format = nil
@details = initialize_details({}, details)
- self.view_paths = view_paths
+ @view_paths = build_view_paths(view_paths)
end
def digest_cache
- details_key
+ @digest_cache ||= DetailsKey.digest_cache(@details)
+ end
+
+ def with_prepended_formats(formats)
+ details = @details.dup
+ details[:formats] = formats
+
+ self.class.new(@view_paths, details, @prefixes)
end
def initialize_details(target, details)
@@ -245,7 +280,15 @@ module ActionView
# add :html as fallback to :js.
def formats=(values)
if values
+ values = values.dup
values.concat(default_formats) if values.delete "*/*"
+ values.uniq!
+
+ invalid_values = (values - Template::Types.symbols)
+ unless invalid_values.empty?
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
+ end
+
if values == [:js]
values << :html
@html_fallback_for_js = true
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 12d06bf376..a25e1d3d02 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -6,11 +6,13 @@ require "rails"
module ActionView
# = Action View Railtie
class Railtie < Rails::Engine # :nodoc:
+ NULL_OPTION = Object.new
+
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.embed_authenticity_token_in_remote_forms = nil
config.action_view.debug_missing_translation = true
config.action_view.default_enforce_utf8 = nil
- config.action_view.finalize_compiled_template_methods = true
+ config.action_view.finalize_compiled_template_methods = NULL_OPTION
config.eager_load_namespaces << ActionView
@@ -48,8 +50,11 @@ module ActionView
initializer "action_view.finalize_compiled_template_methods" do |app|
ActiveSupport.on_load(:action_view) do
- ActionView::Template.finalize_compiled_template_methods =
- app.config.action_view.delete(:finalize_compiled_template_methods)
+ option = app.config.action_view.delete(:finalize_compiled_template_methods)
+
+ if option != NULL_OPTION
+ ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 20b2523cac..475452f1bb 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -17,7 +17,7 @@ module ActionView
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
- delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
+ delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
@@ -27,6 +27,53 @@ module ActionView
raise NotImplementedError
end
+ class RenderedCollection # :nodoc:
+ def self.empty(format)
+ EmptyCollection.new format
+ end
+
+ attr_reader :rendered_templates
+
+ def initialize(rendered_templates, spacer)
+ @rendered_templates = rendered_templates
+ @spacer = spacer
+ end
+
+ def body
+ @rendered_templates.map(&:body).join(@spacer.body).html_safe
+ end
+
+ def format
+ rendered_templates.first.format
+ end
+
+ class EmptyCollection
+ attr_reader :format
+
+ def initialize(format)
+ @format = format
+ end
+
+ def body; nil; end
+ end
+ end
+
+ class RenderedTemplate # :nodoc:
+ attr_reader :body, :layout, :template
+
+ def initialize(body, layout, template)
+ @body = body
+ @layout = layout
+ @template = template
+ end
+
+ def format
+ template.format
+ end
+
+ EMPTY_SPACER = Struct.new(:body).new
+ end
+
private
def extract_details(options) # :doc:
@@ -38,8 +85,6 @@ module ActionView
end
def instrument(name, **options) # :doc:
- options[:identifier] ||= (@template && @template.identifier) || @path
-
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
yield payload
end
@@ -51,5 +96,13 @@ module ActionView
@lookup_context.formats = formats | @lookup_context.formats
end
+
+ def build_rendered_template(content, template, layout = nil)
+ RenderedTemplate.new content, layout, template
+ end
+
+ def build_rendered_collection(templates, spacer)
+ RenderedCollection.new templates, spacer
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index cb850d75ee..ed8d5cf54e 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -295,43 +295,60 @@ module ActionView
end
def render(context, options, block)
- setup(context, options, block)
- @template = find_partial
+ as = as_variable(options)
+ setup(context, options, as, block)
- @lookup_context.rendered_format ||= begin
- if @template && @template.formats.present?
- @template.formats.first
+ if @path
+ if @has_object || @collection
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
+ @template_keys = retrieve_template_keys(@variable)
else
- formats.first
+ @template_keys = @locals.keys
+ end
+ template = find_partial(@path, @template_keys)
+ @variable ||= template.variable
+ else
+ if options[:cached]
+ raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
end
+ template = nil
end
if @collection
- render_collection
+ render_collection(context, template)
else
- render_partial
+ render_partial(context, template)
end
end
private
- def render_collection
- instrument(:collection, count: @collection.size) do |payload|
- return nil if @collection.blank?
+ def render_collection(view, template)
+ identifier = (template && template.identifier) || @path
+ instrument(:collection, identifier: identifier, count: @collection.size) do |payload|
+ return RenderedCollection.empty(@lookup_context.formats.first) if @collection.blank?
- if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
+ spacer = if @options.key?(:spacer_template)
+ spacer_template = find_template(@options[:spacer_template], @locals.keys)
+ build_rendered_template(spacer_template.render(view, @locals), spacer_template)
+ else
+ RenderedTemplate::EMPTY_SPACER
end
- cache_collection_render(payload) do
- @template ? collection_with_template : collection_without_template
- end.join(spacer).html_safe
+ collection_body = if template
+ cache_collection_render(payload, view, template) do
+ collection_with_template(view, template)
+ end
+ else
+ collection_without_template(view)
+ end
+ build_rendered_collection(collection_body, spacer)
end
end
- def render_partial
- instrument(:partial) do |payload|
- view, locals, block = @view, @locals, @block
+ def render_partial(view, template)
+ instrument(:partial, identifier: template.identifier) do |payload|
+ locals, block = @locals, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
@@ -341,13 +358,13 @@ module ActionView
object = locals[as] if object.nil? # Respect object when object is false
locals[as] = object if @has_object
- content = @template.render(view, locals) do |*name|
+ content = template.render(view, locals) do |*name|
view._layout_for(*name, &block)
end
content = layout.render(view, locals) { content } if layout
- payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
- content
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
+ build_rendered_template(content, template, layout)
end
end
@@ -358,16 +375,13 @@ module ActionView
# If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
# set to that string. Otherwise, the +options[:partial]+ object must
# respond to +to_partial_path+ in order to setup the path.
- def setup(context, options, block)
- @view = context
+ def setup(context, options, as, block)
@options = options
@block = block
- @locals = options[:locals] ? options[:locals].symbolize_keys : {}
+ @locals = options[:locals] || {}
@details = extract_details(options)
- prepend_formats(options[:formats])
-
partial = options[:partial]
if String === partial
@@ -381,26 +395,26 @@ module ActionView
@collection = collection_from_object || collection_from_options
if @collection
- paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.one? ? paths.first : nil
+ paths = @collection_data = @collection.map { |o| partial_path(o, context) }
+ if paths.uniq.length == 1
+ @path = paths.first
+ else
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
+ @path = nil
+ end
else
- @path = partial_path
+ @path = partial_path(@object, context)
end
end
+ self
+ end
+
+ def as_variable(options)
if as = options[:as]
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
- as = as.to_sym
- end
-
- if @path
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
- @template_keys = retrieve_template_keys
- else
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
+ as.to_sym
end
-
- self
end
def collection_from_options
@@ -414,8 +428,8 @@ module ActionView
@object.to_ary if @object.respond_to?(:to_ary)
end
- def find_partial
- find_template(@path, @template_keys) if @path
+ def find_partial(path, template_keys)
+ find_template(path, template_keys)
end
def find_template(path, locals)
@@ -423,8 +437,8 @@ module ActionView
@lookup_context.find_template(path, prefixes, true, locals, @details)
end
- def collection_with_template
- view, locals, template = @view, @locals, @template
+ def collection_with_template(view, template)
+ locals = @locals
as, counter, iteration = @variable, @variable_counter, @variable_iteration
if layout = @options[:layout]
@@ -441,12 +455,12 @@ module ActionView
content = template.render(view, locals)
content = layout.render(view, locals) { content } if layout
partial_iteration.iterate!
- content
+ build_rendered_template(content, template, layout)
end
end
- def collection_without_template
- view, locals, collection_data = @view, @locals, @collection_data
+ def collection_without_template(view)
+ locals, collection_data = @locals, @collection_data
cache = {}
keys = @locals.keys
@@ -463,7 +477,7 @@ module ActionView
template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
content = template.render(view, locals)
partial_iteration.iterate!
- content
+ build_rendered_template(content, template)
end
end
@@ -474,7 +488,7 @@ module ActionView
#
# If +prefix_partial_path_with_controller_namespace+ is true, then this
# method will prefix the partial paths with a namespace.
- def partial_path(object = @object)
+ def partial_path(object, view)
object = object.to_model if object.respond_to?(:to_model)
path = if object.respond_to?(:to_partial_path)
@@ -483,7 +497,7 @@ module ActionView
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
end
- if @view.prefix_partial_path_with_controller_namespace
+ if view.prefix_partial_path_with_controller_namespace
prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
else
path
@@ -511,9 +525,9 @@ module ActionView
end
end
- def retrieve_template_keys
+ def retrieve_template_keys(variable)
keys = @locals.keys
- keys << @variable if @has_object || @collection
+ keys << variable
if @collection
keys << @variable_counter
keys << @variable_iteration
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index 5aa6f77902..ed59033e27 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -11,13 +11,13 @@ module ActionView
end
private
- def cache_collection_render(instrumentation_payload)
+ def cache_collection_render(instrumentation_payload, view, template)
return yield unless @options[:cached]
# Result is a hash with the key represents the
# key used for cache lookup and the value is the item
# on which the partial is being rendered
- keyed_collection = collection_by_cache_keys
+ keyed_collection = collection_by_cache_keys(view, template)
# Pull all partials from cache
# Result is a hash, key matches the entry in
@@ -40,7 +40,7 @@ module ActionView
rendered_partials = @collection.empty? ? [] : yield
index = 0
- fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
+ fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
# This block is called once
# for every cache miss while preserving order.
rendered_partials[index].tap { index += 1 }
@@ -51,23 +51,21 @@ module ActionView
@options[:cached].respond_to?(:call)
end
- def collection_by_cache_keys
+ def collection_by_cache_keys(view, template)
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
+ digest_path = view.digest_path_from_template(template)
+
@collection.each_with_object({}) do |item, hash|
- hash[expanded_cache_key(seed.call(item))] = item
+ hash[expanded_cache_key(seed.call(item), view, template, digest_path)] = item
end
end
- def expanded_cache_key(key)
- key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path, digest_path: digest_path))
+ def expanded_cache_key(key, view, template, digest_path)
+ key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
- def digest_path
- @digest_path ||= @view.digest_path_from_virtual(@template.virtual_path)
- end
-
# `order_by` is an enumerable object containing keys of the cache,
# all keys are passed in whether found already or not.
#
@@ -83,11 +81,13 @@ module ActionView
#
# If the partial is not already cached it will also be
# written back to the underlying cache store.
- def fetch_or_cache_partial(cached_partials, order_by:)
+ def fetch_or_cache_partial(cached_partials, template, order_by:)
order_by.map do |cache_key|
- cached_partials.fetch(cache_key) do
+ if content = cached_partials[cache_key]
+ build_rendered_template(content, template)
+ else
yield.tap do |rendered_partial|
- collection_cache.write(cache_key, rendered_partial)
+ collection_cache.write(cache_key, rendered_partial.body)
end
end
end
diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb
index 3f3a97529d..485eb1a5b4 100644
--- a/actionview/lib/action_view/renderer/renderer.rb
+++ b/actionview/lib/action_view/renderer/renderer.rb
@@ -19,10 +19,14 @@ module ActionView
# Main render entry point shared by Action View and Action Controller.
def render(context, options)
+ render_to_object(context, options).body
+ end
+
+ def render_to_object(context, options) # :nodoc:
if options.key?(:partial)
- render_partial(context, options)
+ render_partial_to_object(context, options)
else
- render_template(context, options)
+ render_template_to_object(context, options)
end
end
@@ -41,16 +45,24 @@ module ActionView
# Direct access to template rendering.
def render_template(context, options) #:nodoc:
- TemplateRenderer.new(@lookup_context).render(context, options)
+ render_template_to_object(context, options).body
end
# Direct access to partial rendering.
def render_partial(context, options, &block) #:nodoc:
- PartialRenderer.new(@lookup_context).render(context, options, block)
+ render_partial_to_object(context, options, &block).body
end
def cache_hits # :nodoc:
@cache_hits ||= {}
end
+
+ def render_template_to_object(context, options) #:nodoc:
+ TemplateRenderer.new(@lookup_context).render(context, options)
+ end
+
+ def render_partial_to_object(context, options, &block) #:nodoc:
+ PartialRenderer.new(@lookup_context).render(context, options, block)
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index bb9db21e32..279ef3c680 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -43,14 +43,14 @@ module ActionView
# For streaming, instead of rendering a given a template, we return a Body
# object that responds to each. This object is initialized with a block
# that knows how to render the template.
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
- return [super] unless layout_name && template.supports_streaming?
+ def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
+ return [super.body] unless layout_name && template.supports_streaming?
locals ||= {}
layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
Body.new do |buffer|
- delayed_render(buffer, template, layout, @view, locals)
+ delayed_render(buffer, template, layout, view, locals)
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index ce8908924a..9548fe12c4 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -5,15 +5,12 @@ require "active_support/core_ext/object/try"
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
def render(context, options)
- @view = context
@details = extract_details(options)
template = determine_template(options)
- prepend_formats(template.formats)
+ prepend_formats(template.format)
- @lookup_context.rendered_format ||= (template.formats.first || formats.first)
-
- render_template(template, options[:layout], options[:locals])
+ render_template(context, template, options[:layout], options[:locals] || {})
end
private
@@ -29,15 +26,20 @@ module ActionView
elsif options.key?(:html)
Template::HTML.new(options[:html], formats.first)
elsif options.key?(:file)
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
+ @lookup_context.with_fallbacks.find_file(options[:file], nil, false, keys, @details)
elsif options.key?(:inline)
handler = Template.handler_for_extension(options[:type] || "erb")
- Template.new(options[:inline], "inline template", handler, locals: keys)
+ format = if handler.respond_to?(:default_format)
+ handler.default_format
+ else
+ @lookup_context.formats.first
+ end
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
elsif options.key?(:template)
if options[:template].respond_to?(:render)
options[:template]
else
- find_template(options[:template], options[:prefixes], false, keys, @details)
+ @lookup_context.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, :html or :body option."
@@ -46,27 +48,25 @@ module ActionView
# Renders the given template. A string representing the layout can be
# supplied as well.
- def render_template(template, layout_name = nil, locals = nil)
- view, locals = @view, locals || {}
-
- render_with_layout(layout_name, locals) do |layout|
+ def render_template(view, template, layout_name, locals)
+ render_with_layout(view, template, layout_name, locals) do |layout|
instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
template.render(view, locals) { |*name| view._layout_for(*name) }
end
end
end
- def render_with_layout(path, locals)
+ def render_with_layout(view, template, path, locals)
layout = path && find_layout(path, locals.keys, [formats.first])
content = yield(layout)
- if layout
- view = @view
+ body = if layout
view.view_flow.set(:layout, content)
layout.render(view, locals) { |*name| view._layout_for(*name) }
else
content
end
+ build_rendered_template(body, template, layout)
end
# This is the method which actually finds the layout using details in the lookup
@@ -84,16 +84,16 @@ module ActionView
when String
begin
if layout.start_with?("/")
- with_fallbacks { find_template(layout, nil, false, [], details) }
+ @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
else
- find_template(layout, nil, false, [], details)
+ @lookup_context.find_template(layout, nil, false, [], details)
end
rescue ActionView::MissingTemplate
all_details = @details.merge(formats: @lookup_context.default_formats)
raise unless template_exists?(layout, nil, false, [], all_details)
end
when Proc
- resolve_layout(layout.call(formats), keys, formats)
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
else
layout
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 4e5fdfbb2d..5a06bd9da6 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -26,6 +26,13 @@ module ActionView
extend ActiveSupport::Concern
include ActionView::ViewPaths
+ attr_reader :rendered_format
+
+ def initialize
+ @rendered_format = nil
+ super
+ end
+
# Overwrite process to setup I18n proxy.
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
@@ -35,47 +42,59 @@ module ActionView
end
module ClassMethods
- def view_context_class
- @view_context_class ||= begin
- supports_path = supports_path?
- routes = respond_to?(:_routes) && _routes
- helpers = respond_to?(:_helpers) && _helpers
-
- Class.new(ActionView::Base) do
- if routes
- include routes.url_helpers(supports_path)
- include routes.mounted_helpers
- end
-
- if helpers
- include helpers
- end
+ def _routes
+ end
+
+ def _helpers
+ end
+
+ def build_view_context_class(klass, supports_path, routes, helpers)
+ Class.new(klass) do
+ if routes
+ include routes.url_helpers(supports_path)
+ include routes.mounted_helpers
+ end
+
+ if helpers
+ include helpers
end
end
end
- end
- attr_internal_writer :view_context_class
+ def view_context_class
+ klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
+
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
+
+ if klass.changed?(@view_context_class)
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
+ end
+
+ @view_context_class
+ end
+ end
def view_context_class
- @_view_context_class ||= self.class.view_context_class
+ self.class.view_context_class
end
# An instance of a view class. The default view class is ActionView::Base.
#
# The view class must have the following methods:
- # View.new[lookup_context, assigns, controller]
- # Create a new ActionView instance for a controller and we can also pass the arguments.
- # View#render(option)
- # Returns String with the rendered template
+ #
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new
+ # ActionView instance for a controller and we can also pass the arguments.
+ #
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
#
# Override this method in a module to change the default behavior.
def view_context
- view_context_class.new(view_renderer, view_assigns, self)
+ view_context_class.new(lookup_context, view_assigns, self)
end
# Returns an object that is able to render templates.
def view_renderer # :nodoc:
+ # Lifespan: Per controller
@_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
@@ -84,10 +103,6 @@ module ActionView
_render_template(options)
end
- def rendered_format
- Template::Types[lookup_context.rendered_format]
- end
-
private
# Find and render a template based on the options given.
@@ -97,17 +112,22 @@ module ActionView
context = view_context
context.assign assigns if assigns
- lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant
- view_renderer.render(context, options)
+ rendered_template = context.in_rendering_context(options) do |renderer|
+ renderer.render_to_object(context, options)
+ end
+
+ rendered_format = rendered_template.format || lookup_context.formats.first
+ @rendered_format = Template::Types[rendered_format]
+
+ rendered_template.body
end
# Assign the rendered format to look up context.
def _process_format(format)
super
- lookup_context.formats = [format.to_sym]
- lookup_context.rendered_format = lookup_context.formats.first
+ lookup_context.formats = [format.to_sym] if format.to_sym
end
# Normalize args by converting render "foo" to render :action => "foo" and
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 070d82cf17..6e3af1536a 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -2,14 +2,22 @@
require "active_support/core_ext/object/try"
require "active_support/core_ext/kernel/singleton_class"
+require "active_support/deprecation"
require "thread"
+require "delegate"
module ActionView
# = Action View Template
class Template
extend ActiveSupport::Autoload
- mattr_accessor :finalize_compiled_template_methods, default: true
+ def self.finalize_compiled_template_methods
+ ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods is deprecated and has no effect"
+ end
+
+ def self.finalize_compiled_template_methods=(_)
+ ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods= is deprecated and has no effect"
+ end
# === Encodings in ActionView::Template
#
@@ -107,42 +115,55 @@ module ActionView
autoload :Error
autoload :Handlers
autoload :HTML
+ autoload :Inline
autoload :Text
autoload :Types
end
extend Template::Handlers
- attr_accessor :locals, :formats, :variants, :virtual_path
-
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
+ attr_reader :variable, :format, :variant, :locals, :virtual_path
- # This finalizer is needed (and exactly with a proc inside another proc)
- # otherwise templates leak in development.
- Finalizer = proc do |method_name, mod| # :nodoc:
- proc do
- mod.module_eval do
- remove_possible_method method_name
- end
+ def initialize(source, identifier, handler, format: nil, variant: nil, locals: nil, virtual_path: nil, updated_at: nil)
+ unless locals
+ ActiveSupport::Deprecation.warn "ActionView::Template#initialize requires a locals parameter"
+ locals = []
end
- end
-
- def initialize(source, identifier, handler, details)
- format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
@source = source
@identifier = identifier
@handler = handler
@compiled = false
- @original_encoding = nil
- @locals = details[:locals] || []
- @virtual_path = details[:virtual_path]
- @updated_at = details[:updated_at] || Time.now
- @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
- @variants = [details[:variant]]
+ @locals = locals
+ @virtual_path = virtual_path
+
+ @variable = if @virtual_path
+ base = @virtual_path[-1] == "/" ? "" : File.basename(@virtual_path)
+ base =~ /\A_?(.*?)(?:\.\w+)*\z/
+ $1.to_sym
+ end
+
+ if updated_at
+ ActiveSupport::Deprecation.warn "ActionView::Template#updated_at is deprecated"
+ @updated_at = updated_at
+ else
+ @updated_at = Time.now
+ end
+ @format = format
+ @variant = variant
@compile_mutex = Mutex.new
end
+ deprecate :original_encoding
+ deprecate :updated_at
+ deprecate def virtual_path=(_); end
+ deprecate def locals=(_); end
+ deprecate def formats=(_); end
+ deprecate def formats; Array(format); end
+ deprecate def variants=(_); end
+ deprecate def variants; [variant]; end
+
# Returns whether the underlying handler supports streaming. If so,
# a streaming buffer *may* be passed when it starts rendering.
def supports_streaming?
@@ -155,17 +176,17 @@ module ActionView
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, buffer = nil, &block)
+ def render(view, locals, buffer = ActionView::OutputBuffer.new, &block)
instrument_render_template do
compile!(view)
- view.send(method_name, locals, buffer, &block)
+ view._run(method_name, self, locals, buffer, &block)
end
rescue => e
handle_render_error(view, e)
end
def type
- @type ||= Types[@formats.first] if @formats.first
+ @type ||= Types[format]
end
# Receives a view object and return a template similar to self by using @virtual_path.
@@ -187,8 +208,12 @@ module ActionView
end
end
+ def short_identifier
+ @short_identifier ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier
+ end
+
def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier
+ "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>"
end
# This method is responsible for properly setting the encoding of the
@@ -202,7 +227,9 @@ module ActionView
# before passing the source on to the template engine, leaving a
# blank line in its stead.
def encode!
- return unless source.encoding == Encoding::BINARY
+ source = self.source
+
+ return source unless source.encoding == Encoding::BINARY
# Look for # encoding: *. If we find one, we'll encode the
# String in that encoding, otherwise, we'll use the
@@ -240,11 +267,11 @@ module ActionView
# to ensure that references to the template object can be marshalled as well. This means forgoing
# the marshalling of the compiler mutex and instantiating that again on unmarshalling.
def marshal_dump # :nodoc:
- [ @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants ]
+ [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ]
end
def marshal_load(array) # :nodoc:
- @source, @identifier, @handler, @compiled, @original_encoding, @locals, @virtual_path, @updated_at, @formats, @variants = *array
+ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array
@compile_mutex = Mutex.new
end
@@ -264,11 +291,7 @@ module ActionView
# re-compilation
return if @compiled
- if view.is_a?(ActionView::CompiledTemplates)
- mod = ActionView::CompiledTemplates
- else
- mod = view.singleton_class
- end
+ mod = view.compiled_method_container
instrument("!compile_template") do
compile(mod)
@@ -281,6 +304,15 @@ module ActionView
end
end
+ class LegacyTemplate < DelegateClass(Template) # :nodoc:
+ attr_reader :source
+
+ def initialize(template, source)
+ super(template)
+ @source = source
+ end
+ end
+
# Among other things, this method is responsible for properly setting
# the encoding of the compiled template.
#
@@ -294,16 +326,14 @@ module ActionView
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
def compile(mod)
- encode!
- code = @handler.call(self)
+ source = encode!
+ code = @handler.call(self, source)
# Make sure that the resulting String to be eval'd is in the
# encoding of the code
source = +<<-end_src
def #{method_name}(local_assigns, output_buffer)
- _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
- ensure
- @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
+ @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
end
end_src
@@ -318,13 +348,10 @@ module ActionView
# handler is valid in the default_internal. This is for handlers
# that handle encoding but screw up
unless source.valid_encoding?
- raise WrongEncodingError.new(@source, Encoding.default_internal)
+ raise WrongEncodingError.new(source, Encoding.default_internal)
end
mod.module_eval(source, identifier, 0)
- if finalize_compiled_template_methods
- ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
- end
end
def handle_render_error(view, e)
@@ -360,7 +387,7 @@ module ActionView
end
def identifier_method_name
- inspect.tr("^a-z_", "_")
+ short_identifier.tr("^a-z_", "_")
end
def instrument(action, &block) # :doc:
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 7ec76dcc3f..ddaac7a100 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/deprecation"
+
module ActionView #:nodoc:
# = Action View Template Handlers
class Template #:nodoc:
@@ -14,7 +16,7 @@ module ActionView #:nodoc:
base.register_template_handler :erb, ERB.new
base.register_template_handler :html, Html.new
base.register_template_handler :builder, Builder.new
- base.register_template_handler :ruby, :source.to_proc
+ base.register_template_handler :ruby, lambda { |_, source| source }
end
@@template_handlers = {}
@@ -24,11 +26,35 @@ module ActionView #:nodoc:
@@template_extensions ||= @@template_handlers.keys
end
+ class LegacyHandlerWrapper < SimpleDelegator # :nodoc:
+ def call(view, source)
+ __getobj__.call(ActionView::Template::LegacyTemplate.new(view, source))
+ end
+ end
+
# Register an object that knows how to handle template files with the given
# extensions. This can be used to implement new template types.
# The handler must respond to +:call+, which will be passed the template
# and should return the rendered template as a String.
def register_template_handler(*extensions, handler)
+ params = if handler.is_a?(Proc)
+ handler.parameters
+ else
+ handler.method(:call).parameters
+ end
+
+ unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2
+ ActiveSupport::Deprecation.warn <<~eowarn
+ Single arity template handlers are deprecated. Template handlers must
+ now accept two parameters, the view object and the source for the view object.
+ Change:
+ >> #{handler.class}#call(#{params.map(&:last).join(", ")})
+ To:
+ >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source)
+ eowarn
+ handler = LegacyHandlerWrapper.new(handler)
+ end
+
raise(ArgumentError, "Extension is required") if extensions.empty?
extensions.each do |extension|
@@template_handlers[extension.to_sym] = handler
diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb
index 61492ce448..e5413cd2a0 100644
--- a/actionview/lib/action_view/template/handlers/builder.rb
+++ b/actionview/lib/action_view/template/handlers/builder.rb
@@ -5,11 +5,11 @@ module ActionView
class Builder
class_attribute :default_format, default: :xml
- def call(template)
+ def call(template, source)
require_engine
"xml = ::Builder::XmlMarkup.new(:indent => 2);" \
"self.output_buffer = xml.target!;" +
- template.source +
+ source +
";xml.target!;"
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 270be0a380..b6314a5bc3 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -17,8 +17,8 @@ module ActionView
class_attribute :escape_ignore_list, default: ["text/plain"]
[self, singleton_class].each do |base|
- base.send(:alias_method, :escape_whitelist, :escape_ignore_list)
- base.send(:alias_method, :escape_whitelist=, :escape_ignore_list=)
+ base.alias_method :escape_whitelist, :escape_ignore_list
+ base.alias_method :escape_whitelist=, :escape_ignore_list=
base.deprecate(
escape_whitelist: "use #escape_ignore_list instead",
@@ -28,8 +28,8 @@ module ActionView
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
- def self.call(template)
- new.call(template)
+ def self.call(template, source)
+ new.call(template, source)
end
def supports_streaming?
@@ -40,17 +40,17 @@ module ActionView
true
end
- def call(template)
+ def call(template, source)
# First, convert to BINARY, so in case the encoding is
# wrong, we can still find an encoding tag
# (<%# encoding %>) inside the String using a regular
# expression
- template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
+ template_source = source.dup.force_encoding(Encoding::ASCII_8BIT)
erb = template_source.gsub(ENCODING_TAG, "")
encoding = $2
- erb.force_encoding valid_encoding(template.source.dup, encoding)
+ erb.force_encoding valid_encoding(source.dup, encoding)
# Always make sure we return a String in the default_internal
erb.encode!
diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb
index db75f028ed..307b852440 100644
--- a/actionview/lib/action_view/template/handlers/erb/erubi.rb
+++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb
@@ -13,7 +13,7 @@ module ActionView
# Dup properties so that we don't modify argument
properties = Hash[properties]
- properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
+ properties[:preamble] = ""
properties[:postamble] = "@output_buffer.to_s"
properties[:bufvar] = "@output_buffer"
properties[:escapefunc] = ""
@@ -22,8 +22,12 @@ module ActionView
end
def evaluate(action_view_erb_handler_context)
- pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
- action_view_erb_handler_context.instance_eval(&pr)
+ src = @src
+ view = Class.new(ActionView::Base) {
+ include action_view_erb_handler_context._routes.url_helpers
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0)
+ }.empty
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
end
private
diff --git a/actionview/lib/action_view/template/handlers/html.rb b/actionview/lib/action_view/template/handlers/html.rb
index 27004a318c..65857d8587 100644
--- a/actionview/lib/action_view/template/handlers/html.rb
+++ b/actionview/lib/action_view/template/handlers/html.rb
@@ -3,7 +3,7 @@
module ActionView
module Template::Handlers
class Html < Raw
- def call(template)
+ def call(template, source)
"ActionView::OutputBuffer.new #{super}"
end
end
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index 5cd23a0060..57ebb169fc 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -3,8 +3,8 @@
module ActionView
module Template::Handlers
class Raw
- def call(template)
- "#{template.source.inspect}.html_safe;"
+ def call(template, source)
+ "#{source.inspect}.html_safe;"
end
end
end
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
index a262c6d9ad..ecd1c31e79 100644
--- a/actionview/lib/action_view/template/html.rb
+++ b/actionview/lib/action_view/template/html.rb
@@ -1,15 +1,21 @@
# frozen_string_literal: true
+require "active_support/deprecation"
+
module ActionView #:nodoc:
# = Action View HTML Template
class Template #:nodoc:
class HTML #:nodoc:
- attr_accessor :type
+ attr_reader :type
def initialize(string, type = nil)
+ unless type
+ ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter"
+ type = :html
+ end
+
@string = string.to_s
- @type = Types[type] || type if type
- @type ||= Types[:html]
+ @type = type
end
def identifier
@@ -26,9 +32,12 @@ module ActionView #:nodoc:
to_str
end
- def formats
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
+ def format
+ @type
end
+
+ def formats; Array(format); end
+ deprecate :formats
end
end
end
diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb
new file mode 100644
index 0000000000..44658487ea
--- /dev/null
+++ b/actionview/lib/action_view/template/inline.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActionView #:nodoc:
+ class Template #:nodoc:
+ class Inline < Template #:nodoc:
+ # This finalizer is needed (and exactly with a proc inside another proc)
+ # otherwise templates leak in development.
+ Finalizer = proc do |method_name, mod| # :nodoc:
+ proc do
+ mod.module_eval do
+ remove_possible_method method_name
+ end
+ end
+ end
+
+ def compile(mod)
+ super
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 08dd6fb510..3b4c8a94bc 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -63,26 +63,11 @@ module ActionView
# Cache the templates returned by the block
def cache(key, name, prefix, partial, locals)
- if Resolver.caching?
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
- else
- fresh_templates = yield
- cached_templates = @data[key][name][prefix][partial][locals]
-
- if templates_have_changed?(cached_templates, fresh_templates)
- @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
- else
- cached_templates || NO_TEMPLATES
- end
- end
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
end
def cache_query(query) # :nodoc:
- if Resolver.caching?
- @query_cache[query] ||= canonical_no_templates(yield)
- else
- yield
- end
+ @query_cache[query] ||= canonical_no_templates(yield)
end
def clear
@@ -112,19 +97,6 @@ module ActionView
def canonical_no_templates(templates)
templates.empty? ? NO_TEMPLATES : templates
end
-
- def templates_have_changed?(cached_templates, fresh_templates)
- # if either the old or new template list is empty, we don't need to (and can't)
- # compare modification times, and instead just check whether the lists are different
- if cached_templates.blank? || fresh_templates.blank?
- return fresh_templates.blank? != cached_templates.blank?
- end
-
- cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
-
- # if a template has changed, it will be now be newer than all the cached templates
- fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
- end
end
cattr_accessor :caching, default: true
@@ -143,14 +115,18 @@ module ActionView
# Normalizes the arguments and passes it on to find_templates.
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
+ locals = locals.map(&:to_s).sort!.freeze
+
cached(key, [name, prefix, partial], details, locals) do
- find_templates(name, prefix, partial, details)
+ find_templates(name, prefix, partial, details, false, locals)
end
end
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
+ locals = locals.map(&:to_s).sort!.freeze
+
cached(key, [name, prefix, partial], details, locals) do
- find_templates(name, prefix, partial, details, true)
+ find_templates(name, prefix, partial, details, true, locals)
end
end
@@ -165,13 +141,8 @@ module ActionView
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
# normalized.
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"
- end
-
- # Helpers that builds a path. Useful for building virtual paths.
- def build_path(name, prefix, partial)
- Path.build(name, prefix, partial)
+ def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = [])
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false, locals = []) method"
end
# Handles templates caching. If a key is given and caching is on
@@ -180,25 +151,13 @@ module ActionView
# resolver is fresher before returning it.
def cached(key, path_info, details, locals)
name, prefix, partial = path_info
- locals = locals.map(&:to_s).sort!
if key
@cache.cache(key, name, prefix, partial, locals) do
- decorate(yield, path_info, details, locals)
+ yield
end
else
- decorate(yield, path_info, details, locals)
- end
- end
-
- # Ensures all the resolver information is set in the template.
- def decorate(templates, path_info, details, locals)
- cached = nil
- templates.each do |t|
- t.locals = locals
- t.formats = details[:formats] || [:html] if t.formats.empty?
- t.variants = details[:variants] || [] if t.variants.empty?
- t.virtual_path ||= (cached ||= build_path(*path_info))
+ yield
end
end
end
@@ -209,34 +168,42 @@ module ActionView
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
def initialize(pattern = nil)
- @pattern = pattern || DEFAULT_PATTERN
+ if pattern
+ ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
+ @pattern = pattern
+ else
+ @pattern = DEFAULT_PATTERN
+ end
super()
end
private
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
+ def find_templates(name, prefix, partial, details, outside_app_allowed = false, locals)
path = Path.build(name, prefix, partial)
- query(path, details, details[:formats], outside_app_allowed)
+ query(path, details, details[:formats], outside_app_allowed, locals)
end
- def query(path, details, formats, outside_app_allowed)
+ def query(path, details, formats, outside_app_allowed, locals)
template_paths = find_template_paths_from_details(path, details)
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
template_paths.map do |template|
- handler, format, variant = extract_handler_and_format_and_variant(template)
- contents = File.binread(template)
-
- Template.new(contents, File.expand_path(template), handler,
- virtual_path: path.virtual,
- format: format,
- variant: variant,
- updated_at: mtime(template)
- )
+ build_template(template, path.virtual, locals)
end
end
+ def build_template(template, virtual_path, locals)
+ handler, format, variant = extract_handler_and_format_and_variant(template)
+
+ FileTemplate.new(File.expand_path(template), handler,
+ virtual_path: virtual_path,
+ format: format,
+ variant: variant,
+ locals: locals
+ )
+ end
+
def reject_files_external_to_app(files)
files.reject { |filename| !inside_path?(@path, filename) }
end
@@ -285,11 +252,6 @@ module ActionView
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
end
- # Returns the file mtime from the filesystem.
- def mtime(p)
- File.mtime(p)
- end
-
# Extract handler, formats and variant from path. If a format cannot be found neither
# from the path, or the handler, we should return the array of formats given
# to the resolver.
@@ -301,50 +263,22 @@ module ActionView
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
- format &&= Template::Types[format]
+ format = if format
+ Template::Types[format]&.ref
+ else
+ if handler.respond_to?(:default_format) # default_format can return nil
+ handler.default_format
+ else
+ nil
+ end
+ end
+ # Template::Types[format] and handler.default_format can return nil
[handler, format, variant]
end
end
- # A resolver that loads files from the filesystem. It allows setting your own
- # resolving pattern. Such pattern can be a glob string supported by some variables.
- #
- # ==== Examples
- #
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
- # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
- #
- # This one allows you to keep files with different formats in separate subdirectories,
- # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
- # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
- #
- # If you don't specify a pattern then the default will be used.
- #
- # In order to use any of the customized resolvers above in a Rails application, you just need
- # to configure ActionController::Base.view_paths in an initializer, for example:
- #
- # ActionController::Base.view_paths = FileSystemResolver.new(
- # Rails.root.join("app/views"),
- # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
- # )
- #
- # ==== Pattern format and variables
- #
- # Pattern has to be a valid glob string, and it allows you to use the
- # following variables:
- #
- # * <tt>:prefix</tt> - usually the controller path
- # * <tt>:action</tt> - name of the action
- # * <tt>:locale</tt> - possible locale versions
- # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
- # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
- #
+ # A resolver that loads files from the filesystem.
class FileSystemResolver < PathResolver
def initialize(path, pattern = nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
@@ -365,6 +299,10 @@ module ActionView
# An Optimized resolver for Rails' most common case.
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
+ def initialize(path)
+ super(path)
+ end
+
private
def find_template_paths_from_details(path, details)
@@ -378,7 +316,7 @@ module ActionView
# This regex match does double duty of finding only files which match
# details (instead of just matching the prefix) and also filtering for
# case-insensitive file systems.
- !filename.match(regex) ||
+ !regex.match?(filename) ||
File.directory?(filename)
end.sort_by do |filename|
# Because we scanned the directory, instead of checking for files
@@ -424,8 +362,8 @@ module ActionView
[new(""), new("/")]
end
- def decorate(*)
- super.each { |t| t.virtual_path = nil }
+ def build_template(template, virtual_path, locals)
+ super(template, nil, locals)
end
end
end
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index f8d6c2811f..c5fd55f1b3 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -8,7 +8,6 @@ module ActionView #:nodoc:
def initialize(string)
@string = string.to_s
- @type = Types[:text]
end
def identifier
@@ -25,9 +24,12 @@ module ActionView #:nodoc:
to_str
end
- def formats
- [@type.ref]
+ def format
+ :text
end
+
+ def formats; Array(format); end
+ deprecate :formats
end
end
end
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 1fad08a689..a16dc0096e 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -8,20 +8,22 @@ module ActionView #:nodoc:
# useful for testing extensions that have no way of knowing what the file
# system will look like at runtime.
class FixtureResolver < PathResolver
- attr_reader :hash
-
def initialize(hash = {}, pattern = nil)
super(pattern)
@hash = hash
end
+ def data
+ @hash
+ end
+
def to_s
@hash.keys.join(", ")
end
private
- def query(path, exts, _, _)
+ def query(path, exts, _, _, locals)
query = +""
EXTENSIONS.each_key do |ext|
query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
@@ -29,15 +31,14 @@ module ActionView #:nodoc:
query = /^(#{Regexp.escape(path)})#{query}$/
templates = []
- @hash.each do |_path, array|
- source, updated_at = array
+ @hash.each do |_path, source|
next unless query.match?(_path)
handler, format, variant = extract_handler_and_format_and_variant(_path)
templates << Template.new(source, _path, handler,
virtual_path: path.virtual,
format: format,
variant: variant,
- updated_at: updated_at
+ locals: locals
)
end
@@ -46,9 +47,9 @@ module ActionView #:nodoc:
end
class NullResolver < PathResolver
- def query(path, exts, _, _)
+ def query(path, exts, _, _, locals)
handler, format, variant = extract_handler_and_format_and_variant(path)
- [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)]
+ [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)]
end
end
end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index d5694d77f4..3ca5aedc14 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -5,13 +5,21 @@ module ActionView
extend ActiveSupport::Concern
included do
- class_attribute :_view_paths, default: ActionView::PathSet.new.freeze
+ ViewPaths.set_view_paths(self, ActionView::PathSet.new.freeze)
end
delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
:locale, :locale=, to: :lookup_context
module ClassMethods
+ def _view_paths
+ ViewPaths.get_view_paths(self)
+ end
+
+ def _view_paths=(paths)
+ ViewPaths.set_view_paths(self, paths)
+ end
+
def _prefixes # :nodoc:
@_prefixes ||= begin
return local_prefixes if superclass.abstract?
@@ -29,6 +37,22 @@ module ActionView
end
end
+ # :stopdoc:
+ @all_view_paths = {}
+
+ def self.get_view_paths(klass)
+ @all_view_paths[klass] || get_view_paths(klass.superclass)
+ end
+
+ def self.set_view_paths(klass, paths)
+ @all_view_paths[klass] = paths
+ end
+
+ def self.all_view_paths
+ @all_view_paths.values.uniq
+ end
+ # :startdoc:
+
# The prefixes used in render "foo" shortcuts.
def _prefixes # :nodoc:
self.class._prefixes
diff --git a/actionview/package.json b/actionview/package.json
index 1f74df79d3..e5c8b9bb7d 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
- "name": "rails-ujs",
- "version": "6.0.0-alpha",
+ "name": "@rails/ujs",
+ "version": "6.0.0-beta3",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index f90626ad9e..fe317bf39e 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -48,7 +48,8 @@ module RenderERBUtils
@view ||= begin
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::PathSet.new([path])
- ActionView::Base.new(view_paths)
+ view = ActionView::Base.with_empty_template_cache
+ view.with_view_paths(view_paths)
end
end
@@ -58,10 +59,11 @@ module RenderERBUtils
template = ActionView::Template.new(
string.strip,
"test template",
- ActionView::Template::Handlers::ERB,
- {})
+ ActionView::Template.handler_for_extension(:erb),
+ format: :html, locals: [])
- template.render(self, {}).strip
+ view = ActionView::Base.with_empty_template_cache
+ template.render(view.empty, {}).strip
end
end
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index 4d4e2b8ef2..eecc19d413 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -229,11 +229,11 @@ module AbstractController
end
class ActionMissingRespondToActionController < AbstractController::Base
- # No actions
- private
- def action_missing(action_name)
- self.response_body = "success"
- end
+ # No actions
+ private
+ def action_missing(action_name)
+ self.response_body = "success"
+ end
end
class RespondToActionController < AbstractController::Base
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index 6d5c97b7fd..838b564c5d 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -70,7 +70,7 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase
end
def test_third_party_template_library_auto_discovers_layout
- with_template_handler :mab, lambda { |template| template.source.inspect } do
+ with_template_handler :mab, lambda { |template, source| source.inspect } do
@controller = ThirdPartyTemplateLibraryController.new
get :hello
assert_response :success
@@ -212,7 +212,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
end
def test_layout_set_when_using_render
- with_template_handler :mab, lambda { |template| template.source.inspect } do
+ with_template_handler :mab, lambda { |template, source| source.inspect } do
@controller = SetsLayoutInRenderController.new
get :hello
assert_includes @response.body, "layouts/third_party_template_library.mab"
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 204903c60c..52c3c54d96 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -174,6 +174,10 @@ class TestController < ActionController::Base
render inline: "<%= controller_name %>"
end
+ def inline_rendered_format_without_format
+ render inline: "test"
+ end
+
# :ported:
def render_custom_code
render plain: "hello world", status: 404
@@ -485,8 +489,8 @@ class TestController < ActionController::Base
render partial: "customer", locals: { customer: Customer.new("david") }
end
- def partial_with_string_locals
- render partial: "customer", locals: { "customer" => Customer.new("david") }
+ def partial_with_hashlike_locals
+ render partial: "customer", locals: ActionController::Parameters.new(customer: Customer.new("david"))
end
def partial_with_form_builder
@@ -659,6 +663,7 @@ class RenderTest < ActionController::TestCase
get :hello_world_from_rxml_using_action, to: "test#hello_world_from_rxml_using_action"
get :hello_world_from_rxml_using_template, to: "test#hello_world_from_rxml_using_template"
get :hello_world_with_layout_false, to: "test#hello_world_with_layout_false"
+ get :inline_rendered_format_without_format, to: "test#inline_rendered_format_without_format"
get :layout_overriding_layout, to: "test#layout_overriding_layout"
get :layout_test, to: "test#layout_test"
get :layout_test_with_different_layout, to: "test#layout_test_with_different_layout"
@@ -691,7 +696,7 @@ class RenderTest < ActionController::TestCase
get :partial_with_locals, to: "test#partial_with_locals"
get :partial_with_nested_object, to: "test#partial_with_nested_object"
get :partial_with_nested_object_shorthand, to: "test#partial_with_nested_object_shorthand"
- get :partial_with_string_locals, to: "test#partial_with_string_locals"
+ get :partial_with_hashlike_locals, to: "test#partial_with_hashlike_locals"
get :partials_list, to: "test#partials_list"
get :render_action_hello_world, to: "test#render_action_hello_world"
get :render_action_hello_world_as_string, to: "test#render_action_hello_world_as_string"
@@ -1015,6 +1020,12 @@ class RenderTest < ActionController::TestCase
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
end
+ def test_rendered_format_without_format
+ get :inline_rendered_format_without_format
+ assert_equal "test", @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
def test_partials_list
get :partials_list
assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
@@ -1292,8 +1303,8 @@ class RenderTest < ActionController::TestCase
assert_equal "Hello: david", @response.body
end
- def test_partial_with_string_locals
- get :partial_with_string_locals
+ def test_partial_with_hashlike_locals
+ get :partial_with_hashlike_locals
assert_equal "Hello: david", @response.body
end
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index 7f3fe0fa08..4449d08496 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -77,7 +77,7 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def test_template_appends_view_path_correctly
- @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller)
+ @controller.instance_variable_set :@template, ActionView::Base.with_view_paths(TestController.view_paths, {}, @controller)
class_view_paths = TestController.view_paths
@controller.append_view_path "foo"
@@ -89,7 +89,7 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def test_template_prepends_view_path_correctly
- @controller.instance_variable_set :@template, ActionView::Base.new(TestController.view_paths, {}, @controller)
+ @controller.instance_variable_set :@template, ActionView::Base.with_view_paths(TestController.view_paths, {}, @controller)
class_view_paths = TestController.view_paths
@controller.prepend_view_path "baz"
@@ -143,8 +143,9 @@ class ViewLoadPathsTest < ActionController::TestCase
"Decorated body",
template.identifier,
template.handler,
- virtual_path: template.virtual_path,
- format: template.formats
+ virtual_path: template.virtual_path,
+ format: template.format,
+ locals: template.locals
)
end
end
diff --git a/actionview/test/activerecord/multifetch_cache_test.rb b/actionview/test/activerecord/multifetch_cache_test.rb
index 12be069e69..f56168bda5 100644
--- a/actionview/test/activerecord/multifetch_cache_test.rb
+++ b/actionview/test/activerecord/multifetch_cache_test.rb
@@ -10,8 +10,10 @@ class MultifetchCacheTest < ActiveRecordTestCase
def setup
view_paths = ActionController::Base.view_paths
+ view_paths.each(&:clear_cache)
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
- @view = Class.new(ActionView::Base) do
+ @view = Class.new(ActionView::Base.with_empty_template_cache) do
def view_cache_dependencies
[]
end
@@ -19,7 +21,7 @@ class MultifetchCacheTest < ActiveRecordTestCase
def combined_fragment_cache_key(key)
[ :views, key ]
end
- end.new(view_paths, {})
+ end.with_view_paths(view_paths, {})
end
def test_only_preloading_for_records_that_miss_the_cache
diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb
index a6befc3ee5..6fe83dcb9a 100644
--- a/actionview/test/activerecord/relation_cache_test.rb
+++ b/actionview/test/activerecord/relation_cache_test.rb
@@ -11,13 +11,14 @@ class RelationCacheTest < ActionView::TestCase
lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
@view_renderer = ActionView::Renderer.new(lookup_context)
@virtual_path = "path"
+ @current_template = lookup_context.find "test/hello_world"
controller.cache_store = ActiveSupport::Cache::MemoryStore.new
end
def test_cache_relation_other
cache(Project.all) { concat("Hello World") }
- assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}")
+ assert_equal "Hello World", controller.cache_store.read("views/test/hello_world:fa9482a68ce25bf7589b8eddad72f736/projects-#{Project.count}")
end
def view_cache_dependencies; []; end
diff --git a/actionview/test/fixtures/actionpack/test/hello.builder b/actionview/test/fixtures/actionpack/test/hello.builder
index b8ab17ad5b..4c34ee85f0 100644
--- a/actionview/test/fixtures/actionpack/test/hello.builder
+++ b/actionview/test/fixtures/actionpack/test/hello.builder
@@ -1,4 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
- xml << render(file: "test/greeting")
+ xml << render(template: "test/greeting")
end
diff --git a/actionview/test/fixtures/test/_first.html.erb b/actionview/test/fixtures/test/_first.html.erb
new file mode 100644
index 0000000000..2e2c825acb
--- /dev/null
+++ b/actionview/test/fixtures/test/_first.html.erb
@@ -0,0 +1 @@
+"HTML"
diff --git a/actionview/test/fixtures/test/_first.xml.erb b/actionview/test/fixtures/test/_first.xml.erb
new file mode 100644
index 0000000000..9cf4f4ec85
--- /dev/null
+++ b/actionview/test/fixtures/test/_first.xml.erb
@@ -0,0 +1 @@
+"XML"
diff --git a/actionview/test/fixtures/test/_first_layer.html.erb b/actionview/test/fixtures/test/_first_layer.html.erb
new file mode 100644
index 0000000000..c1f1acb410
--- /dev/null
+++ b/actionview/test/fixtures/test/_first_layer.html.erb
@@ -0,0 +1,4 @@
+{"format":"HTML", "children":
+[
+ <%= render(partial: "first").chomp.html_safe %>,
+]}
diff --git a/actionview/test/fixtures/test/_first_layer.xml.erb b/actionview/test/fixtures/test/_first_layer.xml.erb
new file mode 100644
index 0000000000..b8581bbbfc
--- /dev/null
+++ b/actionview/test/fixtures/test/_first_layer.xml.erb
@@ -0,0 +1,4 @@
+{"format":"XML", "children":
+[
+ <%= render(partial: "first").chomp.html_safe %>
+]}
diff --git a/actionview/test/fixtures/test/_second.html.erb b/actionview/test/fixtures/test/_second.html.erb
new file mode 100644
index 0000000000..2e2c825acb
--- /dev/null
+++ b/actionview/test/fixtures/test/_second.html.erb
@@ -0,0 +1 @@
+"HTML"
diff --git a/actionview/test/fixtures/test/_second.xml.erb b/actionview/test/fixtures/test/_second.xml.erb
new file mode 100644
index 0000000000..9cf4f4ec85
--- /dev/null
+++ b/actionview/test/fixtures/test/_second.xml.erb
@@ -0,0 +1 @@
+"XML"
diff --git a/actionview/test/fixtures/test/_second_layer.html.erb b/actionview/test/fixtures/test/_second_layer.html.erb
new file mode 100644
index 0000000000..307706abd2
--- /dev/null
+++ b/actionview/test/fixtures/test/_second_layer.html.erb
@@ -0,0 +1,4 @@
+{"format":"HTML", "children":
+[
+ <%= render(partial: "first").chomp.html_safe %>
+]}
diff --git a/actionview/test/fixtures/test/_second_layer.xml.erb b/actionview/test/fixtures/test/_second_layer.xml.erb
new file mode 100644
index 0000000000..b8581bbbfc
--- /dev/null
+++ b/actionview/test/fixtures/test/_second_layer.xml.erb
@@ -0,0 +1,4 @@
+{"format":"XML", "children":
+[
+ <%= render(partial: "first").chomp.html_safe %>
+]}
diff --git a/actionview/test/fixtures/test/hello.builder b/actionview/test/fixtures/test/hello.builder
index b8ab17ad5b..4c34ee85f0 100644
--- a/actionview/test/fixtures/test/hello.builder
+++ b/actionview/test/fixtures/test/hello.builder
@@ -1,4 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
- xml << render(file: "test/greeting")
+ xml << render(template: "test/greeting")
end
diff --git a/actionview/test/fixtures/test/layout_render_file.erb b/actionview/test/fixtures/test/layout_render_file.erb
index 2f8e921c5f..0477743dc4 100644
--- a/actionview/test/fixtures/test/layout_render_file.erb
+++ b/actionview/test/fixtures/test/layout_render_file.erb
@@ -1,2 +1,2 @@
<% content_for :title do %>title<% end -%>
-<%= render :file => 'layouts/yield' -%> \ No newline at end of file
+<%= render template: 'layouts/yield' -%>
diff --git a/actionview/test/fixtures/test/mixing_formats.html.erb b/actionview/test/fixtures/test/mixing_formats.html.erb
new file mode 100644
index 0000000000..c65cdd7dd4
--- /dev/null
+++ b/actionview/test/fixtures/test/mixing_formats.html.erb
@@ -0,0 +1,5 @@
+{"format":"HTML", "children":
+[
+ <%= render(partial: "first", formats: :xml).chomp.html_safe %>,
+ <%= render(partial: "second").chomp.html_safe %>
+]}
diff --git a/actionview/test/fixtures/test/mixing_formats_deep.html.erb b/actionview/test/fixtures/test/mixing_formats_deep.html.erb
new file mode 100644
index 0000000000..e328887eeb
--- /dev/null
+++ b/actionview/test/fixtures/test/mixing_formats_deep.html.erb
@@ -0,0 +1,5 @@
+{"format":"HTML", "children":
+[
+ <%= render(partial: "first_layer", formats: :xml).chomp.html_safe %>,
+ <%= render(partial: "second_layer").chomp.html_safe %>
+]}
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index e68f03d1f4..e371a87614 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -512,26 +512,6 @@ class AssetTagHelperTest < ActionView::TestCase
UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
- def test_image_alt
- [nil, "/", "/foo/bar/", "foo/bar/"].each do |prefix|
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails.png")
- end
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png")
- end
- assert_deprecated do
- assert_equal "Rails", image_alt("#{prefix}rails-f56ef62bc41b040664e801a38f068082a75d506d9048307e8096737463503d0b.png")
- end
- assert_deprecated do
- assert_equal "Long file name with hyphens", image_alt("#{prefix}long-file-name-with-hyphens.png")
- end
- assert_deprecated do
- assert_equal "Long file name with underscores", image_alt("#{prefix}long_file_name_with_underscores.png")
- end
- end
- end
-
def test_image_tag
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index e172497c88..45070674ad 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -5,7 +5,7 @@ require "abstract_unit"
class CaptureHelperTest < ActionView::TestCase
def setup
super
- @av = ActionView::Base.new
+ @av = ActionView::Base.empty
@view_flow = ActionView::OutputFlow.new
end
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index 3cd6448e38..d7f2e8ee07 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -3,7 +3,18 @@
require "abstract_unit"
class CompiledTemplatesTest < ActiveSupport::TestCase
- teardown do
+ attr_reader :view_class
+
+ def setup
+ super
+ view_paths = ActionController::Base.view_paths
+ view_paths.each(&:clear_cache)
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
+ @view_class = ActionView::Base.with_empty_template_cache
+ end
+
+ def teardown
+ super
ActionView::LookupContext::DetailsKey.clear
end
@@ -13,7 +24,7 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
def test_template_with_ruby_keyword_locals
assert_equal "The class is foo",
- render(file: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" })
+ render(template: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" })
end
def test_template_with_invalid_identifier_locals
@@ -23,7 +34,7 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
"d-a-s-h-e-s": "",
"white space": "",
}
- assert_equal locals.inspect, render(file: "test/render_file_inspect_local_assigns", locals: locals)
+ assert_equal locals.inspect, render(template: "test/render_file_inspect_local_assigns", locals: locals)
end
def test_template_with_delegation_reserved_keywords
@@ -33,36 +44,36 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
args: "three",
block: "four",
}
- assert_equal "one two three four", render(file: "test/test_template_with_delegation_reserved_keywords", locals: locals)
+ assert_equal "one two three four", render(template: "test/test_template_with_delegation_reserved_keywords", locals: locals)
end
def test_template_with_unicode_identifier
- assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" })
+ assert_equal "🎂", render(template: "test/render_file_unicode_local", locals: { 🎃: "🎂" })
end
def test_template_with_instance_variable_identifier
- assert_equal "bar", render(file: "test/render_file_instance_variable", locals: { "@foo": "bar" })
+ assert_equal "bar", render(template: "test/render_file_instance_variable", locals: { "@foo": "bar" })
end
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
- assert_equal "one", render(file: "test/render_file_with_locals_and_default")
- assert_equal "two", render(file: "test/render_file_with_locals_and_default", locals: { secret: "two" })
+ assert_equal "one", render(template: "test/render_file_with_locals_and_default")
+ assert_equal "two", render(template: "test/render_file_with_locals_and_default", locals: { secret: "two" })
end
def test_template_changes_are_not_reflected_with_cached_templates
- assert_equal "Hello world!", render(file: "test/hello_world")
+ assert_equal "Hello world!", render(template: "test/hello_world")
modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Hello world!", render(file: "test/hello_world")
+ assert_equal "Hello world!", render(template: "test/hello_world")
end
- assert_equal "Hello world!", render(file: "test/hello_world")
+ assert_equal "Hello world!", render(template: "test/hello_world")
end
def test_template_changes_are_reflected_with_uncached_templates
- assert_equal "Hello world!", render_without_cache(file: "test/hello_world")
+ assert_equal "Hello world!", render_without_cache(template: "test/hello_world")
modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Goodbye world!", render_without_cache(file: "test/hello_world")
+ assert_equal "Goodbye world!", render_without_cache(template: "test/hello_world")
end
- assert_equal "Hello world!", render_without_cache(file: "test/hello_world")
+ assert_equal "Hello world!", render_without_cache(template: "test/hello_world")
end
private
@@ -72,13 +83,13 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
def render_with_cache(*args)
view_paths = ActionController::Base.view_paths
- ActionView::Base.new(view_paths, {}).render(*args)
+ view_class.with_view_paths(view_paths, {}).render(*args)
end
def render_without_cache(*args)
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::PathSet.new([path])
- ActionView::Base.new(view_paths, {}).render(*args)
+ view_class.with_view_paths(view_paths, {}).render(*args)
end
def modify_template(template, content)
diff --git a/actionview/test/template/csp_helper_test.rb b/actionview/test/template/csp_helper_test.rb
new file mode 100644
index 0000000000..1b7fd4665f
--- /dev/null
+++ b/actionview/test/template/csp_helper_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class CspHelperWithCspEnabledTest < ActionView::TestCase
+ tests ActionView::Helpers::CspHelper
+
+ def content_security_policy_nonce
+ "iyhD0Yc0W+c="
+ end
+
+ def content_security_policy?
+ true
+ end
+
+ def test_csp_meta_tag
+ assert_equal "<meta name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag
+ end
+
+ def test_csp_meta_tag_with_options
+ assert_equal "<meta property=\"csp-nonce\" name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag(property: "csp-nonce")
+ end
+end
+
+class CspHelperWithCspDisabledTest < ActionView::TestCase
+ tests ActionView::Helpers::CspHelper
+
+ def content_security_policy?
+ false
+ end
+
+ def test_csp_meta_tag
+ assert_nil csp_meta_tag
+ end
+end
diff --git a/actionview/test/template/csrf_helper_test.rb b/actionview/test/template/csrf_helper_test.rb
new file mode 100644
index 0000000000..dd9821eb6c
--- /dev/null
+++ b/actionview/test/template/csrf_helper_test.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class CsrfHelperTest < ActiveSupport::TestCase
+ cattr_accessor :request_forgery, default: false
+
+ include ActionView::Helpers::CsrfHelper
+ include ActionView::Helpers::TagHelper
+ include Rails::Dom::Testing::Assertions::DomAssertions
+
+ def test_csrf_meta_tags_without_request_forgery_protection
+ assert_dom_equal "", csrf_meta_tags
+ end
+
+ def test_csrf_meta_tags_with_request_forgery_protection
+ self.request_forgery = true
+
+ assert_dom_equal <<~DOM.chomp, csrf_meta_tags
+ <meta name="csrf-param" content="form_token" />
+ <meta name="csrf-token" content="secret" />
+ DOM
+ ensure
+ self.request_forgery = false
+ end
+
+ def test_csrf_meta_tags_without_protect_against_forgery_method
+ self.class.undef_method(:protect_against_forgery?)
+
+ assert_dom_equal "", csrf_meta_tags
+ ensure
+ self.class.define_method(:protect_against_forgery?) { request_forgery }
+ end
+
+ def protect_against_forgery?
+ request_forgery
+ end
+
+ def form_authenticity_token(*args)
+ "secret"
+ end
+
+ def request_forgery_protection_token
+ "form_token"
+ end
+end
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index ef7aeac039..42cb14a18a 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -17,8 +17,8 @@ class FakeTemplate
end
end
-Neckbeard = lambda { |template| template.source }
-Bowtie = lambda { |template| template.source }
+Neckbeard = lambda { |template, source| source }
+Bowtie = lambda { |template, source| source }
class DependencyTrackerTest < ActionView::TestCase
def tracker
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index ddaa7febb3..4515afdfff 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -7,9 +7,8 @@ require "action_view/dependency_tracker"
class FixtureFinder < ActionView::LookupContext
FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__)
- def initialize(details = {})
- super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, [])
- @rendered_format = :html
+ def self.build(details = {})
+ new(ActionView::PathSet.new(["digestor", "digestor/api"]), details, [])
end
end
@@ -146,13 +145,12 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_nested_template_deps_with_non_default_rendered_format
- finder.rendered_format = nil
nested_deps = [{ "comments/comments" => ["comments/comment"] }]
assert_equal nested_deps, nested_dependencies("messages/thread")
end
def test_template_formats_of_nested_deps_with_non_default_rendered_format
- finder.rendered_format = nil
+ @finder = finder.with_prepended_formats([:json])
assert_equal [:json], tree_template_formats("messages/thread").uniq
end
@@ -161,12 +159,10 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_template_dependencies_with_fallback_from_js_to_html_format
- finder.rendered_format = :js
assert_equal ["comments/comment"], dependencies("comments/show")
end
def test_template_digest_with_fallback_from_js_to_html_format
- finder.rendered_format = :js
assert_digest_difference("comments/show") do
change_template("comments/_comment")
end
@@ -219,14 +215,14 @@ class TemplateDigestorTest < ActionView::TestCase
def test_details_are_included_in_cache_key
# Cache the template digest.
- @finder = FixtureFinder.new(formats: [:html])
+ @finder = FixtureFinder.build(formats: [:html])
old_digest = digest("events/_event")
# Change the template; the cached digest remains unchanged.
change_template("events/_event")
# The details are changed, so a new cache key is generated.
- @finder = FixtureFinder.new
+ @finder = FixtureFinder.build
# The cache is busted.
assert_not_equal old_digest, digest("events/_event")
@@ -330,12 +326,14 @@ class TemplateDigestorTest < ActionView::TestCase
def assert_digest_difference(template_name, options = {})
previous_digest = digest(template_name, options)
+ finder.view_paths.each(&:clear_cache)
finder.digest_cache.clear
yield
assert_not_equal previous_digest, digest(template_name, options), "digest didn't change"
finder.digest_cache.clear
+ finder.view_paths.each(&:clear_cache)
end
def digest(template_name, options = {})
@@ -343,9 +341,14 @@ class TemplateDigestorTest < ActionView::TestCase
finder_options = options.extract!(:variants, :format)
finder.variants = finder_options[:variants] || []
- finder.rendered_format = finder_options[:format] if finder_options[:format]
- ActionView::Digestor.digest(name: template_name, finder: finder, dependencies: (options[:dependencies] || []))
+ finder_with_formats = if finder_options[:format]
+ finder.with_prepended_formats(Array(finder_options[:format]))
+ else
+ finder
+ end
+
+ ActionView::Digestor.digest(name: template_name, format: finder_options[:format], finder: finder_with_formats, dependencies: (options[:dependencies] || []))
end
def dependencies(template_name)
@@ -360,7 +363,7 @@ class TemplateDigestorTest < ActionView::TestCase
def tree_template_formats(template_name)
tree = ActionView::Digestor.tree(template_name, finder)
- tree.flatten.map(&:template).compact.flat_map(&:formats)
+ tree.flatten.map(&:template).compact.map(&:format)
end
def disable_resolver_caching
@@ -371,7 +374,7 @@ class TemplateDigestorTest < ActionView::TestCase
end
def finder
- @finder ||= FixtureFinder.new
+ @finder ||= FixtureFinder.build
end
def change_template(template_name, variant = nil)
diff --git a/actionview/test/template/fallback_file_system_resolver_test.rb b/actionview/test/template/fallback_file_system_resolver_test.rb
new file mode 100644
index 0000000000..304cdb8a03
--- /dev/null
+++ b/actionview/test/template/fallback_file_system_resolver_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class FallbackFileSystemResolverTest < ActiveSupport::TestCase
+ def setup
+ @root_resolver = ActionView::FallbackFileSystemResolver.new("/")
+ end
+
+ def test_should_have_no_virtual_path
+ templates = @root_resolver.find_all("hello_world.erb", "#{FIXTURE_LOAD_PATH}/test", false, locale: [], formats: [:html], variants: [], handlers: [:erb])
+ assert_equal 1, templates.size
+ assert_equal "Hello world!", templates[0].source
+ assert_nil templates[0].virtual_path
+ end
+end
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index 6db55a1447..ca117d4a30 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -48,8 +48,16 @@ class FormCollectionsHelperTest < ActionView::TestCase
test "collection radio should sanitize collection values for labels correctly" do
with_collection_radio_buttons :user, :name, ["$0.99", "$1.99"], :to_s, :to_s
- assert_select "label[for=user_name_099]", "$0.99"
- assert_select "label[for=user_name_199]", "$1.99"
+ assert_select "label[for=user_name_0_99]", "$0.99"
+ assert_select "label[for=user_name_1_99]", "$1.99"
+ end
+
+ test "collection radio correctly builds unique DOM IDs for float values" do
+ with_collection_radio_buttons :user, :name, [1.0, 10], :to_s, :to_s
+ assert_select "label[for=user_name_1_0]", "1.0"
+ assert_select "label[for=user_name_10]", "10"
+ assert_select 'input#user_name_1_0[type=radio][value="1.0"]'
+ assert_select 'input#user_name_10[type=radio][value="10"]'
end
test "collection radio accepts checked item" do
@@ -302,8 +310,16 @@ class FormCollectionsHelperTest < ActionView::TestCase
test "collection check box should sanitize collection values for labels correctly" do
with_collection_check_boxes :user, :name, ["$0.99", "$1.99"], :to_s, :to_s
- assert_select "label[for=user_name_099]", "$0.99"
- assert_select "label[for=user_name_199]", "$1.99"
+ assert_select "label[for=user_name_0_99]", "$0.99"
+ assert_select "label[for=user_name_1_99]", "$1.99"
+ end
+
+ test "collection check boxes correctly builds unique DOM IDs for float values" do
+ with_collection_check_boxes :user, :name, [1.0, 10], :to_s, :to_s
+ assert_select "label[for=user_name_1_0]", "1.0"
+ assert_select "label[for=user_name_10]", "10"
+ assert_select 'input#user_name_1_0[type=checkbox][value="1.0"]'
+ assert_select 'input#user_name_10[type=checkbox][value="10"]'
end
test "collection check boxes generates labels for non-English values correctly" do
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index f84c9b2b73..42069340f1 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -994,7 +994,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_as_new_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
@post.persisted = false
@post.stub(:to_key, nil) do
form_with(model: @post) do |f|
@@ -1011,7 +1011,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_as_existing_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: @post) do |f|
concat f.submit
end
@@ -1025,7 +1025,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_without_object_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(scope: :post) do |f|
concat f.submit class: "extra"
end
@@ -1039,7 +1039,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def test_submit_with_object_which_is_overwritten_by_scope_option
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: @post, scope: :another_post) do |f|
concat f.submit
end
@@ -1054,7 +1054,7 @@ class FormWithActsLikeFormForTest < FormWithTest
def test_submit_with_object_which_is_namespaced
blog_post = Blog::Post.new("And his name will be forty and four.", 44)
- with_locale :submit do
+ I18n.with_locale :submit do
form_with(model: blog_post) do |f|
concat f.submit
end
@@ -2357,11 +2357,4 @@ class FormWithActsLikeFormForTest < FormWithTest
def protect_against_forgery?
false
end
-
- def with_locale(testing_locale = :label)
- old_locale, I18n.locale = I18n.locale, testing_locale
- yield
- ensure
- I18n.locale = old_locale
- end
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 5972946074..91052e5ae2 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -203,31 +203,31 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_strings
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body"))
end
end
def test_label_with_human_attribute_name
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost))
end
end
def test_label_with_human_attribute_name_and_options
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_language_spanish">Espanol</label>', label(:post, :language, value: "spanish"))
end
end
def test_label_with_locales_symbols
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
end
end
def test_label_with_locales_and_options
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
'<label for="post_body" class="post_body">Write entire text here</label>',
label(:post, :body, class: "post_body")
@@ -236,13 +236,13 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_and_value
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal('<label for="post_color_red">Rojo</label>', label(:post, :color, value: "red"))
end
end
def test_label_with_locales_and_nested_attributes
- with_locale :label do
+ I18n.with_locale :label do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.label(:body)
@@ -258,7 +258,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_locales_fallback_and_nested_attributes
- with_locale :label do
+ I18n.with_locale :label do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.label(:value)
@@ -358,7 +358,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_block_and_builder
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
'<label for="post_body"><b>Write entire text here</b></label>',
label(:post, :body) { |b| raw("<b>#{b.translation}</b>") }
@@ -381,7 +381,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_to_model_and_overridden_model_name
- with_locale :label do
+ I18n.with_locale :label do
assert_dom_equal(
%{<label for="post_delegator_title">Delegate model_name title</label>},
label(:post_delegator, :title)
@@ -390,19 +390,19 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_without_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_body" name="post[body]" placeholder="Body" type="text" value="Back to the hill and over it again!" />', text_field(:post, :body, placeholder: true))
end
end
def test_text_field_placeholder_with_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_title" name="post[title]" placeholder="What is this about?" type="text" value="Hello World" />', text_field(:post, :title, placeholder: true))
end
end
def test_text_field_placeholder_with_locales_and_to_model
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
'<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate model_name title" type="text" value="Hello World" />',
text_field(:post_delegator, :title, placeholder: true)
@@ -411,7 +411,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_human_attribute_name
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Total cost" type="text" />', text_field(:post, :cost, placeholder: true))
end
end
@@ -424,25 +424,25 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_string_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?"))
end
end
def test_text_field_placeholder_with_human_attribute_name_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Pounds" type="text" />', text_field(:post, :cost, placeholder: :uk))
end
end
def test_text_field_placeholder_with_locales_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal('<input id="post_written_on" name="post[written_on]" placeholder="Escrito en" type="text" value="2004-06-15" />', text_field(:post, :written_on, placeholder: :spanish))
end
end
def test_text_field_placeholder_with_locales_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.text_field(:body, placeholder: true)
@@ -458,7 +458,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_placeholder_with_locales_fallback_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.text_field(:value, placeholder: true)
@@ -861,7 +861,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_without_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_body" name="post[body]" placeholder="Body">\nBack to the hill and over it again!</textarea>},
text_area(:post, :body, placeholder: true)
@@ -870,7 +870,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_title" name="post[title]" placeholder="What is this about?">\nHello World</textarea>},
text_area(:post, :title, placeholder: true)
@@ -879,7 +879,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_human_attribute_name
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="Total cost">\n</textarea>},
text_area(:post, :cost, placeholder: true)
@@ -888,7 +888,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_string_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="HOW MUCH?">\n</textarea>},
text_area(:post, :cost, placeholder: "HOW MUCH?")
@@ -897,7 +897,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_human_attribute_name_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_cost" name="post[cost]" placeholder="Pounds">\n</textarea>},
text_area(:post, :cost, placeholder: :uk)
@@ -906,7 +906,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_and_value
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
assert_dom_equal(
%{<textarea id="post_written_on" name="post[written_on]" placeholder="Escrito en">\n2004-06-15</textarea>},
text_area(:post, :written_on, placeholder: :spanish)
@@ -915,7 +915,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:comments) do |cf|
concat cf.text_area(:body, placeholder: true)
@@ -931,7 +931,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_area_placeholder_with_locales_fallback_and_nested_attributes
- with_locale :placeholder do
+ I18n.with_locale :placeholder do
form_for(@post, html: { id: "create-post" }) do |f|
f.fields_for(:tags) do |cf|
concat cf.text_area(:value, placeholder: true)
@@ -2260,7 +2260,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_as_new_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
@post.persisted = false
@post.stub(:to_key, nil) do
form_for(@post) do |f|
@@ -2277,7 +2277,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_as_existing_record_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(@post) do |f|
concat f.submit
end
@@ -2291,7 +2291,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_without_object_and_locale_strings
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(:post) do |f|
concat f.submit class: "extra"
end
@@ -2305,7 +2305,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_submit_with_object_which_is_overwritten_by_as_option
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(@post, as: :another_post) do |f|
concat f.submit
end
@@ -2320,7 +2320,7 @@ class FormHelperTest < ActionView::TestCase
def test_submit_with_object_which_is_namespaced
blog_post = Blog::Post.new("And his name will be forty and four.", 44)
- with_locale :submit do
+ I18n.with_locale :submit do
form_for(blog_post) do |f|
concat f.submit
end
@@ -3554,7 +3554,6 @@ class FormHelperTest < ActionView::TestCase
end
private
-
def hidden_fields(options = {})
method = options[:method]
@@ -3593,13 +3592,6 @@ class FormHelperTest < ActionView::TestCase
false
end
- def with_locale(testing_locale = :label)
- old_locale, I18n.locale = I18n.locale, testing_locale
- yield
- ensure
- I18n.locale = old_locale
- end
-
def with_default_enforce_utf8(value)
old_value = ActionView::Helpers::FormTagHelper.default_enforce_utf8
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = value
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index a2d1474a94..4ccd3ae336 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -36,6 +36,7 @@ class FormOptionsHelperTest < ActionView::TestCase
module FakeZones
FakeZone = Struct.new(:name) do
def to_s; name; end
+ def =~(_re); end
end
module ClassMethods
diff --git a/actionview/test/template/html_test.rb b/actionview/test/template/html_test.rb
index 5cdff74d60..17f21cbbc5 100644
--- a/actionview/test/template/html_test.rb
+++ b/actionview/test/template/html_test.rb
@@ -4,16 +4,16 @@ require "abstract_unit"
class HTMLTest < ActiveSupport::TestCase
test "formats returns symbol for recognized MIME type" do
- assert_equal [:html], ActionView::Template::HTML.new("", :html).formats
+ assert_equal :html, ActionView::Template::HTML.new("", :html).format
end
test "formats returns string for recognized MIME type when MIME does not have symbol" do
- foo = Mime::Type.lookup("foo")
+ foo = Mime::Type.lookup("text/foo")
assert_nil foo.to_sym
- assert_equal ["foo"], ActionView::Template::HTML.new("", foo).formats
+ assert_equal "text/foo", ActionView::Template::HTML.new("", foo).format
end
test "formats returns string for unknown MIME type" do
- assert_equal ["foo"], ActionView::Template::HTML.new("", "foo").formats
+ assert_equal "foo", ActionView::Template::HTML.new("", "foo").format
end
end
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 9fcf80bb24..85735139c1 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -11,10 +11,12 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def setup
super
- view_paths = ActionController::Base.view_paths
+ ActionView::LookupContext::DetailsKey.clear
+
+ view_paths = ActionController::Base.view_paths
+
lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
- renderer = ActionView::Renderer.new(lookup_context)
- @view = ActionView::Base.new(renderer, {})
+ @view = ActionView::Base.with_empty_template_cache.new(lookup_context, {})
ActionView::LogSubscriber.attach_to :action_view
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 38469cbe3d..3e357fe1a7 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -5,25 +5,37 @@ require "abstract_controller/rendering"
class LookupContextTest < ActiveSupport::TestCase
def setup
- @lookup_context = ActionView::LookupContext.new(FIXTURE_LOAD_PATH, {})
+ @lookup_context = build_lookup_context(FIXTURE_LOAD_PATH, {})
ActionView::LookupContext::DetailsKey.clear
end
+ def build_lookup_context(paths, details)
+ ActionView::LookupContext.new(paths, details)
+ end
+
def teardown
I18n.locale = :en
end
- test "allows to override default_formats with ActionView::Base.default_formats" do
- begin
- formats = ActionView::Base.default_formats
- ActionView::Base.default_formats = [:foo, :bar]
+ test "rendered_format is deprecated" do
+ assert_deprecated do
+ @lookup_context.rendered_format = "foo"
+ end
- assert_equal [:foo, :bar], ActionView::LookupContext.new([]).default_formats
- ensure
- ActionView::Base.default_formats = formats
+ assert_deprecated do
+ assert_equal "foo", @lookup_context.rendered_format
end
end
+ test "allows to override default_formats with ActionView::Base.default_formats" do
+ formats = ActionView::Base.default_formats
+ ActionView::Base.default_formats = [:foo, :bar]
+
+ assert_equal [:foo, :bar], ActionView::LookupContext.new([]).default_formats
+ ensure
+ ActionView::Base.default_formats = formats
+ end
+
test "process view paths on initialization" do
assert_kind_of ActionView::PathSet, @lookup_context.view_paths
end
@@ -55,7 +67,7 @@ class LookupContextTest < ActiveSupport::TestCase
test "handles explicitly defined */* formats fallback to :js" do
@lookup_context.formats = [:js, Mime::ALL]
- assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats
+ assert_equal [:js, *Mime::SET.symbols].uniq, @lookup_context.formats
end
test "adds :html fallback to :js formats" do
@@ -63,6 +75,14 @@ class LookupContextTest < ActiveSupport::TestCase
assert_equal [:js, :html], @lookup_context.formats
end
+ test "raises on invalid format assignment" do
+ ex = assert_raises ArgumentError do
+ @lookup_context.formats = [:html, :invalid, "also bad"]
+ end
+
+ assert_equal 'Invalid formats: :invalid, "also bad"', ex.message
+ end
+
test "provides getters and setters for locale" do
@lookup_context.locale = :pt
assert_equal :pt, @lookup_context.locale
@@ -109,30 +129,43 @@ class LookupContextTest < ActiveSupport::TestCase
assert_equal "Hello texty phone!", template.source
end
- test "found templates respects given formats if one cannot be found from template or handler" do
+ test "found templates have nil format if one cannot be found from template or handler" do
assert_called(ActionView::Template::Handlers::Builder, :default_format, returns: nil) do
@lookup_context.formats = [:text]
template = @lookup_context.find("hello", %w(test))
- assert_equal [:text], template.formats
+ assert_nil template.format
end
end
test "adds fallbacks to view paths when required" do
assert_equal 1, @lookup_context.view_paths.size
- @lookup_context.with_fallbacks do
- assert_equal 3, @lookup_context.view_paths.size
- assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("")
- assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/")
+ assert_deprecated do
+ @lookup_context.with_fallbacks do
+ assert_equal 3, @lookup_context.view_paths.size
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("")
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/")
+ end
end
+
+ @lookup_context = @lookup_context.with_fallbacks
+
+ assert_equal 3, @lookup_context.view_paths.size
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("")
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/")
end
test "add fallbacks just once in nested fallbacks calls" do
- @lookup_context.with_fallbacks do
+ assert_deprecated do
@lookup_context.with_fallbacks do
- assert_equal 3, @lookup_context.view_paths.size
+ @lookup_context.with_fallbacks do
+ assert_equal 3, @lookup_context.view_paths.size
+ end
end
end
+
+ @lookup_context = @lookup_context.with_fallbacks.with_fallbacks
+ assert_equal 3, @lookup_context.view_paths.size
end
test "generates a new details key for each details hash" do
@@ -158,13 +191,13 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "gives the key forward to the resolver, so it can be used as cache key" do
- @lookup_context.view_paths = ActionView::FixtureResolver.new("test/_foo.erb" => "Foo")
+ @lookup_context = build_lookup_context(ActionView::FixtureResolver.new("test/_foo.erb" => "Foo"), {})
template = @lookup_context.find("foo", %w(test), true)
assert_equal "Foo", template.source
# Now we are going to change the template, but it won't change the returned template
# since we will hit the cache.
- @lookup_context.view_paths.first.hash["test/_foo.erb"] = "Bar"
+ @lookup_context.view_paths.first.data["test/_foo.erb"] = "Bar"
template = @lookup_context.find("foo", %w(test), true)
assert_equal "Foo", template.source
@@ -187,7 +220,7 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "can disable the cache on demand" do
- @lookup_context.view_paths = ActionView::FixtureResolver.new("test/_foo.erb" => "Foo")
+ @lookup_context = build_lookup_context(ActionView::FixtureResolver.new("test/_foo.erb" => "Foo"), {})
old_template = @lookup_context.find("foo", %w(test), true)
template = @lookup_context.find("foo", %w(test), true)
@@ -210,56 +243,6 @@ class LookupContextTest < ActiveSupport::TestCase
end
end
-class LookupContextWithFalseCaching < ActiveSupport::TestCase
- def setup
- @resolver = ActionView::FixtureResolver.new("test/_foo.erb" => ["Foo", Time.utc(2000)])
- @lookup_context = ActionView::LookupContext.new(@resolver, {})
- end
-
- test "templates are always found in the resolver but timestamp is checked before being compiled" do
- ActionView::Resolver.stub(:caching?, false) do
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "Foo", template.source
-
- # Now we are going to change the template, but it won't change the returned template
- # since the timestamp is the same.
- @resolver.hash["test/_foo.erb"][0] = "Bar"
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "Foo", template.source
-
- # Now update the timestamp.
- @resolver.hash["test/_foo.erb"][1] = Time.now.utc
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "Bar", template.source
- end
- end
-
- test "if no template was found in the second lookup, with no cache, raise error" do
- ActionView::Resolver.stub(:caching?, false) do
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "Foo", template.source
-
- @resolver.hash.clear
- assert_raise ActionView::MissingTemplate do
- @lookup_context.find("foo", %w(test), true)
- end
- end
- end
-
- test "if no template was cached in the first lookup, retrieval should work in the second call" do
- ActionView::Resolver.stub(:caching?, false) do
- @resolver.hash.clear
- assert_raise ActionView::MissingTemplate do
- @lookup_context.find("foo", %w(test), true)
- end
-
- @resolver.hash["test/_foo.erb"] = ["Foo", Time.utc(2000)]
- template = @lookup_context.find("foo", %w(test), true)
- assert_equal "Foo", template.source
- end
- end
-end
-
class TestMissingTemplate < ActiveSupport::TestCase
def setup
@lookup_context = ActionView::LookupContext.new("/Path/to/views", {})
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index afe68b7ff0..f0fed601f8 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -9,15 +9,21 @@ end
module RenderTestCases
def setup_view(paths)
@assigns = { secret: "in the sauce" }
- @view = Class.new(ActionView::Base) do
+
+ @view = Class.new(ActionView::Base.with_empty_template_cache) do
def view_cache_dependencies; []; end
def combined_fragment_cache_key(key)
[ :views, key ]
end
- end.new(paths, @assigns)
+ end.with_view_paths(paths, @assigns)
+
+ controller = TestController.new
- @controller_view = TestController.new.view_context
+ @controller_view = controller.view_context_class.with_empty_template_cache.new(
+ controller.lookup_context,
+ controller.view_assigns,
+ controller)
# Reload and register danish language for testing
I18n.backend.store_translations "da", {}
@@ -27,6 +33,21 @@ module RenderTestCases
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
end
+ def test_implicit_format_comes_from_parent_template
+ rendered_templates = JSON.parse(@controller_view.render(template: "test/mixing_formats"))
+ assert_equal({ "format" => "HTML",
+ "children" => ["XML", "HTML"] }, rendered_templates)
+ end
+
+ def test_implicit_format_comes_from_parent_template_cascading
+ rendered_templates = JSON.parse(@controller_view.render(template: "test/mixing_formats_deep"))
+ assert_equal({ "format" => "HTML",
+ "children" => [
+ { "format" => "XML", "children" => ["XML"] },
+ { "format" => "HTML", "children" => ["HTML"] },
+ ] }, rendered_templates)
+ end
+
def test_render_without_options
e = assert_raises(ArgumentError) { @view.render() }
assert_match(/You invoked render but did not give any of (.+) option\./, e.message)
@@ -46,11 +67,7 @@ module RenderTestCases
def test_render_template_with_format
assert_match "<h1>No Comment</h1>", @view.render(template: "comments/empty", formats: [:html])
assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: [:xml])
- end
-
- def test_rendered_format_without_format
- @view.render(inline: "test")
- assert_equal :html, @view.lookup_context.rendered_format
+ assert_match "<error>No Comment</error>", @view.render(template: "comments/empty", formats: :xml)
end
def test_render_partial_implicitly_use_format_of_the_rendered_template
@@ -65,7 +82,7 @@ module RenderTestCases
def test_render_partial_use_last_prepended_format_for_partials_with_the_same_names
@view.lookup_context.formats = [:html]
- assert_equal "\nHTML Template, but JSON partial", @view.render(template: "test/change_priority")
+ assert_equal "\nHTML Template, but HTML partial", @view.render(template: "test/change_priority")
end
def test_render_template_with_a_missing_partial_of_another_format
@@ -108,7 +125,7 @@ module RenderTestCases
def test_render_raw_is_html_safe_and_does_not_escape_output
buffer = ActiveSupport::SafeBuffer.new
- buffer << @view.render(file: "plain_text")
+ buffer << @view.render(template: "plain_text")
assert_equal true, buffer.html_safe?
assert_equal buffer, "<%= hello_world %>\n"
end
@@ -121,22 +138,22 @@ module RenderTestCases
assert_equal "4", @view.render(inline: "(2**2).to_s", type: :ruby)
end
- def test_render_file_with_localization_on_context_level
+ def test_render_template_with_localization_on_context_level
old_locale, @view.locale = @view.locale, :da
- assert_equal "Hey verden", @view.render(file: "test/hello_world")
+ assert_equal "Hey verden", @view.render(template: "test/hello_world")
ensure
@view.locale = old_locale
end
- def test_render_file_with_dashed_locale
+ def test_render_template_with_dashed_locale
old_locale, @view.locale = @view.locale, :"pt-BR"
- assert_equal "Ola mundo", @view.render(file: "test/hello_world")
+ assert_equal "Ola mundo", @view.render(template: "test/hello_world")
ensure
@view.locale = old_locale
end
- def test_render_file_at_top_level
- assert_equal "Elastica", @view.render(file: "/shared")
+ def test_render_template_at_top_level
+ assert_equal "Elastica", @view.render(template: "/shared")
end
def test_render_file_with_full_path
@@ -336,6 +353,27 @@ module RenderTestCases
assert_equal "Hello: davidHello: mary", @view.render(partial: "test/customer", collection: customers)
end
+ def test_deprecated_constructor
+ assert_deprecated do
+ ActionView::Base.new
+ end
+
+ assert_deprecated do
+ ActionView::Base.new ["/a"]
+ end
+
+ assert_deprecated do
+ ActionView::Base.new ActionView::PathSet.new ["/a"]
+ end
+ end
+
+ def test_without_compiled_method_container_is_deprecated
+ view = ActionView::Base.with_view_paths(ActionController::Base.view_paths)
+ assert_deprecated("ActionView::Base instances must implement `compiled_method_container`") do
+ assert_equal "Hello world!", view.render(template: "test/hello_world")
+ end
+ end
+
def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns
assert_equal "false", @view.render(partial: "test/partial_name_in_local_assigns")
end
@@ -440,13 +478,31 @@ module RenderTestCases
assert_equal "Hello, World!", @view.render(inline: "Hello, World!", type: :bar)
end
- CustomHandler = lambda do |template|
+ CustomHandler = lambda do |template, source|
"@output_buffer = ''.dup\n" \
- "@output_buffer << 'source: #{template.source.inspect}'\n"
+ "@output_buffer << 'source: #{source.inspect}'\n"
end
def test_render_inline_with_render_from_to_proc
- ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
+ ActionView::Template.register_template_handler :ruby_handler, lambda { |_, source| source }
+ assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
+ ensure
+ ActionView::Template.unregister_template_handler :ruby_handler
+ end
+
+ def test_render_inline_with_render_from_to_proc_deprecated
+ assert_deprecated do
+ ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
+ end
+ assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
+ ensure
+ ActionView::Template.unregister_template_handler :ruby_handler
+ end
+
+ def test_optional_second_arg_works_without_deprecation
+ assert_not_deprecated do
+ ActionView::Template.register_template_handler :ruby_handler, ->(view, source = nil) { source }
+ end
assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
ensure
ActionView::Template.unregister_template_handler :ruby_handler
@@ -494,28 +550,28 @@ module RenderTestCases
def test_render_ignores_templates_with_malformed_template_handlers
%w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists"
- assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") }
+ assert_raises(ActionView::MissingTemplate) { @view.render(template: "test/malformed/#{name}") }
end
end
def test_render_with_layout
assert_equal %(<title></title>\nHello world!\n),
- @view.render(file: "test/hello_world", layout: "layouts/yield")
+ @view.render(template: "test/hello_world", layout: "layouts/yield")
end
def test_render_with_layout_which_has_render_inline
assert_equal %(welcome\nHello world!\n),
- @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_inline_inside")
+ @view.render(template: "test/hello_world", layout: "layouts/yield_with_render_inline_inside")
end
def test_render_with_layout_which_renders_another_partial
assert_equal %(partial html\nHello world!\n),
- @view.render(file: "test/hello_world", layout: "layouts/yield_with_render_partial_inside")
+ @view.render(template: "test/hello_world", layout: "layouts/yield_with_render_partial_inside")
end
def test_render_partial_with_html_only_extension
assert_equal %(<h1>partial html</h1>\nHello world!\n),
- @view.render(file: "test/hello_world", layout: "layouts/render_partial_html")
+ @view.render(template: "test/hello_world", layout: "layouts/render_partial_html")
end
def test_render_layout_with_block_and_yield
@@ -570,17 +626,17 @@ module RenderTestCases
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
- @view.render(file: "test/nested_layout", layout: "layouts/yield")
+ @view.render(template: "test/nested_layout", layout: "layouts/yield")
end
def test_render_with_file_in_layout
assert_equal %(\n<title>title</title>\n\n),
- @view.render(file: "test/layout_render_file")
+ @view.render(template: "test/layout_render_file")
end
def test_render_layout_with_object
assert_equal %(<title>David</title>),
- @view.render(file: "test/layout_render_object")
+ @view.render(template: "test/layout_render_object")
end
def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call
@@ -600,6 +656,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
# Ensure view path cache is primed
def setup
+ ActionView::LookupContext::DetailsKey.clear
view_paths = ActionController::Base.view_paths
assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
setup_view(view_paths)
@@ -617,6 +674,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
# Test the same thing as above, but make sure the view path
# is not eager loaded
def setup
+ ActionView::LookupContext::DetailsKey.clear
path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::PathSet.new([path])
assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first
@@ -630,7 +688,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_magic_comment
with_external_encoding Encoding::ASCII_8BIT do
- result = @view.render(file: "test/utf8_magic", formats: [:html], layouts: "layouts/yield")
+ result = @view.render(template: "test/utf8_magic", formats: [:html], layouts: "layouts/yield")
assert_equal Encoding::UTF_8, result.encoding
assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
@@ -638,7 +696,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_default_external_encoding
with_external_encoding Encoding::UTF_8 do
- result = @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield")
+ result = @view.render(template: "test/utf8", formats: [:html], layouts: "layouts/yield")
assert_equal Encoding::UTF_8, result.encoding
assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
@@ -646,14 +704,14 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def test_render_utf8_template_with_incompatible_external_encoding
with_external_encoding Encoding::SHIFT_JIS do
- e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8", formats: [:html], layouts: "layouts/yield") }
+ e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/utf8", formats: [:html], layouts: "layouts/yield") }
assert_match "Your template was not saved as valid Shift_JIS", e.cause.message
end
end
def test_render_utf8_template_with_partial_with_incompatible_encoding
with_external_encoding Encoding::SHIFT_JIS do
- e = assert_raises(ActionView::Template::Error) { @view.render(file: "test/utf8_magic_with_bare_partial", formats: [:html], layouts: "layouts/yield") }
+ e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/utf8_magic_with_bare_partial", formats: [:html], layouts: "layouts/yield") }
assert_match "Your template was not saved as valid Shift_JIS", e.cause.message
end
end
@@ -674,6 +732,8 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
# Ensure view path cache is primed
setup do
+ ActionView::LookupContext::DetailsKey.clear
+
view_paths = ActionController::Base.view_paths
assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
@@ -683,10 +743,17 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
end
teardown do
- GC.start
I18n.reload!
end
+ test "template body written to cache" do
+ customer = Customer.new("david", 1)
+ key = cache_key(customer, "test/_customer")
+ assert_nil ActionView::PartialRenderer.collection_cache.read(key)
+ @view.render(partial: "test/customer", collection: [customer], cached: true)
+ assert_equal "Hello: david", ActionView::PartialRenderer.collection_cache.read(key)
+ end
+
test "collection caching does not cache by default" do
customer = Customer.new("david", 1)
key = cache_key(customer, "test/_customer")
@@ -717,9 +784,20 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
@view.render(partial: "test/cached_customer", collection: [customer], cached: true)
end
+ test "collection caching does not work on multi-partials" do
+ a = Object.new
+ b = Object.new
+ def a.to_partial_path; "test/partial_iteration_1"; end
+ def b.to_partial_path; "test/partial_iteration_2"; end
+
+ assert_raises(NotImplementedError) do
+ @controller_view.render(partial: [a, b], cached: true)
+ end
+ end
+
private
def cache_key(*names, virtual_path)
- digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: []
+ digest = ActionView::Digestor.digest name: virtual_path, format: :html, finder: @view.lookup_context, dependencies: []
@view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ])
end
end
diff --git a/actionview/test/template/resolver_cache_test.rb b/actionview/test/template/resolver_cache_test.rb
index 8a5db1346a..90b61a2aa1 100644
--- a/actionview/test/template/resolver_cache_test.rb
+++ b/actionview/test/template/resolver_cache_test.rb
@@ -4,6 +4,7 @@ require "abstract_unit"
class ResolverCacheTest < ActiveSupport::TestCase
def test_inspect_shields_cache_internals
+ ActionView::LookupContext::DetailsKey.clear
assert_match %r(#<ActionView::Resolver:0x[0-9a-f]+ @cache=#<ActionView::Resolver::Cache:0x[0-9a-f]+ keys=0 queries=0>>), ActionView::Resolver.new.inspect
end
end
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 1e1a4c5063..22815c8dbe 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -6,7 +6,10 @@ class ResolverPatternsTest < ActiveSupport::TestCase
def setup
path = File.expand_path("../fixtures", __dir__)
pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}"
- @resolver = ActionView::FileSystemResolver.new(path, pattern)
+
+ assert_deprecated do
+ @resolver = ActionView::FileSystemResolver.new(path, pattern)
+ end
end
def test_should_return_empty_list_for_unknown_path
@@ -19,7 +22,7 @@ class ResolverPatternsTest < ActiveSupport::TestCase
assert_equal 1, templates.size, "expected one template"
assert_equal "Hello custom patterns!", templates.first.source
assert_equal "custom_pattern/path", templates.first.virtual_path
- assert_equal [:html], templates.first.formats
+ assert_nil templates.first.format
end
def test_should_return_all_templates_when_ambiguous_pattern
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index f196c42c4f..a5b59a700e 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -7,9 +7,12 @@ end
class SetupFiberedBase < ActiveSupport::TestCase
def setup
+ ActionView::LookupContext::DetailsKey.clear
+
view_paths = ActionController::Base.view_paths
+
@assigns = { secret: "in the sauce", name: nil }
- @view = ActionView::Base.new(view_paths, @assigns)
+ @view = ActionView::Base.with_empty_template_cache.with_view_paths(view_paths, @assigns)
@controller_view = TestController.new.view_context
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index b348d1f17b..71fb99115b 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -18,8 +18,9 @@ class TestERBTemplate < ActiveSupport::TestCase
attr_accessor :formats
end
- class Context
- def initialize
+ class Context < ActionView::Base
+ def initialize(*)
+ super
@output_buffer = "original"
@virtual_path = nil
end
@@ -37,7 +38,9 @@ class TestERBTemplate < ActiveSupport::TestCase
"<%= @virtual_path %>",
"partial",
ERBHandler,
- virtual_path: "partial"
+ virtual_path: "partial",
+ format: :html,
+ locals: []
)
end
@@ -54,8 +57,9 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- def new_template(body = "<%= hello %>", details = { format: :html })
- ActionView::Template.new(body.dup, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details))
+ def new_template(body = "<%= hello %>", details = {})
+ details = { format: :html, locals: [] }.merge details
+ ActionView::Template.new(body.dup, "hello template", details.delete(:handler) || ERBHandler, { virtual_path: "hello" }.merge!(details))
end
def render(locals = {})
@@ -63,7 +67,8 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def setup
- @context = Context.new
+ @context = Context.with_empty_template_cache.empty
+ super
end
def test_basic_template
@@ -99,8 +104,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_locals
- @template = new_template("<%= my_local %>")
- @template.locals = [:my_local]
+ @template = new_template("<%= my_local %>", locals: [:my_local])
assert_equal "I am a local", render(my_local: "I am a local")
end
@@ -118,16 +122,14 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_refresh_with_templates
- @template = new_template("Hello", virtual_path: "test/foo/bar")
- @template.locals = [:key]
+ @template = new_template("Hello", virtual_path: "test/foo/bar", locals: [:key])
assert_called_with(@context.lookup_context, :find_template, ["bar", %w(test/foo), false, [:key]], returns: "template") do
assert_equal "template", @template.refresh(@context)
end
end
def test_refresh_with_partials
- @template = new_template("Hello", virtual_path: "test/_foo")
- @template.locals = [:key]
+ @template = new_template("Hello", virtual_path: "test/_foo", locals: [:key])
assert_called_with(@context.lookup_context, :find_template, ["foo", %w(test), true, [:key]], returns: "partial") do
assert_equal "partial", @template.refresh(@context)
end
@@ -211,4 +213,14 @@ class TestERBTemplate < ActiveSupport::TestCase
ensure
silence_warnings { Encoding.default_external = old }
end
+
+ def test_short_identifier
+ @template = new_template("hello")
+ assert_equal "hello template", @template.short_identifier
+ end
+
+ def test_template_inspect
+ @template = new_template("hello")
+ assert_equal "#<ActionView::Template hello template locals=[]>", @template.inspect
+ end
end
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 976b6bc77e..0b2a2a9911 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -24,6 +24,11 @@ module ActionView
DeveloperStruct = Struct.new(:name)
module SharedTests
+ def setup
+ ActionView::LookupContext::DetailsKey.clear
+ super
+ end
+
def self.included(test_case)
test_case.class_eval do
test "helpers defined on ActionView::TestCase are available" do
@@ -52,7 +57,7 @@ module ActionView
end
test "retrieve non existing config values" do
- assert_nil ActionView::Base.new.config.something_odd
+ assert_nil ActionView::Base.empty.config.something_odd
end
test "works without testing a helper module" do
@@ -279,7 +284,7 @@ module ActionView
@controller.controller_path = "test"
@customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")]
- assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list"))
+ assert_match(/Hello: EloyHello: Manfred/, render(template: "test/list"))
end
test "is able to render partials from templates and also use instance variables after view has been referenced" do
@@ -288,7 +293,7 @@ module ActionView
view
@customers = [DeveloperStruct.new("Eloy"), DeveloperStruct.new("Manfred")]
- assert_match(/Hello: EloyHello: Manfred/, render(file: "test/list"))
+ assert_match(/Hello: EloyHello: Manfred/, render(template: "test/list"))
end
test "is able to use helpers that depend on the view flow" do
diff --git a/actionview/test/template/testing/fixture_resolver_test.rb b/actionview/test/template/testing/fixture_resolver_test.rb
index 9954e3500d..afb6686dac 100644
--- a/actionview/test/template/testing/fixture_resolver_test.rb
+++ b/actionview/test/template/testing/fixture_resolver_test.rb
@@ -15,6 +15,6 @@ class FixtureResolverTest < ActiveSupport::TestCase
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
- assert_equal [:html], templates.first.formats
+ assert_nil templates.first.format
end
end
diff --git a/actionview/test/template/testing/null_resolver_test.rb b/actionview/test/template/testing/null_resolver_test.rb
index 53364c1d90..c7c78804c0 100644
--- a/actionview/test/template/testing/null_resolver_test.rb
+++ b/actionview/test/template/testing/null_resolver_test.rb
@@ -9,6 +9,6 @@ class NullResolverTest < ActiveSupport::TestCase
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s
- assert_equal [:html], templates.first.formats
+ assert_nil templates.first.format
end
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 2925489f5d..e961a770e6 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -361,6 +361,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", line_width: 15))
end
+ def test_word_wrap_with_leading_spaces
+ assert_equal(" This is a paragraph\nthat includes some\nindented lines:\n Like this sample\n blockquote", word_wrap(" This is a paragraph that includes some\nindented lines:\n Like this sample\n blockquote", line_width: 25))
+ end
+
def test_word_wrap_does_not_modify_the_options_hash
options = { line_width: 15 }
passed_options = options.dup
diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb
index 0c6470df21..c837c53587 100644
--- a/actionview/test/template/text_test.rb
+++ b/actionview/test/template/text_test.rb
@@ -3,8 +3,8 @@
require "abstract_unit"
class TextTest < ActiveSupport::TestCase
- test "formats always return :text" do
- assert_equal [:text], ActionView::Template::Text.new("").formats
+ test "format always return :text" do
+ assert_equal :text, ActionView::Template::Text.new("").format
end
test "identifier should return 'text template'" do
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index e756348938..9afdc3c68f 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -36,7 +36,10 @@ class TranslationHelperTest < ActiveSupport::TestCase
}
}
)
- @view = ::ActionView::Base.new(ActionController::Base.view_paths, {})
+ view_paths = ActionController::Base.view_paths
+ view_paths.each(&:clear_cache)
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
+ @view = ::ActionView::Base.with_empty_template_cache.with_view_paths(view_paths, {})
end
teardown do
@@ -124,20 +127,20 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
def test_finds_translation_scoped_by_partial
- assert_equal "Foo", view.render(file: "translations/templates/found").strip
+ assert_equal "Foo", view.render(template: "translations/templates/found").strip
end
def test_finds_array_of_translations_scoped_by_partial
- assert_equal "Foo Bar", @view.render(file: "translations/templates/array").strip
+ assert_equal "Foo Bar", @view.render(template: "translations/templates/array").strip
end
def test_default_lookup_scoped_by_partial
- assert_equal "Foo", view.render(file: "translations/templates/default").strip
+ assert_equal "Foo", view.render(template: "translations/templates/default").strip
end
def test_missing_translation_scoped_by_partial
expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>'
- assert_equal expected, view.render(file: "translations/templates/missing").strip
+ assert_equal expected, view.render(template: "translations/templates/missing").strip
end
def test_translate_does_not_mark_plain_text_as_safe_html
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 1ab28e4749..632b32f09f 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -119,6 +119,16 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_without_protect_against_forgery_method
+ self.class.undef_method(:protect_against_forgery?)
+ assert_dom_equal(
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
+ button_to("Hello", "http://www.example.com")
+ )
+ ensure
+ self.class.define_method(:protect_against_forgery?) { request_forgery }
+ end
+
def test_button_to_with_straight_url
assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
index 778dc1b09a..0f92007007 100644
--- a/actionview/test/ujs/public/test/call-remote.js
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -128,14 +128,14 @@ asyncTest('execution of JS code does not modify current DOM', 1, function() {
})
})
-asyncTest('HTML content should be plain-text', 1, function() {
+asyncTest('HTML document should be parsed', 1, function() {
buildForm({ method: 'post', 'data-type': 'html' })
$('form').append('<input type="text" name="content_type" value="text/html">')
$('form').append('<input type="text" name="content" value="<p>hello</p>">')
submit(function(e, data, status, xhr) {
- ok(data === '<p>hello</p>', 'returned data should be a plain-text string')
+ ok(data instanceof HTMLDocument, 'returned data should be an HTML document')
})
})
diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js
index 645ad494c3..10b8870171 100644
--- a/actionview/test/ujs/public/test/data-disable-with.js
+++ b/actionview/test/ujs/public/test/data-disable-with.js
@@ -95,6 +95,27 @@ asyncTest('form button with "data-disable-with" attribute', 6, function() {
App.checkDisabledState(button, 'submitting ...')
})
+asyncTest('a[data-remote][data-disable-with] within a form disables and re-enables', 6, function() {
+ var form = $('form:not([data-remote])'),
+ link = $('<a data-remote="true" data-disable-with="clicking...">Click me</a>')
+ form.append(link)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(link, 'clicking...')
+ })
+ .bindNative('ajax:complete', function() {
+ setTimeout( function() {
+ App.checkEnabledState(link, 'Click me')
+ link.remove()
+ start()
+ }, 15)
+ })
+ .triggerNative('click')
+})
+
asyncTest('form input[type=submit][data-disable-with] disables', 6, function() {
var form = $('form:not([data-remote])'), input = form.find('input[type=submit]')
@@ -309,7 +330,7 @@ asyncTest('form[data-remote] input|button|textarea[data-disable-with] does not d
start()
})
-asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
+asyncTest('ctrl-clicking on a link does not disable the link', 6, function() {
var link = $('a[data-disable-with]')
App.checkEnabledState(link, 'Click me')
@@ -322,6 +343,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
start()
})
+asyncTest('right/mouse-wheel-clicking on a link does not disable the link', 10, function() {
+ var link = $('a[data-disable-with]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 1 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 1 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 2 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 2 })
+ App.checkEnabledState(link, 'Click me')
+ start()
+})
+
asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() {
var button = $('button[data-remote][data-disable-with]')
diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js
index 88dc801b2f..9f84c4647e 100644
--- a/actionview/test/ujs/public/test/data-disable.js
+++ b/actionview/test/ujs/public/test/data-disable.js
@@ -250,6 +250,25 @@ asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
start()
})
+asyncTest('right/mouse-wheel-clicking on a link does not disable the link', 10, function() {
+ var link = $('a[data-disable]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 1 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 1 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 2 })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { button: 2 })
+ App.checkEnabledState(link, 'Click me')
+ start()
+})
+
asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() {
var button = $('button[data-remote][data-disable]')
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index 3503c2cff3..55d39b0a52 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -63,6 +63,25 @@ asyncTest('ctrl-clicking on a link does not fire ajaxyness', 0, function() {
setTimeout(function() { start() }, 13)
})
+asyncTest('right/mouse-wheel-clicking on a link does not fire ajaxyness', 0, function() {
+ var link = $('a[data-remote]')
+
+ // Ideally, we'd setup an iframe to intercept normal link clicks
+ // and add a test to make sure the iframe:loaded event is triggered.
+ // However, jquery doesn't actually cause a native `click` event and
+ // follow links using `trigger('click')`, it only fires bindings.
+ link
+ .removeAttr('data-params')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+
+ link.triggerNative('click', { button: 1 })
+ link.triggerNative('click', { button: 2 })
+
+ setTimeout(function() { start() }, 13)
+})
+
asyncTest('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', 2, function() {
var link = $('a[data-remote]')
@@ -148,6 +167,25 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() {
.triggerNative('click')
})
+asyncTest('right/mouse-wheel-clicking on a button with data-remote attribute does not fire ajaxyness', 0, function() {
+ var button = $('button[data-remote]')
+
+ // Ideally, we'd setup an iframe to intercept normal link clicks
+ // and add a test to make sure the iframe:loaded event is triggered.
+ // However, jquery doesn't actually cause a native `click` event and
+ // follow links using `trigger('click')`, it only fires bindings.
+ button
+ .removeAttr('data-params')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+
+ button.triggerNative('click', { button: 1 })
+ button.triggerNative('click', { button: 2 })
+
+ setTimeout(function() { start() }, 13)
+})
+
asyncTest('changing a select option with data-remote attribute', 5, function() {
buildSelect()
diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js
index 05677f2595..682d044403 100644
--- a/actionview/test/ujs/public/test/settings.js
+++ b/actionview/test/ujs/public/test/settings.js
@@ -71,7 +71,7 @@ try {
} catch (e) {
_MouseEvent = function(type, options) {
var evt = document.createEvent('MouseEvents')
- evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, null)
+ evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, null)
return evt
}
}
diff --git a/actionview/test/ujs/server.rb b/actionview/test/ujs/server.rb
index 56f436c8b8..d7a6271587 100644
--- a/actionview/test/ujs/server.rb
+++ b/actionview/test/ujs/server.rb
@@ -23,6 +23,7 @@ module UJS
config.public_file_server.enabled = true
config.logger = Logger.new(STDOUT)
config.log_level = :error
+ config.hosts << proc { true }
config.content_security_policy do |policy|
policy.default_src :self, :https