Motivation / Background
---
Strict Locals support was introduced in [#45727][] and announced as part
of the [7.1 Release][]. There are several mentions across the Guides,
but support is rarely mentioned in the API documentation.
Detail
----
Mention the template short identifier (the pathname, in most cases) as
part of the `ArgumentError` message.
This commit adds two test cases to ensure support for splatting
additional arguments, and for forbidding block and positional arguments.
It also makes mention of strict locals in more places, and links to the
guides.
[#45727]: https://github.com/rails/rails/pull/45727
[7.1 Release]: https://edgeguides.rubyonrails.org/7_1_release_notes.html#allow-templates-to-set-strict-locals
The Rails documentation uses the `:include:` directive to inline the
README of the framework into the main documentation page. As the
README's aren't in the root directory from where SDoc is run we need to
add the framework path to the include:
# :include: activesupport/README.md
This results in a warning when installing the gems as generating the rdoc for the gem is run from the gem/framework root:
Couldn't find file to include 'activesupport/README.rdoc' from lib/active_support.rb
The `:include:` RDoc directive supports includes relative to the current
file as well:
# :include: ../README.md
This makes sure it works for the Rails API docs and the separate gems.
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
Fixes#50774
When the server boots up, 2 threads hit the same `UnboundTemplate`
instance before it has set up `@templates`. Both threads get past the
`unless template = @templates[locals]` check because
`@templates[locals]` isn't set yet. However, with `@write_lock`, one
thread waits while the other one proceeds, setting `@templates` to a
frozen hash. The second thread then gets the write lock and tries to
modify `@templates` but it has been frozen.
Currently there's about a 35% difference between tags generated using
the `TagBuilder` and tags generated by passing a positional argument to
`#tag`.
This commit optimizes `TagBuilder` to reduce that difference down to 13%.
The first change is to perform less hash allocations by not splatting
the options twice in the `TagBuilder` (one at the `tag.a` invocation,
and one at `tag_string`). The extra splat for `tag_string` was moved
into `method_missing` since that is the only other caller of this
private method.
The other change is to only escape the content in `tag_string` if it a
non-empty.
Additionally, a test was tweaked to ensure that passing `options` to a
`self_closing_element` is tested as it was previously not.
Benchmark:
```
require "action_view"
require "benchmark/ips"
class Foo
include ActionView::Helpers
end
helpers = Foo.new
Benchmark.ips do |x|
x.report("tag") { helpers.tag("a", href: "foo") }
x.report("tag_builder") { helpers.tag.a(href: "foo") }
x.compare!
end
```
Before:
```
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Warming up --------------------------------------
tag 67.180k i/100ms
tag_builder 50.267k i/100ms
Calculating -------------------------------------
tag 673.064k (± 0.4%) i/s - 3.426M in 5.090520s
tag_builder 504.971k (± 0.4%) i/s - 2.564M in 5.076842s
Comparison:
tag: 673063.7 i/s
tag_builder: 504971.4 i/s - 1.33x slower
```
After:
```
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Warming up --------------------------------------
tag 67.374k i/100ms
tag_builder 59.702k i/100ms
Calculating -------------------------------------
tag 670.837k (± 0.4%) i/s - 3.369M in 5.021714s
tag_builder 592.727k (± 1.3%) i/s - 2.985M in 5.037088s
Comparison:
tag: 670836.6 i/s
tag_builder: 592726.7 i/s - 1.13x slower
```
Co-authored-by: Sean Doyle <seanpdoyle@users.noreply.github.com>
Previously, the `render_template.action_view` and
`render_layout.action_view` events would always be handled as if they
had subscribers even if the log level of its subscribers result in
nothing being logged. For regular `LogSubscriber`s,
`subscribe_log_level` could be used to optimize these cases but the
Start subscriber is not a subclass of `LogSubscriber`.
This commit implements a `#subscribed?` method for the Start subscriber
so that it can also benefit from the `subscribe_log_level` optimization.
Follow-up to #50699.
This prevents a `NoMethodError` from being masked when the missing
method is itself named `render_in`.
Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
Follow-up to #50665.
Unconditionally converting `NoMethodError` to `ArgumentError` can mask a
legitimate `NoMethodError` from within the `render_in` method. This
commit adds a check to prevent that.
When calling `render` with a `:renderable` argument, ensure that the
object responds to `#render_in`. If it doesn't, raise an
`ArgumentError`.
This commit also adjusts the `ArgumentError` that when a `:partial`
argument isn't Active Model compatible. Prior to this commit, the
message used `:` as a prefix to `to_partial_path`. This commit replaces
that with a `#` prefix to denote that it's expected to be an instance
method on the object.
RDoc treats consecutive indented lines as a single code block. For code
examples that span multiple files / languages, this confuses the syntax
highlighter and makes the examples harder to read. Unfortunately, RDoc
doesn't provide syntax to prevent this, and it ignores multiple
consecutive blank lines. However, by inserting an empty tag such as
`<code></code>`, we can force RDoc to recognize separate code blocks.
`ActionView::Helpers::NumberHelper` methods are thin wrappers over
`ActiveSupport::NumberHelper` methods, which are well documented. Thus
this commit points `ActionView::Helpers::NumberHelper` method docs to
`ActiveSupport::NumberHelper` methods instead of duplicating those docs.
Provide examples for rendering objects that respond to `render_in`. Also
highlight that the object can also define a `#format` method to control
how the rendered String should be treated.
Add test coverage for both Action View's and Action Pack's support for
`render` with `:renderable` options.
Now that we dropped support for Ruby 2.7, we no longer
need to check if variables are defined before accessing them
to avoid the undefined variable warning.
Now that we no longer support Ruby 2.7, many `ruby2_keyword` calls
can be eliminated.
The ones that are left could be eliminated but would end up substantially
slower or more compliacated so I left them for now.
This provides a shortcut for setting a Content Security Policy nonce on
a stylesheet_link_tag.
Co-authored-by: AJ Esler <ajesler@users.noreply.github.com>
This seems to be making a pair with `#render_partial` but in reality
it's no longer refered to from anywhere. Since it is marked with
nodoc, I propose to get rid of it.
As far as I can tell from my non-comprehensive research,
this method was introduced in the commit b735761,
became practically private in f984907 and unused in 1bc0a59.
To integrate with [rails-dom-testing][] and its selector assertions,
`ActionView::TestCase` [defines a `#document_root_element`
method][document_root_element] that parses the HTML into a fully valid
HTML document and returns the "root".
In the case of most Action View partials rendered with `render partial:
"..."`, the resulting document would be invalid, so its constituent
parts (its `<html>`, `<head>`, and `<body>` elements) are synthesized in
during the parsing process. This results in a document whose _contents_
are equivalent to the original HTML string, but whose structure is not.
To share a concrete example:
```ruby
irb(main):002:0> rendered = "<h1>Hello world</h1><h2>Goodbye world</h2>"
=> "<h1>Hello world</h1><h2>Goodbye world</h2>"
irb(main):003:0> root = Rails::Dom::Testing.html_document.parse(rendered).root
=>
#(Element:0x57080 {
...
irb(main):004:0> rendered.to_s
=> "<h1>Hello world</h1><h2>Goodbye world</h2>"
irb(main):005:0> root.to_s
=> "<html><head></head><body><h1>Hello world</h1><h2>Goodbye world</h2></body></html>"
irb(main):006:0> rendered.to_s == root.to_s
=> false
```
Prior to this commit, the parsed HTML content returned from calling
`rendered.html` relied on the same mechanisms as
`#document_root_element`, and parsed the HTML fragment into a full
document, with a synthesized `<html>` element as its root. The
`rendered.html` value should reflect the content that was **rendered**
by the partial, and should not behave the same as
`#document_root_element`.
When the parsing class is changed from [Nokogiri::XML::Document][] to
[Nokogiri::XML::DocumentFragment][], the returned value reflects the
same **exact** content as what was rendered.
To elaborate on the previous example:
```ruby
irb(main):007:0> fragment = Rails::Dom::Testing.html_document_fragment.parse(rendered)
=>
#(DocumentFragment:0x62ee4 {
...
irb(main):008:0> fragment.to_s
=> "<h1>Hello world</h1><h2>Goodbye world</h2>"
irb(main):009:0> rendered.to_s == fragment.to_s
=> true
```
This commit changes the default `rendered.html` behavior to rely on
`Nokogiri::XML::DocumentFragment` instead of `Nokogiri::XML::Document`.
[Nokogiri::XML::Document]: https://nokogiri.org/rdoc/Nokogiri/XML/Document.html
[Nokogiri::XML::DocumentFragment]: https://nokogiri.org/rdoc/Nokogiri/XML/DocumentFragment.html
[document_root_element]: https://github.com/rails/rails-dom-testing/blob/v2.2.0/lib/rails/dom/testing/assertions/selector_assertions.rb#L75
Closes#49818
Renames `ActionView::TestCase::Behavior::Content` to
`RenderedViewContent`, with the goal of making it more of an internal
implementation detail that's unlikely to collide with an
application-side `::Content` class.
The `RenderedView`-prefix mirrors the module's `RenderedViewsCollection`
class. Since the intention is to treat it as a private implementation
detail, `RenderedViewContent` is marked with `:nodoc:`.
Along with the rename, this commit also modifies the class inheritance,
replacing the `SimpleDelegator` superclass with `String`. [String.new][]
accepts a `String` positional argument in the same way as
`SimpleDelegator.new` accepts a delegate object positional argument.
Sharing the `String` superclass also makes it a good candidate for being
passed to [Capybara.string][] (and [Capybara::Node::Simple.new][]) like
the documentation suggests.
[Capybara.string]: https://github.com/teamcapybara/capybara/blob/3.39.2/lib/capybara.rb#L212-L242
[Capybara::Node::Simple.new]: https://github.com/teamcapybara/capybara/blob/3.39.2/lib/capybara/node/simple.rb#L23
[String.new]: https://ruby-doc.org/core/String.html#method-c-new
Apparently it had not been clear what visibility they should assume
when they were initially created at https://github.com/rails/rails/pull/35265,
then a big refactoring took place at https://github.com/rails/rails/pull/38594
after which they remain intact.
So I think they can now become properly private as they are nodoc and
not refered to but from within the same class.
Also perform two autocorrects with `bundle exec rubocop -A`:
- fixes a new case of [`Style/RedundantReturn`][1]
- fixes a new case of [`Performance/StringInclude`][2]
[1]: 146b1c2e3389bc70ea0b54abf7843fc1d6c8cd5f
[2]: 3158bbb9f6454dce64dd0b4e2a548351d014c48f
Co-authored-by: David Heinemeier Hansson <david@basecamp.com>
Round mode option was added in 7905bdfd8b
but it was documented in active supports number helper only. Action
views number helper is using active supports helper and accepts
round_mode option so it should be documented there also.
Examples are also unified and it should be more clear that they return
string and not a number.
[ci skip]
This is a continuation of https://github.com/rails/rails/pull/46875
The behavior of looking up the class method when `to: :class` is passed
is a bit error prone because it silently degrades.
By passing the expected owner of the delegated method, we can be more
strict, and also generate a delegator in a module rather than having
to do it at inclusion time.
I made this argument private API because we want it in Rails, but
I'm worried it might be a bit too sharp for public API. I can
be convinced otherwise though.
Define the `ActionView::Helpers::FormBuilder` methods that wrap the
`@template` instance methods inside an
`ActiveSupport::CodeGenerator.batch` call so that the underlying `class`
extensions aren't invoked more than once.