You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The `TagBuilder` instance returned by the
[ActionView::Helpers::TagHelper#tag][] method has the ability to build
various HTML elements through its reliance on `method_missing`. The
magic of that instance hinges on the fact that it has a **minimal**
public interface, and transforms missing methods names into HTML
elements. For example, calling `tag.div` invokes a missing `#div`
method, which renders a `<div>` element.
There are two exceptional cases:
* `tag.p` is defined, since the existing [Kernel#p][] definition would
prevent the underlying `#method_missing` invocation
* `tag.attributes` is a defined method to transform `Hash` instances and
keyword arguments in HTML attribute strings
In addition to those two _intentional_ exceptions, there are also
several methods that are incidentally part of the public interface:
* `#tag_string`
* `#content_tag_string`
* `#tag_options`
* `#boolean_tag_option`
* `#tag_option`
Along with those methods, the class also includes [OutputSafetyHelper][]
and [CaptureHelper][], which expand surface area of the class's public
interface even further.
While it's unlikely that these methods would collide with method
invocations intended to construct HTML elements, they still impact that
design of the object.
The "public" nature of the `TagBuilder` interface has some subtle
nuances. While it **is** marked with `:nodoc:`, instances are returned
by a public `tag` method, despite it being considered a private class
only meant for internal consumption.
That same is true for the incidentally public methods mentioned above:
no consuming applications should be invoking `#tag_string` or
`#tag_options`. While that's true, those methods _are_ being invoked
**directly** by other Action View classes.
This commit removes those invocations, and instead replaces them with
public method calls.
In cases where the tag name is statically know ahead of time, they're
replaced with calls to that method (for example, `content_tag(:option)`
becomes `tag.option`). When they're dynamic, they're replaced by calls
to [public_send][] (for example, `tag(name)` becomes
`tag.public_send(name)`).
The majority of these changes are painless, with one exception. Some
calls to the `#tag` helper also pass an `open` positional argument that
isn't part of the `TagBuilder` class's public interface. As a result,
that becomes slightly more complicated, and requires some String
mutation to continue to pass the test suite.
This commit also removes the `OutputSafetyHelper` and `CaptureHelper`
modules from the class, and delegates to the methods it depended on to
`@view_context`.
Hopefully, calls to `#tag` with positional arguments are less and less
common, since the documentation describes it as a [Legacy Syntax][]
marked for deprecation in future versions of Rails.
[Kernel#p]: https://ruby-doc.org/3.2.2/Kernel.html#method-i-p
[ActionView::Helpers::TagHelper#tag]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag
[public_send]: https://ruby-doc.org/3.2.2/Object.html#method-i-public_send
[Legacy Syntax]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag-label-Legacy+syntax
[OutputSafetyHelper]: https://api.rubyonrails.org/classes/ActionView/Helpers/OutputSafetyHelper.html
[CaptureHelper]: https://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
0 commit comments