Repository: rails/rails
File: actionview/lib/action_view/helpers/tag_helper.rb (Ruby)

Code before:
      #   tag("br", nil, true)
      #   # => <br>
      #
      #   tag("input", type: 'text', disabled: true)
      #   # => <input type="text" disabled="disabled" />
      #
      #   tag("input", type: 'text', class: ["strong", "highlight"])
      #   # => <input class="strong highlight" type="text" />
      #
      #   tag("img", src: "open & shut.png")
      #   # => <img src="open &amp; shut.png" />
      #
      #   tag("img", { src: "open &amp; shut.png" }, false, false)
      #   # => <img src="open &amp; shut.png" />
      #
      #   tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
      #   # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
      #
      #   tag("div", class: { highlight: current_user.admin? })
      #   # => <div class="highlight" />
      def tag(name = nil, options = nil, open = false, escape = true)
        if name.nil?
          tag_builder
        else
          ensure_valid_html5_tag_name(name)
          "<#{name}#{tag_builder.send(:tag_options, options, escape) if options}#{open ? ">" : " />"}".html_safe
        end
      end

      # Returns an HTML block tag of type +name+ surrounding the +content+. Add
      # HTML attributes by passing an attributes hash to +options+.
      # Instead of passing the content as an argument, you can also use a block
      # in which case, you pass your +options+ as the second parameter.
      # Set escape to false to disable escaping.
      # Note: this is legacy syntax, see +tag+ method description for details.
      #
      # ==== Options
      # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
      # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
      # symbols or strings for the attribute names.
      #
      # ==== Examples
      #   content_tag(:p, "Hello world!")
      #    # => <p>Hello world!</p>
      #   content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
      #    # => <div class="strong"><p>Hello world!</p></div>
      #   content_tag(:div, "Hello world!", class: ["strong", "highlight"])
      #    # => <div class="strong highlight">Hello world!</div>
      #   content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
      #    # => <div class="strong highlight">Hello world!</div>
      #   content_tag("select", options, multiple: true)

Review comment (refactor):
The bulk of the value delivered by this proposed set of changes is related to the *removal* of the `CaptureHelper` and `OutputSafetyHelper` module mixins. Changing the access of methods like `#tag_string`, `#tag_options`, and `#content_tag_string` from `public` to `private` are improvements, but the same developer-facing outcomes can be achieved through the use of `# :nodoc:`.

In response to comments like https://github.com/rails/rails/pull/49369#discussion_r1336557277 and https://github.com/rails/rails/pull/49369#discussion_r1336557778, I've reverted changes that coerce calls in the style of `Object#public_send` to instead invoke the private interfaces. Other parts of Action View outside of the `actionview/lib/action_view/helpers/tag_helper.rb` file should interact with the `TagBuilder` instance returned from `#tag` in the way that an end-user would interact with it. Having said that, I acknowledge that there's value in adhering to the current precedent set from prior implementations. Since the methods are within the same file and set of modules at the private method definitions, interacting with the private method directly feels like a more balanced trade-off than previous versions of this PR.

Code after:
      #   tag("br", nil, true)
      #   # => <br>
      #
      #   tag("input", type: 'text', disabled: true)
      #   # => <input type="text" disabled="disabled" />
      #
      #   tag("input", type: 'text', class: ["strong", "highlight"])
      #   # => <input class="strong highlight" type="text" />
      #
      #   tag("img", src: "open & shut.png")
      #   # => <img src="open &amp; shut.png" />
      #
      #   tag("img", { src: "open &amp; shut.png" }, false, false)
      #   # => <img src="open &amp; shut.png" />
      #
      #   tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
      #   # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
      #
      #   tag("div", class: { highlight: current_user.admin? })
      #   # => <div class="highlight" />
      def tag(name = nil, options = nil, open = false, escape = true)
        if name.nil?
          tag_builder
        else
          ensure_valid_html5_tag_name(name)
          "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
        end
      end

      # Returns an HTML block tag of type +name+ surrounding the +content+. Add
      # HTML attributes by passing an attributes hash to +options+.
      # Instead of passing the content as an argument, you can also use a block
      # in which case, you pass your +options+ as the second parameter.
      # Set escape to false to disable escaping.
      # Note: this is legacy syntax, see +tag+ method description for details.
      #
      # ==== Options
      # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
      # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
      # symbols or strings for the attribute names.
      #
      # ==== Examples
      #   content_tag(:p, "Hello world!")
      #    # => <p>Hello world!</p>
      #   content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
      #    # => <div class="strong"><p>Hello world!</p></div>
      #   content_tag(:div, "Hello world!", class: ["strong", "highlight"])
      #    # => <div class="strong highlight">Hello world!</div>
      #   content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
      #    # => <div class="strong highlight">Hello world!</div>
      #   content_tag("select", options, multiple: true)